Decompiled source of Localization v2.0.1

Localization.dll

Decompiled 4 days ago
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