Files

135 lines
5.6 KiB
C#
Raw Permalink Normal View History

using System;
using System.Collections.Generic;
using System.Globalization;
using UnityEngine;
namespace Ironspore
{
public static class GameLocalization
{
public static string PreferenceKey => GameDataPaths.PreferenceKey("settings.locale");
public const string FallbackCode = "en";
public static event Action Changed;
public static string CurrentCode { get; private set; } = FallbackCode;
public static LocalizationCatalog Catalog
{
get; private set;
}
public static CultureInfo Culture => CultureInfo.GetCultureInfo(CurrentCode);
static readonly Dictionary<string, Dictionary<string, string>> tables = new Dictionary<string, Dictionary<string, string>>();
static readonly HashSet<string> missing = new HashSet<string>();
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void Reset()
{
Changed = null;
Catalog = null;
CurrentCode = FallbackCode;
tables.Clear();
missing.Clear();
}
public static void Initialize(LocalizationCatalog catalog)
{
if (!catalog || catalog.locales == null)
throw new InvalidOperationException("Localization catalog is missing.");
Catalog = catalog;
tables.Clear();
missing.Clear();
foreach (var locale in catalog.locales)
tables.Add(locale.code, ReadTable(locale.table));
if (!tables.ContainsKey(FallbackCode))
throw new InvalidOperationException("English fallback table is missing.");
string system = CultureInfo.CurrentUICulture.Name;
if (Application.systemLanguage == SystemLanguage.ChineseSimplified || Application.systemLanguage == SystemLanguage.ChineseTraditional || Application.systemLanguage == SystemLanguage.Chinese)
system = "zh-Hans";
CurrentCode = ResolveInitialLocale(PlayerPrefs.GetString(PreferenceKey, ""), system, tables.Keys);
if (!tables.ContainsKey(CurrentCode))
CurrentCode = FallbackCode;
}
public static Dictionary<string, string> ReadTable(TextAsset source)
{
if (!source)
throw new ArgumentNullException(nameof(source));
var result = new Dictionary<string, string>(StringComparer.Ordinal);
var table = JsonUtility.FromJson<TranslationTable>(source.text);
if (table?.entries == null)
throw new FormatException("Invalid translation table: " + source.name);
foreach (var entry in table.entries)
{
if (string.IsNullOrWhiteSpace(entry.key) || string.IsNullOrWhiteSpace(entry.value))
throw new FormatException("Empty translation in " + source.name);
result.Add(entry.key, entry.value);
}
return result;
}
public static string ResolveInitialLocale(string saved, string system, IEnumerable<string> availableCodes = null)
{
var supported = new HashSet<string>(availableCodes ?? new[] { "en", "zh-Hans" }, StringComparer.OrdinalIgnoreCase);
if (!string.IsNullOrEmpty(saved) && supported.Contains(saved))
return saved;
if (!string.IsNullOrEmpty(system))
{
if (system.StartsWith("zh", StringComparison.OrdinalIgnoreCase) && supported.Contains("zh-Hans"))
return "zh-Hans";
if (supported.Contains(system))
return system;
string language = system.Split('-')[0];
if (supported.Contains(language))
return language;
}
return FallbackCode;
}
public static bool SelectLocale(string code, bool persist = true)
{
if (!tables.ContainsKey(code))
return false;
bool changed = CurrentCode != code;
CurrentCode = code;
if (persist)
{
PlayerPrefs.SetString(PreferenceKey, code);
PlayerPrefs.Save();
}
if (changed)
Changed?.Invoke();
return true;
}
public static string Text(string key, params object[] args)
{
string template = null;
if (tables.TryGetValue(CurrentCode, out var selected))
selected.TryGetValue(key, out template);
if (string.IsNullOrEmpty(template) && tables.TryGetValue(FallbackCode, out var fallback))
fallback.TryGetValue(key, out template);
if (string.IsNullOrEmpty(template))
{
if (Debug.isDebugBuild && missing.Add(key))
Debug.LogWarning("Missing localization key: " + key);
return "[" + key + "]";
}
return args.Length == 0 ? template : string.Format(Culture, template, args);
}
public static string ItemName(string id) => Text("item." + id);
public static LocalizedMessage Message(string key, params object[] args) => new LocalizedMessage(key, args);
}
// Keep keys and parameters so existing notifications and reports follow locale changes.
public sealed class LocalizedMessage
{
readonly string key;
readonly object[] args;
public LocalizedMessage(string key, params object[] args)
{
this.key = key;
this.args = args;
}
public override string ToString() => GameLocalization.Text(key, args);
}
}