diff --git a/docs/release-notes.md b/docs/release-notes.md index 5d113e34..c3955fc0 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ These changes have not been released yet. * Fixed 'received message' logs shown in non-developer mode. * For modders: + * Added support for content pack translations. * Added `IContentPack.HasFile` method. * `this.Monitor.Log` now defaults to the `Trace` log level instead of `Debug`. * Dropped support for all deprecated APIs. diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index 5384d98f..829a7dc1 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -30,6 +30,9 @@ namespace StardewModdingAPI.Framework /// The content pack's manifest. public IManifest Manifest { get; } + /// Provides translations stored in the content pack's i18n folder. See for more info. + public ITranslationHelper Translation { get; } + /********* ** Public methods @@ -38,12 +41,14 @@ namespace StardewModdingAPI.Framework /// The full path to the content pack's folder. /// The content pack's manifest. /// Provides an API for loading content assets. + /// Provides translations stored in the content pack's i18n folder. /// Encapsulates SMAPI's JSON file parsing. - public ContentPack(string directoryPath, IManifest manifest, IContentHelper content, JsonHelper jsonHelper) + public ContentPack(string directoryPath, IManifest manifest, IContentHelper content, ITranslationHelper translation, JsonHelper jsonHelper) { this.DirectoryPath = directoryPath; this.Manifest = manifest; this.Content = content; + this.Translation = translation; this.JsonHelper = jsonHelper; } diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs index 38514959..32870c2a 100644 --- a/src/SMAPI/Framework/IModMetadata.cs +++ b/src/SMAPI/Framework/IModMetadata.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using StardewModdingAPI.Framework.ModHelpers; using StardewModdingAPI.Framework.ModLoading; using StardewModdingAPI.Toolkit.Framework.Clients.WebApi; using StardewModdingAPI.Toolkit.Framework.ModData; @@ -42,6 +43,9 @@ namespace StardewModdingAPI.Framework /// The content pack instance (if loaded and is true). IContentPack ContentPack { get; } + /// The translations for this mod (if loaded). + TranslationHelper Translations { get; } + /// Writes messages to the console and log file as this mod. IMonitor Monitor { get; } @@ -67,12 +71,14 @@ namespace StardewModdingAPI.Framework /// Set the mod instance. /// The mod instance to set. - IModMetadata SetMod(IMod mod); + /// The translations for this mod (if loaded). + IModMetadata SetMod(IMod mod, TranslationHelper translations); /// Set the mod instance. /// The contentPack instance to set. /// Writes messages to the console and log file. - IModMetadata SetMod(IContentPack contentPack, IMonitor monitor); + /// The translations for this mod (if loaded). + IModMetadata SetMod(IContentPack contentPack, IMonitor monitor, TranslationHelper translations); /// Set the mod-provided API instance. /// The mod-provided API. diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index 4ff021b7..39f2f482 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using StardewModdingAPI.Framework.ModHelpers; using StardewModdingAPI.Toolkit.Framework.Clients.WebApi; using StardewModdingAPI.Toolkit.Framework.ModData; using StardewModdingAPI.Toolkit.Framework.UpdateData; @@ -46,6 +47,9 @@ namespace StardewModdingAPI.Framework.ModLoading /// The content pack instance (if loaded and is true). public IContentPack ContentPack { get; private set; } + /// The translations for this mod (if loaded). + public TranslationHelper Translations { get; private set; } + /// Writes messages to the console and log file as this mod. public IMonitor Monitor { get; private set; } @@ -100,26 +104,30 @@ namespace StardewModdingAPI.Framework.ModLoading /// Set the mod instance. /// The mod instance to set. - public IModMetadata SetMod(IMod mod) + /// The translations for this mod (if loaded). + public IModMetadata SetMod(IMod mod, TranslationHelper translations) { if (this.ContentPack != null) throw new InvalidOperationException("A mod can't be both an assembly mod and content pack."); this.Mod = mod; this.Monitor = mod.Monitor; + this.Translations = translations; return this; } /// Set the mod instance. /// The contentPack instance to set. /// Writes messages to the console and log file. - public IModMetadata SetMod(IContentPack contentPack, IMonitor monitor) + /// The translations for this mod (if loaded). + public IModMetadata SetMod(IContentPack contentPack, IMonitor monitor, TranslationHelper translations) { if (this.Mod != null) throw new InvalidOperationException("A mod can't be both an assembly mod and content pack."); this.ContentPack = contentPack; this.Monitor = monitor; + this.Translations = translations; return this; } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index c0d5603b..937b92e1 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -426,8 +426,8 @@ namespace StardewModdingAPI.Framework LocalizedContentManager.LanguageCode languageCode = this.ContentCore.Language; // update mod translation helpers - foreach (IModMetadata mod in this.ModRegistry.GetAll(contentPacks: false)) - (mod.Mod.Helper.Translation as TranslationHelper)?.SetLocale(locale, languageCode); + foreach (IModMetadata mod in this.ModRegistry.GetAll()) + mod.Translations.SetLocale(locale, languageCode); } /// Run a loop handling console input. @@ -724,8 +724,9 @@ namespace StardewModdingAPI.Framework LogSkip(contentPack, errorPhrase, errorDetails); } } - IModMetadata[] loadedContentPacks = this.ModRegistry.GetAll(assemblyMods: false).ToArray(); - IModMetadata[] loadedMods = this.ModRegistry.GetAll(contentPacks: false).ToArray(); + IModMetadata[] loaded = this.ModRegistry.GetAll().ToArray(); + IModMetadata[] loadedContentPacks = loaded.Where(p => p.IsContentPack).ToArray(); + IModMetadata[] loadedMods = loaded.Where(p => !p.IsContentPack).ToArray(); // unlock content packs this.ModRegistry.AreAllModsLoaded = true; @@ -765,10 +766,10 @@ namespace StardewModdingAPI.Framework } // log mod warnings - this.LogModWarnings(this.ModRegistry.GetAll().ToArray(), skippedMods); + this.LogModWarnings(loaded, skippedMods); // initialise translations - this.ReloadTranslations(loadedMods); + this.ReloadTranslations(loaded); // initialise loaded non-content-pack mods foreach (IModMetadata metadata in loadedMods) @@ -918,8 +919,9 @@ namespace StardewModdingAPI.Framework IManifest manifest = mod.Manifest; IMonitor monitor = this.GetSecondaryMonitor(mod.DisplayName); IContentHelper contentHelper = new ContentHelper(this.ContentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, monitor); - IContentPack contentPack = new ContentPack(mod.DirectoryPath, manifest, contentHelper, jsonHelper); - mod.SetMod(contentPack, monitor); + TranslationHelper translationHelper = new TranslationHelper(manifest.UniqueID, manifest.Name, contentCore.GetLocale(), contentCore.Language); + IContentPack contentPack = new ContentPack(mod.DirectoryPath, manifest, contentHelper, translationHelper, jsonHelper); + mod.SetMod(contentPack, monitor, translationHelper); this.ModRegistry.Add(mod); errorReasonPhrase = null; @@ -981,6 +983,7 @@ namespace StardewModdingAPI.Framework // init mod helpers IMonitor monitor = this.GetSecondaryMonitor(mod.DisplayName); + TranslationHelper translationHelper = new TranslationHelper(manifest.UniqueID, manifest.Name, contentCore.GetLocale(), contentCore.Language); IModHelper modHelper; { IModEvents events = new ModEvents(mod, this.EventManager); @@ -991,13 +994,13 @@ namespace StardewModdingAPI.Framework IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, mod.DisplayName, this.Reflection); IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyFactory, monitor); IMultiplayerHelper multiplayerHelper = new MultiplayerHelper(manifest.UniqueID, this.GameInstance.Multiplayer); - ITranslationHelper translationHelper = new TranslationHelper(manifest.UniqueID, manifest.Name, contentCore.GetLocale(), contentCore.Language); IContentPack CreateFakeContentPack(string packDirPath, IManifest packManifest) { IMonitor packMonitor = this.GetSecondaryMonitor(packManifest.Name); IContentHelper packContentHelper = new ContentHelper(contentCore, packDirPath, packManifest.UniqueID, packManifest.Name, packMonitor); - return new ContentPack(packDirPath, packManifest, packContentHelper, this.Toolkit.JsonHelper); + ITranslationHelper packTranslationHelper = new TranslationHelper(packManifest.UniqueID, packManifest.Name, contentCore.GetLocale(), contentCore.Language); + return new ContentPack(packDirPath, packManifest, packContentHelper, packTranslationHelper, this.Toolkit.JsonHelper); } modHelper = new ModHelper(manifest.UniqueID, mod.DirectoryPath, this.GameInstance.Input, events, contentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); @@ -1009,7 +1012,7 @@ namespace StardewModdingAPI.Framework modEntry.Monitor = monitor; // track mod - mod.SetMod(modEntry); + mod.SetMod(modEntry, translationHelper); this.ModRegistry.Add(mod); return true; } @@ -1024,7 +1027,7 @@ namespace StardewModdingAPI.Framework /// Write a summary of mod warnings to the console and log. /// The loaded mods. /// The mods which were skipped, along with the friendly and developer reasons. - private void LogModWarnings(IModMetadata[] mods, IDictionary> skippedMods) + private void LogModWarnings(IEnumerable mods, IDictionary> skippedMods) { // get mods with warnings IModMetadata[] modsWithWarnings = mods.Where(p => p.Warnings != ModWarning.None).ToArray(); @@ -1164,9 +1167,6 @@ namespace StardewModdingAPI.Framework JsonHelper jsonHelper = this.Toolkit.JsonHelper; foreach (IModMetadata metadata in mods) { - if (metadata.IsContentPack) - throw new InvalidOperationException("Can't reload translations for a content pack."); - // read translation files IDictionary> translations = new Dictionary>(); DirectoryInfo translationsDir = new DirectoryInfo(Path.Combine(metadata.DirectoryPath, "i18n")); @@ -1216,8 +1216,7 @@ namespace StardewModdingAPI.Framework } // update translation - TranslationHelper translationHelper = (TranslationHelper)metadata.Mod.Helper.Translation; - translationHelper.SetTranslations(translations); + metadata.Translations.SetTranslations(translations); } } diff --git a/src/SMAPI/IContentPack.cs b/src/SMAPI/IContentPack.cs index 32cbc6fc..7085c538 100644 --- a/src/SMAPI/IContentPack.cs +++ b/src/SMAPI/IContentPack.cs @@ -17,6 +17,9 @@ namespace StardewModdingAPI /// The content pack's manifest. IManifest Manifest { get; } + /// Provides translations stored in the content pack's i18n folder. See for more info. + ITranslationHelper Translation { get; } + /********* ** Public methods