Localize.cs 18 KB

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