com.xmobitea.changx.mini-localization 1.4.9 → 1.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,62 @@
1
+ # XmobiTea Localization Package Notes
2
+
3
+ This file defines the usage contract of this package for AI coding agents and maintainers.
4
+
5
+ ## Scope
6
+
7
+ Applies to everything inside this package folder.
8
+
9
+ ## Hard Rules
10
+
11
+ - `LocalizationManager` is a scene-hosted singleton and must exist before static APIs are used.
12
+ - `LocalizationSettings` must exist in `Resources` under the configured resource path.
13
+ - runtime localization is key-based and dictionary-backed.
14
+ - `ChooseLanguage(...)` is the operation that actually populates the active text dictionary.
15
+ - `GetText(...)` falls back to the original key when translation is missing.
16
+ - UI text updates happen through `LocalizationManager.OnUpdateText`.
17
+ - online localization success clears and rebuilds the active dictionary from the downloaded XML.
18
+ - generated key constants come from editor tooling, not from runtime discovery.
19
+
20
+ ## Required Mental Model
21
+
22
+ Treat this package as:
23
+
24
+ - a scene-managed localization system,
25
+ - XML-driven translation storage,
26
+ - key-based lookup plus UI refresh events,
27
+ - editor-assisted setup and code generation.
28
+
29
+ Do not treat this package as:
30
+
31
+ - an auto-configuring localization framework,
32
+ - a strongly typed localization database,
33
+ - a schema-validated content pipeline,
34
+ - a one-shot synchronous load system when addressable or online sources are enabled.
35
+
36
+ ## Correct Usage Pattern
37
+
38
+ When generating or reviewing code, assume the valid pattern is:
39
+
40
+ 1. Ensure `LocalizationManager` exists in scene.
41
+ 2. Ensure `LocalizationSettings` exists in `Resources`.
42
+ 3. Configure one or more `LocalizationLanguageItem` entries.
43
+ 4. Call `ChooseLanguage(...)` during bootstrap.
44
+ 5. Bind UI through localization components or direct `GetText(...)` calls.
45
+
46
+ ## Incorrect Assumptions
47
+
48
+ These assumptions are false:
49
+
50
+ - translations are active immediately just because the manager exists.
51
+ - generated constants always exist without editor generation.
52
+ - online localization merges into local localization without replacing it.
53
+ - `GetText(...)` returns null when a key is missing.
54
+
55
+ ## Operational Consequences
56
+
57
+ If an agent writes code against this package:
58
+
59
+ - it should mention the need for scene setup and settings asset,
60
+ - it should not assume constants are available unless generation is part of the workflow,
61
+ - it should account for repeated `OnUpdateText` when async sources are enabled,
62
+ - it should document any changed XML or load-order semantics in `README.md`.
package/AGENTS.md.meta ADDED
@@ -0,0 +1,7 @@
1
+ fileFormatVersion: 2
2
+ guid: dbc3c26e0af84321b39329ff2a67cc3b
3
+ TextScriptImporter:
4
+ externalObjects: {}
5
+ userData:
6
+ assetBundleName:
7
+ assetBundleVariant:
package/AI_USAGE.md ADDED
@@ -0,0 +1,84 @@
1
+ # AI Usage Rules For XmobiTea Localization
2
+
3
+ Use this file when you want to generate code without reading the runtime implementation.
4
+
5
+ ## One-Screen Summary
6
+
7
+ - Scene `LocalizationManager` required: `Yes`
8
+ - `Resources` settings asset required: `Yes`
9
+ - Main activation call: `LocalizationManager.ChooseLanguage(...)`
10
+ - Text lookup fallback: original key
11
+ - UI auto-refresh event: `LocalizationManager.OnUpdateText`
12
+ - XML local source: `Yes`
13
+ - Addressables optional source: `Yes`
14
+ - Online optional source: `Yes`
15
+ - Generated key constants automatic at runtime: `No`
16
+
17
+ ## Exact Rules
18
+
19
+ ### Rule 1
20
+
21
+ Always assume localization is inactive until `ChooseLanguage(...)` is called.
22
+
23
+ ### Rule 2
24
+
25
+ Always assume `LocalizationManager` must already exist in scene.
26
+
27
+ ### Rule 3
28
+
29
+ Always assume `LocalizationSettings` must exist in `Resources`.
30
+
31
+ ### Rule 4
32
+
33
+ Use `LocalizationComponent` for `UnityEngine.UI.Text`.
34
+
35
+ ### Rule 5
36
+
37
+ Use `TMP_LocalizationComponent` only when TextMesh Pro support is present.
38
+
39
+ ### Rule 6
40
+
41
+ Treat `GetText(key)` fallback-to-key as normal behavior, not as an exception case.
42
+
43
+ ### Rule 7
44
+
45
+ If online localization is enabled and succeeds, assume it replaces the active dictionary content.
46
+
47
+ ## Safe Code Template
48
+
49
+ ```csharp
50
+ using UnityEngine;
51
+ using XmobiTea.MiniLocalization;
52
+
53
+ public sealed class ExampleLocalizationBootstrap : MonoBehaviour
54
+ {
55
+ private void Start()
56
+ {
57
+ LocalizationManager.ChooseLanguage(SystemLanguage.English);
58
+ }
59
+ }
60
+ ```
61
+
62
+ ```csharp
63
+ using XmobiTea.MiniLocalization;
64
+
65
+ var title = LocalizationManager.GetText("Home_Title");
66
+ ```
67
+
68
+ ## Forbidden AI Assumptions
69
+
70
+ Do not generate code that assumes:
71
+
72
+ - auto-bootstrap of language selection,
73
+ - runtime-generated key constants,
74
+ - null return on missing translation,
75
+ - one single synchronous update when online or addressable sources are involved.
76
+
77
+ ## Recommended AI Output Style
78
+
79
+ When generating new code with this package:
80
+
81
+ - mention manager and settings setup,
82
+ - call out `ChooseLanguage(...)`,
83
+ - prefer generated constants when they are part of the project workflow,
84
+ - keep key-based fallback semantics explicit.
@@ -0,0 +1,7 @@
1
+ fileFormatVersion: 2
2
+ guid: c23a0a9beb6c4aaeac7ef96d935f89c9
3
+ TextScriptImporter:
4
+ externalObjects: {}
5
+ userData:
6
+ assetBundleName:
7
+ assetBundleVariant:
@@ -0,0 +1,158 @@
1
+ namespace XmobiTea.MiniLocalization.Editor
2
+ {
3
+ using System.Collections.Generic;
4
+ using System.Linq;
5
+ using System.Text.RegularExpressions;
6
+ using System.Text;
7
+ using System;
8
+
9
+ public static class CsvParser
10
+ {
11
+ public static List<List<string>> ParseFromString(
12
+ string data,
13
+ bool hasHeader,
14
+ bool removeHeader = true)
15
+ {
16
+ ConvertToCrlf(ref data);
17
+
18
+ var sheet = new List<List<string>>();
19
+ var row = new List<string>();
20
+ var cell = new StringBuilder();
21
+ var insideQuoteCell = false;
22
+ var start = 0;
23
+
24
+ var delimiterSpan = ','.ToString().AsSpan();
25
+ var crlfSpan = "\r\n".AsSpan();
26
+ var oneDoubleQuotSpan = "\"".AsSpan();
27
+ var twoDoubleQuotSpan = "\"\"".AsSpan();
28
+
29
+ while (start < data.Length)
30
+ {
31
+ var length = start <= data.Length - 2 ? 2 : 1;
32
+ var span = data.AsSpan(start, length);
33
+
34
+ if (span.StartsWith(delimiterSpan))
35
+ {
36
+ if (insideQuoteCell)
37
+ {
38
+ cell.Append(',');
39
+ }
40
+ else
41
+ {
42
+ AddCell(row, cell);
43
+ }
44
+
45
+ start += 1;
46
+ }
47
+ else if (span.StartsWith(crlfSpan))
48
+ {
49
+ if (insideQuoteCell)
50
+ {
51
+ cell.Append("\r\n");
52
+ }
53
+ else
54
+ {
55
+ AddCell(row, cell);
56
+
57
+ if (IsRowNonEmpty(row))
58
+ {
59
+ AddRow(sheet, ref row);
60
+ }
61
+ else
62
+ {
63
+ row.Clear(); // Reset row if it's just empty
64
+ }
65
+ }
66
+
67
+ start += 2;
68
+ }
69
+ else if (span.StartsWith(twoDoubleQuotSpan))
70
+ {
71
+ cell.Append("\"");
72
+ start += 2;
73
+ }
74
+ else if (span.StartsWith(oneDoubleQuotSpan))
75
+ {
76
+ insideQuoteCell = !insideQuoteCell;
77
+ start += 1;
78
+ }
79
+ else
80
+ {
81
+ cell.Append(span[0]);
82
+ start += 1;
83
+ }
84
+ }
85
+
86
+ // Add the last cell
87
+ if (IsRowNonEmpty(row) || cell.Length > 0)
88
+ {
89
+ AddCell(row, cell);
90
+ AddRow(sheet, ref row);
91
+ }
92
+
93
+ if (hasHeader && removeHeader && sheet.Count > 0)
94
+ {
95
+ sheet.RemoveAt(0);
96
+ }
97
+
98
+ if (hasHeader && !removeHeader && sheet.Count == 1)
99
+ {
100
+ sheet.Clear();
101
+ }
102
+
103
+ return sheet;
104
+ }
105
+
106
+ private static bool IsRowNonEmpty(List<string> row) =>
107
+ row.Count > 0 &&
108
+ row.Any(cell => !string.IsNullOrWhiteSpace(cell));
109
+
110
+ private static void AddCell(List<string> row, StringBuilder cell)
111
+ {
112
+ row.Add(cell.ToString());
113
+ cell.Clear();
114
+ }
115
+
116
+ private static void AddRow(List<List<string>> sheet, ref List<string> row)
117
+ {
118
+ sheet.Add(new List<string>(row));
119
+ row.Clear();
120
+ }
121
+
122
+ private static void ConvertToCrlf(ref string data)
123
+ {
124
+ data = Regex.Replace(data, @"\r\n|\r|\n", "\r\n");
125
+ }
126
+
127
+ private static object ConvertValue(string value, Type targetType)
128
+ {
129
+ if (string.IsNullOrWhiteSpace(value))
130
+ {
131
+ if (targetType == typeof(string))
132
+ return string.Empty;
133
+
134
+ return targetType.IsValueType ? Activator.CreateInstance(targetType) : null;
135
+ }
136
+
137
+ if (targetType == typeof(string))
138
+ return value;
139
+
140
+ if (targetType == typeof(int) || targetType == typeof(int?))
141
+ return int.TryParse(value, out int intResult) ? intResult : null;
142
+
143
+ if (targetType == typeof(decimal) || targetType == typeof(decimal?))
144
+ return decimal.TryParse(value, out decimal decimalResult) ? decimalResult : null;
145
+
146
+ if (targetType == typeof(DateTime) || targetType == typeof(DateTime?))
147
+ return DateTime.TryParse(value, out DateTime dateResult) ? dateResult : null;
148
+
149
+ if (targetType == typeof(bool) || targetType == typeof(bool?))
150
+ return bool.TryParse(value, out bool boolResult) ? boolResult : null;
151
+
152
+ if (targetType == typeof(double) || targetType == typeof(double?))
153
+ return double.TryParse(value, out double doubleResult) ? doubleResult : null;
154
+
155
+ throw new NotSupportedException($"Type {targetType} is not supported for conversion.");
156
+ }
157
+ }
158
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: c44754ba565bc0249ad8400dfd782fbd
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -1,66 +1,66 @@
1
- namespace XmobiTea.MiniLocalization.Editor
2
- {
3
- using System.IO;
4
- using UnityEditor;
5
-
6
- using UnityEngine;
7
-
8
- [CustomEditor(typeof(LocalizationComponent))]
9
- public class LocalizationComponentEditor : Editor
10
- {
11
- SerializedProperty keyProp;
12
- SerializedProperty mainTxtProp;
13
-
14
- private void OnEnable()
15
- {
16
- keyProp = serializedObject.FindProperty("_key");
17
- mainTxtProp = serializedObject.FindProperty("_mainTxt");
18
- }
19
-
20
- public override void OnInspectorGUI()
21
- {
22
- serializedObject.Update();
23
-
24
- EditorGUILayout.PropertyField(mainTxtProp);
25
-
26
- EditorGUILayout.PropertyField(keyProp);
27
-
28
- if (serializedObject.hasModifiedProperties) serializedObject.ApplyModifiedProperties();
29
-
30
- EditorGUILayout.Space(20);
31
-
32
- if (GUILayout.Button("Reload Text"))
33
- {
34
- var localizationComponentBase = (LocalizationComponentBase)target;
35
- localizationComponentBase.OnUpdateText();
36
- }
37
- }
38
-
39
- [MenuItem("XmobiTea Tools/Localization/Open Settings")]
40
- private static void OpenSettings()
41
- {
42
- var localizationSettings = GetSettings();
43
-
44
- Selection.SetActiveObjectWithContext(localizationSettings, localizationSettings);
45
- }
46
-
47
- public static LocalizationSettings GetSettings()
48
- {
49
- var localizationSettings = Resources.Load<LocalizationSettings>(LocalizationSettings.ResourcesPath);
50
- if (localizationSettings == null)
51
- {
52
- localizationSettings = ScriptableObject.CreateInstance<LocalizationSettings>();
53
-
54
- var dirPath = Application.dataPath + "/Resources";
55
- if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);
56
-
57
- var path = "Assets/Resources/" + LocalizationSettings.ResourcesPath + ".asset";
58
- AssetDatabase.CreateAsset(localizationSettings, path);
59
-
60
- Debug.Log("[Localization] Localization Settings create success at path " + path);
61
- }
62
-
63
- return localizationSettings;
64
- }
65
- }
66
- }
1
+ namespace XmobiTea.MiniLocalization.Editor
2
+ {
3
+ using System.IO;
4
+ using UnityEditor;
5
+
6
+ using UnityEngine;
7
+
8
+ [CustomEditor(typeof(LocalizationComponent))]
9
+ public class LocalizationComponentEditor : Editor
10
+ {
11
+ SerializedProperty keyProp;
12
+ SerializedProperty mainTxtProp;
13
+
14
+ private void OnEnable()
15
+ {
16
+ keyProp = serializedObject.FindProperty("_key");
17
+ mainTxtProp = serializedObject.FindProperty("_mainTxt");
18
+ }
19
+
20
+ public override void OnInspectorGUI()
21
+ {
22
+ serializedObject.Update();
23
+
24
+ EditorGUILayout.PropertyField(mainTxtProp);
25
+
26
+ EditorGUILayout.PropertyField(keyProp);
27
+
28
+ if (serializedObject.hasModifiedProperties) serializedObject.ApplyModifiedProperties();
29
+
30
+ EditorGUILayout.Space(20);
31
+
32
+ if (GUILayout.Button("Reload Text"))
33
+ {
34
+ var localizationComponentBase = (LocalizationComponentBase)target;
35
+ localizationComponentBase.OnUpdateText();
36
+ }
37
+ }
38
+
39
+ [MenuItem("XmobiTea Tools/Localization/Open Settings")]
40
+ private static void OpenSettings()
41
+ {
42
+ var localizationSettings = GetSettings();
43
+
44
+ Selection.SetActiveObjectWithContext(localizationSettings, localizationSettings);
45
+ }
46
+
47
+ public static LocalizationSettings GetSettings()
48
+ {
49
+ var localizationSettings = Resources.Load<LocalizationSettings>(LocalizationSettings.ResourcesPath);
50
+ if (localizationSettings == null)
51
+ {
52
+ localizationSettings = ScriptableObject.CreateInstance<LocalizationSettings>();
53
+
54
+ var dirPath = Application.dataPath + "/Resources";
55
+ if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);
56
+
57
+ var path = "Assets/Resources/" + LocalizationSettings.ResourcesPath + ".asset";
58
+ AssetDatabase.CreateAsset(localizationSettings, path);
59
+
60
+ Debug.Log("[Localization] Localization Settings create success at path " + path);
61
+ }
62
+
63
+ return localizationSettings;
64
+ }
65
+ }
66
+ }