using System; using System.Collections.Generic; using StardewValley; namespace StardewModdingAPI.Framework { /// Provides translations stored in the mod's i18n folder, with one file per locale (like en.json) containing a flat key => value structure. Translations are fetched with locale fallback, so missing translations are filled in from broader locales (like pt-BR.json < pt.json < default.json). internal class TranslationHelper : ITranslationHelper { /********* ** Properties *********/ /// The name of the relevant mod for error messages. private readonly string ModName; /// The translations for each locale. private readonly IDictionary> All = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); /// The translations for the current locale, with locale fallback taken into account. private IDictionary ForLocale; /********* ** Accessors *********/ /// The current locale. public string Locale { get; private set; } /// The game's current language code. public LocalizedContentManager.LanguageCode LocaleEnum { get; private set; } /********* ** Public methods *********/ /// Construct an instance. /// The name of the relevant mod for error messages. /// The initial locale. /// The game's current language code. /// The translations for each locale. public TranslationHelper(string modName, string locale, LocalizedContentManager.LanguageCode languageCode, IDictionary> translations) { // save data this.ModName = modName; foreach (var pair in translations) this.All[pair.Key] = new Dictionary(pair.Value, StringComparer.InvariantCultureIgnoreCase); // set locale this.SetLocale(locale, languageCode); } /// Get all translations for the current locale. public IDictionary GetTranslations() { return new Dictionary(this.ForLocale, StringComparer.InvariantCultureIgnoreCase); } /// Get a translation for the current locale. /// The translation key. public Translation Translate(string key) { this.ForLocale.TryGetValue(key, out string text); return new Translation(this.ModName, this.Locale, key, text); } /// Set the current locale and precache translations. /// The current locale. /// The game's current language code. internal void SetLocale(string locale, LocalizedContentManager.LanguageCode localeEnum) { this.Locale = locale.ToLower().Trim(); this.LocaleEnum = localeEnum; this.ForLocale = new Dictionary(StringComparer.InvariantCultureIgnoreCase); foreach (string next in this.GetRelevantLocales(this.Locale)) { // skip if locale not defined if (!this.All.TryGetValue(next, out IDictionary translations)) continue; // add missing translations foreach (var pair in translations) { if (!this.ForLocale.ContainsKey(pair.Key)) this.ForLocale.Add(pair); } } } /********* ** Private methods *********/ /// Get the locales which can provide translations for the given locale, in precedence order. /// The locale for which to find valid locales. private IEnumerable GetRelevantLocales(string locale) { // given locale yield return locale; // broader locales (like pt-BR => pt) while (true) { int dashIndex = locale.LastIndexOf('-'); if (dashIndex <= 0) break; locale = locale.Substring(0, dashIndex); yield return locale; } // default if (locale != "default") yield return "default"; } } }