using System;
using System.Collections.Generic;
using HarmonyLib;
namespace UXAssist.Common;
///
/// Provides bilingual localization helpers that integrate with the game's system.
///
public static class I18N
{
private static bool _initialized;
private static bool _dirty;
///
/// Invoked whenever the active language is applied or changed and all registered strings have been applied.
///
public static Action OnInitialized;
///
/// Registers the localization hooks with Harmony.
///
public static void Init()
{
Harmony.CreateAndPatchAll(typeof(I18N));
}
///
/// Returns whether the localization system has finished its initial load.
///
/// true if localization is initialized; otherwise false.
public static bool Initialized() => _initialized;
///
/// Holds the English and Simplified Chinese localized values for a single entry.
///
private sealed class LocalizedString
{
public string En { get; }
public string Zh { get; }
public LocalizedString(string en, string zh)
{
En = en;
Zh = zh;
}
}
private static readonly Dictionary Entries = new();
///
/// Registers a localized string pair.
///
/// The lookup key.
/// The English text.
/// The Simplified Chinese text.
public static void Add(string key, string enus, string zhcn = null)
{
// Skip identity fallback entries: if no Chinese translation is provided and the key equals the English text,
// there is no need to register a separate entry because the key already serves as the fallback text.
if (zhcn is null && key == enus) return;
Entries[key] = new LocalizedString(enus, string.IsNullOrEmpty(zhcn) ? enus : zhcn);
_dirty = true;
}
private static void ApplyIndexers()
{
var indexer = Localization.namesIndexer;
var index = indexer.Count;
foreach (var key in Entries.Keys)
{
if (indexer.TryGetValue(key, out _)) continue;
indexer[key] = index;
index++;
}
_dirty = false;
var strings = Localization.strings;
if (strings == null) return;
var len = strings.Length;
for (var i = 0; i < len; i++)
{
ApplyLanguage(i);
if (i != Localization.currentLanguageIndex) continue;
Localization.currentStrings = Localization.strings[i];
Localization.currentFloats = Localization.floats[i];
}
}
private static void ApplyLanguage(int index)
{
var indexerLength = Localization.namesIndexer.Count;
var strs = Localization.strings[index];
if (strs == null) return;
if (strs.Length < indexerLength)
{
var newStrs = new string[indexerLength];
Array.Copy(strs, newStrs, strs.Length);
strs = newStrs;
Localization.strings[index] = strs;
}
var floats = Localization.floats[index];
if (floats != null)
{
if (floats.Length < indexerLength)
{
var newFloats = new float[indexerLength];
Array.Copy(floats, newFloats, floats.Length);
floats = newFloats;
Localization.floats[index] = floats;
}
}
var isChinese = Localization.Languages[index].lcId == 2052;
foreach (var entry in Entries)
{
if (!Localization.namesIndexer.TryGetValue(entry.Key, out var idx)) continue;
strs[idx] = isChinese ? entry.Value.Zh : entry.Value.En;
}
}
///
/// Applies any pending localization changes to the game's localization tables.
///
public static void Apply()
{
if (!_initialized) return;
if (Entries.Count == 0) return;
if (!_dirty) return;
ApplyIndexers();
}
///
/// Returns the localized text for the given key in the currently active language,
/// falling back to the key itself when no entry is registered.
///
/// The lookup key.
/// The localized text, or if no entry exists.
internal static string Translate(string key)
{
if (!Entries.TryGetValue(key, out var loc)) return key;
var languageIndex = Localization.currentLanguageIndex;
if (languageIndex >= 0 && languageIndex < Localization.Languages.Length && Localization.Languages[languageIndex].lcId == 2052)
return loc.Zh;
return loc.En;
}
[HarmonyPostfix, HarmonyPriority(Priority.Last), HarmonyPatch(typeof(Localization), nameof(Localization.LoadSettings))]
private static void Localization_LoadSettings_Postfix()
{
if (_initialized) return;
_initialized = true;
Apply();
}
[HarmonyPostfix, HarmonyPriority(Priority.Last), HarmonyPatch(typeof(Localization), nameof(Localization.LoadLanguage))]
private static void Localization_LoadLanguage_Postfix(int index)
{
if (!_initialized) return;
ApplyLanguage(index);
}
[HarmonyPostfix, HarmonyPriority(Priority.Last), HarmonyPatch(typeof(Localization), nameof(Localization.NotifyLanguageChange))]
private static void Localization_NotifyLanguageChange_Postfix()
{
if (!_initialized) return;
OnInitialized?.Invoke();
}
}