Initial commit: Unity WordConnect project

This commit is contained in:
2025-08-01 19:12:05 +08:00
commit f14db75802
3503 changed files with 448337 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4a3ec51c6680c4484ae299678edb20bc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,29 @@
{
"name": "CandySmith.Localization.Editor",
"rootNamespace": "",
"references": [
" UnityEngine",
" UnityEditor",
"UnityEditor.UI",
"Unity.TextMeshPro.Editor",
"Unity.TextMeshPro",
"Reordable",
"CandySmith.BlockPuzzle.Main",
"CandySmith.Ads",
"CandySmith.IAP",
"DOTween.Modules",
"CandySmith.WordsToolkit.Main",
"VContainer"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 54ed5b0a667e4a0f987a5bacb1b15ba6
timeCreated: 1722189029

View File

@ -0,0 +1,40 @@
// // ©2015 - 2025 Candy Smith
// // All rights reserved
// // Redistribution of this software is strictly not allowed.
// // Copy of this software can be obtained from unity asset store only.
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// // THE SOFTWARE.
using UnityEditor;
using UnityEngine;
namespace WordsToolkit.Scripts.Localization.Editor
{
[CustomPropertyDrawer(typeof(LocalizationIndex))]
public class LocalizationDrawerUIE : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
position.height = EditorGUIUtility.singleLineHeight;
var r1 = position;
r1.width = 1;
var r2 = position;
r2.xMin = r1.xMax + 1;
r2.width = 50;
EditorGUI.BeginProperty(position, label, property);
// EditorGUI.PropertyField(r1, property.FindPropertyRelative("text"),new GUIContent("","Default text"));
EditorGUI.PropertyField(r2, property.FindPropertyRelative("index"), new GUIContent("", "Localization line index"));
r2.x += 50;
r2.width = 300;
EditorGUI.LabelField(r2, "Change text here: Resources/Localization/");
EditorGUI.EndProperty();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 944cf446217df453f8452f1e796751f4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,67 @@
// // ©2015 - 2025 Candy Smith
// // All rights reserved
// // Redistribution of this software is strictly not allowed.
// // Copy of this software can be obtained from unity asset store only.
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// // THE SOFTWARE.
using System;
using UnityEditor;
using UnityEngine;
namespace WordsToolkit.Scripts.Localization.Editor
{
[CustomPropertyDrawer(typeof(LocalizationIndexFolder))]
public class LocalizationFoldDrawer : PropertyDrawer
{
private int offset;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
position.height = EditorGUIUtility.singleLineHeight;
property.isExpanded = EditorGUI.Foldout(position, property.isExpanded, "Text");
if (property.isExpanded)
{
offset = 5;
position.y += EditorGUIUtility.singleLineHeight;
ShowField(position, property, "description", "Description");
position.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
ShowField(position, property, "failed", "Fail Description");
}
EditorGUI.EndProperty();
}
private void ShowField(Rect position, SerializedProperty property, string field, string label)
{
position.x += offset;
var r1 = position;
r1.width = 100;
EditorGUI.LabelField(r1, label);
position.x += offset;
var r2 = position;
r2.xMin = r1.xMax + 10;
EditorGUI.PropertyField(r2, property.FindPropertyRelative(field));
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return property.isExpanded ? EditorGUIUtility.singleLineHeight * 3 + EditorGUIUtility.standardVerticalSpacing * 2 : EditorGUIUtility.singleLineHeight;
}
}
[Serializable]
public class LocalizationIndexFolder
{
[Tooltip("Default text")]
public LocalizationIndex description;
public LocalizationIndex failed;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ceb71aa3d5e3348c6970c0b29d923e26
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,33 @@
// // ©2015 - 2025 Candy Smith
// // All rights reserved
// // Redistribution of this software is strictly not allowed.
// // Copy of this software can be obtained from unity asset store only.
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// // THE SOFTWARE.
using UnityEditor;
namespace WordsToolkit.Scripts.Localization.Editor
{
[InitializeOnLoad]
public class LocalizationInitializer
{
// static LocalizationInitializer()
// {
// EditorApplication.delayCall += Initialize;
// }
//
// static void Initialize()
// {
// if (!Application.isPlaying)
// {
// LocalizationManager.InitializeLocalization();
// }
// }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d9a87bf82ff9497d866a51df91a94948
timeCreated: 1722323590

View File

@ -0,0 +1,49 @@
// // ©2015 - 2025 Candy Smith
// // All rights reserved
// // Redistribution of this software is strictly not allowed.
// // Copy of this software can be obtained from unity asset store only.
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// // THE SOFTWARE.
using TMPro;
using UnityEditor;
using UnityEngine;
namespace WordsToolkit.Scripts.Localization.Editor
{
public class LocalizedTextMeshProUGUICreator
{
[MenuItem("GameObject/UI/Localized TextMeshPro - Text (UI)")]
private static void CreateLocalizedTextMeshProUGUI()
{
// Create a new GameObject
var go = new GameObject("Localized TextMeshPro");
// Add the LocalizedTextMeshProUGUI component
var localizedText = go.AddComponent<LocalizedTextMeshProUGUI>();
localizedText.fontSize = 32;
localizedText.enableAutoSizing = true;
localizedText.fontSizeMin = 16;
localizedText.fontSizeMax = 200;
localizedText.alignment = TextAlignmentOptions.Center;
// Set the parent of the new object
if (Selection.activeGameObject != null)
{
go.transform.SetParent(Selection.activeGameObject.transform, false);
}
// Register the creation for undo
Undo.RegisterCreatedObjectUndo(go, "Create Localized TextMeshPro");
// Select the newly created object
Selection.activeGameObject = go;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5f8e705c10b94f8dbed011f2760f1cac
timeCreated: 1722330317

View File

@ -0,0 +1,99 @@
// // ©2015 - 2025 Candy Smith
// // All rights reserved
// // Redistribution of this software is strictly not allowed.
// // Copy of this software can be obtained from unity asset store only.
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// // THE SOFTWARE.
using TMPro.EditorUtilities;
using UnityEditor;
using UnityEngine;
using System.IO;
namespace WordsToolkit.Scripts.Localization.Editor
{
[CustomEditor(typeof(LocalizedTextMeshProUGUI), true)]
[CanEditMultipleObjects]
public class LocalizedTextMeshProUGUIEditor : TMP_EditorPanelUI
{
private SerializedProperty instanceIDProp;
private LocalizedTextMeshProUGUI localizedText;
private string lastKnownName;
protected override void OnEnable()
{
base.OnEnable();
instanceIDProp = serializedObject.FindProperty("instanceID");
localizedText = (LocalizedTextMeshProUGUI)target;
lastKnownName = localizedText.gameObject.name;
}
public override void OnInspectorGUI()
{
serializedObject.Update();
if (localizedText != null && localizedText.gameObject.name != lastKnownName)
{
localizedText.text = localizedText.gameObject.name;
lastKnownName = localizedText.gameObject.name;
EditorUtility.SetDirty(localizedText);
}
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(instanceIDProp);
if (GUILayout.Button("Edit Localization"))
{
string relativePath = "Assets/WordConnectGameToolkit/Resources/Localization/English.txt";
var asset = AssetDatabase.LoadAssetAtPath<TextAsset>(relativePath);
if (asset != null)
{
AssetDatabase.OpenAsset(asset);
}
else
{
Debug.LogWarning("English.txt file not found at: " + relativePath);
}
}
if (EditorGUI.EndChangeCheck() || Event.current.type == EventType.Layout)
{
serializedObject.ApplyModifiedProperties();
UpdateLocalizedText();
}
EditorGUILayout.Space();
base.OnInspectorGUI();
if (serializedObject.ApplyModifiedProperties())
{
UpdateLocalizedText();
}
}
private void UpdateLocalizedText()
{
if (localizedText != null && !string.IsNullOrEmpty(localizedText.instanceID))
{
var originalText = localizedText.text;
var localizedString = LocalizationManager.instance.GetText(localizedText.instanceID, originalText);
if (localizedText.text != localizedString)
{
localizedText.text = localizedString;
if (PrefabUtility.IsPartOfPrefabInstance(localizedText))
{
PrefabUtility.RecordPrefabInstancePropertyModifications(localizedText);
}
EditorUtility.SetDirty(localizedText);
}
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9ee5acd244824d7bbd981db5e537507f
timeCreated: 1722188512

View File

@ -0,0 +1,111 @@
// // ©2015 - 2025 Candy Smith
// // All rights reserved
// // Redistribution of this software is strictly not allowed.
// // Copy of this software can be obtained from unity asset store only.
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// // THE SOFTWARE.
using System.Reflection;
using TMPro;
using UnityEditor;
namespace WordsToolkit.Scripts.Localization.Editor
{
public class TextMeshProReplacer : EditorWindow
{
[MenuItem("Tools/Replace TMP with LocalizedTMP")]
private static void ReplaceTextMeshProUGUI()
{
var selectedObjects = Selection.gameObjects;
if (selectedObjects.Length == 0)
{
EditorUtility.DisplayDialog("No Selection", "Please select at least one GameObject.", "OK");
return;
}
var replacedCount = 0;
foreach (var obj in selectedObjects)
{
var tmproComponents = obj.GetComponentsInChildren<TextMeshProUGUI>(true);
foreach (var tmproComponent in tmproComponents)
{
if (tmproComponent.GetType() == typeof(TextMeshProUGUI)) // Ensure we're not replacing already customized scripts
{
var localizeText = tmproComponent.GetComponent<LocalizeText>();
var instanceID = localizeText != null ? localizeText.instanceID : "";
// Store all relevant properties
var text = tmproComponent.text;
var font = tmproComponent.font;
var fontMaterial = tmproComponent.fontMaterial;
var color = tmproComponent.color;
var fontStyle = tmproComponent.fontStyle;
var fontSize = tmproComponent.fontSize;
var autoSizeTextContainer = tmproComponent.autoSizeTextContainer;
var enableAutoSizing = tmproComponent.enableAutoSizing;
var characterSpacing = tmproComponent.characterSpacing;
var wordSpacing = tmproComponent.wordSpacing;
var lineSpacing = tmproComponent.lineSpacing;
var paragraphSpacing = tmproComponent.paragraphSpacing;
var alignment = tmproComponent.alignment;
var enableWordWrapping = tmproComponent.enableWordWrapping;
var overflowMode = tmproComponent.overflowMode;
var isRightToLeftText = tmproComponent.isRightToLeftText;
var enableKerning = tmproComponent.enableKerning;
var extraPadding = tmproComponent.extraPadding;
var richText = tmproComponent.richText;
// Remove old component
DestroyImmediate(tmproComponent);
if (localizeText != null)
{
DestroyImmediate(localizeText);
}
// Add new component
var newComponent = obj.AddComponent<LocalizedTextMeshProUGUI>();
// Restore properties
newComponent.text = text;
newComponent.font = font;
newComponent.fontMaterial = fontMaterial;
newComponent.color = color;
newComponent.fontStyle = fontStyle;
newComponent.fontSize = fontSize;
newComponent.autoSizeTextContainer = autoSizeTextContainer;
newComponent.enableAutoSizing = enableAutoSizing;
newComponent.characterSpacing = characterSpacing;
newComponent.wordSpacing = wordSpacing;
newComponent.lineSpacing = lineSpacing;
newComponent.paragraphSpacing = paragraphSpacing;
newComponent.alignment = alignment;
newComponent.enableWordWrapping = enableWordWrapping;
newComponent.overflowMode = overflowMode;
newComponent.isRightToLeftText = isRightToLeftText;
newComponent.enableKerning = enableKerning;
newComponent.extraPadding = extraPadding;
newComponent.richText = richText;
// Set the instanceID using reflection (since it's private)
var fieldInfo = typeof(LocalizedTextMeshProUGUI).GetField("instanceID", BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
fieldInfo.SetValue(newComponent, instanceID);
}
replacedCount++;
}
}
}
EditorUtility.DisplayDialog("Replacement Complete", $"Replaced {replacedCount} TextMeshProUGUI component(s) with LocalizedTextMeshProUGUI.", "OK");
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2f2acec46fea4a4283963e2e0a1dc627
timeCreated: 1722188160

View File

@ -0,0 +1,27 @@
// // ©2015 - 2025 Candy Smith
// // All rights reserved
// // Redistribution of this software is strictly not allowed.
// // Copy of this software can be obtained from unity asset store only.
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// // THE SOFTWARE.
using System.Collections.Generic;
using UnityEngine;
namespace WordsToolkit.Scripts.Localization
{
public interface ILocalizationService
{
string GetText(string key, string defaultText);
SystemLanguage GetCurrentLanguage();
void LoadLanguage(SystemLanguage language);
void LoadLanguageFromBase(TextAsset localizationBase);
public void SetKeysPlaceholders(Dictionary<string, string> keys);
public void SetPairPlaceholder(string key, string value);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b18fe539f7e834fa09fd73d9340bc844
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,24 @@
// // ©2015 - 2025 Candy Smith
// // All rights reserved
// // Redistribution of this software is strictly not allowed.
// // Copy of this software can be obtained from unity asset store only.
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// // THE SOFTWARE.
using System;
using UnityEngine;
namespace WordsToolkit.Scripts.Localization
{
[Serializable]
public class LanguageObject
{
public SystemLanguage language;
public string text;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e8d31ef4542740f7804a1c8570aaaa33
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,27 @@
// // ©2015 - 2025 Candy Smith
// // All rights reserved
// // Redistribution of this software is strictly not allowed.
// // Copy of this software can be obtained from unity asset store only.
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// // THE SOFTWARE.
using System;
using UnityEngine;
namespace WordsToolkit.Scripts.Localization
{
[Serializable]
public class LocalizationIndex
{
[Tooltip("Default text")]
public string text;
[Tooltip("Localization line index")]
public int index;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d8f88401902cf49958b68ffe4f733a34
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,215 @@
// // ©2015 - 2025 Candy Smith
// // All rights reserved
// // Redistribution of this software is strictly not allowed.
// // Copy of this software can be obtained from unity asset store only.
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// // THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using UnityEngine;
using VContainer;
using WordsToolkit.Scripts.Levels;
using WordsToolkit.Scripts.Services;
using WordsToolkit.Scripts.Settings;
using WordsToolkit.Scripts.System;
namespace WordsToolkit.Scripts.Localization
{
public class LocalizationManager : SingletonBehaviour<LocalizationManager>, ILocalizationService
{
private static DebugSettings _debugSettings;
public static Dictionary<string, string> _dic;
private SystemLanguage _currentLanguage;
[Inject]
private LanguageConfiguration languageConfig;
[Inject]
private ILanguageService languageService;
private Dictionary<string, string> _placeholdersDic;
public override void Awake()
{
base.Awake();
InitializeLocalization();
}
public void InitializeLocalization()
{
_debugSettings = Resources.Load("Settings/DebugSettings") as DebugSettings;
LoadLanguage(GetSystemLanguage());
}
public void SetKeysPlaceholders(Dictionary<string, string> keys)
{
if (keys == null || keys.Count == 0)
{
Debug.LogWarning("Localization keys are null or empty. Using default language.");
LoadLanguage(SystemLanguage.English);
return;
}
_placeholdersDic = new Dictionary<string, string>(keys);
}
public void SetPairPlaceholder(string key, string value)
{
_placeholdersDic ??= new Dictionary<string, string>();
_placeholdersDic[key] = value;
}
public void LoadLanguage(SystemLanguage language)
{
_currentLanguage = language;
// Convert language enum to CultureInfo to get the actual file name
var cultureInfo = CultureInfo.GetCultures(CultureTypes.AllCultures)
.FirstOrDefault(c => string.Equals(c.EnglishName, language.ToString(), StringComparison.OrdinalIgnoreCase));
var fileName = cultureInfo != null ? cultureInfo.EnglishName : language.ToString();
var txt = Resources.Load<TextAsset>($"Localization/{fileName}");
if (txt == null)
{
Debug.LogWarning($"Localization file for {fileName} not found. Falling back to English.");
txt = Resources.Load<TextAsset>("Localization/English");
_currentLanguage = SystemLanguage.English;
}
_dic = new Dictionary<string, string>();
var lines = txt.text.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var inp_ln in lines)
{
var l = inp_ln.Split(new[] { ':' }, 2);
if (l.Length == 2)
{
var key = l[0].Trim();
var text = l[1].Trim();
_dic[key] = text;
}
}
}
public void LoadLanguageFromBase(TextAsset localizationBase)
{
if (localizationBase == null)
{
Debug.LogWarning("Localization base is null. Falling back to English.");
LoadLanguage(SystemLanguage.English);
return;
}
_dic = new Dictionary<string, string>();
var lines = localizationBase.text.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var inp_ln in lines)
{
var l = inp_ln.Split(new[] { ':' }, 2);
if (l.Length == 2)
{
var key = l[0].Trim();
var text = l[1].Trim();
_dic[key] = text;
}
}
}
public static SystemLanguage GetSystemLanguage()
{
if (_debugSettings == null)
{
_debugSettings = Resources.Load("Settings/DebugSettings") as DebugSettings;
}
// Try to get language from LanguageService if available
if (instance != null && instance.languageService != null)
{
try
{
var languageCode = instance.languageService.GetCurrentLanguageCode();
var cultureInfo = new CultureInfo(languageCode);
return (SystemLanguage)Enum.Parse(typeof(SystemLanguage), cultureInfo.EnglishName);
}
catch (Exception)
{
// Fallback if there's an issue parsing the language code
Debug.LogWarning("Failed to parse language from LanguageService. Using fallback.");
}
}
// Fallback to PlayerPrefs check for backward compatibility
if (PlayerPrefs.HasKey("SelectedLanguage"))
{
try
{
var value = PlayerPrefs.GetString("SelectedLanguage");
var cultureInfo = new CultureInfo(value);
return (SystemLanguage)Enum.Parse(typeof(SystemLanguage), cultureInfo.EnglishName);
}
catch (Exception)
{
Debug.LogWarning("Failed to parse saved language preference. Using default.");
}
}
if (Application.isEditor)
{
return _debugSettings.TestLanguage;
}
return Application.systemLanguage;
}
public string GetText(string key, string defaultText)
{
var currentLanguage = GetSystemLanguage();
if (_currentLanguage != currentLanguage || _dic == null || _dic.Count == 0)
{
LoadLanguage(currentLanguage);
}
if (!_dic.ContainsKey(key))
{
LoadLanguage(currentLanguage);
}
if (_dic.TryGetValue(key, out var localizedText) && !string.IsNullOrEmpty(localizedText))
{
return PlaceholderManager.ReplacePlaceholders(localizedText, _placeholdersDic);
}
return PlaceholderManager.ReplacePlaceholders(defaultText, _placeholdersDic);
}
public SystemLanguage GetCurrentLanguage()
{
return _currentLanguage;
}
public string GetLocalizedCurrentLanguage()
{
if (languageService != null)
{
var currentLanguageInfo = languageService.GetCurrentLanguageInfo();
if (currentLanguageInfo != null)
{
return currentLanguageInfo.localizedName;
}
}
// Fallback to old logic
foreach (var lang in languageConfig.languages)
{
if (lang.displayName == _currentLanguage.ToString())
{
return lang.localizedName;
}
}
return _currentLanguage.ToString();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d4ba3c33f9f6741ce8e89e40c09c2af2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 800
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,88 @@
// // ©2015 - 2025 Candy Smith
// // All rights reserved
// // Redistribution of this software is strictly not allowed.
// // Copy of this software can be obtained from unity asset store only.
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// // THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using WordsToolkit.Scripts.Settings;
using WordsToolkit.Scripts.Unity_Reorderable_List_master.List;
using WordsToolkit.Scripts.Unity_Reorderable_List_master.List.Attributes;
namespace WordsToolkit.Scripts.Localization
{
public class LocalizeText : MonoBehaviour
{
private TextMeshProUGUI textObject;
public string instanceID;
[SerializeField]
[Reorderable(elementNameProperty = "language")]
[HideInInspector]
public MyList objects;
private string _originalText;
private string _currentText;
private void Awake()
{
textObject = GetComponent<TextMeshProUGUI>();
}
private void OnEnable()
{
//take text from target editor
_originalText = textObject.text;
_currentText = LocalizationManager.instance.GetText(instanceID, _originalText);
textObject.text = _currentText;
}
// private void Update()
// {
// if (_currentText != "")
// {
// textObject.text = _currentText;
// }
// }
}
[Serializable]
public class MyList : ReorderableArray<LanguageObject>
{
private SystemLanguage GetSystemLanguage(DebugSettings _debugSettings)
{
SystemLanguage lang;
if (Application.platform == RuntimePlatform.WindowsEditor || Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.LinuxEditor)
{
lang = _debugSettings.TestLanguage;
}
else
{
lang = Application.systemLanguage;
}
return this.Any(i => i.language == lang) ? _debugSettings.TestLanguage : SystemLanguage.English;
}
public IEnumerable<LanguageObject> GetText(DebugSettings _debugSettings)
{
var systemLanguage = GetSystemLanguage(_debugSettings);
return GetText(systemLanguage);
}
public IEnumerable<LanguageObject> GetText(SystemLanguage systemLanguage)
{
return this.Where(i => i.language == systemLanguage);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 32729a099b98b485a9563986a3d5ef3d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 1900
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,48 @@
// // ©2015 - 2025 Candy Smith
// // All rights reserved
// // Redistribution of this software is strictly not allowed.
// // Copy of this software can be obtained from unity asset store only.
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// // THE SOFTWARE.
using TMPro;
using UnityEngine;
using VContainer;
namespace WordsToolkit.Scripts.Localization
{
public class LocalizedTextMeshProUGUI : TextMeshProUGUI
{
[SerializeField]
public string instanceID;
private string originalText;
private ILocalizationService _localizationService;
[Inject]
public void Construct(ILocalizationService localizationService)
{
_localizationService = localizationService;
}
protected override void OnEnable()
{
base.OnEnable();
originalText = text;
UpdateText();
}
public void UpdateText()
{
if (instanceID != "")
{
text = _localizationService?.GetText(instanceID, originalText) ?? originalText;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2bc838cbc2e148b3a5e7578d063ccb18
timeCreated: 1722187699

View File

@ -0,0 +1,60 @@
// // ©2015 - 2025 Candy Smith
// // All rights reserved
// // Redistribution of this software is strictly not allowed.
// // Copy of this software can be obtained from unity asset store only.
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// // THE SOFTWARE.
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine;
using WordsToolkit.Scripts.Gameplay.Managers;
using WordsToolkit.Scripts.System;
namespace WordsToolkit.Scripts.Localization
{
// This class is used to replace placeholders in localized strings with actual values.
public static class PlaceholderManager
{
public static string GetPlaceholderValue(string placeholderKey, Dictionary<string, string> placeholdersDic)
{
// First check if the key exists in the provided dictionary
if (placeholdersDic != null && placeholdersDic.TryGetValue(placeholderKey, out string value))
{
return value;
}
// If not found in dictionary, check default cases
switch (placeholderKey)
{
case "level":
return GetCurrentLevel().ToString();
default:
if (PlayerPrefs.HasKey(placeholderKey))
{
return PlayerPrefs.GetString(placeholderKey);
}
return "{" + placeholderKey + "}";
}
}
public static string ReplacePlaceholders(string input, Dictionary<string, string> placeholdersDic)
{
return Regex.Replace(input, @"\{(\w+)\}", match =>
{
var placeholderKey = match.Groups[1].Value;
return GetPlaceholderValue(placeholderKey, placeholdersDic);
});
}
private static int GetCurrentLevel()
{
return GameDataManager.GetLevel().number;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fac86d0fb1f54ae1a60853746bd7fb7b
timeCreated: 1725684738