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 translation key.
private readonly string Key;
/// The underlying translation text.
private readonly string Text;
/// The value to return if the translations is undefined.
private readonly string Placeholder;
/*********
** 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" }) or a dictionary of token values.
/// The argument is null.
public Translation Tokens(object tokens)
{
if (tokens == null)
throw new ArgumentNullException(nameof(tokens));
// 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();
}
}
}