using System; using System.Collections.Generic; namespace UnityEngine.Localization.Pseudo { /// /// Replaces characters in the input string. /// [Serializable] public class CharacterSubstitutor : IPseudoLocalizationMethod, ISerializationCallbackReceiver { /// /// The substitution method to use when applying Pseudo-Localization. /// public enum SubstitutionMethod { /// /// Converts all characters to uppercase. /// ToUpper, /// /// Converts all characters to lowercase. /// ToLower, /// /// Replaces all characters with a single character from . /// This method is a good way to find any hard coded text or to test for missing characters in a font. /// List, /// /// Uses to replace each character, when it is present in the dictionary. /// Map } [Serializable] struct CharReplacement { public char original; public char replacement; } /// /// The method to use when selecting a character from . /// public enum ListSelectionMethod { /// /// Select characters at random from . /// Random, /// /// Starts at the position from the previous input string and use each character in , /// looping back to the start if the end of the list is reached. /// LoopFromPrevious, /// /// Starts at the first item in and uses each character in the list /// looping back to the start if the end of the list is reached. /// LoopFromStart } [SerializeField] SubstitutionMethod m_SubstitutionMethod; [SerializeField] ListSelectionMethod m_ListMode; [SerializeField] List m_ReplacementsMap; [SerializeField] List m_ReplacementList = new List { '_' }; internal int m_ReplacementsPosition; /// /// The substitution method to use. /// public SubstitutionMethod Method { get => m_SubstitutionMethod; set => m_SubstitutionMethod = value; } /// /// Dictionary of characters that will be replaced when using . Any value that is not present will be preserved. /// public Dictionary ReplacementMap { get; private set; } = new Dictionary(); /// /// The method to use when selecting a character from . /// public ListSelectionMethod ListMode { get => m_ListMode; set => m_ListMode = value; } /// /// The characters to use when using mode. /// public List ReplacementList => m_ReplacementList; int GetRandomSeed(string input) => input.GetHashCode(); /// /// Attempts to find a replacement character and return it. /// /// The character to replace. /// The replacement character of the same if none could be found. internal char ReplaceCharFromMap(char value) { if (ReplacementMap != null && ReplacementMap.TryGetValue(value, out char replacement)) return replacement; return value; } public void OnBeforeSerialize() { if (m_ReplacementsMap == null) m_ReplacementsMap = new List(); m_ReplacementsMap.Clear(); foreach (var pair in ReplacementMap) { m_ReplacementsMap.Add(new CharReplacement() { original = pair.Key, replacement = pair.Value }); } } public void OnAfterDeserialize() { if (ReplacementMap == null) ReplacementMap = new Dictionary(); ReplacementMap.Clear(); foreach (var d in m_ReplacementsMap) { ReplacementMap[d.original] = d.replacement; } } void TransformFragment(WritableMessageFragment writableFragment) { switch (Method) { case SubstitutionMethod.Map: var converted = new char[writableFragment.Length]; for (int j = 0; j < converted.Length; ++j) { converted[j] = ReplaceCharFromMap(writableFragment[j]); } writableFragment.Text = new string(converted); break; case SubstitutionMethod.ToUpper: writableFragment.Text = writableFragment.Text.ToUpper(); break; case SubstitutionMethod.ToLower: writableFragment.Text = writableFragment.Text.ToLower(); break; case SubstitutionMethod.List: if (m_ReplacementList == null || m_ReplacementList.Count == 0) break; if (m_ReplacementList.Count == 1) { writableFragment.Text = new string(m_ReplacementList[0], writableFragment.Length); break; } var newValues = new char[writableFragment.Length]; if (ListMode == ListSelectionMethod.Random) { Random.InitState(GetRandomSeed(writableFragment.Message.Original)); for (int i = 0; i < newValues.Length; ++i) { newValues[i] = m_ReplacementList[Random.Range(0, m_ReplacementList.Count)]; } } else { if (ListMode == ListSelectionMethod.LoopFromStart) m_ReplacementsPosition = 0; for (int i = 0; i < newValues.Length; ++i, ++m_ReplacementsPosition) { newValues[i] = m_ReplacementList[m_ReplacementsPosition % m_ReplacementList.Count]; } } writableFragment.Text = new string(newValues); break; } } /// /// Replaces each character in the input with a replacement character if one can be found. /// If a replacement character can not be found then the original is kept. /// /// public void Transform(Message message) { foreach (var fragment in message.Fragments) { if (fragment is WritableMessageFragment writableFragment) TransformFragment(writableFragment); } } } }