using System;
using System.Collections.Generic;
using System.Linq;
using StardewValley;
namespace StardewModdingAPI.Framework.ModHelpers
{
/// 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 : BaseHelper, ITranslationHelper
{
/*********
** Fields
*********/
/// 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 unique ID of the relevant mod.
/// The name of the relevant mod for error messages.
/// The initial locale.
/// The game's current language code.
public TranslationHelper(string modID, string modName, string locale, LocalizedContentManager.LanguageCode languageCode)
: base(modID)
{
// save data
this.ModName = modName;
// set locale
this.SetLocale(locale, languageCode);
}
/// Get all translations for the current locale.
public IEnumerable GetTranslations()
{
return this.ForLocale.Values.ToArray();
}
/// Get a translation for the current locale.
/// The translation key.
public Translation Get(string key)
{
this.ForLocale.TryGetValue(key, out Translation translation);
return translation ?? new Translation(this.ModName, this.Locale, key, null);
}
/// 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)
{
// 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.
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.Key, new Translation(this.ModName, this.Locale, pair.Key, pair.Value));
}
}
}
/*********
** 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";
}
}
}