using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Xml.Serialization;
namespace OpenTap.Translation;
///
/// Functions for working with translations
///
public static class TranslationManager
{
private static readonly ITranslator Translator = new Translator();
internal static string CultureAsString(CultureInfo culture)
{
if (CultureInfo.InvariantCulture.Equals(culture)) return "Neutral";
return $"{culture.NativeName} ({culture.EnglishName})";
}
///
/// The default language.
///
[Browsable(false)]
[XmlIgnore]
internal static CultureInfo NeutralLanguage => CultureInfo.InvariantCulture;
///
/// The list of languages supported by installed resource files.
///
public static IEnumerable SupportedLanguages => Translator.SupportedLanguages;
///
/// Get an appropriate DisplayAttribute for the specified reflection data in the requested language.
///
///
///
///
public static DisplayAttribute TranslateMember(IReflectionData mem, CultureInfo language = null)
{
return Translator.TranslateMember(mem, language ?? EngineSettings.Current.Language);
}
///
/// Get an appropriate DisplayAttribute for the specified enum in the requested language.
///
///
///
///
public static DisplayAttribute TranslateEnum(Enum e, CultureInfo language = null)
{
return Translator.TranslateEnum(e, language ?? EngineSettings.Current.Language);
}
///
/// The directory containing translation resource files.
///
public static string TranslationDirectory => Path.Combine(ExecutorClient.ExeDir, "Languages");
///
/// Get an appropriate DisplayAttribute for the specified enum in the requested language.
///
///
///
///
public static string TranslateKey(string key, CultureInfo language = null)
{
return Translator.TranslateString(key, language ?? EngineSettings.Current.Language);
}
///
/// Look for a translated version of the input member and validate the format parameters.
/// If no translation is found, or the parameter count does not match, return the neutral string.
///
/// The string localizer which owns the translated string.
/// The neutral (non-translated) string.
/// Lookup key for finding the translation.
/// The desired translation language
/// The translated string, if a translation exists, and the string format parameters in the translated string matches the source string. Otherwise, returns the neutral string.
public static FormatString TranslateFormat(this IStringLocalizer stringLocalizer, string neutral,
[CallerMemberName] string key = null,
CultureInfo language = null)
{
var translated = Translate(stringLocalizer, neutral, key, language);
if (!ReferenceEquals(neutral, translated))
{
var c1 = CountTemplates(neutral);
var c2 = CountTemplates(translated);
if (c1 != c2)
{
log.ErrorOnce(neutral, $"Template parameter count mismatch detected.\n" +
$"'{neutral}' has {c1} template parameters.\n" +
$"'{translated}' has {c2} template parameters.\n" +
$"The first string will be preferred over the second string.");
// Use neutral string instead
return new FormatString(neutral);
}
}
return new FormatString(translated);
}
///
/// Look for a translated version of the input member. If no translation is found, return the neutral string.
///
/// The string localizer which owns the translated string.
/// The neutral (non-translated) string.
/// Lookup key for finding the translation.
/// The desired translation language
/// The translated string, if a translation exists. Otherwise, returns the neutral string.
public static string Translate(this IStringLocalizer stringLocalizer, string neutral,
[CallerMemberName] string key = null, CultureInfo language = null)
{
return TranslateFunction(stringLocalizer, neutral, key, language);
}
// We add this small level of indirection in order to inject a hook to automatically
// populate a list of lookup keys and strings for creating translations.
// Do not rename this field or change the delegate!!
private static Func TranslateFunction = _translate;
private static string _translate(this IStringLocalizer stringLocalizer, string neutral,
[CallerMemberName] string key = null, CultureInfo language = null)
{
language ??= EngineSettings.Current.Language;
if (language.Equals(NeutralLanguage) || string.IsNullOrWhiteSpace(key))
return neutral;
var type = stringLocalizer.GetType();
if (type.IsGenericType)
type = type.GetGenericTypeDefinition();
var fullKey = type.FullName + $".{key}";
return TranslateKey(fullKey, language) ?? neutral;
}
static int CountTemplates(string s)
{
var chars = s.ToArray();
int n = 0;
bool bracketOpen = false;
for (int i = 0; i < chars.Length; i++)
{
if (!bracketOpen && chars[i] == '{')
{
bracketOpen = true;
}
else if (bracketOpen && chars[i] == '{')
{
/* double bracket escapes */
bracketOpen = false;
}
else if (bracketOpen && chars[i] == '}')
{
bracketOpen = false;
n++;
}
}
return n;
}
private static readonly TraceSource log = Log.CreateSource("Translation Manager");
}
///
/// An IStringLocalizer can define localizable strings by calling the Translate() or TranslateFormat() method from a public computed property.
/// Example: public string MyString => Translate("Neutral string") // returns "Translated string" if available
/// A localizable string defaults to the neutral string, unless, OpenTAP is configured to display in a different
/// language in which a translation is available.
///
public interface IStringLocalizer : ITapPlugin
{
}
///
/// A FormatString is a string which requires format parameters.
///
public class FormatString(string format)
{
///
/// Creates a formatted string based on this instance and the provided format arguments.
///
/// The format arguments
/// The formatted string
public string Format(params object[] args) => string.Format(format, args);
}