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) --- .../Framework/TranslationHelper.cs | 117 +++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/StardewModdingAPI/Framework/TranslationHelper.cs (limited to 'src/StardewModdingAPI/Framework/TranslationHelper.cs') 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 From 12ffd9c334906e2256a1c32976b6a07f75027f25 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 28 May 2017 01:06:35 -0400 Subject: add shortcut indexer to translation API (#296) --- src/StardewModdingAPI/Framework/TranslationHelper.cs | 4 ++++ src/StardewModdingAPI/ITranslationHelper.cs | 4 ++++ 2 files changed, 8 insertions(+) (limited to 'src/StardewModdingAPI/Framework/TranslationHelper.cs') diff --git a/src/StardewModdingAPI/Framework/TranslationHelper.cs b/src/StardewModdingAPI/Framework/TranslationHelper.cs index dece6214..c3104d1b 100644 --- a/src/StardewModdingAPI/Framework/TranslationHelper.cs +++ b/src/StardewModdingAPI/Framework/TranslationHelper.cs @@ -29,6 +29,10 @@ namespace StardewModdingAPI.Framework /// The game's current language code. public LocalizedContentManager.LanguageCode LocaleEnum { get; private set; } + /// Get a translation for the current locale. This is a convenience shortcut for . + /// The translation key. + public Translation this[string key] => this.Translate(key); + /********* ** Public methods diff --git a/src/StardewModdingAPI/ITranslationHelper.cs b/src/StardewModdingAPI/ITranslationHelper.cs index 84571d0e..297a0d5a 100644 --- a/src/StardewModdingAPI/ITranslationHelper.cs +++ b/src/StardewModdingAPI/ITranslationHelper.cs @@ -15,6 +15,10 @@ namespace StardewModdingAPI /// The game's current language code. LocalizedContentManager.LanguageCode LocaleEnum { get; } + /// Get a translation for the current locale. This is a convenience shortcut for . + /// The translation key. + Translation this[string key] { get; } + /********* ** Public methods -- cgit From 5af58c7b18a120ce47f230ede7f116678d97e038 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 28 May 2017 01:49:21 -0400 Subject: refactor translation init for reuse (#296) --- src/StardewModdingAPI.Tests/TranslationTests.cs | 6 +-- src/StardewModdingAPI/Framework/ModHelper.cs | 5 +- .../Framework/TranslationHelper.cs | 20 +++++-- src/StardewModdingAPI/Program.cs | 63 ++++++++++++---------- 4 files changed, 56 insertions(+), 38 deletions(-) (limited to 'src/StardewModdingAPI/Framework/TranslationHelper.cs') diff --git a/src/StardewModdingAPI.Tests/TranslationTests.cs b/src/StardewModdingAPI.Tests/TranslationTests.cs index 3e6e6499..76da8706 100644 --- a/src/StardewModdingAPI.Tests/TranslationTests.cs +++ b/src/StardewModdingAPI.Tests/TranslationTests.cs @@ -40,7 +40,7 @@ namespace StardewModdingAPI.Tests var data = new Dictionary>(); // act - ITranslationHelper helper = new TranslationHelper("ModName", "en", LocalizedContentManager.LanguageCode.en, data); + ITranslationHelper helper = new TranslationHelper("ModName", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); Translation translation = helper.Translate("key"); // assert @@ -62,7 +62,7 @@ namespace StardewModdingAPI.Tests // act var actual = new Dictionary>(); - TranslationHelper helper = new TranslationHelper("ModName", "en", LocalizedContentManager.LanguageCode.en, data); + TranslationHelper helper = new TranslationHelper("ModName", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); foreach (string locale in expected.Keys) { this.AssertSetLocale(helper, locale, LocalizedContentManager.LanguageCode.en); @@ -86,7 +86,7 @@ namespace StardewModdingAPI.Tests // act var actual = new Dictionary>(); - TranslationHelper helper = new TranslationHelper("ModName", "en", LocalizedContentManager.LanguageCode.en, data); + TranslationHelper helper = new TranslationHelper("ModName", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); foreach (string locale in expected.Keys) { this.AssertSetLocale(helper, locale, LocalizedContentManager.LanguageCode.en); diff --git a/src/StardewModdingAPI/Framework/ModHelper.cs b/src/StardewModdingAPI/Framework/ModHelper.cs index 8c578dbe..947b1ae8 100644 --- a/src/StardewModdingAPI/Framework/ModHelper.cs +++ b/src/StardewModdingAPI/Framework/ModHelper.cs @@ -47,10 +47,9 @@ namespace StardewModdingAPI.Framework /// 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, string modDirectory, JsonHelper jsonHelper, IModRegistry modRegistry, CommandManager commandManager, SContentManager contentManager, IReflectionHelper reflection, ITranslationHelper translations) + public ModHelper(string displayName, string modDirectory, JsonHelper jsonHelper, IModRegistry modRegistry, CommandManager commandManager, SContentManager contentManager, IReflectionHelper reflection) { // validate if (string.IsNullOrWhiteSpace(modDirectory)) @@ -69,7 +68,7 @@ namespace StardewModdingAPI.Framework this.ModRegistry = modRegistry; this.ConsoleCommands = new CommandHelper(displayName, commandManager); this.Reflection = reflection; - this.Translation = translations; + this.Translation = new TranslationHelper(displayName, contentManager.GetLocale(), contentManager.GetCurrentLanguage()); } /**** diff --git a/src/StardewModdingAPI/Framework/TranslationHelper.cs b/src/StardewModdingAPI/Framework/TranslationHelper.cs index c3104d1b..ebcd69b1 100644 --- a/src/StardewModdingAPI/Framework/TranslationHelper.cs +++ b/src/StardewModdingAPI/Framework/TranslationHelper.cs @@ -41,13 +41,10 @@ namespace StardewModdingAPI.Framework /// 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) + public TranslationHelper(string modName, string locale, LocalizedContentManager.LanguageCode languageCode) { // 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); @@ -67,6 +64,21 @@ namespace StardewModdingAPI.Framework return new Translation(this.ModName, this.Locale, key, text); } + /// Set the translations to use. + /// The translations to use. + internal TranslationHelper SetTranslations(IDictionary> translations) + { + // reset translations + this.All.Clear(); + foreach (var pair in translations) + this.All[pair.Key] = new Dictionary(pair.Value, StringComparer.InvariantCultureIgnoreCase); + + // rebuild cache + this.SetLocale(this.Locale, this.LocaleEnum); + + return this; + } + /// Set the current locale and precache translations. /// The current locale. /// The game's current language code. diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 47ba7c30..6939a03d 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -640,36 +640,9 @@ namespace StardewModdingAPI continue; } - // get translations - TranslationHelper translations; - { - IDictionary> translationValues = new Dictionary>(); - - // read translation files - DirectoryInfo translationsDir = new DirectoryInfo(Path.Combine(metadata.DirectoryPath, "i18n")); - if (translationsDir.Exists) - { - foreach (FileInfo file in translationsDir.EnumerateFiles("*.json")) - { - string locale = Path.GetFileNameWithoutExtension(file.Name.ToLower().Trim()); - try - { - translationValues[locale] = jsonHelper.ReadJsonFile>(file.FullName); - } - catch (Exception ex) - { - this.Monitor.Log($"Couldn't read {metadata.DisplayName}'s i18n/{locale}.json file: {ex.GetLogSummary()}"); - } - } - } - - // create translation helper - translations = new TranslationHelper(metadata.DisplayName, contentManager.GetLocale(), contentManager.GetCurrentLanguage(), translationValues); - } - // inject data mod.ModManifest = manifest; - mod.Helper = new ModHelper(metadata.DisplayName, metadata.DirectoryPath, jsonHelper, this.ModRegistry, this.CommandManager, contentManager, this.Reflection, translations); + mod.Helper = new ModHelper(metadata.DisplayName, metadata.DirectoryPath, jsonHelper, this.ModRegistry, this.CommandManager, contentManager, this.Reflection); mod.Monitor = this.GetSecondaryMonitor(metadata.DisplayName); mod.PathOnDisk = metadata.DirectoryPath; @@ -685,6 +658,9 @@ namespace StardewModdingAPI } } + // initialise translations + this.ReloadTranslations(); + // initialise loaded mods foreach (IModMetadata metadata in this.ModRegistry.GetMods()) { @@ -711,6 +687,37 @@ namespace StardewModdingAPI return modsLoaded; } + /// Reload translations for all mods. + private void ReloadTranslations() + { + JsonHelper jsonHelper = new JsonHelper(); + foreach (IModMetadata metadata in this.ModRegistry.GetMods()) + { + // read translation files + IDictionary> translations = new Dictionary>(); + DirectoryInfo translationsDir = new DirectoryInfo(Path.Combine(metadata.DirectoryPath, "i18n")); + if (translationsDir.Exists) + { + foreach (FileInfo file in translationsDir.EnumerateFiles("*.json")) + { + string locale = Path.GetFileNameWithoutExtension(file.Name.ToLower().Trim()); + try + { + translations[locale] = jsonHelper.ReadJsonFile>(file.FullName); + } + catch (Exception ex) + { + this.Monitor.Log($"Couldn't read {metadata.DisplayName}'s i18n/{locale}.json file: {ex.GetLogSummary()}"); + } + } + } + + // update translation + TranslationHelper translationHelper = (TranslationHelper)metadata.Mod.Helper.Translation; + translationHelper.SetTranslations(translations); + } + } + /// The method called when the user submits the help command in the console. /// The command name. /// The command arguments. -- cgit From 2958381b54d1f46ae97ae196d3d046fee9264a0e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 28 May 2017 10:51:58 -0400 Subject: shorten translation method name, remove helper method (#296) --- src/StardewModdingAPI.Tests/TranslationTests.cs | 6 +++--- src/StardewModdingAPI/Framework/ModHelper.cs | 10 ---------- src/StardewModdingAPI/Framework/TranslationHelper.cs | 6 +----- src/StardewModdingAPI/IModHelper.cs | 7 ------- src/StardewModdingAPI/ITranslationHelper.cs | 6 +----- 5 files changed, 5 insertions(+), 30 deletions(-) (limited to 'src/StardewModdingAPI/Framework/TranslationHelper.cs') diff --git a/src/StardewModdingAPI.Tests/TranslationTests.cs b/src/StardewModdingAPI.Tests/TranslationTests.cs index 76da8706..1778ed6a 100644 --- a/src/StardewModdingAPI.Tests/TranslationTests.cs +++ b/src/StardewModdingAPI.Tests/TranslationTests.cs @@ -41,7 +41,7 @@ namespace StardewModdingAPI.Tests // act ITranslationHelper helper = new TranslationHelper("ModName", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); - Translation translation = helper.Translate("key"); + Translation translation = helper.Get("key"); // assert Assert.AreEqual("en", helper.Locale, "The locale doesn't match the input value."); @@ -78,7 +78,7 @@ namespace StardewModdingAPI.Tests } [Test(Description = "Assert that the translations returned by the helper has the expected text.")] - public void Helper_Translate_ReturnsExpectedText() + public void Helper_Get_ReturnsExpectedText() { // arrange var data = this.GetSampleData(); @@ -92,7 +92,7 @@ namespace StardewModdingAPI.Tests this.AssertSetLocale(helper, locale, LocalizedContentManager.LanguageCode.en); actual[locale] = new Dictionary(); foreach (string key in expected[locale].Keys) - actual[locale][key] = helper.Translate(key); + actual[locale][key] = helper.Get(key); } // assert diff --git a/src/StardewModdingAPI/Framework/ModHelper.cs b/src/StardewModdingAPI/Framework/ModHelper.cs index 947b1ae8..5a8ce459 100644 --- a/src/StardewModdingAPI/Framework/ModHelper.cs +++ b/src/StardewModdingAPI/Framework/ModHelper.cs @@ -119,16 +119,6 @@ namespace StardewModdingAPI.Framework } - /**** - ** 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/TranslationHelper.cs b/src/StardewModdingAPI/Framework/TranslationHelper.cs index ebcd69b1..e4a178e2 100644 --- a/src/StardewModdingAPI/Framework/TranslationHelper.cs +++ b/src/StardewModdingAPI/Framework/TranslationHelper.cs @@ -29,10 +29,6 @@ namespace StardewModdingAPI.Framework /// The game's current language code. public LocalizedContentManager.LanguageCode LocaleEnum { get; private set; } - /// Get a translation for the current locale. This is a convenience shortcut for . - /// The translation key. - public Translation this[string key] => this.Translate(key); - /********* ** Public methods @@ -58,7 +54,7 @@ namespace StardewModdingAPI.Framework /// Get a translation for the current locale. /// The translation key. - public Translation Translate(string key) + public Translation Get(string key) { this.ForLocale.TryGetValue(key, out string text); return new Translation(this.ModName, this.Locale, key, text); diff --git a/src/StardewModdingAPI/IModHelper.cs b/src/StardewModdingAPI/IModHelper.cs index 38bfd366..116e8508 100644 --- a/src/StardewModdingAPI/IModHelper.cs +++ b/src/StardewModdingAPI/IModHelper.cs @@ -54,12 +54,5 @@ /// The file path relative to the mod directory. /// The model to save. void WriteJsonFile(string path, TModel model) where TModel : class; - - /**** - ** Translations - ****/ - /// Get a translation for the current locale. This is a convenience shortcut for . - /// The translation key. - Translation Translate(string key); } } \ No newline at end of file diff --git a/src/StardewModdingAPI/ITranslationHelper.cs b/src/StardewModdingAPI/ITranslationHelper.cs index 297a0d5a..23980a1b 100644 --- a/src/StardewModdingAPI/ITranslationHelper.cs +++ b/src/StardewModdingAPI/ITranslationHelper.cs @@ -15,10 +15,6 @@ namespace StardewModdingAPI /// The game's current language code. LocalizedContentManager.LanguageCode LocaleEnum { get; } - /// Get a translation for the current locale. This is a convenience shortcut for . - /// The translation key. - Translation this[string key] { get; } - /********* ** Public methods @@ -28,6 +24,6 @@ namespace StardewModdingAPI /// Get a translation for the current locale. /// The translation key. - Translation Translate(string key); + Translation Get(string key); } } -- cgit From 90275f1d56a6d74f93021d9b15a742f381ec4bfd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 28 May 2017 17:53:44 -0400 Subject: add shortcut for translation with tokens (#296) --- src/StardewModdingAPI/Framework/TranslationHelper.cs | 8 ++++++++ src/StardewModdingAPI/ITranslationHelper.cs | 5 +++++ src/StardewModdingAPI/Translation.cs | 4 ++-- 3 files changed, 15 insertions(+), 2 deletions(-) (limited to 'src/StardewModdingAPI/Framework/TranslationHelper.cs') diff --git a/src/StardewModdingAPI/Framework/TranslationHelper.cs b/src/StardewModdingAPI/Framework/TranslationHelper.cs index e4a178e2..1e73c425 100644 --- a/src/StardewModdingAPI/Framework/TranslationHelper.cs +++ b/src/StardewModdingAPI/Framework/TranslationHelper.cs @@ -60,6 +60,14 @@ namespace StardewModdingAPI.Framework return new Translation(this.ModName, this.Locale, key, text); } + /// Get a translation for the current locale. + /// The translation key. + /// An object containing token key/value pairs. This can be an anonymous object (like new { value = 42, name = "Cranberries" }), a dictionary, or a class instance. + public Translation Get(string key, object tokens) + { + return this.Get(key).Tokens(tokens); + } + /// Set the translations to use. /// The translations to use. internal TranslationHelper SetTranslations(IDictionary> translations) diff --git a/src/StardewModdingAPI/ITranslationHelper.cs b/src/StardewModdingAPI/ITranslationHelper.cs index 23980a1b..15f6b3c8 100644 --- a/src/StardewModdingAPI/ITranslationHelper.cs +++ b/src/StardewModdingAPI/ITranslationHelper.cs @@ -25,5 +25,10 @@ namespace StardewModdingAPI /// Get a translation for the current locale. /// The translation key. Translation Get(string key); + + /// Get a translation for the current locale. + /// The translation key. + /// An object containing token key/value pairs. This can be an anonymous object (like new { value = 42, name = "Cranberries" }), a dictionary, or a class instance. + Translation Get(string key, object tokens); } } diff --git a/src/StardewModdingAPI/Translation.cs b/src/StardewModdingAPI/Translation.cs index 31a1b7e1..b86efc10 100644 --- a/src/StardewModdingAPI/Translation.cs +++ b/src/StardewModdingAPI/Translation.cs @@ -83,12 +83,12 @@ namespace StardewModdingAPI } /// Replace tokens in the text like {{value}} with the given values. Returns a new instance. - /// An object containing token key/value pairs. This can be an anonymous object (like new { value = 42, name = "Cranberries" }) or a dictionary of token values. + /// An object containing token key/value pairs. This can be an anonymous object (like new { value = 42, name = "Cranberries" }), a dictionary, or a class instance. /// The argument is null. public Translation Tokens(object tokens) { if (tokens == null) - throw new ArgumentNullException(nameof(tokens)); + return this; // get dictionary of tokens IDictionary tokenLookup = new Dictionary(StringComparer.InvariantCultureIgnoreCase); -- cgit