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

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,67 @@
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
+ - `FetchType` controls the editor fetch source: `None`, `FromGoogleSheets`, or `FromCsv`.
20
+ - CSV fetch reads a `TextAsset` assigned in settings, parses it with `CsvParser`, and writes XML files to disk. It does not alter the runtime dictionary directly.
21
+
22
+ ## Required Mental Model
23
+
24
+ Treat this package as:
25
+
26
+ - a scene-managed localization system,
27
+ - XML-driven translation storage at runtime,
28
+ - CSV-driven or Google-Sheets-driven fetch workflow in the editor (CSV is converted to XML before being used at runtime),
29
+ - key-based lookup plus UI refresh events,
30
+ - editor-assisted setup and code generation.
31
+
32
+ Do not treat this package as:
33
+
34
+ - an auto-configuring localization framework,
35
+ - a strongly typed localization database,
36
+ - a schema-validated content pipeline,
37
+ - a one-shot synchronous load system when addressable or online sources are enabled.
38
+
39
+ ## Correct Usage Pattern
40
+
41
+ When generating or reviewing code, assume the valid pattern is:
42
+
43
+ 1. Ensure `LocalizationManager` exists in scene.
44
+ 2. Ensure `LocalizationSettings` exists in `Resources`.
45
+ 3. Configure one or more `LocalizationLanguageItem` entries.
46
+ 4. Call `ChooseLanguage(...)` during bootstrap.
47
+ 5. Bind UI through localization components or direct `GetText(...)` calls.
48
+
49
+ ## Incorrect Assumptions
50
+
51
+ These assumptions are false:
52
+
53
+ - translations are active immediately just because the manager exists.
54
+ - generated constants always exist without editor generation.
55
+ - online localization merges into local localization without replacing it.
56
+ - `GetText(...)` returns null when a key is missing.
57
+
58
+ ## Operational Consequences
59
+
60
+ If an agent writes code against this package:
61
+
62
+ - it should mention the need for scene setup and settings asset,
63
+ - it should not assume constants are available unless generation is part of the workflow,
64
+ - it should account for repeated `OnUpdateText` when async sources are enabled,
65
+ - it should document any changed XML or load-order semantics in `README.md`,
66
+ - when describing the fetch workflow, it should distinguish between `FromGoogleSheets` (runs an external executable) and `FromCsv` (reads a `TextAsset` inside the project and converts it to XML),
67
+ - it should never imply that CSV is read at runtime — CSV is an editor-only input format.
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,96 @@
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
+ - Editor fetch source options: `None`, `FromGoogleSheets`, `FromCsv`
17
+ - CSV is editor-only: parsed to XML by editor tooling, never read at runtime
18
+
19
+ ## Exact Rules
20
+
21
+ ### Rule 1
22
+
23
+ Always assume localization is inactive until `ChooseLanguage(...)` is called.
24
+
25
+ ### Rule 2
26
+
27
+ Always assume `LocalizationManager` must already exist in scene.
28
+
29
+ ### Rule 3
30
+
31
+ Always assume `LocalizationSettings` must exist in `Resources`.
32
+
33
+ ### Rule 4
34
+
35
+ Use `LocalizationComponent` for `UnityEngine.UI.Text`.
36
+
37
+ ### Rule 5
38
+
39
+ Use `TMP_LocalizationComponent` only when TextMesh Pro support is present.
40
+
41
+ ### Rule 6
42
+
43
+ Treat `GetText(key)` fallback-to-key as normal behavior, not as an exception case.
44
+
45
+ ### Rule 7
46
+
47
+ If online localization is enabled and succeeds, assume it replaces the active dictionary content.
48
+
49
+ ### Rule 8
50
+
51
+ `FetchType` is an enum with three values: `None`, `FromGoogleSheets`, `FromCsv`. It is set separately for local and online fetch.
52
+
53
+ ### Rule 9
54
+
55
+ When `FetchType.FromCsv` is active, the editor reads a `TextAsset` CSV assigned in `LocalizationSettings`, converts it to XML, and writes the result to disk. The runtime never reads CSV directly.
56
+
57
+ ## Safe Code Template
58
+
59
+ ```csharp
60
+ using UnityEngine;
61
+ using XmobiTea.MiniLocalization;
62
+
63
+ public sealed class ExampleLocalizationBootstrap : MonoBehaviour
64
+ {
65
+ private void Start()
66
+ {
67
+ LocalizationManager.ChooseLanguage(SystemLanguage.English);
68
+ }
69
+ }
70
+ ```
71
+
72
+ ```csharp
73
+ using XmobiTea.MiniLocalization;
74
+
75
+ var title = LocalizationManager.GetText("Home_Title");
76
+ ```
77
+
78
+ ## Forbidden AI Assumptions
79
+
80
+ Do not generate code that assumes:
81
+
82
+ - auto-bootstrap of language selection,
83
+ - runtime-generated key constants,
84
+ - null return on missing translation,
85
+ - one single synchronous update when online or addressable sources are involved,
86
+ - CSV files are read at runtime,
87
+ - fetch type is a boolean — it is a `FetchType` enum.
88
+
89
+ ## Recommended AI Output Style
90
+
91
+ When generating new code with this package:
92
+
93
+ - mention manager and settings setup,
94
+ - call out `ChooseLanguage(...)`,
95
+ - prefer generated constants when they are part of the project workflow,
96
+ - 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:
package/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@ All notable changes to this package will be documented in this file.
3
3
 
4
4
  The format is based on (https://www.npmjs.com/org/xmobitea)
5
5
 
6
+ ## [Unreleased]
7
+ ### Added
8
+ - `FetchType` enum (`None`, `FromGoogleSheets`, `FromCsv`) replacing the previous boolean `usingFetch*` fields in `LocalizationSettings`.
9
+ - `CsvParser` editor utility for parsing RFC-4180 CSV files, including quoted fields and embedded newlines.
10
+ - `csvFetchLocalLocalization` and `csvFetchOnlineLocalization` `TextAsset` fields in `LocalizationSettings` for CSV-based fetch.
11
+ - CSV fetch path in `LocalizationManagerEditor`: reads an assigned CSV asset, converts each language column to XML, and writes output to `Local-LocalizationFile/` or `Online-LocalizationFile/`.
12
+
6
13
  ## [1.0.0] - 2021-04-08
7
14
  ### Added
8
15
  - This is the first release of Chang X plugins, as a Package
@@ -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
+ }