using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using CheapLoc;
using Serilog;
namespace Dalamud;
///
/// Class handling localization.
///
[ServiceManager.ProvidedService]
public class Localization : IServiceType
{
///
/// Array of language codes which have a valid translation in Dalamud.
///
public static readonly string[] ApplicableLangCodes = { "de", "ja", "fr", "it", "es", "ko", "no", "ru", "zh", "tw" };
private const string FallbackLangCode = "en";
private readonly string locResourceDirectory;
private readonly string locResourcePrefix;
private readonly bool useEmbedded;
private readonly Assembly assembly;
///
/// Initializes a new instance of the class.
///
/// The working directory to load language files from.
/// The prefix on the loc resource file name (e.g. dalamud_).
/// Use embedded loc resource files.
public Localization(string locResourceDirectory, string locResourcePrefix = "", bool useEmbedded = false)
{
this.DalamudLanguageCultureInfo = CultureInfo.InvariantCulture;
this.locResourceDirectory = locResourceDirectory;
this.locResourcePrefix = locResourcePrefix;
this.useEmbedded = useEmbedded;
this.assembly = Assembly.GetCallingAssembly();
}
///
/// Delegate for the event that occurs when the language is changed.
///
/// The language code of the new language.
public delegate void LocalizationChangedDelegate(string langCode);
///
/// Event that occurs when the language is changed.
///
public event LocalizationChangedDelegate? LocalizationChanged;
///
/// Gets an instance of that corresponds to the language configured from Dalamud Settings.
///
public CultureInfo DalamudLanguageCultureInfo { get; private set; }
///
/// Gets an instance of that corresponds to a Dalamud .
///
/// The language code which should be in .
/// The corresponding instance of .
public static CultureInfo GetCultureInfoFromLangCode(string langCode) =>
CultureInfo.GetCultureInfo(langCode switch
{
"tw" => "zh-hant",
"zh" => "zh-hans",
_ => langCode,
});
///
/// Search the set-up localization data for the provided assembly for the given string key and return it.
/// If the key is not present, the fallback is shown.
/// The fallback is also required to create the string files to be localized.
///
/// The string key to be returned.
/// The fallback string, usually your source language.
/// The localized string, fallback or string key if not found.
// TODO: This breaks loc export, since it's being called without string args. Fix in CheapLoc.
public static string Localize(string key, string fallBack)
{
return Loc.Localize(key, fallBack, Assembly.GetCallingAssembly());
}
///
/// Set up the UI language with the users' local UI culture.
///
public void SetupWithUiCulture()
{
try
{
var currentUiLang = CultureInfo.CurrentUICulture;
Log.Information("Trying to set up Loc for culture {0}", currentUiLang.TwoLetterISOLanguageName);
if (ApplicableLangCodes.Any(langCode => currentUiLang.TwoLetterISOLanguageName == langCode))
{
this.SetupWithLangCode(currentUiLang.TwoLetterISOLanguageName);
}
else
{
this.SetupWithFallbacks();
}
}
catch (Exception ex)
{
Log.Error(ex, "Could not get language information. Setting up fallbacks.");
this.SetupWithFallbacks();
}
}
///
/// Set up the UI language with "fallbacks" (original English text).
///
public void SetupWithFallbacks()
{
this.DalamudLanguageCultureInfo = CultureInfo.InvariantCulture;
Loc.SetupWithFallbacks(this.assembly);
foreach (var d in Delegate.EnumerateInvocationList(this.LocalizationChanged))
{
try
{
d(FallbackLangCode);
}
catch (Exception ex)
{
Log.Error(ex, "Exception during raise of {handler}", d.Method);
}
}
}
///
/// Set up the UI language with the provided language code.
///
/// The language code to set up the UI language with.
public void SetupWithLangCode(string langCode)
{
if (langCode.Equals(FallbackLangCode, StringComparison.InvariantCultureIgnoreCase))
{
this.SetupWithFallbacks();
return;
}
this.DalamudLanguageCultureInfo = GetCultureInfoFromLangCode(langCode);
try
{
Loc.Setup(this.ReadLocData(langCode), this.assembly);
}
catch (Exception ex)
{
Log.Error(ex, "Could not load loc {0}. Setting up fallbacks.", langCode);
this.SetupWithFallbacks();
}
foreach (var d in Delegate.EnumerateInvocationList(this.LocalizationChanged))
{
try
{
d(langCode);
}
catch (Exception ex)
{
Log.Error(ex, "Exception during raise of {handler}", d.Method);
}
}
}
///
/// Saves localizable JSON data in the current working directory for the provided assembly.
///
/// If set to true, this ignores malformed Localize functions instead of failing.
public void ExportLocalizable(bool ignoreInvalidFunctions = false)
{
Loc.ExportLocalizableForAssembly(this.assembly, ignoreInvalidFunctions);
}
///
/// Creates a new instance of the class.
///
/// Path to Dalamud assets.
/// Optional language override.
/// A new instance.
internal static Localization FromAssets(string assetDirectory, string? languageOverride)
{
var t = new Localization(Path.Combine(assetDirectory, "UIRes", "loc", "dalamud"), "dalamud_");
if (!string.IsNullOrEmpty(languageOverride))
t.SetupWithLangCode(languageOverride);
else
t.SetupWithUiCulture();
return t;
}
private string ReadLocData(string langCode)
{
if (this.useEmbedded)
{
var resourceStream = this.assembly.GetManifestResourceStream($"{this.locResourceDirectory}{this.locResourcePrefix}{langCode}.json");
if (resourceStream == null)
return null;
using var reader = new StreamReader(resourceStream);
return reader.ReadToEnd();
}
return File.ReadAllText(Path.Combine(this.locResourceDirectory, $"{this.locResourcePrefix}{langCode}.json"));
}
}