PKHeX/PKHeX.Core/Util/Localization/LocalizationUtil.cs
Kurt 7c0a7fd64e Improve static class localization perf
Every reflection GetValue / SetValue call would do a bunch of allocation; just let the runtime give us the full list it builds each time, then iterate off that.
Improve start index searching; properties do not contain spaces, so we can find the start by just searching for the first character of the splitter (space).
No longer need to trycatch as we're setting foreach property, instead of foreach string.
2022-08-20 12:11:43 -07:00

122 lines
4.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Reflection;
namespace PKHeX.Core;
/// <summary>
/// Localization utility for changing all properties of a static type.
/// </summary>
public static class LocalizationUtil
{
private const string TranslationSplitter = " = ";
private const char TranslationFirst = ' '; // perf; no properties contain spaces.
/// <summary>
/// Gets the names of the properties defined in the given input
/// </summary>
/// <param name="input">Enumerable of translation definitions in the form "Property = Value".</param>
private static string[] GetProperties(ReadOnlySpan<string> input)
{
var result = new string[input.Length];
for (int i = 0; i < input.Length; i++)
{
var l = input[i];
var split = l.IndexOf(TranslationFirst);
result[i] = l[..split];
}
return result;
}
private static string[] DumpStrings(Type t)
{
var ti = t.GetTypeInfo();
var props = (PropertyInfo[])ti.DeclaredProperties;
var result = new string[props.Length];
for (int i = 0; i < props.Length; i++)
{
var p = props[i];
var value = p.GetValue(null);
result[i] = $"{p}{TranslationSplitter}{value}";
}
return result;
}
/// <summary>
/// Gets the current localization in a static class containing language-specific strings
/// </summary>
/// <param name="t"></param>
public static string[] GetLocalization(Type t) => DumpStrings(t);
/// <summary>
/// Gets the current localization in a static class containing language-specific strings
/// </summary>
/// <param name="t"></param>
/// <param name="existingLines">Existing localization lines (if provided)</param>
public static string[] GetLocalization(Type t, ReadOnlySpan<string> existingLines)
{
var currentLines = GetLocalization(t);
var existing = GetProperties(existingLines);
var current = GetProperties(currentLines);
var result = new string[currentLines.Length];
for (int i = 0; i < current.Length; i++)
{
int index = Array.IndexOf(existing, current[i]);
result[i] = index < 0 ? currentLines[i] : existingLines[index];
}
return result;
}
/// <summary>
/// Applies localization to a static class containing language-specific strings.
/// </summary>
/// <param name="t">Type of the static class containing the desired strings.</param>
/// <param name="lines">Lines containing the localized strings</param>
private static void SetLocalization(Type t, ReadOnlySpan<string> lines)
{
if (lines.Length == 0)
return;
var translatable = new Dictionary<string, string>(lines.Length);
foreach (var line in lines)
{
var index = line.IndexOf(TranslationFirst);
if (index < 0)
continue;
var prop = line[..index];
translatable[prop] = line[(index + TranslationSplitter.Length)..];
}
var ti = t.GetTypeInfo();
var props = (PropertyInfo[])ti.DeclaredProperties;
foreach (var p in props)
{
if (translatable.TryGetValue(p.Name, out var value))
p.SetValue(null, value);
}
}
/// <summary>
/// Applies localization to a static class containing language-specific strings.
/// </summary>
/// <param name="t">Type of the static class containing the desired strings.</param>
/// <param name="languageFilePrefix">Prefix of the language file to use. Example: if the target is legality_en.txt, <paramref name="languageFilePrefix"/> should be "legality".</param>
/// <param name="currentCultureCode">Culture information</param>
private static void SetLocalization(Type t, string languageFilePrefix, string currentCultureCode)
{
var lines = Util.GetStringList($"{languageFilePrefix}_{currentCultureCode}");
SetLocalization(t, lines);
}
/// <summary>
/// Applies localization to a static class containing language-specific strings.
/// </summary>
/// <param name="t">Type of the static class containing the desired strings.</param>
/// <remarks>The values used to translate the given static class are retrieved from [TypeName]_[CurrentLangCode2].txt in the resource manager of PKHeX.Core.</remarks>
/// <param name="currentCultureCode">Culture information</param>
public static void SetLocalization(Type t, string currentCultureCode)
{
SetLocalization(t, t.Name, currentCultureCode);
}
}