AppUILocalization.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. using System;
  2. using System.Collections.Generic;
  3. using Newtonsoft.Json;
  4. using TMPro;
  5. using UnityEngine;
  6. using UnityEngine.UI;
  7. #if UNITY_EDITOR
  8. using UnityEditor;
  9. #endif
  10. namespace AppUI.Localization
  11. {
  12. /// <summary>
  13. /// AppUI 独立多语言表。查词时优先读本表,缺失则回退 <see cref="TextAutoLanguage2"/>。
  14. /// 旧 BowArrow 资源与逻辑不做任何修改。
  15. /// </summary>
  16. public static class AppUILocalization
  17. {
  18. const string ResourcesPrefix = "AppUILocalization/";
  19. static LanguageEnum currentLanguageEnum = LanguageEnum.Chinese;
  20. static Dictionary<string, string> languageDictionary;
  21. static bool inited;
  22. public static event Action<LanguageEnum> OnLanguageChanged;
  23. public static void Init()
  24. {
  25. if (inited && languageDictionary != null)
  26. return;
  27. int id = PlayerPrefs.GetInt("Language", 0);
  28. LoadLanguage((LanguageEnum)id, notifyListeners: false);
  29. }
  30. /// <summary>强制按 PlayerPrefs 当前语言重载 json(Editor 预览改 json 后点刷新用)。</summary>
  31. public static void Reload()
  32. {
  33. inited = false;
  34. languageDictionary = null;
  35. int id = PlayerPrefs.GetInt("Language", 0);
  36. LoadLanguage((LanguageEnum)id, notifyListeners: false);
  37. }
  38. /// <summary>
  39. /// 与旧系统同步切换语言。应在设置页等入口调用,保证 TextAutoLanguage / TextAutoLanguage2 / ImageAutoLanguage 一并更新。
  40. /// </summary>
  41. public static void ChangeAllLanguages(LanguageEnum languageEnum)
  42. {
  43. TextAutoLanguage.ChangeLanguage(languageEnum);
  44. TextAutoLanguage2.ChangeLanguage(languageEnum);
  45. ImageAutoLanguage.ChangeLanguage(languageEnum);
  46. ChangeLanguage(languageEnum);
  47. }
  48. /// <summary>仅加载 AppUI 表并刷新 <see cref="AppUITextAutoLanguage"/> 组件。</summary>
  49. public static void ChangeLanguage(LanguageEnum languageEnum)
  50. {
  51. LoadLanguage(languageEnum, notifyListeners: true);
  52. }
  53. static void LoadLanguage(LanguageEnum languageEnum, bool notifyListeners)
  54. {
  55. string fileName = ResolveFileName(languageEnum);
  56. languageDictionary = LoadDictionary(fileName);
  57. currentLanguageEnum = languageEnum;
  58. inited = true;
  59. if (!notifyListeners)
  60. return;
  61. foreach (var item in AppUITextAutoLanguage.RegisteredComponents)
  62. {
  63. try
  64. {
  65. item.ApplyToText();
  66. }
  67. catch (Exception e)
  68. {
  69. Debug.LogError(e.Message);
  70. }
  71. }
  72. OnLanguageChanged?.Invoke(languageEnum);
  73. }
  74. public static LanguageEnum GetLanguage()
  75. {
  76. Init();
  77. return currentLanguageEnum;
  78. }
  79. public static string GetTextByKey(string textKey)
  80. {
  81. return ResolveText(textKey);
  82. }
  83. /// <summary>
  84. /// 查词后按 <see cref="TMP_Text"/> 可用宽度插入 <c>\u200B</c>,便于窄容器内逐字/逐字母换行。
  85. /// <paramref name="maxWidth"/> / <paramref name="maxHeight"/> 为 0 时使用 <paramref name="tmpText"/> 的 RectTransform 尺寸。
  86. /// </summary>
  87. public static string GetTextByKey(string textKey, TMP_Text tmpText, float maxWidth = 0f, float maxHeight = 0f, params object[] formatArgs)
  88. {
  89. return GetTextWithWidthBreak(ResolveText(textKey), tmpText, null, maxWidth, maxHeight, formatArgs);
  90. }
  91. /// <summary>Legacy <see cref="Text"/> 版本。</summary>
  92. public static string GetTextByKey(string textKey, Text legacyText, float maxWidth = 0f, float maxHeight = 0f, params object[] formatArgs)
  93. {
  94. return GetTextWithWidthBreak(ResolveText(textKey), null, legacyText, maxWidth, maxHeight, formatArgs);
  95. }
  96. /// <summary>显式指定布局矩形与 TMP 组件。</summary>
  97. public static string GetTextByKey(string textKey, RectTransform rect, TMP_Text tmpText, float maxWidth = 0f, float maxHeight = 0f, params object[] formatArgs)
  98. {
  99. string text = FormatResolvedText(ResolveText(textKey), formatArgs);
  100. if (tmpText == null || string.IsNullOrEmpty(text))
  101. return text;
  102. maxWidth = AppUILocalizationTextBreak.ResolveMaxWidth(rect != null ? rect : tmpText.rectTransform, maxWidth);
  103. maxHeight = AppUILocalizationTextBreak.ResolveMaxHeight(rect != null ? rect : tmpText.rectTransform, maxHeight);
  104. return AppUILocalizationTextBreak.InsertWidthBreaks(text, tmpText, maxWidth, maxHeight);
  105. }
  106. /// <summary>对已解析文案按宽度插入 <c>\u200B</c>。</summary>
  107. public static string InsertWidthBreaks(string text, TMP_Text tmpText, float maxWidth = 0f, float maxHeight = 0f)
  108. {
  109. return AppUILocalizationTextBreak.InsertWidthBreaks(text, tmpText, maxWidth, maxHeight);
  110. }
  111. /// <summary>Legacy <see cref="Text"/> 版本。</summary>
  112. public static string InsertWidthBreaks(string text, Text legacyText, float maxWidth = 0f, float maxHeight = 0f)
  113. {
  114. return AppUILocalizationTextBreak.InsertWidthBreaks(text, legacyText, maxWidth, maxHeight);
  115. }
  116. static string GetTextWithWidthBreak(string text, TMP_Text tmpText, Text legacyText, float maxWidth, float maxHeight, object[] formatArgs)
  117. {
  118. text = FormatResolvedText(text, formatArgs);
  119. if (string.IsNullOrEmpty(text))
  120. return text;
  121. if (tmpText != null)
  122. return AppUILocalizationTextBreak.InsertWidthBreaks(text, tmpText, maxWidth, maxHeight);
  123. if (legacyText != null)
  124. return AppUILocalizationTextBreak.InsertWidthBreaks(text, legacyText, maxWidth, maxHeight);
  125. return text;
  126. }
  127. static string FormatResolvedText(string text, object[] formatArgs)
  128. {
  129. if (string.IsNullOrEmpty(text))
  130. return text;
  131. if (formatArgs != null && formatArgs.Length > 0)
  132. return string.Format(text, formatArgs);
  133. return text;
  134. }
  135. public static string GetTextByCNKey(string textKey)
  136. {
  137. Init();
  138. if (currentLanguageEnum == LanguageEnum.Chinese)
  139. return textKey;
  140. if (TryGetLocal(textKey, out string localText))
  141. return localText;
  142. return TextAutoLanguage2.GetTextByCNKey(textKey);
  143. }
  144. /// <summary>新表优先,旧表回退;两边都没有时返回 key 并打 Warning。</summary>
  145. public static string ResolveText(string textKey)
  146. {
  147. Init();
  148. if (TryGetLocal(textKey, out string localText))
  149. return localText;
  150. try
  151. {
  152. return TextAutoLanguage2.GetTextByKey(textKey);
  153. }
  154. catch (KeyNotFoundException)
  155. {
  156. Debug.LogWarning($"[AppUILocalization] Missing key: {textKey}");
  157. return textKey;
  158. }
  159. }
  160. static bool TryGetLocal(string textKey, out string text)
  161. {
  162. text = null;
  163. if (languageDictionary == null || string.IsNullOrEmpty(textKey))
  164. return false;
  165. return languageDictionary.TryGetValue(textKey, out text) && text != null;
  166. }
  167. static string ResolveFileName(LanguageEnum languageEnum)
  168. {
  169. if (languageEnum == LanguageEnum.English)
  170. return "en";
  171. if (languageEnum == LanguageEnum.Japan)
  172. return "Japan";
  173. return "cn";
  174. }
  175. static Dictionary<string, string> LoadDictionary(string fileName)
  176. {
  177. TextAsset asset = null;
  178. #if UNITY_EDITOR
  179. if (!Application.isPlaying)
  180. {
  181. asset = AssetDatabase.LoadAssetAtPath<TextAsset>(
  182. $"Assets/AppUI/Resources/AppUILocalization/{fileName}.json");
  183. }
  184. #endif
  185. if (asset == null)
  186. asset = Resources.Load<TextAsset>(ResourcesPrefix + fileName);
  187. if (asset == null)
  188. {
  189. Debug.LogError($"[AppUILocalization] Missing Resources/{ResourcesPrefix}{fileName}.json");
  190. return new Dictionary<string, string>();
  191. }
  192. return JsonConvert.DeserializeObject<Dictionary<string, string>>(asset.text)
  193. ?? new Dictionary<string, string>();
  194. }
  195. }
  196. }