Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Localization v2.0.1
Localization.dll
Decompiled 4 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Timers; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Jotunn.Managers; using Localization.DevTools; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using ServerSync; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Localization")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+3b3b4aed6420cc2f72f6bdae0dea94042e81068d")] [assembly: AssemblyProduct("Localization")] [assembly: AssemblyTitle("Localization")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Localization { internal sealed class BoundedStringDictionary<TValue> { private readonly int capacity; private readonly Dictionary<string, TValue> values; private readonly Queue<string> insertionOrder; public int Count => values.Count; public BoundedStringDictionary(int capacity, IEqualityComparer<string> comparer) { this.capacity = ((capacity <= 0) ? 1 : capacity); values = new Dictionary<string, TValue>(this.capacity, comparer ?? StringComparer.Ordinal); insertionOrder = new Queue<string>(this.capacity); } public bool TryGetValue(string key, out TValue value) { return values.TryGetValue(key, out value); } public bool Set(string key, TValue value, out KeyValuePair<string, TValue> evictedEntry) { evictedEntry = default(KeyValuePair<string, TValue>); if (values.ContainsKey(key)) { values[key] = value; return false; } EvictIfNeeded(out evictedEntry); values.Add(key, value); insertionOrder.Enqueue(key); return evictedEntry.Key != null; } public void Clear() { values.Clear(); insertionOrder.Clear(); } private void EvictIfNeeded(out KeyValuePair<string, TValue> evictedEntry) { evictedEntry = default(KeyValuePair<string, TValue>); while (values.Count >= capacity && insertionOrder.Count > 0) { string key = insertionOrder.Dequeue(); if (values.TryGetValue(key, out var value)) { values.Remove(key); evictedEntry = new KeyValuePair<string, TValue>(key, value); break; } } } } internal sealed class BoundedStringSet { private readonly int capacity; private readonly HashSet<string> values; private readonly Queue<string> insertionOrder; public int Count => values.Count; public BoundedStringSet(int capacity, IEqualityComparer<string> comparer) { this.capacity = ((capacity <= 0) ? 1 : capacity); values = new HashSet<string>(comparer ?? StringComparer.Ordinal); insertionOrder = new Queue<string>(this.capacity); } public bool Contains(string key) { return values.Contains(key); } public bool Add(string key, out string evictedKey) { evictedKey = null; if (values.Contains(key)) { return false; } EvictIfNeeded(out evictedKey); values.Add(key); insertionOrder.Enqueue(key); return true; } public void Clear() { values.Clear(); insertionOrder.Clear(); } private void EvictIfNeeded(out string evictedKey) { evictedKey = null; while (values.Count >= capacity && insertionOrder.Count > 0) { string text = insertionOrder.Dequeue(); if (values.Remove(text)) { evictedKey = text; break; } } } } internal sealed class CompiledTemplateRule { private readonly struct TemplateToken { public bool IsPlaceholder { get; } public string Value { get; } public bool IsFunctionCall { get; } public PlaceholderConstraint Constraint { get; } public TemplateToken(bool isPlaceholder, string value, bool isFunctionCall = false, PlaceholderConstraint constraint = null) { IsPlaceholder = isPlaceholder; Value = value; IsFunctionCall = isFunctionCall; Constraint = constraint; } } private sealed class PlaceholderConstraint { public PlaceholderConstraintKind Kind { get; } public string Signature { get; } private IntegerRange WordCountRange { get; } private IntegerRange IntegerDigitRange { get; } private IntegerRange FractionDigitRange { get; } private NumericPredicate[] NumericPredicates { get; } private string[] OneOfValues { get; } private PlaceholderConstraint(PlaceholderConstraintKind kind, string signature, IntegerRange wordCountRange, IntegerRange integerDigitRange, IntegerRange fractionDigitRange, NumericPredicate[] numericPredicates, string[] oneOfValues) { Kind = kind; Signature = signature; WordCountRange = wordCountRange; IntegerDigitRange = integerDigitRange; FractionDigitRange = fractionDigitRange; NumericPredicates = numericPredicates ?? Array.Empty<NumericPredicate>(); OneOfValues = oneOfValues ?? Array.Empty<string>(); } public static PlaceholderConstraint CreateWord() { return new PlaceholderConstraint(PlaceholderConstraintKind.Word, "word", IntegerRange.Exact(1), default(IntegerRange), default(IntegerRange), Array.Empty<NumericPredicate>(), Array.Empty<string>()); } public static PlaceholderConstraint CreateWords(IntegerRange countRange) { return new PlaceholderConstraint(PlaceholderConstraintKind.Words, "words(count=" + countRange.ToSignature() + ")", countRange, default(IntegerRange), default(IntegerRange), Array.Empty<NumericPredicate>(), Array.Empty<string>()); } public static PlaceholderConstraint CreateInteger(IntegerRange digitRange, NumericPredicate[] predicates) { return new PlaceholderConstraint(PlaceholderConstraintKind.Integer, BuildIntegerSignature(digitRange, predicates), default(IntegerRange), digitRange, default(IntegerRange), predicates, Array.Empty<string>()); } public static PlaceholderConstraint CreateFloat(IntegerRange integerDigitRange, IntegerRange fractionDigitRange, NumericPredicate[] predicates) { return new PlaceholderConstraint(PlaceholderConstraintKind.Float, BuildFloatSignature(integerDigitRange, fractionDigitRange, predicates), default(IntegerRange), integerDigitRange, fractionDigitRange, predicates, Array.Empty<string>()); } public static PlaceholderConstraint CreateOneOf(string[] values) { return new PlaceholderConstraint(PlaceholderConstraintKind.OneOf, "oneof(" + string.Join("|", values) + ")", default(IntegerRange), default(IntegerRange), default(IntegerRange), Array.Empty<NumericPredicate>(), values); } public bool IsMatch(string value) { if (value == null) { return false; } return IsMatch(value, 0, value.Length); } public bool IsMatch(string value, int startIndex, int length) { switch (Kind) { case PlaceholderConstraintKind.Word: case PlaceholderConstraintKind.Words: return MatchesWords(value, startIndex, length, WordCountRange); case PlaceholderConstraintKind.Integer: return MatchesInteger(value, startIndex, length, IntegerDigitRange, NumericPredicates); case PlaceholderConstraintKind.Float: return MatchesFloat(value, startIndex, length, IntegerDigitRange, FractionDigitRange, NumericPredicates); case PlaceholderConstraintKind.OneOf: return MatchesOneOf(value, startIndex, length, OneOfValues); default: return false; } } private static string BuildIntegerSignature(IntegerRange digitRange, NumericPredicate[] predicates) { if (predicates == null || predicates.Length == 0) { if (digitRange.IsAtLeastOne()) { return "int"; } return "int(digits=" + digitRange.ToSignature() + ")"; } if (digitRange.IsAtLeastOne()) { return "int(where=" + JoinPredicates(predicates) + ")"; } return "int(digits=" + digitRange.ToSignature() + ",where=" + JoinPredicates(predicates) + ")"; } private static string BuildFloatSignature(IntegerRange integerDigitRange, IntegerRange fractionDigitRange, NumericPredicate[] predicates) { List<string> list = new List<string>(3); if (!integerDigitRange.IsAtLeastOne()) { list.Add("int=" + integerDigitRange.ToSignature()); } if (!fractionDigitRange.IsAtLeastZero()) { list.Add("frac=" + fractionDigitRange.ToSignature()); } if (predicates != null && predicates.Length != 0) { list.Add("where=" + JoinPredicates(predicates)); } if (list.Count == 0) { return "float"; } return "float(" + string.Join(",", list) + ")"; } private static string JoinPredicates(NumericPredicate[] predicates) { if (predicates == null || predicates.Length == 0) { return string.Empty; } string[] array = new string[predicates.Length]; for (int i = 0; i < predicates.Length; i++) { array[i] = predicates[i].ToSignature(); } return string.Join("&&", array); } private static bool MatchesWords(string value, int startIndex, int length, IntegerRange countRange) { if (string.IsNullOrEmpty(value) || length <= 0) { return false; } int num = startIndex + length; if (char.IsWhiteSpace(value[startIndex]) || char.IsWhiteSpace(value[num - 1])) { return false; } int num2 = 0; bool flag = false; for (int i = startIndex; i < num; i++) { char c = value[i]; if (char.IsWhiteSpace(c)) { flag = false; continue; } if (!IsWordCharacter(c)) { return false; } if (!flag) { num2++; flag = true; } } return countRange.Contains(num2); } private static bool MatchesInteger(string value, int startIndex, int length, IntegerRange digitRange, NumericPredicate[] predicates) { bool flag = predicates != null && predicates.Length != 0; if (!TryParseInteger(value, startIndex, length, flag, out var digitCount, out var numericValue)) { return false; } if (!digitRange.Contains(digitCount)) { return false; } if (flag) { return MatchesNumericPredicates(numericValue, predicates); } return true; } private static bool MatchesFloat(string value, int startIndex, int length, IntegerRange integerDigitRange, IntegerRange fractionDigitRange, NumericPredicate[] predicates) { bool flag = predicates != null && predicates.Length != 0; if (!TryParseFloat(value, startIndex, length, flag, out var integerDigitCount, out var fractionDigitCount, out var numericValue)) { return false; } if (!integerDigitRange.Contains(integerDigitCount) || !fractionDigitRange.Contains(fractionDigitCount)) { return false; } if (flag) { return MatchesNumericPredicates(numericValue, predicates); } return true; } private static bool MatchesOneOf(string value, int startIndex, int length, string[] oneOfValues) { if (string.IsNullOrEmpty(value) || length <= 0) { return false; } foreach (string text in oneOfValues) { if (text.Length == length && string.Compare(value, startIndex, text, 0, length, StringComparison.OrdinalIgnoreCase) == 0) { return true; } } return false; } private static bool MatchesNumericPredicates(decimal numericValue, NumericPredicate[] predicates) { if (predicates == null || predicates.Length == 0) { return true; } for (int i = 0; i < predicates.Length; i++) { if (!predicates[i].Matches(numericValue)) { return false; } } return true; } private static bool TryParseInteger(string value, int startIndex, int length, bool requireNumericValue, out int digitCount, out decimal numericValue) { digitCount = 0; numericValue = default(decimal); if (string.IsNullOrEmpty(value) || length <= 0) { return false; } int num = ((value[startIndex] == '+' || value[startIndex] == '-') ? 1 : 0); if (num >= length) { return false; } for (int i = startIndex + num; i < startIndex + length; i++) { if (!char.IsDigit(value[i])) { return false; } } digitCount = length - num; if (!requireNumericValue) { return true; } return decimal.TryParse(value.Substring(startIndex, length), NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out numericValue); } private static bool TryParseFloat(string value, int startIndex, int length, bool requireNumericValue, out int integerDigitCount, out int fractionDigitCount, out decimal numericValue) { integerDigitCount = 0; fractionDigitCount = 0; numericValue = default(decimal); if (string.IsNullOrEmpty(value) || length <= 0) { return false; } int num = ((value[startIndex] == '+' || value[startIndex] == '-') ? 1 : 0); if (num >= length) { return false; } int num2 = -1; int num3 = startIndex + length; for (int i = startIndex + num; i < num3; i++) { char c = value[i]; if (c == '.') { if (num2 >= 0) { return false; } num2 = i; } else if (!char.IsDigit(c)) { return false; } } if (num2 < 0) { integerDigitCount = length - num; if (integerDigitCount <= 0) { return false; } if (!requireNumericValue) { return true; } return decimal.TryParse(value.Substring(startIndex, length), NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out numericValue); } integerDigitCount = num2 - startIndex - num; fractionDigitCount = num3 - num2 - 1; if (integerDigitCount <= 0 || fractionDigitCount <= 0) { return false; } if (!requireNumericValue) { return true; } return decimal.TryParse(value.Substring(startIndex, length), NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out numericValue); } } private enum PlaceholderConstraintKind { Word, Words, Integer, Float, OneOf } private enum NumericComparisonOperator { LessThan, LessThanOrEqual, GreaterThan, GreaterThanOrEqual, Equal, NotEqual } private readonly struct IntegerRange { public bool HasMinimum { get; } public int Minimum { get; } public bool HasMaximum { get; } public int Maximum { get; } public IntegerRange(bool hasMinimum, int minimum, bool hasMaximum, int maximum) { HasMinimum = hasMinimum; Minimum = minimum; HasMaximum = hasMaximum; Maximum = maximum; } public static IntegerRange Exact(int value) { return new IntegerRange(hasMinimum: true, value, hasMaximum: true, value); } public static IntegerRange AtLeast(int minimum) { return new IntegerRange(hasMinimum: true, minimum, hasMaximum: false, 0); } public bool Contains(int value) { if (HasMinimum && value < Minimum) { return false; } if (HasMaximum && value > Maximum) { return false; } return true; } public bool IsAtLeastOne() { if (HasMinimum && Minimum == 1) { return !HasMaximum; } return false; } public bool IsAtLeastZero() { if (HasMinimum && Minimum == 0) { return !HasMaximum; } return false; } public string ToSignature() { if (HasMinimum && HasMaximum && Minimum == Maximum) { return Minimum.ToString(CultureInfo.InvariantCulture); } if (HasMinimum && HasMaximum) { return Minimum.ToString(CultureInfo.InvariantCulture) + ".." + Maximum.ToString(CultureInfo.InvariantCulture); } if (HasMinimum) { return Minimum.ToString(CultureInfo.InvariantCulture) + ".."; } if (HasMaximum) { return ".." + Maximum.ToString(CultureInfo.InvariantCulture); } return ".."; } } private readonly struct NumericPredicate { public NumericComparisonOperator ComparisonOperator { get; } public decimal ComparisonValue { get; } public NumericPredicate(NumericComparisonOperator comparisonOperator, decimal comparisonValue) { ComparisonOperator = comparisonOperator; ComparisonValue = comparisonValue; } public bool Matches(decimal value) { return ComparisonOperator switch { NumericComparisonOperator.LessThan => value < ComparisonValue, NumericComparisonOperator.LessThanOrEqual => value <= ComparisonValue, NumericComparisonOperator.GreaterThan => value > ComparisonValue, NumericComparisonOperator.GreaterThanOrEqual => value >= ComparisonValue, NumericComparisonOperator.Equal => value == ComparisonValue, _ => value != ComparisonValue, }; } public string ToSignature() { return ComparisonOperator switch { NumericComparisonOperator.LessThan => "<", NumericComparisonOperator.LessThanOrEqual => "<=", NumericComparisonOperator.GreaterThan => ">", NumericComparisonOperator.GreaterThanOrEqual => ">=", NumericComparisonOperator.Equal => "==", _ => "!=", } + ComparisonValue.ToString(CultureInfo.InvariantCulture); } } private const string FunctionStartToken = "{{"; private const string FunctionEndToken = "}}"; private const string LocalizeFunctionName = "localize"; private const string ConstraintOptionCount = "count"; private const string ConstraintOptionDigits = "digits"; private const string ConstraintOptionFractionDigits = "frac"; private const string ConstraintOptionIntegerDigits = "int"; private const string ConstraintOptionWhere = "where"; private readonly TemplateToken[] matchTokens; private readonly TemplateToken[] replaceTokens; private readonly string[] nextLiteralValues; private readonly string leadingLiteralPrefix; private readonly string trailingLiteralSuffix; private readonly string longestLiteralAnchor; private readonly int minimumInputLength; private readonly int placeholderCount; private readonly int uniquePlaceholderCount; private readonly int literalSegmentCount; private readonly bool startsWithPlaceholder; private readonly bool endsWithPlaceholder; private readonly bool hasRepeatedPlaceholderNames; private readonly bool hasConstrainedPlaceholders; private readonly int[] matchPlaceholderSlots; private readonly int[] replacePlaceholderSlots; public string Signature { get; } public string ReplaceTemplate { get; } public string SourcePath { get; } public string LeadingLiteralPrefix => leadingLiteralPrefix; public string TrailingLiteralSuffix => trailingLiteralSuffix; public string LongestLiteralAnchor => longestLiteralAnchor; public int MinimumInputLength => minimumInputLength; public int PlaceholderCount => placeholderCount; public int LiteralSegmentCount => literalSegmentCount; public bool StartsWithPlaceholder => startsWithPlaceholder; public bool EndsWithPlaceholder => endsWithPlaceholder; public bool HasRepeatedPlaceholderNames => hasRepeatedPlaceholderNames; internal bool UsesConstrainedPlaceholders => hasConstrainedPlaceholders; internal int DispatchOrder { get; private set; } private CompiledTemplateRule(string signature, string replaceTemplate, string sourcePath, TemplateToken[] matchTokens, TemplateToken[] replaceTokens) { Signature = signature; ReplaceTemplate = replaceTemplate ?? string.Empty; SourcePath = sourcePath ?? string.Empty; this.matchTokens = matchTokens; this.replaceTokens = replaceTokens; nextLiteralValues = BuildNextLiteralValues(matchTokens); InitializeMatchMetadata(matchTokens, out leadingLiteralPrefix, out trailingLiteralSuffix, out longestLiteralAnchor, out minimumInputLength, out placeholderCount, out literalSegmentCount, out startsWithPlaceholder, out endsWithPlaceholder, out hasRepeatedPlaceholderNames); hasConstrainedPlaceholders = HasConstrainedPlaceholders(matchTokens); BuildPlaceholderSlots(matchTokens, replaceTokens, out matchPlaceholderSlots, out replacePlaceholderSlots, out uniquePlaceholderCount); } public static bool TryCreate(string matchTemplate, string replaceTemplate, out CompiledTemplateRule rule, out string error) { return TryCreate(matchTemplate, replaceTemplate, null, out rule, out error); } public static bool TryCreate(string matchTemplate, string replaceTemplate, string sourcePath, out CompiledTemplateRule rule, out string error) { rule = null; error = null; if (!TryTokenize(matchTemplate, requirePlaceholder: true, allowFunctions: false, allowConstraints: true, allowAdjacentPlaceholders: false, out var tokens, out error)) { return false; } if (!TryNormalizeMatchTokens(matchTemplate, tokens, out var normalizedTokens, out error)) { return false; } if (!TryValidateRepeatedPlaceholderConstraints(matchTemplate, normalizedTokens, out error)) { return false; } if (!TryTokenize(replaceTemplate, requirePlaceholder: false, allowFunctions: true, allowConstraints: false, allowAdjacentPlaceholders: true, out var tokens2, out error)) { return false; } rule = new CompiledTemplateRule(matchTemplate, replaceTemplate, sourcePath, normalizedTokens, tokens2); return true; } public bool TryTranslate(TextMatchNormalizer.NormalizedText normalizedInput, out string translated) { translated = null; string value = normalizedInput.Value; if (value == null || value.Length < minimumInputLength) { return false; } if (leadingLiteralPrefix != null && !StartsWith(value, 0, leadingLiteralPrefix)) { return false; } if (trailingLiteralSuffix != null && !EndsWith(value, trailingLiteralSuffix)) { return false; } int[] array = ((uniquePlaceholderCount == 0) ? Array.Empty<int>() : new int[uniquePlaceholderCount]); int[] captureLengths = ((uniquePlaceholderCount == 0) ? Array.Empty<int>() : new int[uniquePlaceholderCount]); for (int i = 0; i < array.Length; i++) { array[i] = -1; } if (!(hasConstrainedPlaceholders ? TryTranslateConstrained(value, 0, 0, array, captureLengths) : TryTranslateFast(value, array, captureLengths))) { return false; } translated = normalizedInput.WrapWholeMatch(BuildTranslatedOutput(value, array, captureLengths)); return true; } private bool TryTranslateFast(string normalizedValue, int[] captureStarts, int[] captureLengths) { int num = 0; for (int i = 0; i < matchTokens.Length; i++) { TemplateToken token = matchTokens[i]; if (!token.IsPlaceholder) { if (!StartsWith(normalizedValue, num, token.Value)) { return false; } num += token.Value.Length; continue; } string text = nextLiteralValues[i]; int captureStartIndex = num; int captureLength; if (text == null) { captureLength = normalizedValue.Length - num; num = normalizedValue.Length; } else { int num2 = normalizedValue.IndexOf(text, num, StringComparison.OrdinalIgnoreCase); if (num2 < 0) { return false; } captureLength = num2 - num; num = num2; } if (!TryStoreCapture(token, matchPlaceholderSlots[i], normalizedValue, captureStartIndex, captureLength, captureStarts, captureLengths, out var _, out var _, out var _)) { return false; } } return num == normalizedValue.Length; } private bool TryTranslateConstrained(string normalizedValue, int tokenIndex, int position, int[] captureStarts, int[] captureLengths) { if (tokenIndex >= matchTokens.Length) { return position == normalizedValue.Length; } TemplateToken templateToken = matchTokens[tokenIndex]; if (!templateToken.IsPlaceholder) { if (!StartsWith(normalizedValue, position, templateToken.Value)) { return false; } return TryTranslateConstrained(normalizedValue, tokenIndex + 1, position + templateToken.Value.Length, captureStarts, captureLengths); } string text = nextLiteralValues[tokenIndex]; if (text == null) { return TryMatchPlaceholderCandidate(normalizedValue, tokenIndex, position, normalizedValue.Length, captureStarts, captureLengths); } int num = position; while (num <= normalizedValue.Length) { int num2 = normalizedValue.IndexOf(text, num, StringComparison.OrdinalIgnoreCase); if (num2 < 0) { return false; } if (TryMatchPlaceholderCandidate(normalizedValue, tokenIndex, position, num2, captureStarts, captureLengths)) { return true; } num = num2 + 1; } return false; } private bool TryMatchPlaceholderCandidate(string normalizedValue, int tokenIndex, int captureStartIndex, int nextPosition, int[] captureStarts, int[] captureLengths) { int num = matchPlaceholderSlots[tokenIndex]; int captureLength = nextPosition - captureStartIndex; if (!TryStoreCapture(matchTokens[tokenIndex], num, normalizedValue, captureStartIndex, captureLength, captureStarts, captureLengths, out var previousStart, out var previousLength, out var assignedNew)) { return false; } bool num2 = TryTranslateConstrained(normalizedValue, tokenIndex + 1, nextPosition, captureStarts, captureLengths); if (!num2 && assignedNew) { captureStarts[num] = previousStart; captureLengths[num] = previousLength; } return num2; } private bool TryStoreCapture(TemplateToken token, int captureSlot, string normalizedValue, int captureStartIndex, int captureLength, int[] captureStarts, int[] captureLengths, out int previousStart, out int previousLength, out bool assignedNew) { previousStart = captureStarts[captureSlot]; previousLength = captureLengths[captureSlot]; assignedNew = false; if (previousStart >= 0 && !SegmentsEqual(normalizedValue, previousStart, previousLength, captureStartIndex, captureLength)) { return false; } if (token.Constraint != null && !token.Constraint.IsMatch(normalizedValue, captureStartIndex, captureLength)) { return false; } if (previousStart < 0) { captureStarts[captureSlot] = captureStartIndex; captureLengths[captureSlot] = captureLength; assignedNew = true; } return true; } private string BuildTranslatedOutput(string normalizedValue, int[] captureStarts, int[] captureLengths) { StringBuilder stringBuilder = new StringBuilder(normalizedValue.Length + 16); for (int i = 0; i < replaceTokens.Length; i++) { TemplateToken templateToken = replaceTokens[i]; if (!templateToken.IsPlaceholder) { stringBuilder.Append(templateToken.Value); continue; } int num = replacePlaceholderSlots[i]; if (num < 0 || num >= captureStarts.Length) { continue; } int num2 = captureStarts[num]; if (num2 >= 0) { int num3 = captureLengths[num]; if (templateToken.IsFunctionCall) { stringBuilder.Append(TranslationRuntime.ResolveTemplatePlaceholder(normalizedValue.Substring(num2, num3))); } else { stringBuilder.Append(normalizedValue, num2, num3); } } } return stringBuilder.ToString(); } private static bool SegmentsEqual(string value, int leftStart, int leftLength, int rightStart, int rightLength) { if (leftLength != rightLength) { return false; } if (leftLength == 0) { return true; } return string.Compare(value, leftStart, value, rightStart, leftLength, StringComparison.Ordinal) == 0; } private static bool HasConstrainedPlaceholders(TemplateToken[] tokens) { for (int i = 0; i < tokens.Length; i++) { if (tokens[i].Constraint != null) { return true; } } return false; } private static bool TryValidateRepeatedPlaceholderConstraints(string template, TemplateToken[] matchTokens, out string error) { error = null; Dictionary<string, string> dictionary = null; for (int i = 0; i < matchTokens.Length; i++) { TemplateToken templateToken = matchTokens[i]; if (!templateToken.IsPlaceholder) { continue; } if (dictionary == null) { dictionary = new Dictionary<string, string>(StringComparer.Ordinal); } string text = ((templateToken.Constraint == null) ? string.Empty : templateToken.Constraint.Signature); if (dictionary.TryGetValue(templateToken.Value, out var value)) { if (!string.Equals(value, text, StringComparison.Ordinal)) { error = "Template '" + template + "' uses placeholder '" + templateToken.Value + "' with conflicting constraints."; return false; } } else { dictionary[templateToken.Value] = text; } } return true; } private static void BuildPlaceholderSlots(TemplateToken[] matchTokens, TemplateToken[] replaceTokens, out int[] matchPlaceholderSlots, out int[] replacePlaceholderSlots, out int uniquePlaceholderCount) { matchPlaceholderSlots = new int[matchTokens.Length]; replacePlaceholderSlots = new int[replaceTokens.Length]; uniquePlaceholderCount = 0; Dictionary<string, int> dictionary = null; for (int i = 0; i < matchTokens.Length; i++) { if (matchTokens[i].IsPlaceholder) { if (dictionary == null) { dictionary = new Dictionary<string, int>(StringComparer.Ordinal); } if (!dictionary.TryGetValue(matchTokens[i].Value, out var value)) { value = uniquePlaceholderCount; uniquePlaceholderCount++; dictionary[matchTokens[i].Value] = value; } matchPlaceholderSlots[i] = value; } } for (int j = 0; j < replaceTokens.Length; j++) { if (replaceTokens[j].IsPlaceholder) { replacePlaceholderSlots[j] = -1; if (dictionary != null && dictionary.TryGetValue(replaceTokens[j].Value, out var value2)) { replacePlaceholderSlots[j] = value2; } } } } private static bool TryNormalizeMatchTokens(string template, TemplateToken[] sourceTokens, out TemplateToken[] normalizedTokens, out string error) { List<TemplateToken> list = new List<TemplateToken>(sourceTokens.Length); for (int i = 0; i < sourceTokens.Length; i++) { TemplateToken item = sourceTokens[i]; if (item.IsPlaceholder) { if (list.Count > 0 && list[list.Count - 1].IsPlaceholder) { normalizedTokens = Array.Empty<TemplateToken>(); error = "Template '" + template + "' becomes ambiguous after stripping rich-text tags."; return false; } list.Add(item); } else { string value = TextMatchNormalizer.Normalize(item.Value); if (!string.IsNullOrEmpty(value)) { list.Add(new TemplateToken(isPlaceholder: false, value)); } } } if (list.Count == 0) { normalizedTokens = Array.Empty<TemplateToken>(); error = "Template '" + template + "' becomes empty after stripping rich-text tags."; return false; } normalizedTokens = list.ToArray(); error = null; return true; } internal void SetDispatchOrder(int dispatchOrder) { DispatchOrder = dispatchOrder; } private static string[] BuildNextLiteralValues(TemplateToken[] tokens) { string[] array = new string[tokens.Length]; string text = null; for (int num = tokens.Length - 1; num >= 0; num--) { array[num] = text; if (!tokens[num].IsPlaceholder) { text = tokens[num].Value; } } return array; } private static bool StartsWith(string text, int startIndex, string value) { if (startIndex + value.Length > text.Length) { return false; } return string.Compare(text, startIndex, value, 0, value.Length, StringComparison.OrdinalIgnoreCase) == 0; } private static bool EndsWith(string text, string value) { if (value.Length > text.Length) { return false; } return string.Compare(text, text.Length - value.Length, value, 0, value.Length, StringComparison.OrdinalIgnoreCase) == 0; } private static void InitializeMatchMetadata(TemplateToken[] tokens, out string leadingLiteralPrefix, out string trailingLiteralSuffix, out string longestLiteralAnchor, out int minimumInputLength, out int placeholderCount, out int literalSegmentCount, out bool startsWithPlaceholder, out bool endsWithPlaceholder, out bool hasRepeatedPlaceholderNames) { leadingLiteralPrefix = null; trailingLiteralSuffix = null; longestLiteralAnchor = null; minimumInputLength = 0; placeholderCount = 0; literalSegmentCount = 0; startsWithPlaceholder = tokens.Length != 0 && tokens[0].IsPlaceholder; endsWithPlaceholder = tokens.Length != 0 && tokens[^1].IsPlaceholder; hasRepeatedPlaceholderNames = false; HashSet<string> hashSet = null; for (int i = 0; i < tokens.Length; i++) { TemplateToken templateToken = tokens[i]; if (templateToken.IsPlaceholder) { placeholderCount++; if (hashSet == null) { hashSet = new HashSet<string>(StringComparer.Ordinal); } if (!hashSet.Add(templateToken.Value)) { hasRepeatedPlaceholderNames = true; } continue; } literalSegmentCount++; minimumInputLength += templateToken.Value.Length; if (i == 0) { leadingLiteralPrefix = templateToken.Value; } if (i == tokens.Length - 1) { trailingLiteralSuffix = templateToken.Value; } if (longestLiteralAnchor == null || templateToken.Value.Length > longestLiteralAnchor.Length) { longestLiteralAnchor = templateToken.Value; } } } private static bool TryTokenize(string template, bool requirePlaceholder, bool allowFunctions, bool allowConstraints, bool allowAdjacentPlaceholders, out TemplateToken[] tokens, out string error) { tokens = Array.Empty<TemplateToken>(); error = null; if (string.IsNullOrWhiteSpace(template)) { error = "Template cannot be empty."; return false; } List<TemplateToken> list = new List<TemplateToken>(); int num = 0; bool flag = false; while (num < template.Length) { int num2 = template.IndexOf("{{", num, StringComparison.Ordinal); if (num2 < 0) { list.Add(new TemplateToken(isPlaceholder: false, template.Substring(num))); num = template.Length; break; } if (num2 > num) { list.Add(new TemplateToken(isPlaceholder: false, template.Substring(num, num2 - num))); } if (!TryParseToken(template, num2, allowFunctions, allowConstraints, out var token, out var nextCursor, out error)) { return false; } num = nextCursor; if (!allowAdjacentPlaceholders && list.Count > 0 && list[list.Count - 1].IsPlaceholder) { error = "Template '" + template + "' cannot contain adjacent placeholders."; return false; } list.Add(token); flag = true; } if (requirePlaceholder && !flag) { error = "Template '" + template + "' must contain at least one placeholder."; return false; } if (list.Count == 0) { list.Add(new TemplateToken(isPlaceholder: false, template)); } tokens = list.ToArray(); return true; } private static bool IsValidPlaceholderName(string value) { if (string.IsNullOrWhiteSpace(value)) { return false; } if (!IsValidPlaceholderStartCharacter(value[0])) { return false; } for (int i = 1; i < value.Length; i++) { if (!IsValidPlaceholderCharacter(value[i])) { return false; } } return true; } private static bool TryParseToken(string template, int tokenStart, bool allowFunctions, bool allowConstraints, out TemplateToken token, out int nextCursor, out string error) { token = default(TemplateToken); nextCursor = tokenStart; error = null; int num = template.IndexOf("}}", tokenStart + "{{".Length, StringComparison.Ordinal); if (num < 0) { error = "Template '" + template + "' has an unclosed function token."; return false; } string text = template.Substring(tokenStart + "{{".Length, num - tokenStart - "{{".Length).Trim(); if (string.IsNullOrWhiteSpace(text)) { error = "Template '" + template + "' contains an empty function token."; return false; } if (text[0] == '$') { if (!TryParsePlaceholderToken(template, text, allowConstraints, out token, out error)) { return false; } nextCursor = num + "}}".Length; return true; } if (!allowFunctions) { error = "Template '" + template + "' cannot use function tokens in the match template."; return false; } int num2 = text.IndexOf('('); int num3 = text.LastIndexOf(')'); if (num2 <= 0 || num3 != text.Length - 1) { error = "Template '" + template + "' has an invalid function token '" + text + "'."; return false; } string text2 = text.Substring(0, num2).Trim(); if (!string.Equals(text2, "localize", StringComparison.Ordinal)) { error = "Template '" + template + "' uses an unsupported function '" + text2 + "'."; return false; } string text3 = text.Substring(num2 + 1, num3 - num2 - 1).Trim(); if (string.IsNullOrWhiteSpace(text3)) { error = "Template '" + template + "' function '" + text2 + "' requires one placeholder argument."; return false; } string[] array = text3.Split(new char[1] { ',' }); if (array.Length != 1) { error = "Template '" + template + "' function '" + text2 + "' currently supports exactly one placeholder argument."; return false; } string text4 = array[0].Trim(); if (text4.Length < 2 || text4[0] != '$') { error = "Template '" + template + "' function '" + text2 + "' must reference a placeholder like $name."; return false; } string text5 = text4.Substring(1).Trim(); if (!IsValidPlaceholderName(text5)) { error = "Template '" + template + "' has an invalid placeholder name '" + text5 + "'."; return false; } token = new TemplateToken(isPlaceholder: true, text5, isFunctionCall: true); nextCursor = num + "}}".Length; return true; } private static bool TryParsePlaceholderToken(string template, string tokenContent, bool allowConstraints, out TemplateToken token, out string error) { token = default(TemplateToken); error = null; string text = tokenContent.Substring(1).Trim(); int num = text.IndexOf(':'); string text2 = ((num < 0) ? text : text.Substring(0, num).Trim()); if (!IsValidPlaceholderName(text2)) { error = "Template '" + template + "' has an invalid placeholder name '" + text2 + "'."; return false; } PlaceholderConstraint constraint = null; if (num >= 0) { if (!allowConstraints) { error = "Template '" + template + "' cannot use constrained placeholders in the replacement template."; return false; } string text3 = text.Substring(num + 1).Trim(); if (string.IsNullOrWhiteSpace(text3)) { error = "Template '" + template + "' placeholder '" + text2 + "' has an empty constraint."; return false; } if (!TryParsePlaceholderConstraint(template, text2, text3, out constraint, out error)) { return false; } } token = new TemplateToken(isPlaceholder: true, text2, isFunctionCall: false, constraint); return true; } private static bool TryParsePlaceholderConstraint(string template, string placeholderName, string constraintText, out PlaceholderConstraint constraint, out string error) { constraint = null; error = null; int num = constraintText.IndexOf('('); string text = constraintText; string text2 = null; if (num >= 0) { int num2 = constraintText.LastIndexOf(')'); if (num == 0 || num2 != constraintText.Length - 1) { error = "Template '" + template + "' placeholder '" + placeholderName + "' has an invalid constraint '" + constraintText + "'."; return false; } text = constraintText.Substring(0, num).Trim(); text2 = constraintText.Substring(num + 1, num2 - num - 1).Trim(); } if (string.IsNullOrWhiteSpace(text)) { error = "Template '" + template + "' placeholder '" + placeholderName + "' has an empty constraint type."; return false; } if (string.Equals(text, "word", StringComparison.OrdinalIgnoreCase)) { if (!string.IsNullOrWhiteSpace(text2)) { error = "Template '" + template + "' placeholder '" + placeholderName + "' constraint 'word' does not accept options."; return false; } constraint = PlaceholderConstraint.CreateWord(); return true; } if (string.Equals(text, "words", StringComparison.OrdinalIgnoreCase)) { if (!TryParseConstraintOptions(template, placeholderName, text, text2, out var options, out error)) { return false; } IntegerRange range = IntegerRange.AtLeast(1); if (options.TryGetValue("count", out var value) && !TryParseIntegerRangeOption(template, placeholderName, text, "count", value, allowZeroMinimum: false, out range, out error)) { return false; } if (!EnsureOnlySupportedOptions(template, placeholderName, text, options, "count", out error)) { return false; } constraint = PlaceholderConstraint.CreateWords(range); return true; } if (string.Equals(text, "int", StringComparison.OrdinalIgnoreCase)) { if (!TryParseConstraintOptions(template, placeholderName, text, text2, out var options2, out error)) { return false; } IntegerRange range2 = IntegerRange.AtLeast(1); if (options2.TryGetValue("digits", out var value2) && !TryParseIntegerRangeOption(template, placeholderName, text, "digits", value2, allowZeroMinimum: false, out range2, out error)) { return false; } NumericPredicate[] predicates = Array.Empty<NumericPredicate>(); if (options2.TryGetValue("where", out var value3) && !TryParseNumericPredicates(template, placeholderName, text, value3, out predicates, out error)) { return false; } if (!EnsureOnlySupportedOptions(template, placeholderName, text, options2, "digits", "where", out error)) { return false; } constraint = PlaceholderConstraint.CreateInteger(range2, predicates); return true; } if (string.Equals(text, "float", StringComparison.OrdinalIgnoreCase)) { if (!TryParseConstraintOptions(template, placeholderName, text, text2, out var options3, out error)) { return false; } IntegerRange range3 = IntegerRange.AtLeast(1); if (options3.TryGetValue("int", out var value4) && !TryParseIntegerRangeOption(template, placeholderName, text, "int", value4, allowZeroMinimum: false, out range3, out error)) { return false; } IntegerRange range4 = IntegerRange.AtLeast(0); if (options3.TryGetValue("frac", out var value5) && !TryParseIntegerRangeOption(template, placeholderName, text, "frac", value5, allowZeroMinimum: true, out range4, out error)) { return false; } NumericPredicate[] predicates2 = Array.Empty<NumericPredicate>(); if (options3.TryGetValue("where", out var value6) && !TryParseNumericPredicates(template, placeholderName, text, value6, out predicates2, out error)) { return false; } if (!EnsureOnlySupportedOptions(template, placeholderName, text, options3, "int", "frac", "where", out error)) { return false; } constraint = PlaceholderConstraint.CreateFloat(range3, range4, predicates2); return true; } if (string.Equals(text, "oneof", StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(text2)) { error = "Template '" + template + "' placeholder '" + placeholderName + "' constraint 'oneof' requires at least one value."; return false; } string[] array = text2.Split(new char[1] { '|' }); List<string> list = new List<string>(array.Length); for (int i = 0; i < array.Length; i++) { string text3 = array[i].Trim(); if (string.IsNullOrEmpty(text3)) { error = "Template '" + template + "' placeholder '" + placeholderName + "' constraint 'oneof' contains an empty option."; return false; } list.Add(text3); } constraint = PlaceholderConstraint.CreateOneOf(list.ToArray()); return true; } error = "Template '" + template + "' placeholder '" + placeholderName + "' uses an unsupported constraint type '" + text + "'."; return false; } private static bool TryParseConstraintOptions(string template, string placeholderName, string constraintType, string argumentText, out Dictionary<string, string> options, out string error) { options = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); error = null; if (string.IsNullOrWhiteSpace(argumentText)) { return true; } string[] array = argumentText.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (string.IsNullOrEmpty(text)) { error = "Template '" + template + "' placeholder '" + placeholderName + "' constraint '" + constraintType + "' contains an empty option."; return false; } int num = text.IndexOf('='); if (num <= 0 || num == text.Length - 1) { error = "Template '" + template + "' placeholder '" + placeholderName + "' constraint '" + constraintType + "' has an invalid option '" + text + "'."; return false; } string text2 = text.Substring(0, num).Trim(); string value = text.Substring(num + 1).Trim(); if (string.IsNullOrEmpty(text2) || string.IsNullOrEmpty(value)) { error = "Template '" + template + "' placeholder '" + placeholderName + "' constraint '" + constraintType + "' has an invalid option '" + text + "'."; return false; } if (options.ContainsKey(text2)) { error = "Template '" + template + "' placeholder '" + placeholderName + "' constraint '" + constraintType + "' repeats option '" + text2 + "'."; return false; } options[text2] = value; } return true; } private static bool EnsureOnlySupportedOptions(string template, string placeholderName, string constraintType, Dictionary<string, string> options, string supportedOption, out string error) { return EnsureOnlySupportedOptions(template, placeholderName, constraintType, options, new string[1] { supportedOption }, out error); } private static bool EnsureOnlySupportedOptions(string template, string placeholderName, string constraintType, Dictionary<string, string> options, string supportedOptionOne, string supportedOptionTwo, out string error) { return EnsureOnlySupportedOptions(template, placeholderName, constraintType, options, new string[2] { supportedOptionOne, supportedOptionTwo }, out error); } private static bool EnsureOnlySupportedOptions(string template, string placeholderName, string constraintType, Dictionary<string, string> options, string supportedOptionOne, string supportedOptionTwo, string supportedOptionThree, out string error) { return EnsureOnlySupportedOptions(template, placeholderName, constraintType, options, new string[3] { supportedOptionOne, supportedOptionTwo, supportedOptionThree }, out error); } private static bool EnsureOnlySupportedOptions(string template, string placeholderName, string constraintType, Dictionary<string, string> options, string[] supportedOptions, out string error) { error = null; HashSet<string> hashSet = new HashSet<string>(supportedOptions, StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair<string, string> option in options) { if (!hashSet.Contains(option.Key)) { error = "Template '" + template + "' placeholder '" + placeholderName + "' constraint '" + constraintType + "' does not support option '" + option.Key + "'."; return false; } } return true; } private static bool TryParseIntegerRangeOption(string template, string placeholderName, string constraintType, string optionName, string valueText, bool allowZeroMinimum, out IntegerRange range, out string error) { if (!TryParseIntegerRange(valueText, allowZeroMinimum, out range, out error)) { error = "Template '" + template + "' placeholder '" + placeholderName + "' constraint '" + constraintType + "' has an invalid '" + optionName + "' range: " + error; return false; } return true; } private static bool TryParseIntegerRange(string valueText, bool allowZeroMinimum, out IntegerRange range, out string error) { range = default(IntegerRange); error = null; if (string.IsNullOrWhiteSpace(valueText)) { error = "range cannot be empty."; return false; } string text = valueText.Trim(); int num = text.IndexOf("..", StringComparison.Ordinal); if (num < 0) { if (!TryParseBoundValue(text, allowZeroMinimum, out var value, out error)) { return false; } range = IntegerRange.Exact(value); return true; } string text2 = text.Substring(0, num).Trim(); string text3 = text.Substring(num + 2).Trim(); bool flag = !string.IsNullOrEmpty(text2); bool flag2 = !string.IsNullOrEmpty(text3); if (!flag && !flag2) { error = "range must specify at least one bound."; return false; } int value2 = 0; if (flag && !TryParseBoundValue(text2, allowZeroMinimum, out value2, out error)) { return false; } int value3 = 0; if (flag2 && !TryParseBoundValue(text3, allowZeroMinimum: true, out value3, out error)) { return false; } if (flag && flag2 && value2 > value3) { error = "range minimum cannot be greater than maximum."; return false; } range = new IntegerRange(flag, value2, flag2, value3); return true; } private static bool TryParseBoundValue(string valueText, bool allowZeroMinimum, out int value, out string error) { value = 0; error = null; if (!int.TryParse(valueText, NumberStyles.None, CultureInfo.InvariantCulture, out value)) { error = "'" + valueText + "' is not a valid integer."; return false; } if (value < 0) { error = "range values must be non-negative."; return false; } if (!allowZeroMinimum && value == 0) { error = "range values must be greater than zero."; return false; } return true; } private static bool TryParseNumericPredicates(string template, string placeholderName, string constraintType, string whereText, out NumericPredicate[] predicates, out string error) { predicates = Array.Empty<NumericPredicate>(); error = null; if (string.IsNullOrWhiteSpace(whereText)) { error = "Template '" + template + "' placeholder '" + placeholderName + "' constraint '" + constraintType + "' has an empty 'where' clause."; return false; } string[] array = whereText.Split(new string[1] { "&&" }, StringSplitOptions.None); List<NumericPredicate> list = new List<NumericPredicate>(array.Length); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (string.IsNullOrEmpty(text)) { error = "Template '" + template + "' placeholder '" + placeholderName + "' constraint '" + constraintType + "' contains an empty numeric predicate."; return false; } if (!TryParseNumericPredicate(text, out var predicate, out var error2)) { error = "Template '" + template + "' placeholder '" + placeholderName + "' constraint '" + constraintType + "' has an invalid numeric predicate '" + text + "': " + error2; return false; } list.Add(predicate); } predicates = list.ToArray(); return true; } private static bool TryParseNumericPredicate(string clause, out NumericPredicate predicate, out string error) { predicate = default(NumericPredicate); error = null; NumericComparisonOperator comparisonOperator; string text; if (clause.StartsWith(">=", StringComparison.Ordinal)) { comparisonOperator = NumericComparisonOperator.GreaterThanOrEqual; text = clause.Substring(2).Trim(); } else if (clause.StartsWith("<=", StringComparison.Ordinal)) { comparisonOperator = NumericComparisonOperator.LessThanOrEqual; text = clause.Substring(2).Trim(); } else if (clause.StartsWith("!=", StringComparison.Ordinal)) { comparisonOperator = NumericComparisonOperator.NotEqual; text = clause.Substring(2).Trim(); } else if (clause.StartsWith("==", StringComparison.Ordinal)) { comparisonOperator = NumericComparisonOperator.Equal; text = clause.Substring(2).Trim(); } else if (clause.StartsWith(">", StringComparison.Ordinal)) { comparisonOperator = NumericComparisonOperator.GreaterThan; text = clause.Substring(1).Trim(); } else { if (!clause.StartsWith("<", StringComparison.Ordinal)) { error = "predicate must start with one of <, <=, >, >=, ==, or !=."; return false; } comparisonOperator = NumericComparisonOperator.LessThan; text = clause.Substring(1).Trim(); } if (!decimal.TryParse(text, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var result)) { error = "'" + text + "' is not a valid numeric value."; return false; } predicate = new NumericPredicate(comparisonOperator, result); return true; } private static bool IsValidPlaceholderStartCharacter(char character) { if (!char.IsLetter(character)) { return character == '_'; } return true; } private static bool IsValidPlaceholderCharacter(char character) { if (!char.IsLetterOrDigit(character)) { return character == '_'; } return true; } private static bool IsWordCharacter(char character) { if (!char.IsLetterOrDigit(character) && character != '_' && character != '-') { return character == '\''; } return true; } } internal static class GameTranslationResolver { private enum CacheEvictionKind { Resolve, ResolveMiss, Localize, LocalizeMiss, ItemNameToken, ItemNameTokenMiss } private const int MaxResolveCacheEntries = 4096; private const int MaxResolveMissCacheEntries = 2048; private const int MaxLocalizeCacheEntries = 4096; private const int MaxLocalizeMissCacheEntries = 2048; private const int MaxItemNameTokenCacheEntries = 4096; private const int MaxItemNameTokenMissCacheEntries = 2048; private static readonly Type ObjectDbType = AccessTools.TypeByName("ObjectDB"); private static readonly Type ZNetSceneType = AccessTools.TypeByName("ZNetScene"); private static readonly Type ItemDropType = AccessTools.TypeByName("ItemDrop"); private static readonly Type LocalizationType = AccessTools.TypeByName("Localization"); private static readonly FieldInfo ObjectDbInstanceField = AccessTools.Field(ObjectDbType, "instance"); private static readonly PropertyInfo ObjectDbInstanceProperty = AccessTools.Property(ObjectDbType, "instance"); private static readonly MethodInfo GetItemPrefabMethod = AccessTools.Method(ObjectDbType, "GetItemPrefab", new Type[1] { typeof(string) }, (Type[])null); private static readonly FieldInfo ZNetSceneInstanceField = AccessTools.Field(ZNetSceneType, "instance"); private static readonly PropertyInfo ZNetSceneInstanceProperty = AccessTools.Property(ZNetSceneType, "instance"); private static readonly MethodInfo GetPrefabMethod = AccessTools.Method(ZNetSceneType, "GetPrefab", new Type[1] { typeof(string) }, (Type[])null); private static readonly FieldInfo ItemDataField = AccessTools.Field(ItemDropType, "m_itemData"); private static readonly FieldInfo SharedDataField = AccessTools.Field(AccessTools.Inner(ItemDropType, "ItemData") ?? AccessTools.TypeByName("ItemDrop+ItemData"), "m_shared"); private static readonly FieldInfo SharedNameField = AccessTools.Field(AccessTools.Inner(ItemDropType, "ItemData+SharedData") ?? AccessTools.TypeByName("ItemDrop+ItemData+SharedData"), "m_name"); private static readonly FieldInfo LocalizationInstanceField = AccessTools.Field(LocalizationType, "instance"); private static readonly PropertyInfo LocalizationInstanceProperty = AccessTools.Property(LocalizationType, "instance"); private static readonly MethodInfo LocalizeMethod = AccessTools.Method(LocalizationType, "Localize", new Type[1] { typeof(string) }, (Type[])null); private static readonly BoundedStringDictionary<string> resolveCache = new BoundedStringDictionary<string>(4096, StringComparer.Ordinal); private static readonly BoundedStringSet resolveMissCache = new BoundedStringSet(2048, StringComparer.Ordinal); private static readonly BoundedStringDictionary<string> localizeCache = new BoundedStringDictionary<string>(4096, StringComparer.Ordinal); private static readonly BoundedStringSet localizeMissCache = new BoundedStringSet(2048, StringComparer.Ordinal); private static readonly BoundedStringDictionary<string> itemNameTokenCache = new BoundedStringDictionary<string>(4096, StringComparer.Ordinal); private static readonly BoundedStringSet itemNameTokenMissCache = new BoundedStringSet(2048, StringComparer.Ordinal); public static bool TryResolve(string input, out string resolved) { resolved = null; if (string.IsNullOrWhiteSpace(input)) { return false; } if (resolveCache.TryGetValue(input, out resolved)) { return true; } if (resolveMissCache.Contains(input)) { return false; } if (!TryResolveCore(input, out resolved)) { AddMiss(resolveMissCache, CacheEvictionKind.ResolveMiss, input); return false; } AddValue(resolveCache, CacheEvictionKind.Resolve, input, resolved); return true; } internal static void ClearCaches() { resolveCache.Clear(); resolveMissCache.Clear(); localizeCache.Clear(); localizeMissCache.Clear(); itemNameTokenCache.Clear(); itemNameTokenMissCache.Clear(); } private static bool TryResolveCore(string input, out string resolved) { resolved = null; if (TryLocalize(input, out resolved)) { return true; } if (!TryResolveItemNameToken(input, out var itemNameToken)) { return false; } if (TryLocalize(itemNameToken, out resolved)) { return true; } if (!string.Equals(itemNameToken, input, StringComparison.Ordinal)) { resolved = itemNameToken; return true; } return false; } private static bool TryResolveItemNameToken(string prefabName, out string itemNameToken) { itemNameToken = null; if (itemNameTokenCache.TryGetValue(prefabName, out itemNameToken)) { return true; } if (itemNameTokenMissCache.Contains(prefabName)) { return false; } GameObject val = TryGetItemPrefab(prefabName) ?? TryGetScenePrefab(prefabName); if ((Object)(object)val == (Object)null || ItemDropType == null) { AddMiss(itemNameTokenMissCache, CacheEvictionKind.ItemNameTokenMiss, prefabName); return false; } Component component = val.GetComponent(ItemDropType); if ((Object)(object)component == (Object)null || ItemDataField == null || SharedDataField == null || SharedNameField == null) { AddMiss(itemNameTokenMissCache, CacheEvictionKind.ItemNameTokenMiss, prefabName); return false; } object value = ItemDataField.GetValue(component); if (value == null) { AddMiss(itemNameTokenMissCache, CacheEvictionKind.ItemNameTokenMiss, prefabName); return false; } object value2 = SharedDataField.GetValue(value); if (value2 == null) { AddMiss(itemNameTokenMissCache, CacheEvictionKind.ItemNameTokenMiss, prefabName); return false; } itemNameToken = SharedNameField.GetValue(value2) as string; if (string.IsNullOrWhiteSpace(itemNameToken)) { AddMiss(itemNameTokenMissCache, CacheEvictionKind.ItemNameTokenMiss, prefabName); return false; } AddValue(itemNameTokenCache, CacheEvictionKind.ItemNameToken, prefabName, itemNameToken); return true; } private static GameObject TryGetItemPrefab(string prefabName) { object obj = ObjectDbInstanceField?.GetValue(null) ?? ObjectDbInstanceProperty?.GetValue(null, null); if (obj == null || GetItemPrefabMethod == null) { return null; } object? obj2 = GetItemPrefabMethod.Invoke(obj, new object[1] { prefabName }); return (GameObject)((obj2 is GameObject) ? obj2 : null); } private static GameObject TryGetScenePrefab(string prefabName) { object obj = ZNetSceneInstanceField?.GetValue(null) ?? ZNetSceneInstanceProperty?.GetValue(null, null); if (obj == null || GetPrefabMethod == null) { return null; } object? obj2 = GetPrefabMethod.Invoke(obj, new object[1] { prefabName }); return (GameObject)((obj2 is GameObject) ? obj2 : null); } private static bool TryLocalize(string input, out string localized) { localized = null; if (localizeCache.TryGetValue(input, out localized)) { return true; } if (localizeMissCache.Contains(input)) { return false; } object obj = LocalizationInstanceField?.GetValue(null) ?? LocalizationInstanceProperty?.GetValue(null, null); if (obj == null || LocalizeMethod == null) { return false; } try { localized = LocalizeMethod.Invoke(obj, new object[1] { input }) as string; } catch (Exception ex) { Exception exception = ex; PluginMain.DebugLog(() => "Game localization lookup failed for '" + PluginMain.FormatTextForDebug(input) + "': " + exception.Message); return false; } if (string.IsNullOrWhiteSpace(localized) || string.Equals(localized, input, StringComparison.Ordinal)) { AddMiss(localizeMissCache, CacheEvictionKind.LocalizeMiss, input); localized = null; return false; } AddValue(localizeCache, CacheEvictionKind.Localize, input, localized); return true; } private static void AddValue(BoundedStringDictionary<string> cache, CacheEvictionKind evictionKind, string key, string value) { if (cache.Set(key, value, out var _)) { RecordEviction(evictionKind); } } private static void AddMiss(BoundedStringSet cache, CacheEvictionKind evictionKind, string key) { if (cache.Add(key, out var evictedKey) && evictedKey != null) { RecordEviction(evictionKind); } } private static void RecordEviction(CacheEvictionKind evictionKind) { switch (evictionKind) { case CacheEvictionKind.Resolve: TranslationPerformanceMetrics.RecordGameResolveCacheEviction(); break; case CacheEvictionKind.ResolveMiss: TranslationPerformanceMetrics.RecordGameResolveMissCacheEviction(); break; case CacheEvictionKind.Localize: TranslationPerformanceMetrics.RecordGameLocalizeCacheEviction(); break; case CacheEvictionKind.LocalizeMiss: TranslationPerformanceMetrics.RecordGameLocalizeMissCacheEviction(); break; case CacheEvictionKind.ItemNameToken: TranslationPerformanceMetrics.RecordGameItemNameTokenCacheEviction(); break; case CacheEvictionKind.ItemNameTokenMiss: TranslationPerformanceMetrics.RecordGameItemNameTokenMissCacheEviction(); break; } } } internal static class PluginLocalization { private const string EnglishLanguage = "English"; private const string TraditionalChineseLanguage = "Chinese_Trad"; private const string FileNamePrefix = "Localization-"; private const string FileExtension = ".json"; private const double ReloadDebounceMilliseconds = 250.0; private static readonly IReadOnlyList<KeyValuePair<string, string>> DefaultEntries = BuildDefaultEntries(); private static readonly Dictionary<string, string> DefaultValuesByKey = BuildDefaultValuesByKey(); private static readonly Dictionary<string, string> DefaultLegacyValuesByKey = BuildDefaultLegacyValuesByKey(); private static readonly Dictionary<string, string> TraditionalChineseValuesByKey = BuildTraditionalChineseValuesByKey(); private static readonly Dictionary<string, string> TraditionalChineseLegacyValuesByKey = BuildTraditionalChineseLegacyValuesByKey(); private static readonly Dictionary<string, string> DefaultKeysByFallback = BuildDefaultKeysByFallback(); private static Dictionary<string, string> localizedValues = new Dictionary<string, string>(StringComparer.Ordinal); private static FileSystemWatcher watcher; private static System.Timers.Timer reloadDebounceTimer; private static bool initialized; private static string currentLanguage = "English"; internal static event Action Reloaded; internal static void Initialize() { if (!initialized) { initialized = true; currentLanguage = ResolveLanguage(null); Directory.CreateDirectory(TranslationRuleLoader.ConfigRootPath); ReloadCurrentLanguage(); SetupWatcher(); } } internal static void Shutdown() { if (initialized) { if (watcher != null) { watcher.EnableRaisingEvents = false; watcher.Changed -= OnLocalizationFileChanged; watcher.Created -= OnLocalizationFileChanged; watcher.Deleted -= OnLocalizationFileChanged; watcher.Renamed -= OnLocalizationFileRenamed; watcher.Dispose(); watcher = null; } if (reloadDebounceTimer != null) { reloadDebounceTimer.Stop(); reloadDebounceTimer.Elapsed -= OnReloadDebounceElapsed; reloadDebounceTimer.Dispose(); reloadDebounceTimer = null; } localizedValues = new Dictionary<string, string>(StringComparer.Ordinal); initialized = false; currentLanguage = "English"; } } internal static void NotifyLanguageChanged(string languageName) { currentLanguage = ResolveLanguage(languageName); ReloadCurrentLanguage(); } internal static void NotifyLanguageReset() { NotifyLanguageChanged("English"); } internal static string Get(string key, string fallback) { if (!string.IsNullOrWhiteSpace(key) && localizedValues.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value)) { return value; } if (!string.IsNullOrWhiteSpace(fallback)) { return fallback; } return key ?? string.Empty; } internal static string Format(string key, string fallback, params object[] args) { string text = Get(key, fallback); if (args != null && args.Length != 0) { return string.Format(CultureInfo.InvariantCulture, text, args); } return text; } internal static string TranslateFallback(string fallback) { if (string.IsNullOrEmpty(fallback)) { return fallback ?? string.Empty; } if (!DefaultKeysByFallback.TryGetValue(fallback, out var value)) { return fallback; } return Get(value, fallback); } private static void SetupWatcher() { watcher = new FileSystemWatcher(TranslationRuleLoader.ConfigRootPath, "Localization-*.json") { IncludeSubdirectories = false, NotifyFilter = (NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime), SynchronizingObject = ThreadingHelper.SynchronizingObject }; watcher.Changed += OnLocalizationFileChanged; watcher.Created += OnLocalizationFileChanged; watcher.Deleted += OnLocalizationFileChanged; watcher.Renamed += OnLocalizationFileRenamed; watcher.EnableRaisingEvents = true; reloadDebounceTimer = new System.Timers.Timer(250.0) { AutoReset = false, SynchronizingObject = ThreadingHelper.SynchronizingObject }; reloadDebounceTimer.Elapsed += OnReloadDebounceElapsed; } private static void OnLocalizationFileChanged(object sender, FileSystemEventArgs e) { ScheduleReload(); } private static void OnLocalizationFileRenamed(object sender, RenamedEventArgs e) { ScheduleReload(); } private static void ScheduleReload() { if (reloadDebounceTimer == null) { ReloadCurrentLanguage(); return; } reloadDebounceTimer.Stop(); reloadDebounceTimer.Start(); } private static void OnReloadDebounceElapsed(object sender, ElapsedEventArgs e) { ReloadCurrentLanguage(); } private static void ReloadCurrentLanguage() { string text = (currentLanguage = ResolveLanguage(currentLanguage)); try { Directory.CreateDirectory(TranslationRuleLoader.ConfigRootPath); Dictionary<string, string> dictionary = EnsureFileAndLoad("English", GetFilePath("English"), DefaultValuesByKey); EnsureFileAndLoad("Chinese_Trad", GetFilePath("Chinese_Trad"), TraditionalChineseValuesByKey); Dictionary<string, string> dictionary2 = new Dictionary<string, string>(dictionary, StringComparer.Ordinal); if (!string.Equals(text, "English", StringComparison.Ordinal)) { foreach (KeyValuePair<string, string> item in EnsureFileAndLoad(text, GetFilePath(text), GetSeedValuesForLanguage(text))) { if (!string.IsNullOrWhiteSpace(item.Value)) { dictionary2[item.Key] = item.Value; } } } localizedValues = dictionary2; } catch (Exception ex) { localizedValues = new Dictionary<string, string>(DefaultValuesByKey, StringComparer.Ordinal); ManualLogSource log = PluginMain.Log; if (log != null) { log.LogError((object)("Failed to reload plugin localization files: " + ex.Message)); } } PluginLocalization.Reloaded?.Invoke(); } private static Dictionary<string, string> EnsureFileAndLoad(string languageName, string filePath, IReadOnlyDictionary<string, string> seedValues) { bool exists; Dictionary<string, string> dictionary = TryLoadFile(filePath, out exists) ?? new Dictionary<string, string>(StringComparer.Ordinal); IReadOnlyDictionary<string, string> legacyValuesForLanguage = GetLegacyValuesForLanguage(languageName); bool flag = !exists; foreach (KeyValuePair<string, string> defaultEntry in DefaultEntries) { string value; string text = ((seedValues != null && seedValues.TryGetValue(defaultEntry.Key, out value)) ? value : defaultEntry.Value); if (!dictionary.ContainsKey(defaultEntry.Key)) { dictionary[defaultEntry.Key] = text; flag = true; } else if (ShouldUpdateSeedValue(defaultEntry.Key, dictionary[defaultEntry.Key], defaultEntry.Value, text, legacyValuesForLanguage)) { dictionary[defaultEntry.Key] = text; flag = true; } } if (flag) { File.WriteAllText(filePath, JsonConvert.SerializeObject((object)dictionary, (Formatting)1)); } return dictionary; } private static Dictionary<string, string> TryLoadFile(string filePath, out bool exists) { exists = File.Exists(filePath); if (!exists) { return null; } try { Dictionary<string, string> dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(filePath)); return (dictionary != null) ? new Dictionary<string, string>(dictionary, StringComparer.Ordinal) : new Dictionary<string, string>(StringComparer.Ordinal); } catch (Exception ex) { ManualLogSource log = PluginMain.Log; if (log != null) { log.LogError((object)("Failed to read plugin localization file '" + filePath + "': " + ex.Message)); } return new Dictionary<string, string>(StringComparer.Ordinal); } } private static IReadOnlyDictionary<string, string> GetSeedValuesForLanguage(string languageName) { if (!string.Equals(languageName, "Chinese_Trad", StringComparison.Ordinal)) { return DefaultValuesByKey; } return TraditionalChineseValuesByKey; } private static IReadOnlyDictionary<string, string> GetLegacyValuesForLanguage(string languageName) { if (!string.Equals(languageName, "Chinese_Trad", StringComparison.Ordinal)) { return DefaultLegacyValuesByKey; } return TraditionalChineseLegacyValuesByKey; } private static bool ShouldUpdateSeedValue(string key, string currentValue, string defaultValue, string seedValue, IReadOnlyDictionary<string, string> legacyValues) { if (currentValue == null) { return true; } if (string.Equals(currentValue, seedValue, StringComparison.Ordinal)) { return false; } if (!string.Equals(seedValue, defaultValue, StringComparison.Ordinal) && string.Equals(currentValue, defaultValue, StringComparison.Ordinal)) { return true; } if (legacyValues != null && legacyValues.TryGetValue(key, out var value)) { return string.Equals(currentValue, value, StringComparison.Ordinal); } return false; } private static string ResolveLanguage(string languageName) { if (!string.IsNullOrWhiteSpace(languageName)) { return languageName; } string @string = PlayerPrefs.GetString("language", "English"); if (!string.IsNullOrWhiteSpace(@string)) { return @string; } return "English"; } private static string GetFilePath(string languageName) { return Path.Combine(TranslationRuleLoader.ConfigRootPath, "Localization-" + ResolveLanguage(languageName) + ".json"); } private static IReadOnlyList<KeyValuePair<string, string>> BuildDefaultEntries() { return new KeyValuePair<string, string>[252] { new KeyValuePair<string, string>("common.no", "No"), new KeyValuePair<string, string>("common.off", "Off"), new KeyValuePair<string, string>("common.on", "On"), new KeyValuePair<string, string>("common.yes", "Yes"), new KeyValuePair<string, string>("common.clear", "Clear"), new KeyValuePair<string, string>("common.search", "Search"), new KeyValuePair<string, string>("common.debug.null", "<null>"), new KeyValuePair<string, string>("common.debug.truncated_suffix", "... [truncated]"), new KeyValuePair<string, string>("common.unknown", "<unknown>"), new KeyValuePair<string, string>("plugin.loaded", "{0} loaded."), new KeyValuePair<string, string>("plugin.loaded_dedicated", "{0} loaded in dedicated server mode. ServerSync remains active; translation runtime and UI patches are skipped."), new KeyValuePair<string, string>("plugin.log.devtools_not_ready", "Localization devtools UI is not ready yet."), new KeyValuePair<string, string>("plugin.log.devtools_update_failed", "Failed to update {0} from the devtools UI: {1}"), new KeyValuePair<string, string>("runtime.analysis.final_output", "Final output: '{0}'"), new KeyValuePair<string, string>("runtime.analysis.pass_result_loop", "Pass 2 result: a translation loop back to the original input was detected, so the pass 1 output was kept."), new KeyValuePair<string, string>("runtime.analysis.pass_result_no_change", "Pass 1 result: no translation rule changed the input."), new KeyValuePair<string, string>("runtime.analysis.pass_result_no_further_change", "Pass 2 result: no further translation rule changed the first pass output."), new KeyValuePair<string, string>("runtime.analysis.pass_result_updated", "Pass 2 result: output updated to '{0}'."), new KeyValuePair<string, string>("runtime.analysis.runtime_disabled", "Translation is currently disabled; analysis is using the current in-memory rule state."), new KeyValuePair<string, string>("runtime.analysis.runtime_empty", "Analysis stopped because the selected string is empty."), new KeyValuePair<string, string>("runtime.analysis.runtime_not_initialized", "Analysis stopped because the translation runtime is not initialized."), new KeyValuePair<string, string>("runtime.analysis.runtime_pass_chain", "Top-level pass chain: fixed 2 passes. Additional whole string longest match rewrites are shown as step N inside a pass."), new KeyValuePair<string, string>("runtime.analysis.runtime_state", "Runtime State"), new KeyValuePair<string, string>("runtime.analysis.runtime_initialized", "Runtime initialized: {0}"), new KeyValuePair<string, string>("runtime.analysis.translation_enabled", "Translation enabled: {0}"), new KeyValuePair<string, string>("runtime.analysis.pass_title_one", "Pass 1"), new KeyValuePair<string, string>("runtime.analysis.pass_title_two", "Pass 2"), new KeyValuePair<string, string>("runtime.analysis.final_title", "Final"), new KeyValuePair<string, string>("runtime.analysis.stage.exact", "Exact"), new KeyValuePair<string, string>("runtime.analysis.stage.normalized_exact", "Normalized Exact"), new KeyValuePair<string, string>("runtime.analysis.stage.template", "Template"), new KeyValuePair<string, string>("runtime.analysis.stage.whole_string_longest_match", "Whole String Longest Match"), new KeyValuePair<string, string>("runtime.analysis.pass_stage_not_executed", "{0} {1}: not executed ({2} already produced output)"), new KeyValuePair<string, string>("runtime.analysis.stage_summary_output", "Output: '{0}'"), new KeyValuePair<string, string>("runtime.analysis.stage_summary_miss", "No rule matched."), new KeyValuePair<string, string>("runtime.analysis.stage_summary_not_executed", "Skipped because {0} already produced output."), new KeyValuePair<string, string>("runtime.analysis.pass_input", "{0} input: '{1}'"), new KeyValuePair<string, string>("runtime.analysis.pass_exact_hit", "{0} exact: hit target='{1}', match='{2}', replace='{3}', file='{4}' -> '{5}'"), new KeyValuePair<string, string>("runtime.analysis.pass_exact_miss", "{0} exact: miss"), new KeyValuePair<string, string>("runtime.analysis.pass_normalized_input", "{0} normalized input: '{1}'"), new KeyValuePair<string, string>("runtime.analysis.pass_normalized_exact_hit", "{0} normalized exact: hit target='{1}', match='{2}', replace='{3}', file='{4}' -> '{5}'"), new KeyValuePair<string, string>("runtime.analysis.pass_normalized_exact_miss", "{0} normalized exact: miss"), new KeyValuePair<string, string>("runtime.analysis.pass_template_hit", "{0} template: hit target='{1}', match='{2}', replace='{3}', file='{4}' -> '{5}'"), new KeyValuePair<string, string>("runtime.analysis.pass_template_miss", "{0} template: miss"), new KeyValuePair<string, string>("runtime.analysis.pass_whole_string_longest_match_step_input", "{0} whole string longest match step {1} input: '{2}'"), new KeyValuePair<string, string>("runtime.analysis.pass_whole_string_longest_match_step_hit", "{0} whole string longest match step {1} hit [{2}..{3}): target='{4}', match='{5}', replace='{6}', file='{7}'"), new KeyValuePair<string, string>("runtime.analysis.pass_whole_string_longest_match_step_output", "{0} whole string longest match step {1} output: '{2}'"), new KeyValuePair<string, string>("runtime.analysis.pass_whole_string_longest_match_loop", "{0} whole string longest match: detected a loop and kept the last non-loop output."), new KeyValuePair<string, string>("runtime.analysis.pass_whole_string_longest_match_final", "{0} whole string longest match: final -> '{1}'"), new KeyValuePair<string, string>("runtime.analysis.pass_whole_string_longest_match_matched", "{0} whole string longest match: matched -> '{1}'"), new KeyValuePair<string, string>("runtime.analysis.pass_whole_string_longest_match_miss", "{0} whole string longest match: miss"), new KeyValuePair<string, string>("runtime.log.config_root", "Translation config root: {0}"), new KeyValuePair<string, string>("runtime.log.disabled_after_reason", "Translation runtime disabled; skipped rule loading after {0}."), new KeyValuePair<string, string>("runtime.log.load_result", "{0} translation rules for '{1}'. English='{2}' (local={3}, synced={4}), Active='{5}' (local={6}, synced={7}), exact={8}, templates={9}."), new KeyValuePair<string, string>("runtime.settings.read_failed", "Failed to read runtime settings from '{0}': {1}"), new KeyValuePair<string, string>("runtime.settings.backfill_failed", "Failed to backfill missing runtime settings in '{0}': {1}"), new KeyValuePair<string, string>("runtime.settings.summary", "Runtime settings via '{0}' ({1}): translation_enabled={2}, debug={3}, whole_string_longest_match_mode={4}, whole_string_longest_match_min_length={5}, trace_feed_max_entries={6}."), new KeyValuePair<string, string>("serversync.log.source_of_truth", "ServerSync source-of-truth enabled; local config hot reload is active."), new KeyValuePair<string, string>("serversync.log.snapshot_published", "Published local translation snapshot after {0}: {1} language folder(s), {2} file(s)."), new KeyValuePair<string, string>("devtools.app.title", "Localization Devtools"), new KeyValuePair<string, string>("devtools.page.browser", "Browser"), new KeyValuePair<string, string>("devtools.page.performance", "Performance"), new KeyValuePair<string, string>("devtools.page.playground", "Playground"), new KeyValuePair<string, string>("devtools.page.plugin", "Plugin"), new KeyValuePair<string, string>("devtools.page.trace", "Trace"), new KeyValuePair<string, string>("devtools.plugin.section.overview", "Overview"), new KeyValuePair<string, string>("devtools.plugin.section.devtools", "Devtools"), new KeyValuePair<string, string>("devtools.plugin.section.runtime", "Runtime"), new KeyValuePair<string, string>("devtools.plugin.section.status", "Status"), new KeyValuePair<string, string>("devtools.plugin.row.plugin_version", "Plugin Version"), new KeyValuePair<string, string>("devtools.plugin.row.current_language", "Current Language"), new KeyValuePair<string, string>("devtools.plugin.row.remote_snapshot", "Remote Snapshot"), new KeyValuePair<string, string>("devtools.plugin.row.overlay_enabled", "Overlay Enabled"), new KeyValuePair<string, string>("devtools.plugin.row.toggle_shortcut", "Toggle Shortcut"), new KeyValuePair<string, string>("devtools.plugin.row.close_actions", "Close Actions"), new KeyValuePair<string, string>("devtools.plugin.row.translation_enabled", "Translation Enabled"), new KeyValuePair<string, string>("devtools.plugin.row.debug_enabled", "Debug Enabled"), new KeyValuePair<string, string>("devtools.plugin.row.whole_string_longest_match_mode", "Whole String Longest Match Mode"), new KeyValuePair<string, string>("devtools.plugin.row.minimum_match_length", "Minimum Match Length"), new KeyValuePair<string, string>("devtools.plugin.row.trace_feed_capacity", "Trace Feed Capacity"), new KeyValuePair<string, string>("devtools.plugin.row.hot_update", "Hot Update"), new KeyValuePair<string, string>("devtools.plugin.row.shortcut_capture", "Shortcut Capture"), new KeyValuePair<string, string>("devtools.plugin.row.last_config_action", "Last Config Action"), new KeyValuePair<string, string>("devtools.plugin.action.capture", "Capture"), new KeyValuePair<string, string>("devtools.plugin.action.next", "Next"), new KeyValuePair<string, string>("devtools.plugin.action.prev", "Prev"), new KeyValuePair<string, string>("devtools.plugin.action.reset", "Reset"), new KeyValuePair<string, string>("devtools.plugin.action.toggle", "Toggle"), new KeyValuePair<string, string>("devtools.plugin.close_actions_value", "ESC"), new KeyValuePair<string, string>("devtools.plugin.hot_update_value", "Immediate"), new KeyValuePair<string, string>("devtools.plugin.shortcut.capture_prompt", "Press a new shortcut..."), new KeyValuePair<string, string>("devtools.plugin.shortcut.listening", "Listening (ESC cancels)"), new KeyValuePair<string, string>("devtools.plugin.shortcut.idle", "Idle"), new KeyValuePair<string, string>("devtools.plugin.status.ready", "Ready"), new KeyValuePair<string, string>("devtools.plugin.status.capture_cancelled", "Shortcut capture cancelled."), new KeyValuePair<string, string>("devtools.plugin.status.capture_started", "Press the new toggle shortcut. ESC cancels."), new KeyValuePair<string, string>("devtools.plugin.status.toggle_shortcut_updated", "Toggle Shortcut -> {0}"), new KeyValuePair<string, string>("devtools.plugin.status.overlay_enabled_updated", "Overlay Enabled -> {0}"), new KeyValuePair<string, string>("devtools.plugin.status.translation_enabled_updated", "Translation Enabled -> {0}"), new KeyValuePair<string, string>("devtools.plugin.status.debug_enabled_updated", "Debug Enabled -> {0}"), new KeyValuePair<string, string>("devtools.plugin.status.whole_string_longest_match_mode_updated", "Whole String Longest Match Mode -> {0}"), new KeyValuePair<string, string>("devtools.plugin.status.min_length_updated", "Minimum Match Length -> {0}"), new KeyValuePair<string, string>("devtools.plugin.status.trace_feed_capacity_updated", "Trace Feed Capacity -> {0}"), new KeyValuePair<string, string>("devtools.plugin.status.setting_failed", "{0} update failed: {1}"), new KeyValuePair<string, string>("devtools.plugin.mode.off", "Off"), new KeyValuePair<string, string>("devtools.plugin.mode.min_length", "Min Length"), new KeyValuePair<string, string>("devtools.plugin.mode.per_word", "Per Word"), new KeyValuePair<string, string>("devtools.performance.section.loaded_content", "Loaded Content"), new KeyValuePair<string, string>("devtools.performance.section.hot_path", "Hot Path"), new KeyValuePair<string, string>("devtools.performance.section.caches", "Caches"), new KeyValuePair<string, string>("devtools.performance.section.timings", "Timings"), new KeyValuePair<string, string>("devtools.performance.row.exact_rules", "Exact Rules"), new KeyValuePair<string, string>("devtools.performance.row.template_rules", "Template Rules"), new KeyValuePair<string, string>("devtools.performance.row.local_english_files", "Local English Files"), new KeyValuePair<string, string>("devtools.performance.row.synced_english_files", "Synced English Files"), new KeyValuePair<string, string>("devtools.performance.row.local_active_files", "Local Active Files"), new KeyValuePair<string, string>("devtools.performance.row.synced_active_files", "Synced Active Files"), new KeyValuePair<string, string>("devtools.performance.row.intercepts", "Intercepts"), new KeyValuePair<string, string>("devtools.performance.row.reuse_hits", "Reuse Hits"), new KeyValuePair<string, string>("devtools.performance.row.reuse_hit_rate", "Reuse Hit Rate"), new KeyValuePair<string, string>("devtools.performance.row.known_output_skips", "Known Output Skips"), new KeyValuePair<string, string>("devtools.performance.row.known_output_skip_rate", "Known Output Skip Rate"), new KeyValuePair<string, string>("devtools.performance.row.translation_cache_hits", "Translation Cache Hits"), new KeyValuePair<string, string>("devtools.performance.row.translation_cache_calls", "Translation Cache Calls"), new KeyValuePair<string, string>("devtools.performance.row.translation_cache_hit_rate", "Translation Cache Rate"), new KeyValuePair<string, string>("devtools.performance.row.translation_miss_cache_hits", "Miss Cache Hits"), new KeyValuePair<string, string>("devtools.performance.row.translation_miss_cache_calls", "Miss Cache Calls"), new KeyValuePair<string, string>("devtools.performance.row.miss_cache_hit_rate", "Miss Cache Rate"), new KeyValuePair<string, string>("devtools.performance.row.normalizer_cache_hits", "Normalizer Cache Hits"), new KeyValuePair<string, string>("devtools.performance.row.normalizer_cache_calls", "Normalizer Cache Calls"), new KeyValuePair<string, string>("devtools.performance.row.normalizer_cache_hit_rate", "Normalizer Cache Rate"), new KeyValuePair<string, string>("devtools.performance.row.avg_template_candidates", "Avg Template Candidates"), new KeyValuePair<string, string>("devtools.performance.row.translation_cache", "Translation Cache"), new KeyValuePair<string, string>("devtools.performance.row.translation_miss_cache", "Translation Miss Cache"), new KeyValuePair<string, string>("devtools.performance.row.placeholder_cache", "Placeholder Cache"), new KeyValuePair<string, string>("devtools.performance.row.placeholder_miss_cache", "Placeholder Miss Cache"), new KeyValuePair<string, string>("devtools.performance.row.runtime_evictions", "Runtime Evictions"), new KeyValuePair<string, string>("devtools.performance.row.resolver_evictions", "Resolver Evictions"), new KeyValuePair<string, string>("devtools.performance.row.avg_intercept", "Avg Intercept"), new KeyValuePair<string, string>("devtools.performance.row.avg_translate", "Avg Translate"), new KeyValuePair<string, string>("devtools.performance.row.avg_normalize", "Avg Normalize"), new KeyValuePair<string, string>("devtools.performance.row.avg_template", "Avg Template"), new KeyValuePair<string, string>("devtools.performance.row.avg_whole_string_longest_match", "Avg Whole String Longest Match"), new KeyValuePair<string, string>("devtools.performance.row.template_attempts", "Template Attempts"), new KeyValuePair<string, string>("devtools.performance.row.template_matches", "Template Hits"), new KeyValuePair<string, string>("devtools.performance.row.template_constrained", "Template Constrained"), new KeyValuePair<string, string>("devtools.performance.row.whole_string_longest_match_attempts", "Whole String Longest Match Attempts"), new KeyValuePair<string, string>("devtools.performance.row.whole_string_longest_match_matches", "Whole String Longest Match Hits"), new KeyValuePair<string, string>("devtools.performance.row.metrics", "Metrics"), new KeyValuePair<string, string>("devtools.performance.status.cumulative", "Cumulative since startup"), new KeyValuePair<string, string>("devtools.performance.status.reset_now", "Reset just now"), new KeyValuePair<string, string>("devtools.performance.value.count", "Count"), new KeyValuePair<string, string>("devtools.performance.value.hit", "Hit"), new KeyValuePair<string, string>("devtools.performance.value.total", "Total"), new KeyValuePair<string, string>("devtools.performance.value.rate", "Rate"), new KeyValuePair<string, string>("devtools.performance.value.attempts", "Attempts"), new KeyValuePair<string, string>("devtools.performance.value.matches", "Matches"), new KeyValuePair<string, string>("devtools.performance.value.constrained", "Constrained"), new KeyValuePair<string, string>("devtools.trace.section.how_to_use", "How To Use"), new KeyValuePair<string, string>("devtools.trace.section.overview", "Overview"), new KeyValuePair<string, string>("devtools.trace.header.feed", "Trace Feed"), new KeyValuePair<string, string>("devtools.trace.header.analysis", "Translation Analysis"), new KeyValuePair<string, string>("devtools.trace.header.selected_source", "Selected Source"), new KeyValuePair<string, string>("devtools.trace.analysis.status.hit", "Hit"), new KeyValuePair<string, string>("devtools.trace.analysis.status.miss", "Miss"), new KeyValuePair<string, string>("devtools.trace.analysis.status.not_executed", "Not Executed"), new KeyValuePair<string, string>("devtools.trace.button.start_feed", "Start Feed"), new KeyValuePair<string, string>("devtools.trace.button.stop_feed", "Stop Feed"), new KeyValuePair<string, string>("devtools.trace.button.copy_input", "Copy Input"), new KeyValuePair<string, string>("devtools.trace.button.copy_report", "Copy Report"), new KeyValuePair<string, string>("devtools.trace.report.title", "Translation Analysis Report"), new KeyValuePair<string, string>("devtools.trace.feed.status", "Feed: {0} | Unique: {1} / {2}"), new KeyValuePair<string, string>("devtools.trace.feed.unique_count", "Unique Strings: {0} / {1}"), new KeyValuePair<string, string>("devtools.trace.search.placeholder", "Search captured strings or sources..."), new KeyValuePair<string, string>("devtools.trace.overview.input", "Input: {0}"), new KeyValuePair<string, string>("devtools.trace.overview.source", "Source: {0}"), new KeyValuePair<string, string>("devtools.trace.overview.output", "Current Output: {0}"), new KeyValuePair<string, string>("devtools.trace.usage.select", "Click a captured string on the left to run a fresh translation analysis."), new KeyValuePair<string, string>("devtools.trace.usage.replay", "The analysis always replays the translation flow live for the selected input."), new KeyValuePair<string, string>("devtools.trace.usage.capacity", "Feed only stores unique strings and stops at {0} entries."), new KeyValuePair<string, string>("devtools.trace.source.empty", "Select a captured string to inspect its source."), new KeyValuePair<string, string>("devtools.trace.ui.truncated_suffix", "... [truncated in UI]"), new KeyValuePair<string, string>("devtools.browser.header.navigator", "Translation Browser"), new KeyValuePair<string, string>("devtools.browser.header.details", "File Details"), new KeyValuePair<string, string>("devtools.browser.search.placeholder", "Search file path, match, or replace..."), new KeyValuePair<string, string>("devtools.browser.status.results", "Showing {0} / {1} files"), new KeyValuePair<string, string>("devtools.browser.status.selection", "{0} | {1} exact / {2} template"), new KeyValuePair<string, string>("devtools.browser.source.local", "Local"), new KeyValuePair<string, string>("devtools.browser.source.server", "Server"), new KeyValuePair<string, string>("devtools.browser.empty.no_results", "No translation files matched the current search."), new KeyValuePair<string, string>("devtools.browser.empty.no_selection", "Select a translation file on the left to inspect its rules."), new KeyValuePair<string, string>("devtools.browser.empty.no_rules", "This file does not contain any rules."), new KeyValuePair<string, string>("devtools.browser.file.summary", "{0} exact / {1} template"), new KeyValuePair<string, string>("devtools.browser.rule.title", "Rule #{0}"), new KeyValuePair<string, string>("devtools.playground.section.scenario", "Scenario"), new KeyValuePair<string, string>("devtools.playground.section.rule_editor", "Rule Editor"), new KeyValuePair<string, string>("devtools.playground.section.enabled_rules", "Enabled Rules"), new KeyValuePair<string, string>("devtools.playground.section.preview", "Preview"), new KeyValuePair<string, string>("devtools.playground.section.generated_test_set", "Generated Test Set"), new KeyValuePair<string, string>("devtools.playground.label.include_external_rules", "Include translation files loaded outside the Playground in the preview."), new KeyValuePair<string, string>("devtools.playground.label.copy_json_help", "Copy only the saved playground rules as a bare Localization rule file."), new KeyValuePair<string, string>("devtools.playground.field.test_input", "Test Input"), new KeyValuePair<string, string>("devtools.playground.field.match", "Match"), new KeyValuePair<string, string>("devtools.playground.field.replace", "Replace"), new KeyValuePair<string, string>("devtools.playground.field.summary", "Summary"), new KeyValuePair<string, string>("devtools.playground.field.matched_input", "Matched Input"), new KeyValuePair<string, string>("devtools.playground.field.rule_editor_match_preview", "Rule Editor Match Preview"), new KeyValuePair<string, string>("devtools.playground.field.output", "Output"), new KeyValuePair<string, string>("devtools.playground.field.json_preview", "JSON Preview"), new KeyValuePair<string, string>("devtools.playground.placeholder.test_input", "Enter text to evaluate against the active playground rule set."), new KeyValuePair<string, string>("devtools.playground.placeholder.match", "Exact text or template match."), new KeyValuePair<string, string>("devtools.playground.placeholder.replace", "Translated output for the match above."), new KeyValuePair<string, string>("devtools.playground.button.add_rule", "Add Rule"), new KeyValuePair<string, string>("devtools.playground.button.clear_rules", "Clear Rules"), new KeyValuePair<string, string>("devtools.playground.button.copy_json", "Copy JSON"), new KeyValuePair<string, string>("devtools.playground.button.remove", "Remove"), new KeyValuePair<string, string>("devtools.playground.button.external_rules_on", "External Rules: On"), new KeyValuePair<string, string>("devtools.playground.button.external_rules_off", "External Rules: Off"), new KeyValuePair<string, string>("devtools.playground.button.rule_type", "Rule Type: {0}"), new KeyValuePair<string, string>("devtools.playground.empty_rules", "No saved playground rules yet."), new KeyValuePair<string, string>("devtools.playground.status.ready", "Ready."), new KeyValuePair<string, string>("devtools.playground.status.include_external_rules_enabled", "Translation files loaded outside the Playground are now included in preview evaluation."), new KeyValuePair<string, string>("devtools.playground.status.include_external_rules_disabled", "Preview evaluation now uses only Playground rules."), new KeyValuePair<string, string>("devtools.playground.status.draft_mode_switched", "Draft mode switched to {0}."), new KeyValuePair<string, string>("devtools.playground.status.rule_saved", "Saved {0} rule #{1}."), new KeyValuePair<string, string>("devtools.playground.status.rules_cleared", "Cleared all saved playground rules."), new KeyValuePair<string, string>("devtools.playground.status.rule_removed", "Removed {0} rule #{1}."), new KeyValuePair<string, string>("devtools.playground.status.json_copied", "Copied playground JSON to clipboard."), new KeyValuePair<string, string>("devtools.playground.status.output_empty", "Enter input to see the translated output."), new KeyValuePair<string, string>("devtools.playground.status.draft_empty", "Draft empty."), new KeyValuePair<string, string>("devtools.playground.status.draft_incomplete", "Draft incomplete. Both match and replace are required."), new KeyValuePair<string, string>("devtools.playground.status.draft_invalid", "Template invalid: {0}"), new KeyValuePair<string, string>("devtools.playground.status.draft_ready", "Draft ready as {0}."), new KeyValuePair<string, string>("devtools.playground.status.no_input", "No input"), new KeyValuePair<string, string>("devtools.playground.status.no_match", "No Match"), new KeyValuePair<string, string>("devtools.playground.status.normalized_exact", "Normalized Exact"), new KeyValuePair<string, string>("devtools.playground.status.whole_string_longest_match", "Whole String Longest Match"), new KeyValuePair<string, string>("devtools.playground.status.highlight_prompt", "Enter input to see highlighted matches."), new KeyValuePair<string, string>("devtools.playground.summary.saved_rules", "Saved rules: {0} ({1} exact / {2} template)"), new KeyValuePair<string, string>("devtools.playground.summary.preview_rules_with_external", "Preview rule set: {0} exact / {1} template + external rules {2}"), new KeyValuePair<string, string>("devtools.playground.summary.winning_stage", "Winning stage: {0}"), new KeyValuePair<string, string>("devtools.playground.summary.stacked_result_winning_stage", "Stacked result winning stage: {0}"), new KeyValuePair<string, string>("devtools.playground.summary.match_preview_scope", "Rule editor match preview uses only the current Match field. Output remains the full stacked rule result."), new KeyValuePair<string, string>("devtools.playground.summary.draft", "Draft: {0}"), new KeyValuePair<string, string>("devtools.playground.preview.no_input", "Enter test input to preview the current rule editor match."), new KeyValuePair<string, string>("devtools.playground.preview.no_draft", "Fill Match and Replace to preview the current rule editor match."), new KeyValuePair<string, string>("devtools.playground.preview.current_match", "Current Match field: {0}"), new KeyValuePair<string, string>("devtools.playground.preview.applies", "Applies to current test input: {0}"), new KeyValuePair<string, string>("devtools.playground.preview.stage", "Draft preview stage: {0}"), new KeyValuePair<string, string>("devtools.playground.rule_row", "#{0} [{1}] {2} => {3}"), new KeyValuePair<string, string>("devtools.playground.kind.exact", "Exact"), new KeyValuePair<string, string>("devtools.playground.kind.template", "Template") }; } private static Dictionary<string, string> BuildDefaultValuesByKey() { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); for (int i = 0; i < DefaultEntries.Count; i++) { KeyValuePair<string, string> keyValuePair = DefaultEntries[i]; dictionary[keyValuePair.Key] = keyValuePair.Value; } return dictionary; } private static Dictionary<string, string> BuildDefaultLegacyValuesByKey() { return new Dictionary<string, string>(StringComparer.Ordinal) { ["runtime.settings.summary"] = "Runtime settings via '{0}' ({1}): translation_enabled={2}, debug={3}, whole_string_longest_match_mode={4}, whole_string_longest_match_min_length={5}.", ["devtools.trace.usage.capacity"] = "Feed only stores unique strings and stops at 1024 entries.", ["devtools.browser.status.selection"] = "{0} / {1} / {2} | {3} exact / {4} template" }; } private static Dictionary<string, string> BuildDefaultKeysByFallback() { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); for (int i = 0; i