using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using TechbloxModdingAPI; using TechbloxModdingAPI.App; using TechbloxModdingAPI.Commands; using TechbloxModdingAPI.Utility; using HarmonyLib; using IllusionPlugin; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using ServiceLayer; namespace Localization { public class LocalizationMod : IEnhancedPlugin { private readonly DirectoryInfo _pluginFolder = new DirectoryInfo(Path.Combine("Plugins", "Localization")); private Dictionary _origStrings; private readonly string[] _embeddedLanguages = Assembly.GetExecutingAssembly().GetManifestResourceNames(); public override void OnApplicationStart() { Main.Init(); if (!_pluginFolder.Exists) _pluginFolder.Create(); string settingsPath = Path.Combine(_pluginFolder.FullName, "settings.json"); JObject settings; if (File.Exists(settingsPath)) { try { using (var stream = new JsonTextReader(File.OpenText(settingsPath))) settings = JObject.Load(stream); string lang = (string) (settings["lang"] ?? (settings["lang"] = "en")); lang = lang.ToLower(); if (lang != "en") LoadTranslation(lang); } catch (Exception e) { Logging.LogError(e); settings = new JObject(); } } else settings = new JObject(); CommandBuilder.Builder("SetLanguage", "Sets the game's language") .Action(lang => { lang = lang.ToLower(); settings["lang"] = lang; using (var stream = new JsonTextWriter(new StreamWriter(File.OpenWrite(settingsPath)))) settings.WriteTo(stream); LoadTranslation(lang); }).Build(); CommandBuilder.Builder("ListLanguages", "Lists available languages") .Action(() => { Logging.CommandLog(new[] {"en"}.Concat(_pluginFolder.EnumerateFiles() .Where(file => file.Extension == ".json" && (file.Name.Length < 8 || file.Name.Contains("-"))) .Select(file => file.Name.Replace(file.Extension, ""))) .Concat(_embeddedLanguages) .Select(lang => lang.Replace("Localization.", "").Replace(".json", "")) .Distinct().Aggregate((a, b) => a + ", " + b)); }).Build(); CommandBuilder.Builder("DumpLangStrings", "Dumps the current strings. Set language to en first.") .Action(() => { File.WriteAllText(Path.Combine(_pluginFolder.FullName, "dumped.json"), JsonConvert.SerializeObject(_origStrings, Formatting.Indented)); }).Build(); if (!settings.ContainsKey("autoload")) { settings["autoload"] = true; using (var stream = new JsonTextWriter(new StreamWriter(File.OpenWrite(settingsPath)))) settings.WriteTo(stream); } if ((bool) settings["autoload"]) Client.EnterMenu += TryLoadSystemLanguageMenuEnterEvent; } public override void OnApplicationQuit() { Main.Shutdown(); } private void LoadTranslation(string lang) { Logging.CommandLog("Loading translation for " + lang + "..."); LoadOriginalStrings(); string langPath = Path.Combine(_pluginFolder.FullName, lang + ".json"); if (!File.Exists(langPath)) { if (_embeddedLanguages.Contains("Localization." + lang + ".json")) langPath = null; else { if (lang == "en") { AccessTools.Method(typeof(LocalizationService), "Init").Invoke(null, new object[0]); _origStrings = null; //Reset as the object changed LoadOriginalStrings(); Logging.CommandLog("Strings reset to default. (" + _origStrings.Count + " strings in total)"); return; } Logging.CommandLogError("Could not find json file!"); return; } } string jsonText = langPath != null ? File.ReadAllText(langPath) : null; if (jsonText == null) { var stream = Assembly.GetExecutingAssembly() .GetManifestResourceStream("Localization." + lang + ".json"); if (stream != null) using (var st = new StreamReader(stream)) jsonText = st.ReadToEnd(); else { Logging.CommandLogError("Failed to load embedded translation for " + lang); return; } } var strings = JsonConvert.DeserializeObject>(jsonText); foreach (var kv in strings) { if (!_origStrings.Remove(kv.Key)) Logging.CommandLogWarning(kv.Key + " wasn't in the original file."); _origStrings.Add(kv.Key, kv.Value); } Logging.CommandLog("Updated " + strings.Count + " strings (" + _origStrings.Count + " strings in total)"); } private void LoadOriginalStrings() { if (_origStrings != null) return; _origStrings = (Dictionary) AccessTools.Field(typeof(LocalizationService), "LocalizedStrings") .GetValue(null); } private void TryLoadSystemLanguageMenuEnterEvent(object sender, MenuEventArgs args) { TryLoadSystemLanguage(); Client.EnterMenu -= TryLoadSystemLanguageMenuEnterEvent; } private void TryLoadSystemLanguage() { string lang = CultureInfo.CurrentUICulture.EnglishName.Split(' ')[0].ToLower(); string first2 = lang.Substring(0, 2); // try some possibly valid language codes if (File.Exists(Path.Combine(_pluginFolder.FullName, lang + ".json"))) { Logging.MetaLog($"Automatically detected system UI language {lang}, loading translations..."); LoadTranslation(lang); } else if (File.Exists(Path.Combine(_pluginFolder.FullName, first2 + ".json"))) { lang = first2; Logging.MetaLog($"Automatically detected system UI language {lang}, loading translations..."); LoadTranslation(lang); } else if (File.Exists(Path.Combine(_pluginFolder.FullName, first2 + "-" + first2 + ".json"))) { // a lot of standard languages codes are like fr-fr (French from France), but this won't work for English (nor fr-CA French from Canada) lang = first2 + "-" + first2; Logging.MetaLog($"Automatically detected system UI language {lang}, loading translations..."); LoadTranslation(lang); } else { Logging.MetaLog($"Translations for system UI language {lang} are not available, skipping..."); } } public override string Name { get; } = "LocalizationMod"; public override string Version { get; } = "1.0.0"; } }