From 929dccb75a1405737975d76648e015a3e7c00177 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 7 Oct 2017 23:07:10 -0400 Subject: reorganise repo structure --- src/SMAPI/Translation.cs | 154 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 src/SMAPI/Translation.cs (limited to 'src/SMAPI/Translation.cs') diff --git a/src/SMAPI/Translation.cs b/src/SMAPI/Translation.cs new file mode 100644 index 00000000..ce344f81 --- /dev/null +++ b/src/SMAPI/Translation.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace StardewModdingAPI +{ + /// A translation string with a fluent API to customise it. + public class Translation + { + /********* + ** Properties + *********/ + /// The placeholder text when the translation is null or empty, where {0} is the translation key. + internal const string PlaceholderText = "(no translation:{0})"; + + /// The name of the relevant mod for error messages. + private readonly string ModName; + + /// The locale for which the translation was fetched. + private readonly string Locale; + + /// The underlying translation text. + private readonly string Text; + + /// The value to return if the translations is undefined. + private readonly string Placeholder; + + + /********* + ** Accessors + *********/ + /// The original translation key. + public string Key { get; } + + + /********* + ** Public methods + *********/ + /// Construct an isntance. + /// The name of the relevant mod for error messages. + /// The locale for which the translation was fetched. + /// The translation key. + /// The underlying translation text. + internal Translation(string modName, string locale, string key, string text) + : this(modName, locale, key, text, string.Format(Translation.PlaceholderText, key)) { } + + /// Construct an isntance. + /// The name of the relevant mod for error messages. + /// The locale for which the translation was fetched. + /// The translation key. + /// The underlying translation text. + /// The value to return if the translations is undefined. + internal Translation(string modName, string locale, string key, string text, string placeholder) + { + this.ModName = modName; + this.Locale = locale; + this.Key = key; + this.Text = text; + this.Placeholder = placeholder; + } + + /// Throw an exception if the translation text is null or empty. + /// There's no available translation matching the requested key and locale. + public Translation Assert() + { + if (!this.HasValue()) + throw new KeyNotFoundException($"The '{this.ModName}' mod doesn't have a translation with key '{this.Key}' for the '{this.Locale}' locale or its fallbacks."); + return this; + } + + /// Replace the text if it's null or empty. If you set a null or empty value, the translation will show the fallback "no translation" placeholder (see if you want to disable that). Returns a new instance if changed. + /// The default value. + public Translation Default(string @default) + { + return this.HasValue() + ? this + : new Translation(this.ModName, this.Locale, this.Key, @default); + } + + /// Whether to return a "no translation" placeholder if the translation is null or empty. Returns a new instance. + /// Whether to return a placeholder. + public Translation UsePlaceholder(bool use) + { + return new Translation(this.ModName, this.Locale, this.Key, this.Text, use ? string.Format(Translation.PlaceholderText, this.Key) : null); + } + + /// 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" }), a dictionary, or a class instance. + /// The argument is null. + public Translation Tokens(object tokens) + { + if (string.IsNullOrWhiteSpace(this.Text) || tokens == null) + return this; + + // get dictionary of tokens + IDictionary tokenLookup = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + { + // from dictionary + if (tokens is IDictionary inputLookup) + { + foreach (DictionaryEntry entry in inputLookup) + { + string key = entry.Key?.ToString().Trim(); + if (key != null) + tokenLookup[key] = entry.Value?.ToString(); + } + } + + // from object properties + else + { + Type type = tokens.GetType(); + foreach (PropertyInfo prop in type.GetProperties()) + tokenLookup[prop.Name] = prop.GetValue(tokens)?.ToString(); + foreach (FieldInfo field in type.GetFields()) + tokenLookup[field.Name] = field.GetValue(tokens)?.ToString(); + } + } + + // format translation + string text = Regex.Replace(this.Text, @"{{([ \w\.\-]+)}}", match => + { + string key = match.Groups[1].Value.Trim(); + return tokenLookup.TryGetValue(key, out string value) + ? value + : match.Value; + }); + return new Translation(this.ModName, this.Locale, this.Key, text); + } + + /// Get whether the translation has a defined value. + public bool HasValue() + { + return !string.IsNullOrEmpty(this.Text); + } + + /// Get the translation text. Calling this method isn't strictly necessary, since you can assign a value directly to a string. + public override string ToString() + { + return this.Placeholder != null && !this.HasValue() + ? this.Placeholder + : this.Text; + } + + /// Get a string representation of the given translation. + /// The translation key. + public static implicit operator string(Translation translation) + { + return translation?.ToString(); + } + } +} -- cgit