From 79dabe26717654364d50c927678f52caed1ab93c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 24 May 2017 13:48:17 -0400 Subject: add translation API (#296) --- src/StardewModdingAPI/Framework/CommandHelper.cs | 2 +- src/StardewModdingAPI/Framework/ModHelper.cs | 19 +++- src/StardewModdingAPI/Framework/SContentManager.cs | 6 ++ .../Framework/TranslationHelper.cs | 117 +++++++++++++++++++++ 4 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/TranslationHelper.cs (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/CommandHelper.cs b/src/StardewModdingAPI/Framework/CommandHelper.cs index 2e9dea8e..86734fc5 100644 --- a/src/StardewModdingAPI/Framework/CommandHelper.cs +++ b/src/StardewModdingAPI/Framework/CommandHelper.cs @@ -50,4 +50,4 @@ namespace StardewModdingAPI.Framework return this.CommandManager.Trigger(name, arguments); } } -} \ No newline at end of file +} diff --git a/src/StardewModdingAPI/Framework/ModHelper.cs b/src/StardewModdingAPI/Framework/ModHelper.cs index f939b83c..8c578dbe 100644 --- a/src/StardewModdingAPI/Framework/ModHelper.cs +++ b/src/StardewModdingAPI/Framework/ModHelper.cs @@ -32,22 +32,25 @@ namespace StardewModdingAPI.Framework /// An API for managing console commands. public ICommandHelper ConsoleCommands { get; } + /// 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). + public ITranslationHelper Translation { get; } + /********* ** Public methods *********/ /// Construct an instance. /// The mod's display name. - /// The manifest for the associated mod. /// The full path to the mod's folder. /// Encapsulate SMAPI's JSON parsing. /// Metadata about loaded mods. /// Manages console commands. /// The content manager which loads content assets. /// Simplifies access to private game code. + /// Provides translations stored in the mod folder. /// An argument is null or empty. /// The path does not exist on disk. - public ModHelper(string displayName, IManifest manifest, string modDirectory, JsonHelper jsonHelper, IModRegistry modRegistry, CommandManager commandManager, SContentManager contentManager, IReflectionHelper reflection) + public ModHelper(string displayName, string modDirectory, JsonHelper jsonHelper, IModRegistry modRegistry, CommandManager commandManager, SContentManager contentManager, IReflectionHelper reflection, ITranslationHelper translations) { // validate if (string.IsNullOrWhiteSpace(modDirectory)) @@ -66,6 +69,7 @@ namespace StardewModdingAPI.Framework this.ModRegistry = modRegistry; this.ConsoleCommands = new CommandHelper(displayName, commandManager); this.Reflection = reflection; + this.Translation = translations; } /**** @@ -115,6 +119,17 @@ namespace StardewModdingAPI.Framework this.JsonHelper.WriteJsonFile(path, model); } + + /**** + ** Translation + ****/ + /// Get a translation for the current locale. This is a convenience shortcut for . + /// The translation key. + public Translation Translate(string key) + { + return this.Translation.Translate(key); + } + /**** ** Disposal ****/ diff --git a/src/StardewModdingAPI/Framework/SContentManager.cs b/src/StardewModdingAPI/Framework/SContentManager.cs index 54349a91..acd3e108 100644 --- a/src/StardewModdingAPI/Framework/SContentManager.cs +++ b/src/StardewModdingAPI/Framework/SContentManager.cs @@ -145,6 +145,12 @@ namespace StardewModdingAPI.Framework this.Cache[assetName] = value; } + /// Get the current content locale. + public string GetLocale() + { + return this.GetKeyLocale.Invoke(); + } + /********* ** Private methods *********/ diff --git a/src/StardewModdingAPI/Framework/TranslationHelper.cs b/src/StardewModdingAPI/Framework/TranslationHelper.cs new file mode 100644 index 00000000..dece6214 --- /dev/null +++ b/src/StardewModdingAPI/Framework/TranslationHelper.cs @@ -0,0 +1,117 @@ +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"; + } + } +} -- cgit