using System; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using CheapLoc; using Dalamud.Configuration.Internal; using Serilog; namespace Dalamud; /// /// Class handling localization. /// [ServiceManager.EarlyLoadedService] public class Localization : IServiceType { /// /// Array of language codes which have a valid translation in Dalamud. /// public static readonly string[] ApplicableLangCodes = { "de", "ja", "fr", "it", "es", "ko", "no", "ru", "zh", "tw" }; private const string FallbackLangCode = "en"; private readonly string locResourceDirectory; private readonly string locResourcePrefix; private readonly bool useEmbedded; private readonly Assembly assembly; /// /// Initializes a new instance of the class. /// /// The working directory to load language files from. /// The prefix on the loc resource file name (e.g. dalamud_). /// Use embedded loc resource files. public Localization(string locResourceDirectory, string locResourcePrefix = "", bool useEmbedded = false) { this.locResourceDirectory = locResourceDirectory; this.locResourcePrefix = locResourcePrefix; this.useEmbedded = useEmbedded; this.assembly = Assembly.GetCallingAssembly(); } [ServiceManager.ServiceConstructor] private Localization(Dalamud dalamud, DalamudConfiguration configuration) : this(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "loc", "dalamud"), "dalamud_") { if (!string.IsNullOrEmpty(configuration.LanguageOverride)) this.SetupWithLangCode(configuration.LanguageOverride); else this.SetupWithUiCulture(); } /// /// Delegate for the event that occurs when the language is changed. /// /// The language code of the new language. public delegate void LocalizationChangedDelegate(string langCode); /// /// Event that occurs when the language is changed. /// public event LocalizationChangedDelegate LocalizationChanged; /// /// Search the set-up localization data for the provided assembly for the given string key and return it. /// If the key is not present, the fallback is shown. /// The fallback is also required to create the string files to be localized. /// /// The string key to be returned. /// The fallback string, usually your source language. /// The localized string, fallback or string key if not found. // TODO: This breaks loc export, since it's being called without string args. Fix in CheapLoc. public static string Localize(string key, string fallBack) { return Loc.Localize(key, fallBack, Assembly.GetCallingAssembly()); } /// /// Set up the UI language with the users' local UI culture. /// public void SetupWithUiCulture() { try { var currentUiLang = CultureInfo.CurrentUICulture; Log.Information("Trying to set up Loc for culture {0}", currentUiLang.TwoLetterISOLanguageName); if (ApplicableLangCodes.Any(langCode => currentUiLang.TwoLetterISOLanguageName == langCode)) { this.SetupWithLangCode(currentUiLang.TwoLetterISOLanguageName); } else { this.SetupWithFallbacks(); } } catch (Exception ex) { Log.Error(ex, "Could not get language information. Setting up fallbacks."); this.SetupWithFallbacks(); } } /// /// Set up the UI language with "fallbacks"(original English text). /// public void SetupWithFallbacks() { this.LocalizationChanged?.Invoke(FallbackLangCode); Loc.SetupWithFallbacks(this.assembly); } /// /// Set up the UI language with the provided language code. /// /// The language code to set up the UI language with. public void SetupWithLangCode(string langCode) { if (langCode.ToLower() == FallbackLangCode) { this.SetupWithFallbacks(); return; } this.LocalizationChanged?.Invoke(langCode); try { Loc.Setup(this.ReadLocData(langCode), this.assembly); } catch (Exception ex) { Log.Error(ex, "Could not load loc {0}. Setting up fallbacks.", langCode); this.SetupWithFallbacks(); } } /// /// Saves localizable JSON data in the current working directory for the provided assembly. /// /// If set to true, this ignores malformed Localize functions instead of failing. public void ExportLocalizable(bool ignoreInvalidFunctions = false) { Loc.ExportLocalizableForAssembly(this.assembly, ignoreInvalidFunctions); } private string ReadLocData(string langCode) { if (this.useEmbedded) { var resourceStream = this.assembly.GetManifestResourceStream($"{this.locResourceDirectory}{this.locResourcePrefix}{langCode}.json"); if (resourceStream == null) return null; using var reader = new StreamReader(resourceStream); return reader.ReadToEnd(); } return File.ReadAllText(Path.Combine(this.locResourceDirectory, $"{this.locResourcePrefix}{langCode}.json")); } }