Lang/Loc.cs
using System;
using System.Collections.Generic;

namespace Science;

/// <summary>One UI language: its code, its own name for the language list, the font that has its glyphs, and its phrases.</summary>
public sealed class LangInfo
{
	public string Code;
	/// <summary>Written in the language itself, so a player can always find their own.</summary>
	public string Name;
	/// <summary>HUD class that switches the font. Roboto Condensed covers Latin and Cyrillic; Japanese needs Mochiy Pop One.</summary>
	public string FontClass;
	public Func<Dictionary<string, string>> Table;
}

/// <summary>
/// UI text in every supported language. English is the master table and the fallback for anything another table
/// lacks. Phrases use {0}-style placeholders and get their numbers already formatted. Tools/check_loc.py checks
/// every table for missing keys, placeholders, glyphs and label widths.
/// </summary>
public static partial class Loc
{
	public static readonly LangInfo[] Languages =
	{
		new() { Code = "en", Name = "English", FontClass = "font-main", Table = English },
		new() { Code = "es", Name = "Español", FontClass = "font-main", Table = Spanish },
		new() { Code = "fr", Name = "Français", FontClass = "font-main", Table = French },
		new() { Code = "de", Name = "Deutsch", FontClass = "font-main", Table = German },
		new() { Code = "it", Name = "Italiano", FontClass = "font-main", Table = Italian },
		new() { Code = "pt", Name = "Português (Brasil)", FontClass = "font-main", Table = Portuguese },
		new() { Code = "pl", Name = "Polski", FontClass = "font-main", Table = Polish },
		new() { Code = "tr", Name = "Türkçe", FontClass = "font-main", Table = Turkish },
		new() { Code = "ru", Name = "Русский", FontClass = "font-main", Table = Russian },
		new() { Code = "ja", Name = "日本語", FontClass = "font-mochiy", Table = Japanese },
	};

	static Dictionary<string, string> _english;
	static Dictionary<string, string> _current;

	public static LangInfo Current { get; private set; } = Languages[0];
	public static string Code => Current.Code;
	/// <summary>Bumped whenever the language changes, so the HUD knows to redraw.</summary>
	public static int Version { get; private set; }

	static Dictionary<string, string> EnglishTable => _english ??= English();

	public static LangInfo Find( string code )
	{
		if ( string.IsNullOrEmpty( code ) ) return null;
		code = code.Trim().ToLowerInvariant().Replace( '_', '-' );
		foreach ( var l in Languages ) if ( l.Code == code ) return l;
		// "pt-br", "es-419", "en-gb" and the like match on the language alone
		int dash = code.IndexOf( '-' );
		if ( dash > 0 )
		{
			var root = code.Substring( 0, dash );
			foreach ( var l in Languages ) if ( l.Code == root ) return l;
		}
		return null;
	}

	/// <summary>Switch language. Unknown codes fall back to English.</summary>
	public static void Set( string code )
	{
		var lang = Find( code ) ?? Languages[0];
		if ( lang == Current && _current != null ) return;
		Current = lang;
		_current = lang.Code == "en" ? EnglishTable : lang.Table();
		Version++;
	}

	/// <summary>The phrase for key in the current language, else English, else the key itself.</summary>
	public static string T( string key )
	{
		if ( key == null ) return "";
		_current ??= EnglishTable;
		if ( _current.TryGetValue( key, out var s ) && !string.IsNullOrEmpty( s ) ) return s;
		if ( EnglishTable.TryGetValue( key, out var e ) ) return e;
		return key;
	}

	public static string T( string key, params object[] args )
	{
		var phrase = T( key );
		try
		{
			return string.Format( phrase, args );
		}
		catch ( FormatException )
		{
			// a broken translation must never take the HUD down: show the English phrase instead
			try { return string.Format( EnglishTable.TryGetValue( key, out var e ) ? e : key, args ); }
			catch ( FormatException ) { return phrase; }
		}
	}

	/// <summary>Key form of an internal English id: "Salt dissolve" becomes "salt_dissolve".</summary>
	public static string Slug( string id ) => (id ?? "").Trim().ToLowerInvariant().Replace( ' ', '_' );

	/// <summary>Upper case that respects Turkish dotted/dotless i, for headings.</summary>
	public static string Upper( string s )
	{
		if ( string.IsNullOrEmpty( s ) ) return s ?? "";
		if ( Code == "tr" ) s = s.Replace( 'i', 'İ' );
		return s.ToUpperInvariant();
	}
}