From 85553832ead1a96f88726cd2b35cb6ff1d8b8cc8 Mon Sep 17 00:00:00 2001 From: Andrew Lee Date: Sat, 24 Aug 2019 15:24:57 -0400 Subject: Attempt number 2 on localization --- .../Lean/Localization/Scripts/Behaviours.meta | 10 + .../Scripts/Behaviours/LeanLocalizedAudioSource.cs | 47 + .../Behaviours/LeanLocalizedAudioSource.cs.meta | 8 + .../Scripts/Behaviours/LeanLocalizedImage.cs | 48 + .../Scripts/Behaviours/LeanLocalizedImage.cs.meta | 8 + .../Scripts/Behaviours/LeanLocalizedRenderer.cs | 55 ++ .../Behaviours/LeanLocalizedRenderer.cs.meta | 12 + .../Behaviours/LeanLocalizedSpriteRenderer.cs | 47 + .../Behaviours/LeanLocalizedSpriteRenderer.cs.meta | 8 + .../Scripts/Behaviours/LeanLocalizedText.cs | 48 + .../Scripts/Behaviours/LeanLocalizedText.cs.meta | 8 + .../Scripts/Behaviours/LeanLocalizedTextFont.cs | 48 + .../Behaviours/LeanLocalizedTextFont.cs.meta | 12 + .../Scripts/Behaviours/LeanLocalizedTextMesh.cs | 46 + .../Behaviours/LeanLocalizedTextMesh.cs.meta | 13 + .../Behaviours/LeanLocalizedTextMeshFont.cs | 46 + .../Behaviours/LeanLocalizedTextMeshFont.cs.meta | 13 + .../Lean/Localization/Scripts/LeanLanguage.cs | 43 + .../Lean/Localization/Scripts/LeanLanguage.cs.meta | 8 + .../Lean/Localization/Scripts/LeanLanguageCSV.cs | 303 ++++++ .../Localization/Scripts/LeanLanguageCSV.cs.meta | 8 + .../Scripts/LeanLanguageNameAttribute.cs | 46 + .../Scripts/LeanLanguageNameAttribute.cs.meta | 8 + .../Lean/Localization/Scripts/LeanLocalization.cs | 1016 ++++++++++++++++++++ .../Localization/Scripts/LeanLocalization.cs.meta | 8 + .../Localization/Scripts/LeanLocalizedBehaviour.cs | 121 +++ .../Scripts/LeanLocalizedBehaviour.cs.meta | 8 + .../Lean/Localization/Scripts/LeanPhrase.cs | 299 ++++++ .../Lean/Localization/Scripts/LeanPhrase.cs.meta | 8 + .../Lean/Localization/Scripts/LeanPrefab.cs | 176 ++++ .../Lean/Localization/Scripts/LeanPrefab.cs.meta | 8 + .../Lean/Localization/Scripts/LeanSource.cs | 30 + .../Lean/Localization/Scripts/LeanSource.cs.meta | 8 + .../Lean/Localization/Scripts/LeanToken.cs | 130 +++ .../Lean/Localization/Scripts/LeanToken.cs.meta | 8 + .../Lean/Localization/Scripts/LeanTranslation.cs | 191 ++++ .../Localization/Scripts/LeanTranslation.cs.meta | 8 + .../Scripts/LeanTranslationNameAttribute.cs | 54 ++ .../Scripts/LeanTranslationNameAttribute.cs.meta | 8 + 39 files changed, 2974 insertions(+) create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedAudioSource.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedAudioSource.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedImage.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedImage.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedRenderer.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedRenderer.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedSpriteRenderer.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedSpriteRenderer.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedText.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedText.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextFont.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextFont.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMesh.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMesh.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMeshFont.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMeshFont.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanLanguage.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanLanguage.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanLanguageCSV.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanLanguageCSV.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanLanguageNameAttribute.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanLanguageNameAttribute.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanLocalization.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanLocalization.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanLocalizedBehaviour.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanLocalizedBehaviour.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanPhrase.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanPhrase.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanPrefab.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanPrefab.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanSource.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanSource.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanToken.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanToken.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanTranslation.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanTranslation.cs.meta create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanTranslationNameAttribute.cs create mode 100644 Assets/Packages/Lean/Localization/Scripts/LeanTranslationNameAttribute.cs.meta (limited to 'Assets/Packages/Lean/Localization/Scripts') diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours.meta b/Assets/Packages/Lean/Localization/Scripts/Behaviours.meta new file mode 100644 index 0000000..f442d8e --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: dfd423fd99d68f647853387ae5466692 +folderAsset: yes +timeCreated: 1555734116 +licenseType: Store +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedAudioSource.cs b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedAudioSource.cs new file mode 100644 index 0000000..7b45595 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedAudioSource.cs @@ -0,0 +1,47 @@ +using UnityEngine; + +namespace Lean.Localization +{ + /// This component will update an AudioSource component with localized text, or use a fallback if none is found. + [ExecuteInEditMode] + [DisallowMultipleComponent] + [RequireComponent(typeof(AudioSource))] + [HelpURL(LeanLocalization.HelpUrlPrefix + "LeanLocalizedAudioSource")] + [AddComponentMenu(LeanLocalization.ComponentPathPrefix + "Localized AudioSource")] + public class LeanLocalizedAudioSource : LeanLocalizedBehaviour + { + [Tooltip("If PhraseName couldn't be found, this clip will be used")] + public AudioClip FallbackAudioClip; + + // This gets called every time the translation needs updating + public override void UpdateTranslation(LeanTranslation translation) + { + // Get the AudioSource component attached to this GameObject + var audioSource = GetComponent(); + + // Use translation? + if (translation != null && translation.Data is AudioClip) + { + audioSource.clip = (AudioClip)translation.Data; + } + // Use fallback? + else + { + audioSource.clip = FallbackAudioClip; + } + } + + protected virtual void Awake() + { + // Should we set FallbackAudioClip? + if (FallbackAudioClip == null) + { + // Get the AudioSource component attached to this GameObject + var audioSource = GetComponent(); + + // Copy current sprite to fallback + FallbackAudioClip = audioSource.clip; + } + } + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedAudioSource.cs.meta b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedAudioSource.cs.meta new file mode 100644 index 0000000..479d2ca --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedAudioSource.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4bbcd1203ed53d54e9a127af382c0181 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedImage.cs b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedImage.cs new file mode 100644 index 0000000..664ec95 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedImage.cs @@ -0,0 +1,48 @@ +using UnityEngine; +using UnityEngine.UI; + +namespace Lean.Localization +{ + /// This component will update an Image component with a localized sprite, or use a fallback if none is found + [ExecuteInEditMode] + [DisallowMultipleComponent] + [RequireComponent(typeof(Image))] + [HelpURL(LeanLocalization.HelpUrlPrefix + "LeanLocalizedImage")] + [AddComponentMenu(LeanLocalization.ComponentPathPrefix + "Localized Image")] + public class LeanLocalizedImage : LeanLocalizedBehaviour + { + [Tooltip("If PhraseName couldn't be found, this sprite will be used")] + public Sprite FallbackSprite; + + // This gets called every time the translation needs updating + public override void UpdateTranslation(LeanTranslation translation) + { + // Get the Image component attached to this GameObject + var image = GetComponent(); + + // Use translation? + if (translation != null && translation.Data is Sprite) + { + image.sprite = (Sprite)translation.Data; + } + // Use fallback? + else + { + image.sprite = FallbackSprite; + } + } + + protected virtual void Awake() + { + // Should we set FallbackSprite? + if (FallbackSprite == null) + { + // Get the SpriteRenderer component attached to this GameObject + var spriteRenderer = GetComponent(); + + // Copy current sprite to fallback + FallbackSprite = spriteRenderer.sprite; + } + } + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedImage.cs.meta b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedImage.cs.meta new file mode 100644 index 0000000..b7be84d --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedImage.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5b28f7e33d1e3e0408d7ff2c8a92a650 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedRenderer.cs b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedRenderer.cs new file mode 100644 index 0000000..701e538 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedRenderer.cs @@ -0,0 +1,55 @@ +using UnityEngine; + +namespace Lean.Localization +{ + /// This component will update a Renderer component's sharedMaterial with a localized material, or use a fallback if none is found. + [ExecuteInEditMode] + [DisallowMultipleComponent] + [RequireComponent(typeof(Renderer))] + [HelpURL(LeanLocalization.HelpUrlPrefix + "LeanLocalizedRenderer")] + [AddComponentMenu(LeanLocalization.ComponentPathPrefix + "Localized Renderer")] + public class LeanLocalizedRenderer : LeanLocalizedBehaviour + { + [Tooltip("If PhraseName couldn't be found, this material will be used")] + public Material FallbackMaterial; + + [Tooltip("The material index you want to replace.")] + public int Index; + + // This gets called every time the translation needs updating + public override void UpdateTranslation(LeanTranslation translation) + { + // Get the Renderer component attached to this GameObject + var renderer = GetComponent(); + + // Get the shared materials of this component + var sharedMaterials = renderer.sharedMaterials; + + // Use translation? + if (translation != null && translation.Data is Material) + { + sharedMaterials[Index] = (Material)translation.Data; + } + // Use fallback? + else + { + sharedMaterials[Index] = FallbackMaterial; + } + + renderer.sharedMaterials = sharedMaterials; + } + + protected virtual void Awake() + { + // Should we set FallbackFont? + if (FallbackMaterial == null) + { + // Get the Renderer component attached to this GameObject + var renderer = GetComponent(); + + // Copy current material to fallback + FallbackMaterial = renderer.sharedMaterials[Index]; + } + } + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedRenderer.cs.meta b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedRenderer.cs.meta new file mode 100644 index 0000000..4fcb5ed --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedRenderer.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b42c83c3c333a7e4a8e498fec50ce1fb +timeCreated: 1478133615 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedSpriteRenderer.cs b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedSpriteRenderer.cs new file mode 100644 index 0000000..e1170b0 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedSpriteRenderer.cs @@ -0,0 +1,47 @@ +using UnityEngine; + +namespace Lean.Localization +{ + /// This component will update a SpriteRenderer component with a localized sprite, or use a fallback if none is found + [ExecuteInEditMode] + [DisallowMultipleComponent] + [RequireComponent(typeof(SpriteRenderer))] + [HelpURL(LeanLocalization.HelpUrlPrefix + "LeanLocalizedSpriteRenderer")] + [AddComponentMenu(LeanLocalization.ComponentPathPrefix + "Localized SpriteRenderer")] + public class LeanLocalizedSpriteRenderer : LeanLocalizedBehaviour + { + [Tooltip("If PhraseName couldn't be found, this sprite will be used")] + public Sprite FallbackSprite; + + // This gets called every time the translation needs updating + public override void UpdateTranslation(LeanTranslation translation) + { + // Get the SpriteRenderer component attached to this GameObject + var spriteRenderer = GetComponent(); + + // Use translation? + if (translation != null && translation.Data is Sprite) + { + spriteRenderer.sprite = (Sprite)translation.Data; + } + // Use fallback? + else + { + spriteRenderer.sprite = FallbackSprite; + } + } + + protected virtual void Awake() + { + // Should we set FallbackSprite? + if (FallbackSprite == null) + { + // Get the SpriteRenderer component attached to this GameObject + var spriteRenderer = GetComponent(); + + // Copy current sprite to fallback + FallbackSprite = spriteRenderer.sprite; + } + } + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedSpriteRenderer.cs.meta b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedSpriteRenderer.cs.meta new file mode 100644 index 0000000..84485b2 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedSpriteRenderer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 84f599809c9879044b69f17af8c248c6 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedText.cs b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedText.cs new file mode 100644 index 0000000..df63743 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedText.cs @@ -0,0 +1,48 @@ +using UnityEngine; +using UnityEngine.UI; + +namespace Lean.Localization +{ + /// This component will update a UI.Text component with localized text, or use a fallback if none is found. + [ExecuteInEditMode] + [DisallowMultipleComponent] + [RequireComponent(typeof(Text))] + [HelpURL(LeanLocalization.HelpUrlPrefix + "LeanLocalizedText")] + [AddComponentMenu(LeanLocalization.ComponentPathPrefix + "Localized Text")] + public class LeanLocalizedText : LeanLocalizedBehaviour + { + [Tooltip("If PhraseName couldn't be found, this text will be used")] + public string FallbackText; + + // This gets called every time the translation needs updating + public override void UpdateTranslation(LeanTranslation translation) + { + // Get the Text component attached to this GameObject + var text = GetComponent(); + + // Use translation? + if (translation != null && translation.Data is string) + { + text.text = LeanTranslation.FormatText((string)translation.Data, text.text, this); + } + // Use fallback? + else + { + text.text = LeanTranslation.FormatText(FallbackText, text.text, this); + } + } + + protected virtual void Awake() + { + // Should we set FallbackText? + if (string.IsNullOrEmpty(FallbackText) == true) + { + // Get the Text component attached to this GameObject + var text = GetComponent(); + + // Copy current text to fallback + FallbackText = text.text; + } + } + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedText.cs.meta b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedText.cs.meta new file mode 100644 index 0000000..8b76311 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedText.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7ee4021ce59f313499a5cab041f02aba +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextFont.cs b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextFont.cs new file mode 100644 index 0000000..6730956 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextFont.cs @@ -0,0 +1,48 @@ +using UnityEngine; +using UnityEngine.UI; + +namespace Lean.Localization +{ + /// This component will update a Text component's Font with a localized font, or use a fallback if none is found. + [ExecuteInEditMode] + [DisallowMultipleComponent] + [RequireComponent(typeof(Text))] + [HelpURL(LeanLocalization.HelpUrlPrefix + "LeanLocalizedTextFont")] + [AddComponentMenu(LeanLocalization.ComponentPathPrefix + "Localized TextFont")] + public class LeanLocalizedTextFont : LeanLocalizedBehaviour + { + [Tooltip("If PhraseName couldn't be found, this font will be used")] + public Font FallbackFont; + + // This gets called every time the translation needs updating + public override void UpdateTranslation(LeanTranslation translation) + { + // Get the Text component attached to this GameObject + var text = GetComponent(); + + // Use translation? + if (translation != null && translation.Data is Font) + { + text.font = (Font)translation.Data; + } + // Use fallback? + else + { + text.font = FallbackFont; + } + } + + protected virtual void Awake() + { + // Should we set FallbackFont? + if (FallbackFont == null) + { + // Get the Text component attached to this GameObject + var text = GetComponent(); + + // Copy current font to fallback + FallbackFont = text.font; + } + } + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextFont.cs.meta b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextFont.cs.meta new file mode 100644 index 0000000..5b0ceea --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextFont.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: fce53ba05f9c84246b062afd12f4619f +timeCreated: 1478133615 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMesh.cs b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMesh.cs new file mode 100644 index 0000000..c9fcbc6 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMesh.cs @@ -0,0 +1,46 @@ +using UnityEngine; + +namespace Lean.Localization +{ + /// This component will update a TMPro.TextMeshProUGUI component with localized text, or use a fallback if none is found. + [ExecuteInEditMode] + [DisallowMultipleComponent] + [RequireComponent(typeof(TextMesh))] + [AddComponentMenu(LeanLocalization.ComponentPathPrefix + "Localized TextMesh")] + public class LeanLocalizedTextMesh : LeanLocalizedBehaviour + { + [Tooltip("If PhraseName couldn't be found, this text will be used")] + public string FallbackText; + + // This gets called every time the translation needs updating + public override void UpdateTranslation(LeanTranslation translation) + { + // Get the TextMeshProUGUI component attached to this GameObject + var text = GetComponent(); + + // Use translation? + if (translation != null && translation.Data is string) + { + text.text = LeanTranslation.FormatText((string)translation.Data, text.text, this); + } + // Use fallback? + else + { + text.text = LeanTranslation.FormatText(FallbackText, text.text, this); + } + } + + protected virtual void Awake() + { + // Should we set FallbackText? + if (string.IsNullOrEmpty(FallbackText) == true) + { + // Get the TextMeshProUGUI component attached to this GameObject + var text = GetComponent(); + + // Copy current text to fallback + FallbackText = text.text; + } + } + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMesh.cs.meta b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMesh.cs.meta new file mode 100644 index 0000000..6af3498 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMesh.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 365897690d9e7c54f9fd65401bffb0d3 +timeCreated: 1561293177 +licenseType: Store +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMeshFont.cs b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMeshFont.cs new file mode 100644 index 0000000..a0046a7 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMeshFont.cs @@ -0,0 +1,46 @@ +using UnityEngine; + +namespace Lean.Localization +{ + /// This component will update a TextMesh component's Font with a localized font, or use a fallback if none is found. + [ExecuteInEditMode] + [DisallowMultipleComponent] + [RequireComponent(typeof(TextMesh))] + [AddComponentMenu(LeanLocalization.ComponentPathPrefix + "Localized TextMesh Font")] + public class LeanLocalizedTextMeshFont : LeanLocalizedBehaviour + { + [Tooltip("If PhraseName couldn't be found, this font asset will be used")] + public Font FallbackFont; + + // This gets called every time the translation needs updating + public override void UpdateTranslation(LeanTranslation translation) + { + // Get the TextMesh component attached to this GameObject + var text = GetComponent(); + + // Use translation? + if (translation != null && translation.Data is Font) + { + text.font = (Font)translation.Data; + } + // Use fallback? + else + { + text.font = FallbackFont; + } + } + + protected virtual void Awake() + { + // Should we set FallbackFont? + if (FallbackFont == null) + { + // Get the TextMesh component attached to this GameObject + var text = GetComponent(); + + // Copy current text to fallback + FallbackFont = text.font; + } + } + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMeshFont.cs.meta b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMeshFont.cs.meta new file mode 100644 index 0000000..d8c179f --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/Behaviours/LeanLocalizedTextMeshFont.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 3904cf0d2d9103140ab648dbbee38d3f +timeCreated: 1561293177 +licenseType: Store +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanLanguage.cs b/Assets/Packages/Lean/Localization/Scripts/LeanLanguage.cs new file mode 100644 index 0000000..1a39708 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanLanguage.cs @@ -0,0 +1,43 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace Lean.Localization +{ + /// This class stores information about a single language, and any of its optional cultures. + [System.Serializable] + public class LeanLanguage + { + [SerializeField] + private string name; + + [SerializeField] + private List cultures; + + public string Name + { + set + { + name = value; + } + + get + { + return name; + } + } + + /// This culture names for this language (e.g. en-GB, en-US). + public List Cultures + { + get + { + if (cultures == null) + { + cultures = new List(); + } + + return cultures; + } + } + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanLanguage.cs.meta b/Assets/Packages/Lean/Localization/Scripts/LeanLanguage.cs.meta new file mode 100644 index 0000000..31a8774 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanLanguage.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 225ec97158446a94d9cb40a9f742a8b1 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanLanguageCSV.cs b/Assets/Packages/Lean/Localization/Scripts/LeanLanguageCSV.cs new file mode 100644 index 0000000..f8a89b2 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanLanguageCSV.cs @@ -0,0 +1,303 @@ +using UnityEngine; +using System.Collections.Generic; +using Lean.Common; +#if UNITY_EDITOR +using UnityEditor; + +namespace Lean.Localization.Examples +{ + [CanEditMultipleObjects] + [CustomEditor(typeof(LeanLanguageCSV), true)] + public class LeanLanguageCSV_Inspector : LeanInspector + { + protected override void DrawInspector() + { + Draw("Source", "The text asset that contains all the translations."); + Draw("Language", "The language of the translations in the source file."); + Draw("Separator", "The string separating the phrase name from the translation."); + Draw("NewLine", "The string denoting a new line within a translation."); + Draw("Comment", "The (optional) string separating the translation from the comment (empty = no comments)."); + + EditorGUILayout.Separator(); + + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.LabelField("CollectItem" + Target.Separator + "アイテム" + Target.NewLine + "集めました" + Target.Comment + "Comment here"); + EditorGUI.EndDisabledGroup(); + + EditorGUILayout.Separator(); + + EditorGUILayout.BeginHorizontal(); + if (Any(t => t.Entries.Count > 0)) + { + if (GUILayout.Button("Clear") == true) + { + Each(t => t.Clear()); + } + } + if (GUILayout.Button("Load Now") == true) + { + Each(t => t.LoadFromSource()); + } + if (GUILayout.Button("Export") == true) + { + Each(t => t.ExportTextAsset()); + } + EditorGUILayout.EndHorizontal(); + + if (Targets.Length == 1) + { + var entries = Target.Entries; + + if (entries.Count > 0) + { + EditorGUILayout.Separator(); + + EditorGUI.BeginDisabledGroup(true); + foreach (var entry in entries) + { + EditorGUILayout.TextField(entry.Name, entry.Text); + } + EditorGUI.EndDisabledGroup(); + } + } + } + } +} +#endif + +namespace Lean.Localization +{ + /// This component will load localizations from a CSV file. By default they should be in the format: + /// Phrase Name Here = Translation Here // Optional Comment Here + [ExecuteInEditMode] + [HelpURL(LeanLocalization.HelpUrlPrefix + "LeanLanguageCSV")] + [AddComponentMenu(LeanLocalization.ComponentPathPrefix + "Language CSV")] + public class LeanLanguageCSV : LeanSource + { + [System.Serializable] + public class Entry + { + public string Name; + public string Text; + } + + /// The text asset that contains all the translations. + public TextAsset Source; + + /// The language of the translations in the source file. + [LeanLanguageName] + public string Language; + + /// The string separating the phrase name from the translation. + public string Separator = " = "; + + /// The string denoting a new line within a translation. + public string NewLine = "\\n"; + + /// The (optional) string separating the translation from the comment (empty = no comments). + public string Comment = " // "; + + /// This stores all currently loaded translations from this CSV file. + public List Entries { get { if (entries == null) entries = new List(); return entries; } } [SerializeField] private List entries; + + /// The characters used to separate each translation. + private static readonly char[] newlineCharacters = new char[] { '\r', '\n' }; + + private static Stack entryPool = new Stack(); + + public override void Compile(string primaryLanguage, string secondaryLanguage) + { + if (entries == null || entries.Count == 0) + { + if (Application.isPlaying == true) + { + LoadFromSource(); + } + } + + if (entries != null) + { + for (var i = entries.Count - 1; i >= 0; i--) + { + var entry = entries[i]; + var translation = LeanLocalization.RegisterTranslation(entry.Name); + + translation.Register(Language, this); + + if (Language == primaryLanguage) + { + translation.Data = entry.Text; + translation.Primary = true; + } + else if (Language == secondaryLanguage && translation.Primary == false) + { + translation.Data = entry.Text; + } + } + } + } + + [ContextMenu("Clear")] + public void Clear() + { + if (entries != null) + { + entries.Clear(); + + // Update translations? + if (LeanLocalization.CurrentLanguage == Language) + { + LeanLocalization.UpdateTranslations(); + } + } + } + + [ContextMenu("Load From Source")] + public void LoadFromSource() + { + if (Source != null && string.IsNullOrEmpty(Language) == false) + { + for (var i = Entries.Count - 1; i >= 0; i--) // NOTE: Property + { + entryPool.Push(entries[i]); + } + + entries.Clear(); + + // Split file into lines, and loop through them all + var lines = Source.text.Split(newlineCharacters, System.StringSplitOptions.RemoveEmptyEntries); + + for (var i = 0; i < lines.Length; i++) + { + var line = lines[i]; + var equalsIndex = line.IndexOf(Separator); + + // Only consider lines with the Separator character + if (equalsIndex != -1) + { + var name = line.Substring(0, equalsIndex).Trim(); + var text = line.Substring(equalsIndex + Separator.Length).Trim(); + + // Does this entry have a comment? + if (string.IsNullOrEmpty(Comment) == false) + { + var commentIndex = text.LastIndexOf(Comment); + + if (commentIndex != -1) + { + text = text.Substring(0, commentIndex).Trim(); + } + } + + // Replace newline markers with actual newlines + if (string.IsNullOrEmpty(NewLine) == false) + { + text = text.Replace(NewLine, System.Environment.NewLine); + } + + var entry = entryPool.Count > 0 ? entryPool.Pop() : new Entry(); + + entry.Name = name; + entry.Text = text; + + entries.Add(entry); + } + } + + // Update translations? + if (LeanLocalization.CurrentLanguage == Language) + { + LeanLocalization.UpdateTranslations(); + } + } + } + +#if UNITY_EDITOR + [ContextMenu("Export Text Asset")] + public void ExportTextAsset() + { + if (string.IsNullOrEmpty(Language) == false) + { + // Find where we want to save the file + var path = EditorUtility.SaveFilePanelInProject("Export Text Asset for " + Language, Language, "txt", ""); + + // Make sure we didn't cancel the panel + if (string.IsNullOrEmpty(path) == false) + { + if (LeanLocalization.CurrentLanguage == Language) + { + DoExportTextAsset(path); + } + else + { + var oldLanguage = LeanLocalization.CurrentLanguage; + + LeanLocalization.CurrentLanguage = Language; + + DoExportTextAsset(path); + + LeanLocalization.CurrentLanguage = oldLanguage; + } + } + } + } + + private void DoExportTextAsset(string path) + { + var data = ""; + var gaps = false; + + // Add all phrase names and existing translations to lines + foreach (var pair in LeanLocalization.CurrentTranslations) + { + var translation = pair.Value; + + if (gaps == true) + { + data += System.Environment.NewLine; + } + + data += pair.Key + Separator; + gaps = true; + + if (translation.Data is string) + { + var text = (string)translation.Data; + + // Replace all new line permutations with the new line token + text = text.Replace("\r\n", NewLine); + text = text.Replace("\n\r", NewLine); + text = text.Replace("\n", NewLine); + text = text.Replace("\r", NewLine); + + data += text; + } + } + + // Write text to file + using (var file = System.IO.File.OpenWrite(path)) + { + var encoding = new System.Text.UTF8Encoding(); + var bytes = encoding.GetBytes(data); + + file.Write(bytes, 0, bytes.Length); + } + + // Import asset into project + AssetDatabase.ImportAsset(path); + + // Replace Soure with new Text Asset? + var textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(path, typeof(TextAsset)); + + if (textAsset != null) + { + Source = textAsset; + + EditorGUIUtility.PingObject(textAsset); + + EditorUtility.SetDirty(this); + } + } +#endif + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanLanguageCSV.cs.meta b/Assets/Packages/Lean/Localization/Scripts/LeanLanguageCSV.cs.meta new file mode 100644 index 0000000..b472917 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanLanguageCSV.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 086ae75064fca9f46b315403da7f5d20 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanLanguageNameAttribute.cs b/Assets/Packages/Lean/Localization/Scripts/LeanLanguageNameAttribute.cs new file mode 100644 index 0000000..e613d38 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanLanguageNameAttribute.cs @@ -0,0 +1,46 @@ +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; + +namespace Lean.Localization +{ + [CustomPropertyDrawer(typeof(LeanLanguageNameAttribute))] + public class LeanLanguageNameDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + var rectA = position; rectA.xMax -= 37.0f; + var rectB = position; rectB.xMin = rectB.xMax - 35.0f; + + EditorGUI.PropertyField(rectA, property); + + if (GUI.Button(rectB, "List") == true) + { + var menu = new GenericMenu(); + + foreach (var languageName in LeanLocalization.CurrentLanguages.Keys) + { + menu.AddItem(new GUIContent(languageName), property.stringValue == languageName, () => { property.stringValue = languageName; property.serializedObject.ApplyModifiedProperties(); }); + } + + if (menu.GetItemCount() > 0) + { + menu.DropDown(rectB); + } + else + { + Debug.LogWarning("Your scene doesn't contain any languages, so the language name list couldn't be created."); + } + } + } + } +} +#endif + +namespace Lean.Localization +{ + /// This attribute allows you to modify a normal string field into one that has a dropdown list that allows you to pick a language. + public class LeanLanguageNameAttribute : PropertyAttribute + { + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanLanguageNameAttribute.cs.meta b/Assets/Packages/Lean/Localization/Scripts/LeanLanguageNameAttribute.cs.meta new file mode 100644 index 0000000..5869dc2 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanLanguageNameAttribute.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d7c5bcbe9b4b15742a9a64d98f3f8305 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanLocalization.cs b/Assets/Packages/Lean/Localization/Scripts/LeanLocalization.cs new file mode 100644 index 0000000..48c6013 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanLocalization.cs @@ -0,0 +1,1016 @@ +using UnityEngine; +using System.Collections.Generic; +using Lean.Common; +#if UNITY_EDITOR +using UnityEditor; + +namespace Lean.Localization +{ + [CustomEditor(typeof(LeanLocalization))] + public class LeanLocalization_Inspector : LeanInspector + { + static LeanLocalization_Inspector() + { + AddPresetLanguage("Chinese", "ChineseSimplified", "ChineseTraditional", "zh", "zh-TW", "zh-CN", "zh-HK", "zh-SG", "zh-MO"); + AddPresetLanguage("English", "en", "en-GB", "en-US", "en-AU", "en-CA", "en-NZ", "en-IE", "en-ZA", "en-JM", "en-en029", "en-BZ", "en-BZ", "en-TT", "en-ZW", "en-PH"); + AddPresetLanguage("Spanish", "es", "es-ES", "es-MX", "es-GT", "es-CR", "es-PA", "es-DO", "es-VE", "es-CO", "es-PE", "es-AR", "es-EC", "es-CL", "es-UY", "es-PY", "es-BO", "es-SV", "es-SV", "es-HN", "es-NI", "es-PR"); + AddPresetLanguage("Arabic", "ar", "ar-SA", "ar-IQ", "ar-EG", "ar-LY", "ar-DZ", "ar-MA", "ar-TN", "ar-OM", "ar-YE", "ar-SY", "ar-JO", "ar-LB", "ar-KW", "ar-AE", "ar-BH", "ar-QA"); + AddPresetLanguage("German", "de", "de-DE", "de-CH", "de-AT", "de-LU", "de-LI"); + AddPresetLanguage("Korean", "ko", "ko-KR"); + AddPresetLanguage("French", "fr", "fr-FR", "fr-BE", "fr-CA", "fr-CH", "fr-LU", "fr-MC"); + AddPresetLanguage("Russian", "ru", "ru-RU"); + AddPresetLanguage("Japanese", "ja", "ja-JP"); + AddPresetLanguage("Italian", "it", "it-IT", "it-CH"); + AddPresetLanguage("Portuguese", "pt", "pt-BR", "pt-PT"); + AddPresetLanguage("Other..."); + } + + class PresetLanguage + { + public string Name; + public string[] Cultures; + } + + private static List presetLanguages = new List(); + + protected override void DrawInspector() + { + LeanLocalization.UpdateTranslations(); + + DrawCurrentLanguage(); + Draw("SaveLanguage", "Automatically save/load the CurrentLanguage selection to PlayerPrefs? (can be cleared with ClearSave context menu option)"); + + EditorGUILayout.Separator(); + + Draw("DefaultLanguage", "If the application is started and no language has been loaded or auto detected, this language will be used."); + Draw("DetectLanguage", "How should the cultures be used to detect the user's device language?"); + EditorGUI.BeginDisabledGroup(true); + EditorGUI.indentLevel++; + switch (Target.DetectLanguage) + { + case LeanLocalization.DetectType.SystemLanguage: + EditorGUILayout.TextField("SystemLanguage", Application.systemLanguage.ToString()); + break; + case LeanLocalization.DetectType.CurrentCulture: + EditorGUILayout.TextField("CurrentCulture", System.Globalization.CultureInfo.CurrentCulture.ToString()); + break; + case LeanLocalization.DetectType.CurrentUICulture: + EditorGUILayout.TextField("CurrentUICulture", System.Globalization.CultureInfo.CurrentUICulture.ToString()); + break; + } + EditorGUI.indentLevel--; + EditorGUI.EndDisabledGroup(); + + EditorGUILayout.Separator(); + + DrawLanguages(); + + EditorGUILayout.Separator(); + + DrawPrefabs(); + + EditorGUILayout.Separator(); + + DrawTokens(); + + EditorGUILayout.Separator(); + + DrawTranslations(); + } + + private void DrawCurrentLanguage() + { + var rect = Reserve(); + var rectA = rect; rectA.xMax -= 37.0f; + var rectB = rect; rectB.xMin = rectB.xMax - 35.0f; + + LeanLocalization.CurrentLanguage = EditorGUI.TextField(rectA, "Current Language", LeanLocalization.CurrentLanguage); + + if (GUI.Button(rectB, "List") == true) + { + var menu = new GenericMenu(); + + foreach (var pair in LeanLocalization.CurrentLanguages) + { + var languageName = pair.Key; + + menu.AddItem(new GUIContent(languageName), LeanLocalization.CurrentLanguage == languageName, () => { LeanLocalization.CurrentLanguage = languageName; }); + } + + if (menu.GetItemCount() > 0) + { + menu.DropDown(rectB); + } + else + { + Debug.LogWarning("Your scene doesn't contain any languages, so the language name list couldn't be created."); + } + } + } + + private void DrawLanguages() + { + var languagesProperty = serializedObject.FindProperty("languages"); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Languages", EditorStyles.boldLabel); + if (GUILayout.Button("Add", EditorStyles.miniButton, GUILayout.Width(35.0f)) == true) + { + var menu = new GenericMenu(); + + foreach (var presetLanguage in presetLanguages) + { + var preset = presetLanguage; menu.AddItem(new GUIContent(presetLanguage.Name), Target.LanguageExists(presetLanguage.Name), () => AddLanguage(preset)); + } + + menu.ShowAsContext(); + } + EditorGUILayout.EndHorizontal(); + + if (languagesProperty.arraySize == 0) + { + EditorGUILayout.HelpBox("Click the 'Add' button, and select a language.", MessageType.Info); + } + + EditorGUI.indentLevel++; + for (var i = 0; i < languagesProperty.arraySize; i++) + { + EditorGUILayout.PropertyField(languagesProperty.GetArrayElementAtIndex(i), true); + } + EditorGUI.indentLevel--; + } + + private void DrawPrefabs() + { + var rectA = Reserve(); + var rectB = rectA; rectB.xMin += EditorGUIUtility.labelWidth; + EditorGUI.LabelField(rectA, "Prefabs", EditorStyles.boldLabel); + var newPrefab = EditorGUI.ObjectField(rectB, "", default(Object), typeof(Object), false); + if (newPrefab != null) + { + Undo.RecordObject(Target, "Add Source"); + + Target.AddPrefab(newPrefab); + + Dirty(); + } + + EditorGUI.indentLevel++; + for (var i = 0; i < Target.Prefabs.Count; i++) + { + DrawPrefabs(i); + } + EditorGUI.indentLevel--; + } + + private int expandPrefab = -1; + + private void DrawPrefabs(int index) + { + var rectA = Reserve(); + var rectB = rectA; rectB.xMax -= 22.0f; + var rectC = rectA; rectC.xMin = rectC.xMax - 20.0f; + var prefab = Target.Prefabs[index]; + var rebuilt = false; + var expand = EditorGUI.Foldout(new Rect(rectA.x, rectA.y, 20, rectA.height), expandPrefab == index, ""); + + if (expand == true) + { + expandPrefab = index; + } + else if (expandPrefab == index) + { + expandPrefab = -1; + } + + EditorGUI.BeginDisabledGroup(true); + BeginError(prefab.Root == null); + EditorGUI.ObjectField(rectB, prefab.Root, typeof(Object), false); + EndError(); + if (prefab.Root != null) + { + Undo.RecordObject(Target, "Rebuild Sources"); + + rebuilt |= prefab.RebuildSources(); + + if (expand == true) + { + var sources = prefab.Sources; + + EditorGUI.indentLevel++; + foreach (var source in sources) + { + EditorGUI.ObjectField(Reserve(), source, typeof(LeanSource), false); + } + EditorGUI.indentLevel--; + } + } + EditorGUI.EndDisabledGroup(); + if (rebuilt == true) + { + Dirty(); + } + if (GUI.Button(rectC, "X", EditorStyles.miniButton) == true) + { + Undo.RecordObject(Target, "Remove Prefab"); + + Target.Prefabs.RemoveAt(index); + + Dirty(); + + if (expand == true) + { + expandPrefab = -1; + } + } + } + + private static string translationFilter; + + private LeanTranslation expandTranslation; + + private void DrawTranslations() + { + var rectA = Reserve(); + var rectB = rectA; rectB.xMin += EditorGUIUtility.labelWidth; rectB.xMax -= 37.0f; + var rectC = rectA; rectC.xMin = rectC.xMax - 35.0f; + EditorGUI.LabelField(rectA, "Translations", EditorStyles.boldLabel); + translationFilter = EditorGUI.TextField(rectB, "", translationFilter); + EditorGUI.BeginDisabledGroup(string.IsNullOrEmpty(translationFilter) == true || LeanLocalization.CurrentTranslations.ContainsKey(translationFilter) == true); + if (GUI.Button(rectC, "Add", EditorStyles.miniButton) == true) + { + var phrase = LeanLocalization.AddPhraseToFirst(translationFilter); + + LeanLocalization.UpdateTranslations(); + + Selection.activeObject = phrase; + + EditorGUIUtility.PingObject(phrase); + } + EditorGUI.EndDisabledGroup(); + + if (LeanLocalization.CurrentTranslations.Count == 0 && string.IsNullOrEmpty(translationFilter) == true) + { + EditorGUILayout.HelpBox("Type in the name of a translation, and click the 'Add' button. Or, drag and drop a prefab that contains some.", MessageType.Info); + } + else + { + var total = 0; + + EditorGUI.indentLevel++; + foreach (var pair in LeanLocalization.CurrentTranslations) + { + var name = pair.Key; + + if (string.IsNullOrEmpty(translationFilter) == true || name.IndexOf(translationFilter, System.StringComparison.InvariantCultureIgnoreCase) >= 0) + { + var translation = pair.Value; + var rectT = Reserve(); + var expand = EditorGUI.Foldout(new Rect(rectT.x, rectT.y, 20, rectT.height), expandTranslation == translation, ""); + + if (expand == true) + { + expandTranslation = translation; + } + else if (expandTranslation == translation) + { + expandTranslation = null; + } + + CalculateTranslation(pair.Value); + + var data = translation.Data; + + total++; + + EditorGUI.BeginDisabledGroup(true); + BeginError(missing.Count > 0 || clashes.Count > 0); + if (data is Object) + { + EditorGUI.ObjectField(rectT, name, (Object)data, typeof(Object), true); + } + else + { + EditorGUI.TextField(rectT, name, data != null ? data.ToString() : ""); + } + EndError(); + + if (expand == true) + { + EditorGUI.indentLevel++; + foreach (var entry in translation.Entries) + { + BeginError(clashes.Contains(entry.Language) == true); + EditorGUILayout.ObjectField(entry.Language, entry.Owner, typeof(Object), true); + EndError(); + } + EditorGUI.indentLevel--; + } + EditorGUI.EndDisabledGroup(); + + if (expand == true) + { + foreach (var language in missing) + { + EditorGUILayout.HelpBox("This translation isn't defined for the " + language + " language.", MessageType.Warning); + } + + foreach (var language in clashes) + { + EditorGUILayout.HelpBox("This translation is defined multiple times for the " + language + " language.", MessageType.Warning); + } + } + } + } + EditorGUI.indentLevel--; + + if (total == 0) + { + EditorGUILayout.HelpBox("No translation with this name exists, click the 'Add' button to create it.", MessageType.Info); + } + } + } + + private static List missing = new List(); + + private static List clashes = new List(); + + private static void CalculateTranslation(LeanTranslation translation) + { + missing.Clear(); + clashes.Clear(); + + foreach (var language in LeanLocalization.CurrentLanguages.Keys) + { + if (translation.Entries.Exists(e => e.Language == language) == false) + { + missing.Add(language); + } + } + + foreach (var entry in translation.Entries) + { + var language = entry.Language; + + if (clashes.Contains(language) == false) + { + if (translation.LanguageCount(language) > 1) + { + clashes.Add(language); + } + } + } + } + + private static string tokensFilter; + + private void DrawTokens() + { + var rectA = Reserve(); + var rectB = rectA; rectB.xMin += EditorGUIUtility.labelWidth; rectB.xMax -= 37.0f; + var rectC = rectA; rectC.xMin = rectC.xMax - 35.0f; + EditorGUI.LabelField(rectA, "Tokens", EditorStyles.boldLabel); + tokensFilter = EditorGUI.TextField(rectB, "", tokensFilter); + EditorGUI.BeginDisabledGroup(string.IsNullOrEmpty(tokensFilter) == true || LeanLocalization.CurrentTokens.ContainsKey(tokensFilter) == true); + if (GUI.Button(rectC, "Add", EditorStyles.miniButton) == true) + { + var token = LeanLocalization.AddTokenToFirst(tokensFilter); + + LeanLocalization.UpdateTranslations(); + + Selection.activeObject = token; + + EditorGUIUtility.PingObject(token); + } + EditorGUI.EndDisabledGroup(); + + if (LeanLocalization.CurrentTokens.Count > 0 || string.IsNullOrEmpty(tokensFilter) == false) + { + var total = 0; + + EditorGUI.indentLevel++; + EditorGUI.BeginDisabledGroup(true); + foreach (var pair in LeanLocalization.CurrentTokens) + { + if (string.IsNullOrEmpty(tokensFilter) == true || pair.Key.IndexOf(tokensFilter, System.StringComparison.InvariantCultureIgnoreCase) >= 0) + { + EditorGUILayout.ObjectField(pair.Key, pair.Value, typeof(Object), true); total++; + } + } + EditorGUI.EndDisabledGroup(); + EditorGUI.indentLevel--; + + if (total == 0) + { + EditorGUILayout.HelpBox("No token with this name exists, click the 'Add' button to create it.", MessageType.Info); + } + } + } + + private void AddLanguage(PresetLanguage presetLanguage) + { + Undo.RecordObject(Target, "Add Language"); + + Target.AddLanguage(presetLanguage.Name, presetLanguage.Cultures); + + Dirty(); + } + + private static void AddPresetLanguage(string name, params string[] cultures) + { + var presetLanguage = new PresetLanguage(); + + presetLanguage.Name = name; + presetLanguage.Cultures = cultures; + + presetLanguages.Add(presetLanguage); + } + + [MenuItem("GameObject/Lean/Localization", false, 1)] + private static void CreateLocalization() + { + var gameObject = new GameObject(typeof(LeanLocalization).Name); + + Undo.RegisterCreatedObjectUndo(gameObject, "Create LeanLocalization"); + + gameObject.AddComponent(); + + Selection.activeGameObject = gameObject; + } + } +} +#endif + +namespace Lean.Localization +{ + /// This component manages a global list of translations for easy access. + /// Translations are gathered from the prefabs list, as well as from any active and enabled LeanSource components in the scene. + [ExecuteInEditMode] + [HelpURL(HelpUrlPrefix + "LeanLocalization")] + [AddComponentMenu(ComponentPathPrefix + "Localization")] + public class LeanLocalization : MonoBehaviour + { + public enum DetectType + { + None, + SystemLanguage, + CurrentCulture, + CurrentUICulture + } + + public const string HelpUrlPrefix = LeanHelper.HelpUrlPrefix + "LeanLocalization#"; + + public const string ComponentPathPrefix = LeanHelper.ComponentPathPrefix + "Localization/Lean "; + + /// All active and enabled LeanLocalization components. + public static List Instances = new List(); + + public static Dictionary CurrentTokens = new Dictionary(); + + public static Dictionary CurrentLanguages = new Dictionary(); + + /// Dictionary of all the phrase names mapped to their current translations. + public static Dictionary CurrentTranslations = new Dictionary(); + + /// If the application is started and no language has been loaded or auto detected, this language will be used. + [LeanLanguageName] + public string DefaultLanguage; + + /// How should the cultures be used to detect the user's device language? + public DetectType DetectLanguage = DetectType.SystemLanguage; + + /// Automatically save/load the CurrentLanguage selection to PlayerPrefs? (can be cleared with ClearSave context menu option) + public bool SaveLanguage = true; + + [SerializeField] + private List languages; + + [SerializeField] + private List prefabs; + + /// Called when the language or translations change. + public static System.Action OnLocalizationChanged; + + /// The currently set language. + private static string currentLanguage; + + private static bool pendingUpdates; + + private static Dictionary tempTranslations = new Dictionary(); + + /// This stores all languages and their aliases managed by this LeanLocalization instance. + public List Languages + { + get + { + if (languages == null) + { + languages = new List(); + } + + return languages; + } + } + + /// This stores all prefabs and folders managed by this LeanLocalization instance. + public List Prefabs + { + get + { + if (prefabs == null) + { + prefabs = new List(); + } + + return prefabs; + } + } + + /// Does at least one localization have 'SaveLanguage' set? + public static bool CurrentSaveLanguage + { + get + { + for (var i = 0; i < Instances.Count; i++) + { + if (Instances[i].SaveLanguage == true) + { + return true; + } + } + + return false; + } + } + + /// Change the current language of this instance? + public static string CurrentLanguage + { + set + { + if (CurrentLanguage != value) + { + currentLanguage = value; + + UpdateTranslations(); + + if (CurrentSaveLanguage == true) + { + PlayerPrefs.SetString("LeanLocalization.CurrentLanguage", value); + } + } + } + + get + { + return currentLanguage; + } + } + + /// When rebuilding translations this method is called from any LeanSource components that define a transition. + public static LeanTranslation RegisterTranslation(string name) + { + var translation = default(LeanTranslation); + + if (string.IsNullOrEmpty(name) == false && CurrentTranslations.TryGetValue(name, out translation) == false) + { + if (tempTranslations.TryGetValue(name, out translation) == true) + { + tempTranslations.Remove(name); + + CurrentTranslations.Add(name, translation); + } + else + { + translation = new LeanTranslation(name); + + CurrentTranslations.Add(name, translation); + } + } + + return translation; + } + + [ContextMenu("Clear Save")] + public void ClearSave() + { + PlayerPrefs.DeleteKey("LeanLocalization.CurrentLanguage"); + } + + /// This sets the current language using the specified string. + public void SetCurrentLanguage(string newLanguage) + { + CurrentLanguage = newLanguage; + } + + /// This sets the current language using the specified index based on the Languages list, where 0 is the first language. + public void SetCurrentLanguage(int newLanguageIndex) + { + if (newLanguageIndex >= 0 && newLanguageIndex < Instances.Count) + { + SetCurrentLanguage(Instances[newLanguageIndex].name); + } + } + + public bool LanguageExists(string languageName) + { + var language = default(LeanLanguage); + + return TryGetLanguage(languageName, ref language); + } + + public bool TryGetLanguage(string languageName, ref LeanLanguage language) + { + if (languages != null) + { + for (var i = languages.Count - 1; i >= 0; i--) + { + language = languages[i]; + + if (language.Name == languageName) + { + return true; + } + } + } + + return false; + } + + /// This adds the specified UnityEngine.Object to this LeanLocalization instance, allowing it to be registered as a prefab. + public void AddPrefab(Object root) + { + for (var i = Prefabs.Count - 1; i >= 0; i--) // NOTE: Property + { + if (prefabs[i].Root == root) + { + return; + } + } + + var prefab = new LeanPrefab(); + + prefab.Root = root; + + prefabs.Add(prefab); + } + + /// This adds a new language to this LeanLocalization instance, with the specified name and cultures. + public LeanLanguage AddLanguage(string languageName, string[] cultures) + { + var language = default(LeanLanguage); + + if (TryGetLanguage(languageName, ref language) == false) + { + language = new LeanLanguage(); + + language.Name = languageName; + + if (languages == null) + { + languages = new List(); + } + + languages.Add(language); + } + + language.Cultures.Clear(); + language.Cultures.AddRange(cultures); + + return language; + } + + /// This calls AddToken on the first active and enabled LeanLocalization instance, or creates one first. + public static LeanToken AddTokenToFirst(string name) + { + if (Instances.Count == 0) + { + new GameObject("LeanLocalization").AddComponent(); + } + + return Instances[0].AddToken(name); + } + + /// This creates a new token with the specified name, and adds it to the current GameObject. + public LeanToken AddToken(string name) + { + if (string.IsNullOrEmpty(name) == false) + { + var root = new GameObject(name); + var token = root.AddComponent(); + + root.transform.SetParent(transform, false); + + return token; + } + + return null; + } + + /// This allows you to set the value of the token with the specified name. + /// If no token exists and allowCreation is enabled, then one will be created for you. + public static void SetToken(string name, string value, bool allowCreation = true) + { + if (string.IsNullOrEmpty(name) == false) + { + var token = default(LeanToken); + + if (CurrentTokens.TryGetValue(name, out token) == true) + { + token.Value = value; + } + else if (allowCreation == true) + { + token = AddTokenToFirst(name); + + token.Value = value; + } + } + } + + /// This allows you to get the value of the token with the specified name. + /// If no token exists, then the defaultValue will be returned. + public static string GetToken(string name, string defaultValue = null) + { + var token = default(LeanToken); + + if (string.IsNullOrEmpty(name) == false) + { + if (CurrentTokens.TryGetValue(name, out token) == true) + { + return token.Value; + } + } + + return defaultValue; + } + + /// This calls AddPhrase on the first active and enabled LeanLocalization instance, or creates one first. + public static LeanPhrase AddPhraseToFirst(string name) + { + if (Instances.Count == 0) + { + new GameObject("LeanLocalization").AddComponent(); + } + + return Instances[0].AddPhrase(name); + } + + /// This creates a new phrase with the specified name, and adds it to the current GameObject. + public LeanPhrase AddPhrase(string name) + { + if (string.IsNullOrEmpty(name) == false) + { + var root = new GameObject(name); + var phrase = root.AddComponent(); + + root.transform.SetParent(transform, false); + + return phrase; + } + + return null; + } + + /// This will return the translation with the specified name, or null if none was found. + public static LeanTranslation GetTranslation(string name) + { + var translation = default(LeanTranslation); + + if (string.IsNullOrEmpty(name) == false) + { + CurrentTranslations.TryGetValue(name, out translation); + } + + return translation; + } + + /// This will return the translated string with the specified name, or the fallback if none is found. + public static string GetTranslationText(string name, string fallback = null) + { + var translation = default(LeanTranslation); + + if (string.IsNullOrEmpty(name) == false && CurrentTranslations.TryGetValue(name, out translation) == true && translation.Data is string) + { + return (string)translation.Data; + } + + return fallback; + } + + /// This will return the translated UnityEngine.Object with the specified name, or the fallback if none is found. + public static T GetTranslationObject(string name, T fallback = null) + where T : Object + { + var translation = default(LeanTranslation); + + if (string.IsNullOrEmpty(name) == false && CurrentTranslations.TryGetValue(name, out translation) == true && translation.Data is T) + { + return (T)translation.Data; + } + + return fallback; + } + + /// This rebuilds the dictionary used to quickly map phrase names to translations for the current language. + public static void UpdateTranslations() + { + pendingUpdates = false; + + // Copy previous translations to temp dictionary + tempTranslations.Clear(); + + foreach (var pair in CurrentTranslations) + { + var translation = pair.Value; + + translation.Clear(); + + tempTranslations.Add(pair.Key, translation); + } + + // Clear currents + CurrentTokens.Clear(); + CurrentLanguages.Clear(); + CurrentTranslations.Clear(); + + // Rebuild all currents + for (var i = 0; i < Instances.Count; i++) + { + Instances[i].RegisterAndBuild(); + } + + // Notify changes? + if (OnLocalizationChanged != null) + { + OnLocalizationChanged(); + } + } + + /// If you call this method, then UpdateTranslations will be called next Update. + public static void DelayUpdateTranslations() + { + pendingUpdates = true; + +#if UNITY_EDITOR + // Go through all enabled phrases + for (var i = 0; i < Instances.Count; i++) + { + EditorUtility.SetDirty(Instances[i].gameObject); + } +#endif + } + + /// Set the instance, merge old instance, and update translations. + protected virtual void OnEnable() + { + Instances.Add(this); + + UpdateCurrentLanguage(); + + UpdateTranslations(); + } + + /// Unset instance? + protected virtual void OnDisable() + { + Instances.Remove(this); + + UpdateTranslations(); + } + + protected virtual void Update() + { + if (pendingUpdates == true) + { + UpdateTranslations(); + } + } +#if UNITY_EDITOR + // Inspector modified? + protected virtual void OnValidate() + { + UpdateTranslations(); + } +#endif + private void RegisterAndBuild() + { + if (languages != null) + { + for (var i = 0; i < languages.Count; i++) + { + var language = languages[i]; + + if (language != null && string.IsNullOrEmpty(language.Name) == false) + { + if (CurrentLanguages.ContainsKey(language.Name) == false) + { + CurrentLanguages.Add(language.Name, language); + } + } + } + } + + if (prefabs != null) + { + for (var i = 0; i < prefabs.Count; i++) + { + var sources = prefabs[i].Sources; + + for (var j = 0; j < sources.Count; j++) + { + sources[j].Compile(currentLanguage, DefaultLanguage); + } + } + } + + var source = LeanSource.Instances.First; + + for (var i = LeanSource.Instances.Count - 1; i >= 0; i--) + { + source.Value.Compile(currentLanguage, DefaultLanguage); + + source = source.Next; + } + } + + private void UpdateCurrentLanguage() + { + // Load saved language? + if (string.IsNullOrEmpty(currentLanguage) == true) + { + if (SaveLanguage == true) + { + currentLanguage = PlayerPrefs.GetString("LeanLocalization.CurrentLanguage"); + } + } + + // Find language by culture? + if (string.IsNullOrEmpty(currentLanguage) == true) + { + switch (DetectLanguage) + { + case DetectType.SystemLanguage: + { + currentLanguage = FindLanguageName(Application.systemLanguage.ToString()); + } + break; + + case DetectType.CurrentCulture: + { + var cultureInfo = System.Globalization.CultureInfo.CurrentCulture; + + if (cultureInfo != null) + { + currentLanguage = FindLanguageName(cultureInfo.Name); + } + } + break; + + case DetectType.CurrentUICulture: + { + var cultureInfo = System.Globalization.CultureInfo.CurrentUICulture; + + if (cultureInfo != null) + { + currentLanguage = FindLanguageName(cultureInfo.Name); + } + } + break; + } + } + + // Use default language? + if (string.IsNullOrEmpty(currentLanguage) == true) + { + currentLanguage = DefaultLanguage; + } + } + + private string FindLanguageName(string alias) + { + for (var i = Languages.Count - 1; i >= 0; i--) + { + var language = Languages[i]; + + if (language.Name == alias) + { + return language.Name; + } + + if (language.Cultures != null) + { + for (var j = language.Cultures.Count - 1; j >= 0; j--) + { + if (language.Cultures[j] == alias) + { + return language.Name; + } + } + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanLocalization.cs.meta b/Assets/Packages/Lean/Localization/Scripts/LeanLocalization.cs.meta new file mode 100644 index 0000000..77c6c41 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanLocalization.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 806f0696300aeac44a0e17efee222854 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanLocalizedBehaviour.cs b/Assets/Packages/Lean/Localization/Scripts/LeanLocalizedBehaviour.cs new file mode 100644 index 0000000..4163464 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanLocalizedBehaviour.cs @@ -0,0 +1,121 @@ +using UnityEngine; +using UnityEngine.Serialization; +using System.Collections.Generic; +using Lean.Common; +#if UNITY_EDITOR +using UnityEditor; + +namespace Lean.Localization +{ + [CanEditMultipleObjects] + [CustomEditor(typeof(LeanLocalizedBehaviour), true)] + public class LeanLocalizedBehaviour_Inspector : LeanInspector + { + } +} +#endif + +namespace Lean.Localization +{ + /// This component simplifies the updating process, extend it if you want to cause a specific object to get localized + public abstract class LeanLocalizedBehaviour : MonoBehaviour + { + [Tooltip("The name of the phrase we want to use for this localized component")] + [SerializeField] + [LeanTranslationName] + [FormerlySerializedAs("phraseName")] + [FormerlySerializedAs("translationTitle")] + private string translationName; + + [System.NonSerialized] + private HashSet tokens; + + /// This is the name of the translation this script uses. + public string TranslationName + { + set + { + if (translationName != value) + { + translationName = value; + + UpdateLocalization(); + } + } + + get + { + return translationName; + } + } + + public void Register(LeanToken token) + { + if (token != null) + { + if (tokens == null) + { + tokens = new HashSet(); + } + + tokens.Add(token); + } + } + + public void Unregister(LeanToken token) + { + if (tokens != null) + { + tokens.Remove(token); + } + } + + public void UnregisterAll() + { + if (tokens != null) + { + foreach (var token in tokens) + { + token.Unregister(this); + } + + tokens.Clear(); + } + } + + // This gets called every time the translation needs updating + // NOTE: translation may be null if it can't be found + public abstract void UpdateTranslation(LeanTranslation translation); + + /// If you call this then this component will update using the translation for the specified phrase. + [ContextMenu("Update Localization")] + public void UpdateLocalization() + { + UpdateTranslation(LeanLocalization.GetTranslation(translationName)); + } + + protected virtual void OnEnable() + { + LeanLocalization.OnLocalizationChanged += UpdateLocalization; + + UpdateLocalization(); + } + + protected virtual void OnDisable() + { + LeanLocalization.OnLocalizationChanged -= UpdateLocalization; + + UnregisterAll(); + } + +#if UNITY_EDITOR + protected virtual void OnValidate() + { + if (isActiveAndEnabled == true) + { + UpdateLocalization(); + } + } +#endif + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanLocalizedBehaviour.cs.meta b/Assets/Packages/Lean/Localization/Scripts/LeanLocalizedBehaviour.cs.meta new file mode 100644 index 0000000..970eeae --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanLocalizedBehaviour.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1fbd7e2a5aacea442bccddc4c9c84fa9 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanPhrase.cs b/Assets/Packages/Lean/Localization/Scripts/LeanPhrase.cs new file mode 100644 index 0000000..9c329e4 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanPhrase.cs @@ -0,0 +1,299 @@ +using UnityEngine; +using System.Collections.Generic; +using Lean.Common; +#if UNITY_EDITOR +using UnityEditor; + +namespace Lean.Localization +{ + [CustomEditor(typeof(LeanPhrase))] + public class LeanPhrase_Inspector : LeanInspector + { + private static List languageNames = new List(); + + private static List entries = new List(); + + protected override void DrawInspector() + { + entries.Clear(); + entries.AddRange(Target.Entries); + + languageNames.Clear(); + languageNames.AddRange(LeanLocalization.CurrentLanguages.Keys); + + Target.Data = (LeanPhrase.DataType)GUILayout.Toolbar((int)Target.Data, new string[] { "Text", "Object", "Sprite" }); + + EditorGUILayout.Separator(); + + foreach (var languageName in languageNames) + { + var entry = default(LeanPhrase.Entry); + + if (Target.TryFindTranslation(languageName, ref entry) == true) + { + DrawEntry(entry, false); + + entries.Remove(entry); + } + else + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(languageName, EditorStyles.boldLabel); + if (GUILayout.Button("Create", EditorStyles.miniButton, GUILayout.Width(45.0f)) == true) + { + Undo.RecordObject(Target, "Create Translation"); + + Target.AddEntry(languageName); + + Dirty(); + } + EditorGUILayout.EndHorizontal(); + } + + EditorGUILayout.Separator(); + } + + if (entries.Count > 0) + { + foreach (var entry in entries) + { + DrawEntry(entry, true); + } + } + } + + private void DrawEntry(LeanPhrase.Entry entry, bool unexpected) + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(entry.Language, EditorStyles.boldLabel); + if (GUILayout.Button("Remove", EditorStyles.miniButton, GUILayout.Width(55.0f)) == true) + { + Undo.RecordObject(Target, "Remove Translation"); + + Target.RemoveTranslation(entry.Language); + + Dirty(); + } + EditorGUILayout.EndHorizontal(); + + if (unexpected == true) + { + EditorGUILayout.HelpBox("Your LeanLocalization component doesn't define the " + entry.Language + " language.", MessageType.Warning); + } + + Undo.RecordObject(Target, "Modified Translation"); + + EditorGUI.BeginChangeCheck(); + + switch (Target.Data) + { + case LeanPhrase.DataType.Text: + entry.Text = EditorGUILayout.TextArea(entry.Text ?? "", GUILayout.MinHeight(40.0f)); + break; + case LeanPhrase.DataType.Object: + entry.Object = EditorGUILayout.ObjectField(entry.Object, typeof(Object), true); + break; + case LeanPhrase.DataType.Sprite: + entry.Object = EditorGUILayout.ObjectField(entry.Object, typeof(Sprite), true); + break; + } + + if (EditorGUI.EndChangeCheck() == true) + { + Dirty(); LeanLocalization.UpdateTranslations(); + } + + EditorGUILayout.Separator(); + } + + [MenuItem("Assets/Create/Lean/Localization/Lean Phrase")] + private static void CreatePhrase() + { + var instance = new GameObject("New Phrase").AddComponent(); + var path = AssetDatabase.GetAssetPath(Selection.activeObject); + + if (string.IsNullOrEmpty(path) == true) + { + path = "Assets"; + } + + path = AssetDatabase.GenerateUniqueAssetPath(path + "/New Phrase.asset"); + + AssetDatabase.CreateAsset(instance, path); + + Selection.activeObject = instance; + } + } +} +#endif + +namespace Lean.Localization +{ + /// This contains data about each phrase, which is then translated into different languages. + [ExecuteInEditMode] + [DisallowMultipleComponent] + [HelpURL(LeanLocalization.HelpUrlPrefix + "LeanPhrase")] + [AddComponentMenu(LeanLocalization.ComponentPathPrefix + "Phrase")] + public class LeanPhrase : LeanSource + { + public enum DataType + { + Text, + Object, + Sprite + } + + [System.Serializable] + public class Entry + { + /// The language of this translation. + public string Language; + + /// The translated text. + public string Text; + + /// The translated object (e.g. language specific texture). + public Object Object; + } + + public DataType Data { set { data = value; } get { return data; } } [SerializeField] private DataType data; + + /// This list stores all translations of this phrase in each language. + [SerializeField] + [UnityEngine.Serialization.FormerlySerializedAs("translations")] + private List entries; + + public List Entries + { + get + { + if (entries == null) + { + entries = new List(); + } + + return entries; + } + } + + public void Clear() + { + if (entries != null) + { + entries.Clear(); + } + } + + public override void Compile(string primaryLanguage, string secondaryLanguage) + { + var translation = LeanLocalization.RegisterTranslation(name); + + if (entries != null) + { + for (var i = entries.Count - 1; i >= 0; i--) + { + var entry = entries[i]; + + translation.Register(entry.Language, this); + + if (entry.Language == primaryLanguage) + { + Compile(translation, entry, true); + } + else if (entry.Language == secondaryLanguage && translation.Primary == false) + { + Compile(translation, entry, false); + } + } + } + } + + private void Compile(LeanTranslation translation, Entry entry, bool primary) + { + switch (data) + { + case DataType.Text: + { + Compile(translation, entry.Text, primary); + } + break; + case DataType.Object: + case DataType.Sprite: + { + Compile(translation, entry.Object, primary); + } + break; + } + } + + private void Compile(LeanTranslation translation, object data, bool primary) + { + translation.Data = data; + + if (primary == true) + { + translation.Primary = true; + } + } + + /// This will return the translation of this phrase for the specified language. + public bool TryFindTranslation(string languageName, ref Entry entry) + { + if (entries != null) + { + for (var i = entries.Count - 1; i >= 0; i--) + { + entry = entries[i]; + + if (entry.Language == languageName) + { + return true; + } + } + } + + return false; + } + + public void RemoveTranslation(string languageName) + { + if (entries != null) + { + for (var i = entries.Count - 1; i >= 0; i--) + { + if (entries[i].Language == languageName) + { + entries.RemoveAt(i); + + return; + } + } + } + } + + /// Add a new translation to this phrase for the specified language, or return the current one. + public Entry AddEntry(string languageName, string text = null, Object obj = null) + { + var translation = default(Entry); + + if (TryFindTranslation(languageName, ref translation) == false) + { + translation = new Entry(); + + translation.Language = languageName; + + if (entries == null) + { + entries = new List(); + } + + entries.Add(translation); + } + + translation.Text = text; + translation.Object = obj; + + return translation; + } + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanPhrase.cs.meta b/Assets/Packages/Lean/Localization/Scripts/LeanPhrase.cs.meta new file mode 100644 index 0000000..13d65e0 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanPhrase.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eb15fa0e7301ff849bf4d6dcede7c12b +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanPrefab.cs b/Assets/Packages/Lean/Localization/Scripts/LeanPrefab.cs new file mode 100644 index 0000000..1507767 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanPrefab.cs @@ -0,0 +1,176 @@ +using UnityEngine; +using System.IO; +using System.Collections.Generic; + +namespace Lean.Localization +{ + [System.Serializable] + public class LeanPrefab + { + public Object Root; + + [SerializeField] + private List sources; + + [System.NonSerialized] + private int buildingCount; + + [System.NonSerialized] + private bool buildingModified; + + private static List tempSources = new List(); + + public List Sources + { + get + { + if (sources == null) + { + sources = new List(); + } + + return sources; + } + } + + public bool RebuildSources() + { + if (sources == null) + { + sources = new List(); + } + + if (Root != null) + { + if (Root is LeanSource) + { + buildingCount = 0; + buildingModified = false; + + AddSource((LeanSource)Root); + + return FinalizeBuild(); + } + else if (Root is GameObject) + { + buildingCount = 0; + buildingModified = false; + + FindFromGameObject(((GameObject)Root).transform); + + return FinalizeBuild(); + } +#if UNITY_EDITOR + else // Folder + { + var rootPath = UnityEditor.AssetDatabase.GetAssetPath(Root); + + if (string.IsNullOrEmpty(rootPath) == false) + { + buildingCount = 0; + buildingModified = false; + + var basePath = Application.dataPath; + var baseTail = "Assets"; + + if (basePath.EndsWith(baseTail) == true) + { + basePath = basePath.Substring(0, basePath.Length - baseTail.Length); + } + + FindFromFolder(basePath, rootPath); + + return FinalizeBuild(); + } + } +#endif + } + + return false; + } + + private bool FinalizeBuild() + { + for (var i = sources.Count - 1; i >= buildingCount; i--) + { + sources.RemoveAt(i); buildingModified = true; + } + + return buildingModified; + } + + private void AddSource(LeanSource source) + { + if (buildingCount < sources.Count) + { + if (sources[buildingCount] != source) + { + sources[buildingCount] = source; + + buildingModified = true; + } + } + else + { + sources.Add(source); + + buildingModified = true; + } + + buildingCount++; + } + + private void FindFromGameObject(Transform prefab) + { + prefab.GetComponents(tempSources); + + if (tempSources.Count > 0) + { + for (var i = 0; i < tempSources.Count; i++) + { + AddSource(tempSources[i]); + } + } + else + { + for (var i = 0; i < prefab.childCount; i++) + { + FindFromGameObject(prefab.GetChild(i)); + } + } + } +#if UNITY_EDITOR + private void FindFromFolder(string basePath, string rootPath) + { + var fullPath = basePath + rootPath; + + if (Directory.Exists(fullPath) == true) + { + var subFolders = Directory.GetDirectories(fullPath); + + for (var i = 0; i < subFolders.Length; i++) + { + FindFromFolder(basePath, subFolders[i].Substring(basePath.Length)); + } + + var subAssets = Directory.GetFiles(fullPath, "*.prefab"); + + for (var i = 0; i < subAssets.Length; i++) + { + FindFromFolder(basePath, subAssets[i].Substring(basePath.Length)); + } + } + // File + else + { + var subGameObject = UnityEditor.AssetDatabase.LoadAssetAtPath(rootPath); + + if (subGameObject != null) + { + FindFromGameObject(subGameObject.transform); + } + } + } +#endif + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanPrefab.cs.meta b/Assets/Packages/Lean/Localization/Scripts/LeanPrefab.cs.meta new file mode 100644 index 0000000..19da17c --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanPrefab.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a580c5d337407a9439b51424c8590dd3 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanSource.cs b/Assets/Packages/Lean/Localization/Scripts/LeanSource.cs new file mode 100644 index 0000000..7480245 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanSource.cs @@ -0,0 +1,30 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace Lean.Localization +{ + /// This is the interface used for all translation sources. When a translation source is built, it will populate the LeanLocalization class with its translation data. + public abstract class LeanSource : MonoBehaviour + { + public static LinkedList Instances = new LinkedList(); + + [System.NonSerialized] + private LinkedListNode node; + + public abstract void Compile(string primaryLanguage, string secondaryLanguage); + + protected virtual void OnEnable() + { + node = Instances.AddLast(this); + + LeanLocalization.DelayUpdateTranslations(); + } + + protected virtual void OnDisable() + { + Instances.Remove(node); + + LeanLocalization.DelayUpdateTranslations(); + } + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanSource.cs.meta b/Assets/Packages/Lean/Localization/Scripts/LeanSource.cs.meta new file mode 100644 index 0000000..57802a9 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanSource.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f0fcf02b7e3c0524a84d768d686b3634 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanToken.cs b/Assets/Packages/Lean/Localization/Scripts/LeanToken.cs new file mode 100644 index 0000000..c6ca7f8 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanToken.cs @@ -0,0 +1,130 @@ +using UnityEngine; +using System.Collections.Generic; +using Lean.Common; +#if UNITY_EDITOR +using UnityEditor; + +namespace Lean.Localization +{ + [CustomEditor(typeof(LeanToken))] + public class LeanToken_Inspector : LeanInspector + { + protected override void DrawInspector() + { + Draw("value"); + } + } +} +#endif + +namespace Lean.Localization +{ + /// The class stores a token name (e.g. "AGE"), allowing it to be replaced with the token value (e.g. "20"). + /// To use the token in your text, simply include the token name surrounded by braces (e.g. "I am {AGE} years old!") + [ExecuteInEditMode] + [HelpURL(LeanLocalization.HelpUrlPrefix + "LeanToken")] + [AddComponentMenu(LeanLocalization.ComponentPathPrefix + "Token")] + public class LeanToken : LeanSource + { + [SerializeField] + private string value; + + [System.NonSerialized] + private HashSet behaviours; + + [System.NonSerialized] + private static HashSet tempBehaviours = new HashSet(); + + public string Value + { + set + { + if (this.value != value) + { + this.value = value; + + if (behaviours != null) + { + tempBehaviours.Clear(); + + tempBehaviours.UnionWith(behaviours); + + foreach (var behaviour in tempBehaviours) + { + behaviour.UpdateLocalization(); + } + } + } + } + + get + { + return value; + } + } + + public void SetValue(float value) + { + Value = value.ToString(); + } + + public void SetValue(string value) + { + Value = value; + } + + public void SetValue(int value) + { + Value = value.ToString(); + } + + public void Register(LeanLocalizedBehaviour behaviour) + { + if (behaviour != null) + { + if (behaviours == null) + { + behaviours = new HashSet(); + } + + behaviours.Add(behaviour); + } + } + + public void Unregister(LeanLocalizedBehaviour behaviour) + { + if (behaviours != null) + { + behaviours.Remove(behaviour); + } + } + + public void UnregisterAll() + { + if (behaviours != null) + { + foreach (var behaviour in behaviours) + { + behaviour.Unregister(this); + } + + behaviours.Clear(); + } + } + + public override void Compile(string primaryLanguage, string secondaryLanguage) + { + if (string.IsNullOrEmpty(name) == false) + { + LeanLocalization.CurrentTokens.Add(name, this); + } + } + + protected override void OnDisable() + { + base.OnDisable(); + + UnregisterAll(); + } + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanToken.cs.meta b/Assets/Packages/Lean/Localization/Scripts/LeanToken.cs.meta new file mode 100644 index 0000000..ccef956 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanToken.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6b00b1c461a103b4898dcc53ee709d22 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanTranslation.cs b/Assets/Packages/Lean/Localization/Scripts/LeanTranslation.cs new file mode 100644 index 0000000..5fc5e18 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanTranslation.cs @@ -0,0 +1,191 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace Lean.Localization +{ + /// This contains the translated value for the current language, and other associated data. + public class LeanTranslation + { + public struct Entry + { + public string Language; + + public Object Owner; + } + + /// The name of this translation. + public string Name { get { return name; } } [SerializeField] private string name; + + /// The data of this translation (e.g. string or Object). + /// NOTE: This is a System.Object, so you must correctly cast it back before use. + public object Data; + + /// If Data has been filled with data for the primary language, then this will be set to true. + public bool Primary; + + /// This stores a list of all LeanSource instances that are currently managing the current value of this translation in the current language. + /// NOTE: If this is empty then no LeanSource of this name is localized for the current language. + public List Entries { get { return entries; } } private List entries = new List(); + + private static bool buffering; + + private static System.Text.StringBuilder current = new System.Text.StringBuilder(); + + private static System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + + private static List tokens = new List(); + + public LeanTranslation(string newName) + { + name = newName; + } + + public void Register(string language, Object owner) + { + var entry = new Entry(); + + entry.Language = language; + entry.Owner = owner; + + entries.Add(entry); + } + + public void Clear() + { + Data = null; + Primary = false; + + entries.Clear(); + } + + public int LanguageCount(string language) + { + var total = 0; + + for (var i = entries.Count - 1; i >= 0; i--) + { + if (entries[i].Language == language) + { + total += 1; + } + } + + return total; + } + + /// This returns Text with all tokens substituted using the LeanLocalization.Tokens list. + public static string FormatText(string rawText, string currentText, LeanLocalizedBehaviour behaviour) + { + if (string.IsNullOrEmpty(currentText) == true) + { + currentText = rawText; + } + + if (rawText != null) + { + current.Length = 0; + buffer.Length = 0; + tokens.Clear(); + + for (var i = 0; i < rawText.Length; i++) + { + var rawChar = rawText[i]; + + if (rawChar == '{') + { + if (buffering == true) + { + buffering = false; + + buffer.Length = 0; + } + else + { + buffering = true; + } + } + else if (rawChar == '}') + { + if (buffering == true) + { + if (buffer.Length > 0) + { + var token = default(LeanToken); + + if (buffer.Length > 0 && LeanLocalization.CurrentTokens.TryGetValue(buffer.ToString(), out token) == true) // TODO: Avoid ToString here? + { + current.Append(token.Value); + + tokens.Add(token); + } + else + { + current.Append('{').Append(buffer).Append('}'); + } + + buffer.Length = 0; + } + + buffering = false; + } + } + else + { + if (buffering == true) + { + buffer.Append(rawChar); + } + else + { + current.Append(rawChar); + } + } + } + + if (Match(currentText, current) == false) + { + if (behaviour != null) + { + behaviour.UnregisterAll(); + } + + for (var i = tokens.Count - 1; i >= 0; i--) + { + var token = tokens[i]; + + token.Register(behaviour); + + behaviour.Register(token); + } + + return current.ToString(); + } + } + + return currentText; + } + + private static bool Match(string a, System.Text.StringBuilder b) + { + if (a == null && b.Length > 0) + { + return false; + } + + if (a.Length != b.Length) + { + return false; + } + + for (var i = 0; i < a.Length; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanTranslation.cs.meta b/Assets/Packages/Lean/Localization/Scripts/LeanTranslation.cs.meta new file mode 100644 index 0000000..6f16358 --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanTranslation.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 262535ccf26608d488744fb304c45389 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanTranslationNameAttribute.cs b/Assets/Packages/Lean/Localization/Scripts/LeanTranslationNameAttribute.cs new file mode 100644 index 0000000..740d43d --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanTranslationNameAttribute.cs @@ -0,0 +1,54 @@ +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; + +namespace Lean.Localization +{ + [CustomPropertyDrawer(typeof(LeanTranslationNameAttribute))] + public class LeanTranslationNameDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + var left = position; left.xMax -= 40; + var right = position; right.xMin = left.xMax + 2; + var color = GUI.color; + + if (LeanLocalization.CurrentTranslations.ContainsKey(property.stringValue) == false) + { + GUI.color = Color.red; + } + + EditorGUI.PropertyField(left, property); + + GUI.color = color; + + if (GUI.Button(right, "List") == true) + { + var menu = new GenericMenu(); + + foreach (var translationName in LeanLocalization.CurrentTranslations.Keys) + { + menu.AddItem(new GUIContent(translationName), property.stringValue == translationName, () => { property.stringValue = translationName; property.serializedObject.ApplyModifiedProperties(); }); + } + + if (menu.GetItemCount() > 0) + { + menu.DropDown(right); + } + else + { + Debug.LogWarning("Your scene doesn't contain any phrases, so the phrase name list couldn't be created."); + } + } + } + } +} +#endif + +namespace Lean.Localization +{ + /// This attribute allows you to select a translation from all the localizations in the scene. + public class LeanTranslationNameAttribute : PropertyAttribute + { + } +} \ No newline at end of file diff --git a/Assets/Packages/Lean/Localization/Scripts/LeanTranslationNameAttribute.cs.meta b/Assets/Packages/Lean/Localization/Scripts/LeanTranslationNameAttribute.cs.meta new file mode 100644 index 0000000..e63dbdc --- /dev/null +++ b/Assets/Packages/Lean/Localization/Scripts/LeanTranslationNameAttribute.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cdfebeb32b399cb498725e695b85d6eb +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: -- cgit v1.2.3