507 lines
18 KiB
C#
507 lines
18 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Linq;
|
|||
|
using System.Text;
|
|||
|
using UnityEngine;
|
|||
|
using UnityEngine.Events;
|
|||
|
using Object = UnityEngine.Object;
|
|||
|
|
|||
|
namespace I2.Loc
|
|||
|
{
|
|||
|
[AddComponentMenu("I2/Localization/I2 Localize")]
|
|||
|
public class Localize : MonoBehaviour
|
|||
|
{
|
|||
|
#region Variables: Term
|
|||
|
public string Term
|
|||
|
{
|
|||
|
get { return mTerm; }
|
|||
|
set { SetTerm(value); }
|
|||
|
}
|
|||
|
public string SecondaryTerm
|
|||
|
{
|
|||
|
get { return mTermSecondary; }
|
|||
|
set { SetTerm(null, value); }
|
|||
|
}
|
|||
|
|
|||
|
public string mTerm = string.Empty, // if Target is a Label, this will be the text, if sprite, this will be the spriteName, etc
|
|||
|
mTermSecondary = string.Empty; // if Target is a Label, this will be the font Name, if sprite, this will be the Atlas name, etc
|
|||
|
|
|||
|
// 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)
|
|||
|
// This are set when the component starts
|
|||
|
[NonSerialized] public string FinalTerm, FinalSecondaryTerm;
|
|||
|
|
|||
|
public enum TermModification { DontModify, ToUpper, ToLower, ToUpperFirst, ToTitle/*, CustomRange*/}
|
|||
|
public TermModification PrimaryTermModifier = TermModification.DontModify,
|
|||
|
SecondaryTermModifier = TermModification.DontModify;
|
|||
|
public string TermPrefix, TermSuffix;
|
|||
|
|
|||
|
public bool LocalizeOnAwake = true;
|
|||
|
|
|||
|
string LastLocalizedLanguage; // Used to avoid Localizing everytime the object is Enabled
|
|||
|
|
|||
|
#if UNITY_EDITOR
|
|||
|
public ILanguageSource Source; // Source used while in the Editor to preview the Terms (can be of type LanguageSource or LanguageSourceAsset)
|
|||
|
#endif
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Variables: Target
|
|||
|
|
|||
|
public bool IgnoreRTL; // If false, no Right To Left processing will be done
|
|||
|
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
|
|||
|
public bool IgnoreNumbersInRTL = true; // If the language is RTL, the translation will not convert numbers (will preserve them like: e.g. 123)
|
|||
|
|
|||
|
public bool CorrectAlignmentForRTL = true; // If true, when Right To Left language, alignment will be set to Right
|
|||
|
|
|||
|
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.
|
|||
|
public bool AllowLocalizedParameters=true;
|
|||
|
public bool AllowParameters=true;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Variables: References
|
|||
|
|
|||
|
public List<Object> TranslatedObjects = new List<Object>(); // For targets that reference objects (e.g. AudioSource, UITexture,etc)
|
|||
|
// this keeps a reference to the possible options.
|
|||
|
// If the value is not the name of any of this objects then it will try to load the object from the Resources
|
|||
|
|
|||
|
|
|||
|
[NonSerialized] public Dictionary<string, Object> mAssetDictionary = new Dictionary<string, Object>(StringComparer.Ordinal); //This is used to overcome the issue with Unity not serializing Dictionaries
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Variable Translation Modifiers
|
|||
|
|
|||
|
|
|||
|
public UnityEvent LocalizeEvent = new UnityEvent(); // This allows scripts to modify the translations : e.g. "Player {0} wins" -> "Player Red wins"
|
|||
|
|
|||
|
|
|||
|
public static string MainTranslation, SecondaryTranslation; // The callback should use and modify this variables
|
|||
|
public static string CallBackTerm, CallBackSecondaryTerm; // during the callback, this will hold the FinalTerm and FinalSecondary to know what terms are originating the translation
|
|||
|
public static Localize CurrentLocalizeComponent; // while in the LocalizeCallBack, this points to the Localize calling the callback
|
|||
|
|
|||
|
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)
|
|||
|
|
|||
|
[SerializeField] public EventCallback LocalizeCallBack = new EventCallback(); //LocalizeCallBack is deprecated. Please use LocalizeEvent instead.
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Variables: Editor Related
|
|||
|
public bool mGUI_ShowReferences;
|
|||
|
public bool mGUI_ShowTems = true;
|
|||
|
public bool mGUI_ShowCallback;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Variables: Runtime (LocalizeTarget)
|
|||
|
|
|||
|
public ILocalizeTarget mLocalizeTarget;
|
|||
|
public string mLocalizeTargetName; // Used to resolve multiple targets in a prefab
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Localize
|
|||
|
|
|||
|
void Awake()
|
|||
|
{
|
|||
|
UpdateAssetDictionary();
|
|||
|
FindTarget();
|
|||
|
|
|||
|
if (LocalizeOnAwake)
|
|||
|
OnLocalize();
|
|||
|
}
|
|||
|
|
|||
|
#if UNITY_EDITOR
|
|||
|
void OnValidate()
|
|||
|
{
|
|||
|
if (LocalizeCallBack.HasCallback())
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
var methodInfo = UnityEventBase.GetValidMethodInfo(LocalizeCallBack.Target, LocalizeCallBack.MethodName, Array.Empty<Type>());
|
|||
|
|
|||
|
if (methodInfo != null)
|
|||
|
{
|
|||
|
UnityAction methodDelegate = Delegate.CreateDelegate(typeof(UnityAction), LocalizeCallBack.Target, methodInfo, false) as UnityAction;
|
|||
|
}
|
|||
|
}
|
|||
|
catch(Exception)
|
|||
|
{}
|
|||
|
|
|||
|
LocalizeCallBack.Target = null;
|
|||
|
LocalizeCallBack.MethodName = null;
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
void OnEnable()
|
|||
|
{
|
|||
|
OnLocalize ();
|
|||
|
}
|
|||
|
|
|||
|
public bool HasCallback()
|
|||
|
{
|
|||
|
if (LocalizeCallBack.HasCallback())
|
|||
|
return true;
|
|||
|
return LocalizeEvent.GetPersistentEventCount() > 0;
|
|||
|
}
|
|||
|
|
|||
|
public void OnLocalize( bool Force = false )
|
|||
|
{
|
|||
|
if (!Force && (!enabled || gameObject==null || !gameObject.activeInHierarchy))
|
|||
|
return;
|
|||
|
|
|||
|
if (string.IsNullOrEmpty(LocalizationManager.CurrentLanguage))
|
|||
|
return;
|
|||
|
|
|||
|
if (!AlwaysForceLocalize && !Force && !HasCallback() && LastLocalizedLanguage==LocalizationManager.CurrentLanguage)
|
|||
|
return;
|
|||
|
LastLocalizedLanguage = LocalizationManager.CurrentLanguage;
|
|||
|
|
|||
|
// 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)
|
|||
|
if (string.IsNullOrEmpty(FinalTerm) || string.IsNullOrEmpty(FinalSecondaryTerm))
|
|||
|
GetFinalTerms( out FinalTerm, out FinalSecondaryTerm );
|
|||
|
|
|||
|
|
|||
|
bool hasCallback = I2Utils.IsPlaying() && HasCallback();
|
|||
|
|
|||
|
if (!hasCallback && string.IsNullOrEmpty (FinalTerm) && string.IsNullOrEmpty (FinalSecondaryTerm))
|
|||
|
return;
|
|||
|
|
|||
|
CallBackTerm = FinalTerm;
|
|||
|
CallBackSecondaryTerm = FinalSecondaryTerm;
|
|||
|
MainTranslation = string.IsNullOrEmpty(FinalTerm) || FinalTerm=="-" ? null : LocalizationManager.GetTranslation (FinalTerm, false);
|
|||
|
SecondaryTranslation = string.IsNullOrEmpty(FinalSecondaryTerm) || FinalSecondaryTerm == "-" ? null : LocalizationManager.GetTranslation (FinalSecondaryTerm, false);
|
|||
|
|
|||
|
if (!hasCallback && /*string.IsNullOrEmpty (MainTranslation)*/ string.IsNullOrEmpty(FinalTerm) && string.IsNullOrEmpty (SecondaryTranslation))
|
|||
|
return;
|
|||
|
|
|||
|
CurrentLocalizeComponent = this;
|
|||
|
|
|||
|
{
|
|||
|
LocalizeCallBack.Execute (this); // This allows scripts to modify the translations : e.g. "Player {0} wins" -> "Player Red wins"
|
|||
|
LocalizeEvent.Invoke();
|
|||
|
if (AllowParameters)
|
|||
|
LocalizationManager.ApplyLocalizationParams (ref MainTranslation, gameObject, AllowLocalizedParameters);
|
|||
|
}
|
|||
|
|
|||
|
if (!FindTarget())
|
|||
|
return;
|
|||
|
bool applyRTL = LocalizationManager.IsRight2Left && !IgnoreRTL;
|
|||
|
|
|||
|
if (MainTranslation != null)
|
|||
|
{
|
|||
|
switch (PrimaryTermModifier)
|
|||
|
{
|
|||
|
case TermModification.ToUpper: MainTranslation = MainTranslation.ToUpper(); break;
|
|||
|
case TermModification.ToLower: MainTranslation = MainTranslation.ToLower(); break;
|
|||
|
case TermModification.ToUpperFirst: MainTranslation = GoogleTranslation.UppercaseFirst(MainTranslation); break;
|
|||
|
case TermModification.ToTitle: MainTranslation = GoogleTranslation.TitleCase(MainTranslation); break;
|
|||
|
}
|
|||
|
if (!string.IsNullOrEmpty(TermPrefix))
|
|||
|
MainTranslation = applyRTL ? MainTranslation + TermPrefix : TermPrefix + MainTranslation;
|
|||
|
if (!string.IsNullOrEmpty(TermSuffix))
|
|||
|
MainTranslation = applyRTL ? TermSuffix + MainTranslation : MainTranslation + TermSuffix;
|
|||
|
|
|||
|
if (AddSpacesToJoinedLanguages && LocalizationManager.HasJoinedWords && !string.IsNullOrEmpty(MainTranslation))
|
|||
|
{
|
|||
|
var sb = new StringBuilder();
|
|||
|
sb.Append(MainTranslation[0]);
|
|||
|
for (int i = 1, imax = MainTranslation.Length; i < imax; ++i)
|
|||
|
{
|
|||
|
sb.Append(' ');
|
|||
|
sb.Append(MainTranslation[i]);
|
|||
|
}
|
|||
|
|
|||
|
MainTranslation = sb.ToString();
|
|||
|
}
|
|||
|
if (applyRTL && mLocalizeTarget.AllowMainTermToBeRTL() && !string.IsNullOrEmpty(MainTranslation))
|
|||
|
MainTranslation = LocalizationManager.ApplyRTLfix(MainTranslation, MaxCharactersInRTL, IgnoreNumbersInRTL);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
if (SecondaryTranslation != null)
|
|||
|
{
|
|||
|
switch (SecondaryTermModifier)
|
|||
|
{
|
|||
|
case TermModification.ToUpper: SecondaryTranslation = SecondaryTranslation.ToUpper(); break;
|
|||
|
case TermModification.ToLower: SecondaryTranslation = SecondaryTranslation.ToLower(); break;
|
|||
|
case TermModification.ToUpperFirst: SecondaryTranslation = GoogleTranslation.UppercaseFirst(SecondaryTranslation); break;
|
|||
|
case TermModification.ToTitle: SecondaryTranslation = GoogleTranslation.TitleCase(SecondaryTranslation); break;
|
|||
|
}
|
|||
|
if (applyRTL && mLocalizeTarget.AllowSecondTermToBeRTL() && !string.IsNullOrEmpty(SecondaryTranslation))
|
|||
|
SecondaryTranslation = LocalizationManager.ApplyRTLfix(SecondaryTranslation);
|
|||
|
}
|
|||
|
|
|||
|
if (LocalizationManager.HighlightLocalizedTargets)
|
|||
|
{
|
|||
|
MainTranslation = "LOC:" + FinalTerm;
|
|||
|
}
|
|||
|
|
|||
|
mLocalizeTarget.DoLocalize( this, MainTranslation, SecondaryTranslation );
|
|||
|
|
|||
|
CurrentLocalizeComponent = null;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Finding Target
|
|||
|
|
|||
|
public bool FindTarget()
|
|||
|
{
|
|||
|
if (mLocalizeTarget != null && mLocalizeTarget.IsValid(this))
|
|||
|
return true;
|
|||
|
|
|||
|
if (mLocalizeTarget!=null)
|
|||
|
{
|
|||
|
DestroyImmediate(mLocalizeTarget);
|
|||
|
mLocalizeTarget = null;
|
|||
|
mLocalizeTargetName = null;
|
|||
|
}
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(mLocalizeTargetName))
|
|||
|
{
|
|||
|
foreach (var desc in LocalizationManager.mLocalizeTargets)
|
|||
|
{
|
|||
|
if (mLocalizeTargetName == desc.GetTargetType().ToString())
|
|||
|
{
|
|||
|
if (desc.CanLocalize(this))
|
|||
|
mLocalizeTarget = desc.CreateTarget(this);
|
|||
|
if (mLocalizeTarget!=null)
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
foreach (var desc in LocalizationManager.mLocalizeTargets)
|
|||
|
{
|
|||
|
if (!desc.CanLocalize(this))
|
|||
|
continue;
|
|||
|
mLocalizeTarget = desc.CreateTarget(this);
|
|||
|
mLocalizeTargetName = desc.GetTargetType().ToString();
|
|||
|
if (mLocalizeTarget != null)
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Finding Term
|
|||
|
|
|||
|
// Returns the term that will actually be translated
|
|||
|
// its either the Term value in this class or the text of the label if there is no term
|
|||
|
public void GetFinalTerms( out string primaryTerm, out string secondaryTerm )
|
|||
|
{
|
|||
|
primaryTerm = string.Empty;
|
|||
|
secondaryTerm = string.Empty;
|
|||
|
|
|||
|
if (!FindTarget())
|
|||
|
return;
|
|||
|
|
|||
|
|
|||
|
// if either the primary or secondary term is missing, get them. (e.g. from the label's text and font name)
|
|||
|
if (mLocalizeTarget != null)
|
|||
|
{
|
|||
|
mLocalizeTarget.GetFinalTerms(this, mTerm, mTermSecondary, out primaryTerm, out secondaryTerm);
|
|||
|
primaryTerm = I2Utils.GetValidTermName(primaryTerm);
|
|||
|
}
|
|||
|
|
|||
|
// If there are values already set, go with those
|
|||
|
if (!string.IsNullOrEmpty(mTerm))
|
|||
|
primaryTerm = mTerm;
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(mTermSecondary))
|
|||
|
secondaryTerm = mTermSecondary;
|
|||
|
|
|||
|
if (primaryTerm != null)
|
|||
|
primaryTerm = primaryTerm.Trim();
|
|||
|
if (secondaryTerm != null)
|
|||
|
secondaryTerm = secondaryTerm.Trim();
|
|||
|
}
|
|||
|
|
|||
|
public string GetMainTargetsText()
|
|||
|
{
|
|||
|
string primary = null, secondary = null;
|
|||
|
|
|||
|
if (mLocalizeTarget!=null)
|
|||
|
mLocalizeTarget.GetFinalTerms( this, null, null, out primary, out secondary );
|
|||
|
|
|||
|
return string.IsNullOrEmpty(primary) ? mTerm : primary;
|
|||
|
}
|
|||
|
|
|||
|
public void SetFinalTerms( string Main, string Secondary, out string primaryTerm, out string secondaryTerm, bool RemoveNonASCII )
|
|||
|
{
|
|||
|
primaryTerm = RemoveNonASCII ? I2Utils.GetValidTermName(Main) : Main;
|
|||
|
secondaryTerm = Secondary;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Misc
|
|||
|
|
|||
|
public void SetTerm (string primary)
|
|||
|
{
|
|||
|
if (!string.IsNullOrEmpty(primary))
|
|||
|
FinalTerm = mTerm = primary;
|
|||
|
|
|||
|
OnLocalize (true);
|
|||
|
}
|
|||
|
|
|||
|
public void SetTerm(string primary, string secondary )
|
|||
|
{
|
|||
|
if (!string.IsNullOrEmpty(primary))
|
|||
|
FinalTerm = mTerm = primary;
|
|||
|
FinalSecondaryTerm = mTermSecondary = secondary;
|
|||
|
|
|||
|
OnLocalize(true);
|
|||
|
}
|
|||
|
|
|||
|
internal T GetSecondaryTranslatedObj<T>( ref string mainTranslation, ref string secondaryTranslation ) where T: Object
|
|||
|
{
|
|||
|
string newMain, newSecond;
|
|||
|
|
|||
|
//--[ Allow main translation to override Secondary ]-------------------
|
|||
|
DeserializeTranslation(mainTranslation, out newMain, out newSecond);
|
|||
|
|
|||
|
T obj = null;
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(newSecond))
|
|||
|
{
|
|||
|
obj = GetObject<T>(newSecond);
|
|||
|
if (obj != null)
|
|||
|
{
|
|||
|
mainTranslation = newMain;
|
|||
|
secondaryTranslation = newSecond;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (obj == null)
|
|||
|
obj = GetObject<T>(secondaryTranslation);
|
|||
|
|
|||
|
return obj;
|
|||
|
}
|
|||
|
|
|||
|
public void UpdateAssetDictionary()
|
|||
|
{
|
|||
|
TranslatedObjects.RemoveAll(x => x == null);
|
|||
|
mAssetDictionary = TranslatedObjects.Distinct()
|
|||
|
.GroupBy(o => o.name)
|
|||
|
.ToDictionary(g => g.Key, g => g.First());
|
|||
|
}
|
|||
|
|
|||
|
internal T GetObject<T>( string Translation) where T: Object
|
|||
|
{
|
|||
|
if (string.IsNullOrEmpty (Translation))
|
|||
|
return null;
|
|||
|
T obj = GetTranslatedObject<T>(Translation);
|
|||
|
|
|||
|
//if (obj==null)
|
|||
|
//{
|
|||
|
// Remove path and search by name
|
|||
|
//int Index = Translation.LastIndexOfAny("/\\".ToCharArray());
|
|||
|
//if (Index>=0)
|
|||
|
//{
|
|||
|
// Translation = Translation.Substring(Index+1);
|
|||
|
// obj = GetTranslatedObject<T>(Translation);
|
|||
|
//}
|
|||
|
//}
|
|||
|
return obj;
|
|||
|
}
|
|||
|
|
|||
|
T GetTranslatedObject<T>( string Translation) where T: Object
|
|||
|
{
|
|||
|
T Obj = FindTranslatedObject<T>(Translation);
|
|||
|
/*if (Obj == null)
|
|||
|
return null;
|
|||
|
|
|||
|
if ((Obj as T) != null)
|
|||
|
return Obj as T;
|
|||
|
|
|||
|
// If the found Obj is not of type T, then try finding a component inside
|
|||
|
if (Obj as Component != null)
|
|||
|
return (Obj as Component).GetComponent(typeof(T)) as T;
|
|||
|
|
|||
|
if (Obj as GameObject != null)
|
|||
|
return (Obj as GameObject).GetComponent(typeof(T)) as T;
|
|||
|
*/
|
|||
|
return Obj;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// translation format: "[secondary]value" [secondary] is optional
|
|||
|
void DeserializeTranslation( string translation, out string value, out string secondary )
|
|||
|
{
|
|||
|
if (!string.IsNullOrEmpty(translation) && translation.Length>1 && translation[0]=='[')
|
|||
|
{
|
|||
|
int Index = translation.IndexOf(']');
|
|||
|
if (Index>0)
|
|||
|
{
|
|||
|
secondary = translation.Substring(1, Index-1);
|
|||
|
value = translation.Substring(Index+1);
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
value = translation;
|
|||
|
secondary = string.Empty;
|
|||
|
}
|
|||
|
|
|||
|
public T FindTranslatedObject<T>( string value) where T : Object
|
|||
|
{
|
|||
|
if (string.IsNullOrEmpty(value))
|
|||
|
return null;
|
|||
|
|
|||
|
if (mAssetDictionary == null || mAssetDictionary.Count != TranslatedObjects.Count)
|
|||
|
{
|
|||
|
UpdateAssetDictionary();
|
|||
|
}
|
|||
|
|
|||
|
foreach (var kvp in mAssetDictionary)
|
|||
|
{
|
|||
|
if (kvp.Value is T && value.EndsWith(kvp.Key, StringComparison.OrdinalIgnoreCase))
|
|||
|
{
|
|||
|
// Check if the value is just the name or has a path
|
|||
|
if (string.Compare(value, kvp.Key, StringComparison.OrdinalIgnoreCase)==0)
|
|||
|
return (T) kvp.Value;
|
|||
|
|
|||
|
// Check if the path matches
|
|||
|
//Resources.get TranslatedObjects[i].
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
T obj = LocalizationManager.FindAsset(value) as T;
|
|||
|
if (obj)
|
|||
|
return obj;
|
|||
|
|
|||
|
obj = ResourceManager.pInstance.GetAsset<T>(value);
|
|||
|
return obj;
|
|||
|
}
|
|||
|
|
|||
|
public bool HasTranslatedObject( Object Obj )
|
|||
|
{
|
|||
|
if (TranslatedObjects.Contains(Obj))
|
|||
|
return true;
|
|||
|
return ResourceManager.pInstance.HasAsset(Obj);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
public void AddTranslatedObject( Object Obj )
|
|||
|
{
|
|||
|
if (TranslatedObjects.Contains(Obj))
|
|||
|
return;
|
|||
|
TranslatedObjects.Add(Obj);
|
|||
|
UpdateAssetDictionary();
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Utilities
|
|||
|
// This can be used to set the language when a button is clicked
|
|||
|
public void SetGlobalLanguage( string Language )
|
|||
|
{
|
|||
|
LocalizationManager.CurrentLanguage = Language;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|