Localize.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. using System;
  2. using UnityEngine;
  3. using UnityEngine.Events;
  4. using UnityEngine.Serialization;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using Object = UnityEngine.Object;
  8. namespace I2.Loc
  9. {
  10. [AddComponentMenu("I2/Localization/I2 Localize")]
  11. public partial class Localize : MonoBehaviour
  12. {
  13. #region Variables: Term
  14. public string Term
  15. {
  16. get { return mTerm; }
  17. set { SetTerm(value); }
  18. }
  19. public string SecondaryTerm
  20. {
  21. get { return mTermSecondary; }
  22. set { SetTerm(null, value); }
  23. }
  24. public string mTerm = string.Empty, // if Target is a Label, this will be the text, if sprite, this will be the spriteName, etc
  25. mTermSecondary = string.Empty; // if Target is a Label, this will be the font Name, if sprite, this will be the Atlas name, etc
  26. // This are the terms actually used (will be mTerm/mSecondaryTerm or will get them from the objects if those are missing. e.g. Labels' text and font name)
  27. // This are set when the component starts
  28. [NonSerialized] public string FinalTerm, FinalSecondaryTerm;
  29. public enum TermModification { DontModify, ToUpper, ToLower, ToUpperFirst, ToTitle/*, CustomRange*/}
  30. public TermModification PrimaryTermModifier = TermModification.DontModify,
  31. SecondaryTermModifier = TermModification.DontModify;
  32. public string TermPrefix, TermSuffix;
  33. public bool LocalizeOnAwake = true;
  34. string LastLocalizedLanguage; // Used to avoid Localizing everytime the object is Enabled
  35. #if UNITY_EDITOR
  36. public ILanguageSource Source; // Source used while in the Editor to preview the Terms (can be of type LanguageSource or LanguageSourceAsset)
  37. #endif
  38. #endregion
  39. #region Variables: Target
  40. public bool IgnoreRTL = false; // If false, no Right To Left processing will be done
  41. public int MaxCharactersInRTL = 0; // If the language is RTL, the translation will be split in lines not longer than this amount and the RTL fix will be applied per line
  42. public bool IgnoreNumbersInRTL = true; // If the language is RTL, the translation will not convert numbers (will preserve them like: e.g. 123)
  43. public bool CorrectAlignmentForRTL = true; // If true, when Right To Left language, alignment will be set to Right
  44. public bool AddSpacesToJoinedLanguages; // Some languages (e.g. Chinese, Japanese and Thai) don't add spaces to their words (all characters are placed toguether), making this variable true, will add spaces to all characters to allow wrapping long texts into multiple lines.
  45. public bool AllowLocalizedParameters=true;
  46. #endregion
  47. #region Variables: References
  48. public List<Object> TranslatedObjects = new List<Object>(); // For targets that reference objects (e.g. AudioSource, UITexture,etc)
  49. // this keeps a reference to the possible options.
  50. // If the value is not the name of any of this objects then it will try to load the object from the Resources
  51. [NonSerialized] public Dictionary<string, Object> mAssetDictionary = new Dictionary<string, Object>(StringComparer.Ordinal); //This is used to overcome the issue with Unity not serializing Dictionaries
  52. #endregion
  53. #region Variable Translation Modifiers
  54. public UnityEvent LocalizeEvent = new UnityEvent(); // This allows scripts to modify the translations : e.g. "Player {0} wins" -> "Player Red wins"
  55. public static string MainTranslation, SecondaryTranslation; // The callback should use and modify this variables
  56. public static string CallBackTerm, CallBackSecondaryTerm; // during the callback, this will hold the FinalTerm and FinalSecondary to know what terms are originating the translation
  57. public static Localize CurrentLocalizeComponent; // while in the LocalizeCallBack, this points to the Localize calling the callback
  58. public bool AlwaysForceLocalize = false; // Force localization when the object gets enabled (useful for callbacks and parameters that change the localization even through the language is the same as in the previous time it was localized)
  59. [SerializeField] public EventCallback LocalizeCallBack = new EventCallback(); //LocalizeCallBack is deprecated. Please use LocalizeEvent instead.
  60. #endregion
  61. #region Variables: Editor Related
  62. public bool mGUI_ShowReferences = false;
  63. public bool mGUI_ShowTems = true;
  64. public bool mGUI_ShowCallback = false;
  65. #endregion
  66. #region Variables: Runtime (LocalizeTarget)
  67. public ILocalizeTarget mLocalizeTarget;
  68. public string mLocalizeTargetName; // Used to resolve multiple targets in a prefab
  69. #endregion
  70. #region Localize
  71. void Awake()
  72. {
  73. #if UNITY_EDITOR
  74. if (UnityEditor.BuildPipeline.isBuildingPlayer)
  75. return;
  76. #endif
  77. UpdateAssetDictionary();
  78. FindTarget();
  79. if (LocalizeOnAwake)
  80. OnLocalize();
  81. }
  82. #if UNITY_EDITOR
  83. void OnValidate()
  84. {
  85. if (LocalizeCallBack.HasCallback())
  86. {
  87. try
  88. {
  89. var methodInfo = UnityEvent.GetValidMethodInfo(LocalizeCallBack.Target, LocalizeCallBack.MethodName, new Type[0]);
  90. if (methodInfo != null)
  91. {
  92. UnityAction methodDelegate = System.Delegate.CreateDelegate(typeof(UnityAction), LocalizeCallBack.Target, methodInfo, false) as UnityAction;
  93. if (methodDelegate != null)
  94. UnityEditor.Events.UnityEventTools.AddPersistentListener(LocalizeEvent, methodDelegate);
  95. }
  96. }
  97. catch(Exception)
  98. {}
  99. LocalizeCallBack.Target = null;
  100. LocalizeCallBack.MethodName = null;
  101. }
  102. }
  103. #endif
  104. void OnEnable()
  105. {
  106. OnLocalize ();
  107. }
  108. public bool HasCallback()
  109. {
  110. if (LocalizeCallBack.HasCallback())
  111. return true;
  112. return LocalizeEvent.GetPersistentEventCount() > 0;
  113. }
  114. public void OnLocalize( bool Force = false )
  115. {
  116. if (!Force && (!enabled || gameObject==null || !gameObject.activeInHierarchy))
  117. return;
  118. if (string.IsNullOrEmpty(LocalizationManager.CurrentLanguage))
  119. return;
  120. if (!AlwaysForceLocalize && !Force && !HasCallback() && LastLocalizedLanguage==LocalizationManager.CurrentLanguage)
  121. return;
  122. LastLocalizedLanguage = LocalizationManager.CurrentLanguage;
  123. // These are the terms actually used (will be mTerm/mSecondaryTerm or will get them from the objects if those are missing. e.g. Labels' text and font name)
  124. if (string.IsNullOrEmpty(FinalTerm) || string.IsNullOrEmpty(FinalSecondaryTerm))
  125. GetFinalTerms( out FinalTerm, out FinalSecondaryTerm );
  126. bool hasCallback = I2Utils.IsPlaying() && HasCallback();
  127. if (!hasCallback && string.IsNullOrEmpty (FinalTerm) && string.IsNullOrEmpty (FinalSecondaryTerm))
  128. return;
  129. CallBackTerm = FinalTerm;
  130. CallBackSecondaryTerm = FinalSecondaryTerm;
  131. MainTranslation = (string.IsNullOrEmpty(FinalTerm) || FinalTerm=="-") ? null : LocalizationManager.GetTranslation (FinalTerm, false);
  132. SecondaryTranslation = (string.IsNullOrEmpty(FinalSecondaryTerm) || FinalSecondaryTerm == "-") ? null : LocalizationManager.GetTranslation (FinalSecondaryTerm, false);
  133. if (!hasCallback && /*string.IsNullOrEmpty (MainTranslation)*/ string.IsNullOrEmpty(FinalTerm) && string.IsNullOrEmpty (SecondaryTranslation))
  134. return;
  135. CurrentLocalizeComponent = this;
  136. {
  137. LocalizeCallBack.Execute (this); // This allows scripts to modify the translations : e.g. "Player {0} wins" -> "Player Red wins"
  138. LocalizeEvent.Invoke();
  139. LocalizationManager.ApplyLocalizationParams (ref MainTranslation, gameObject, AllowLocalizedParameters);
  140. }
  141. if (!FindTarget())
  142. return;
  143. bool applyRTL = LocalizationManager.IsRight2Left && !IgnoreRTL;
  144. if (MainTranslation != null)
  145. {
  146. switch (PrimaryTermModifier)
  147. {
  148. case TermModification.ToUpper: MainTranslation = MainTranslation.ToUpper(); break;
  149. case TermModification.ToLower: MainTranslation = MainTranslation.ToLower(); break;
  150. case TermModification.ToUpperFirst: MainTranslation = GoogleTranslation.UppercaseFirst(MainTranslation); break;
  151. case TermModification.ToTitle: MainTranslation = GoogleTranslation.TitleCase(MainTranslation); break;
  152. }
  153. if (!string.IsNullOrEmpty(TermPrefix))
  154. MainTranslation = applyRTL ? MainTranslation + TermPrefix : TermPrefix + MainTranslation;
  155. if (!string.IsNullOrEmpty(TermSuffix))
  156. MainTranslation = applyRTL ? TermSuffix + MainTranslation : MainTranslation + TermSuffix;
  157. if (AddSpacesToJoinedLanguages && LocalizationManager.HasJoinedWords && !string.IsNullOrEmpty(MainTranslation))
  158. {
  159. var sb = new System.Text.StringBuilder();
  160. sb.Append(MainTranslation[0]);
  161. for (int i = 1, imax = MainTranslation.Length; i < imax; ++i)
  162. {
  163. sb.Append(' ');
  164. sb.Append(MainTranslation[i]);
  165. }
  166. MainTranslation = sb.ToString();
  167. }
  168. if (applyRTL && mLocalizeTarget.AllowMainTermToBeRTL() && !string.IsNullOrEmpty(MainTranslation))
  169. MainTranslation = LocalizationManager.ApplyRTLfix(MainTranslation, MaxCharactersInRTL, IgnoreNumbersInRTL);
  170. }
  171. if (SecondaryTranslation != null)
  172. {
  173. switch (SecondaryTermModifier)
  174. {
  175. case TermModification.ToUpper: SecondaryTranslation = SecondaryTranslation.ToUpper(); break;
  176. case TermModification.ToLower: SecondaryTranslation = SecondaryTranslation.ToLower(); break;
  177. case TermModification.ToUpperFirst: SecondaryTranslation = GoogleTranslation.UppercaseFirst(SecondaryTranslation); break;
  178. case TermModification.ToTitle: SecondaryTranslation = GoogleTranslation.TitleCase(SecondaryTranslation); break;
  179. }
  180. if (applyRTL && mLocalizeTarget.AllowSecondTermToBeRTL() && !string.IsNullOrEmpty(SecondaryTranslation))
  181. SecondaryTranslation = LocalizationManager.ApplyRTLfix(SecondaryTranslation);
  182. }
  183. if (LocalizationManager.HighlightLocalizedTargets)
  184. {
  185. MainTranslation = "LOC:" + FinalTerm;
  186. }
  187. mLocalizeTarget.DoLocalize( this, MainTranslation, SecondaryTranslation );
  188. CurrentLocalizeComponent = null;
  189. }
  190. #endregion
  191. #region Finding Target
  192. public bool FindTarget()
  193. {
  194. if (mLocalizeTarget != null && mLocalizeTarget.IsValid(this))
  195. return true;
  196. if (mLocalizeTarget!=null)
  197. {
  198. DestroyImmediate(mLocalizeTarget);
  199. mLocalizeTarget = null;
  200. mLocalizeTargetName = null;
  201. }
  202. if (!string.IsNullOrEmpty(mLocalizeTargetName))
  203. {
  204. foreach (var desc in LocalizationManager.mLocalizeTargets)
  205. {
  206. if (mLocalizeTargetName == desc.GetTargetType().ToString())
  207. {
  208. if (desc.CanLocalize(this))
  209. mLocalizeTarget = desc.CreateTarget(this);
  210. if (mLocalizeTarget!=null)
  211. return true;
  212. }
  213. }
  214. }
  215. foreach (var desc in LocalizationManager.mLocalizeTargets)
  216. {
  217. if (!desc.CanLocalize(this))
  218. continue;
  219. mLocalizeTarget = desc.CreateTarget(this);
  220. mLocalizeTargetName = desc.GetTargetType().ToString();
  221. if (mLocalizeTarget != null)
  222. return true;
  223. }
  224. return false;
  225. }
  226. #endregion
  227. #region Finding Term
  228. // Returns the term that will actually be translated
  229. // its either the Term value in this class or the text of the label if there is no term
  230. public void GetFinalTerms( out string primaryTerm, out string secondaryTerm )
  231. {
  232. primaryTerm = string.Empty;
  233. secondaryTerm = string.Empty;
  234. if (!FindTarget())
  235. return;
  236. // if either the primary or secondary term is missing, get them. (e.g. from the label's text and font name)
  237. if (mLocalizeTarget != null)
  238. {
  239. mLocalizeTarget.GetFinalTerms(this, mTerm, mTermSecondary, out primaryTerm, out secondaryTerm);
  240. primaryTerm = I2Utils.GetValidTermName(primaryTerm, false);
  241. }
  242. // If there are values already set, go with those
  243. if (!string.IsNullOrEmpty(mTerm))
  244. primaryTerm = mTerm;
  245. if (!string.IsNullOrEmpty(mTermSecondary))
  246. secondaryTerm = mTermSecondary;
  247. if (primaryTerm != null)
  248. primaryTerm = primaryTerm.Trim();
  249. if (secondaryTerm != null)
  250. secondaryTerm = secondaryTerm.Trim();
  251. }
  252. public string GetMainTargetsText()
  253. {
  254. string primary = null, secondary = null;
  255. if (mLocalizeTarget!=null)
  256. mLocalizeTarget.GetFinalTerms( this, null, null, out primary, out secondary );
  257. return string.IsNullOrEmpty(primary) ? mTerm : primary;
  258. }
  259. public void SetFinalTerms( string Main, string Secondary, out string primaryTerm, out string secondaryTerm, bool RemoveNonASCII )
  260. {
  261. primaryTerm = RemoveNonASCII ? I2Utils.GetValidTermName(Main) : Main;
  262. secondaryTerm = Secondary;
  263. }
  264. #endregion
  265. #region Misc
  266. public void SetTerm (string primary)
  267. {
  268. if (!string.IsNullOrEmpty(primary))
  269. FinalTerm = mTerm = primary;
  270. OnLocalize (true);
  271. }
  272. public void SetTerm(string primary, string secondary )
  273. {
  274. if (!string.IsNullOrEmpty(primary))
  275. FinalTerm = mTerm = primary;
  276. FinalSecondaryTerm = mTermSecondary = secondary;
  277. OnLocalize(true);
  278. }
  279. internal T GetSecondaryTranslatedObj<T>( ref string mainTranslation, ref string secondaryTranslation ) where T: Object
  280. {
  281. string newMain, newSecond;
  282. //--[ Allow main translation to override Secondary ]-------------------
  283. DeserializeTranslation(mainTranslation, out newMain, out newSecond);
  284. T obj = null;
  285. if (!string.IsNullOrEmpty(newSecond))
  286. {
  287. obj = GetObject<T>(newSecond);
  288. if (obj != null)
  289. {
  290. mainTranslation = newMain;
  291. secondaryTranslation = newSecond;
  292. }
  293. }
  294. if (obj == null)
  295. obj = GetObject<T>(secondaryTranslation);
  296. return obj;
  297. }
  298. public void UpdateAssetDictionary()
  299. {
  300. TranslatedObjects.RemoveAll(x => x == null);
  301. mAssetDictionary = TranslatedObjects.Distinct()
  302. .GroupBy(o => o.name)
  303. .ToDictionary(g => g.Key, g => g.First());
  304. }
  305. internal T GetObject<T>( string Translation) where T: Object
  306. {
  307. if (string.IsNullOrEmpty (Translation))
  308. return null;
  309. T obj = GetTranslatedObject<T>(Translation);
  310. //if (obj==null)
  311. //{
  312. // Remove path and search by name
  313. //int Index = Translation.LastIndexOfAny("/\\".ToCharArray());
  314. //if (Index>=0)
  315. //{
  316. // Translation = Translation.Substring(Index+1);
  317. // obj = GetTranslatedObject<T>(Translation);
  318. //}
  319. //}
  320. return obj;
  321. }
  322. T GetTranslatedObject<T>( string Translation) where T: Object
  323. {
  324. T Obj = FindTranslatedObject<T>(Translation);
  325. /*if (Obj == null)
  326. return null;
  327. if ((Obj as T) != null)
  328. return Obj as T;
  329. // If the found Obj is not of type T, then try finding a component inside
  330. if (Obj as Component != null)
  331. return (Obj as Component).GetComponent(typeof(T)) as T;
  332. if (Obj as GameObject != null)
  333. return (Obj as GameObject).GetComponent(typeof(T)) as T;
  334. */
  335. return Obj;
  336. }
  337. // translation format: "[secondary]value" [secondary] is optional
  338. void DeserializeTranslation( string translation, out string value, out string secondary )
  339. {
  340. if (!string.IsNullOrEmpty(translation) && translation.Length>1 && translation[0]=='[')
  341. {
  342. int Index = translation.IndexOf(']');
  343. if (Index>0)
  344. {
  345. secondary = translation.Substring(1, Index-1);
  346. value = translation.Substring(Index+1);
  347. return;
  348. }
  349. }
  350. value = translation;
  351. secondary = string.Empty;
  352. }
  353. public T FindTranslatedObject<T>( string value) where T : Object
  354. {
  355. if (string.IsNullOrEmpty(value))
  356. return null;
  357. if (mAssetDictionary == null || mAssetDictionary.Count != TranslatedObjects.Count)
  358. {
  359. UpdateAssetDictionary();
  360. }
  361. foreach (var kvp in mAssetDictionary)
  362. {
  363. if (kvp.Value is T && value.EndsWith(kvp.Key, StringComparison.OrdinalIgnoreCase))
  364. {
  365. // Check if the value is just the name or has a path
  366. if (string.Compare(value, kvp.Key, StringComparison.OrdinalIgnoreCase)==0)
  367. return (T) kvp.Value;
  368. // Check if the path matches
  369. //Resources.get TranslatedObjects[i].
  370. }
  371. }
  372. T obj = LocalizationManager.FindAsset(value) as T;
  373. if (obj)
  374. return obj;
  375. obj = ResourceManager.pInstance.GetAsset<T>(value);
  376. return obj;
  377. }
  378. public bool HasTranslatedObject( Object Obj )
  379. {
  380. if (TranslatedObjects.Contains(Obj))
  381. return true;
  382. return ResourceManager.pInstance.HasAsset(Obj);
  383. }
  384. public void AddTranslatedObject( Object Obj )
  385. {
  386. if (TranslatedObjects.Contains(Obj))
  387. return;
  388. TranslatedObjects.Add(Obj);
  389. UpdateAssetDictionary();
  390. }
  391. #endregion
  392. #region Utilities
  393. // This can be used to set the language when a button is clicked
  394. public void SetGlobalLanguage( string Language )
  395. {
  396. LocalizationManager.CurrentLanguage = Language;
  397. }
  398. #endregion
  399. }
  400. }