Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,11 @@ jobs:
- unityVersion: 6000.5.11f1
targetPlatform: StandaloneOSX
samplePackages: '{"com.unity.ugui": "2.0.0"}'
# This build also compiles the samples for Addressables, Unity Localization and VContainer. R3 stays out, since
# it needs DLLs from NuGet in Assets.
- unityVersion: 6000.5.11f1
targetPlatform: StandaloneLinux64
samplePackages: '{"com.unity.ugui": "2.0.0"}'
samplePackages: '{"com.unity.ugui": "2.0.0", "com.unity.addressables": "2.9.1", "com.unity.localization": "1.5.13", "jp.hadashikick.vcontainer": "https://github.com/hadashiA/VContainer.git?path=VContainer/Assets/VContainer#1.18.0"}'
steps:
- name: Free disk space for the Unity image
uses: jlumbroso/free-disk-space@ceedf095f4ec1a097402bc6bd80831f2e1a6fde6 # v2.0.0
Expand Down
51 changes: 28 additions & 23 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,35 @@
10. An LLM handoff: missing or outdated entries copied as a text bundle with rules for the model, its plural forms included, and the model's reply imported from the clipboard, with no API keys.
11. Pseudo-localization: a `Pseudo` language made from the source text, accented, a third longer and bracketed, so hardcoded text, truncation and overflow show before any translation exists. The editor previews it, Play Mode and development builds list it, and release builds never ship it.
12. Character sets, `Tools/ReactiveLocalizer/Character Sets`: every character a language can show, the fallback text of missing entries and number symbols included, copied or saved for TextMeshPro's Font Asset Creator.
13. Bindings that update text on every language switch without allocating, and messages that are formatted again only when their arguments change.
14. Live reload: saving a table file updates bound text at once, in Play Mode and Edit Mode.
15. A Scene view overlay that picks the preview language. Components preview their text live in Edit Mode through `EditModePreview`, and scenes and prefabs always save the text they were authored with.
16. Rename, move and delete entries across every language at once. Moved entries keep working through `@formerly Table.Entry`, at runtime and in generated code.
17. Tables loaded on demand: `@loading OnDemand` tables stay in memory only while bindings or `HoldTable` handles use them, and follow every language switch while they do.
18. Streaming delivery: `@delivery Streaming` tables ship in StreamingAssets and load without blocking the main thread, through UnityWebRequest on Android and WebGL.
19. Fan translations and mods: `FolderTableSource` compiles `.lang` files from a folder at runtime, `RegisterLanguage` adds new languages, and sources asked first patch shipped languages entry by entry.
20. Plural rules and number formats for every CLDR 48 locale, built in without `CultureInfo`, with digits and separators each language can override.
21. Fallback languages loaded only for tables a translation leaves gaps in, so a complete translation costs no extra memory.
22. `ReactiveText` for view models and data binding, and `BindCharacters` for text that updates without allocating, such as TextMeshPro's.
23. Import checks that report problems at their line and column, check every translation against its source text and the plural forms of its language, and suggest the key an orphan most likely meant.
24. *Show source change* finds the source text an outdated translation was made from in git, and marks the words that changed.
25. One canonical layout for table files, written by every tool, with keys in natural order, such as `Line9` before `Line10`.
26. Creating a catalog asks for its source language and culture, and checks the culture against CLDR.
27. Lock-free lookups from any thread, with language switches applied all at once on the main thread, and rapid switches sharing what they load.
28. Missing keys shown as `[Table.Key]` in every build and reported once, and failures that never throw into game code.
29. Unity integration: `UnityHost`, tables packed into builds, the editor reading table files directly, and `EntryReference` and `CatalogReference` Inspector fields.
30. Quick Start and TextMeshPro samples, the TextMeshPro component previewing its text in Edit Mode.
13. Samples for every common integration: TextMeshPro with a font per language and a check of which characters each font can't draw, UI Toolkit with a UXML data binding on Unity 6 and code binding on 2022.3, uGUI Text, Addressables, VContainer, R3, Google Sheets two-way sync, mods, a localized editor tool, and a migration from Unity Localization.
14. Migration from Unity Localization: locales become languages and string tables become .lang files, with keys, comments and plain placeholders kept, and a report of what needs converting by hand.
15. `@delivery Custom` tables that builds don't pack, for a project's own source such as Addressables, and sources that answer asynchronously can decline a request so the next source is asked with nothing reported.
16. Bindings that update text on every language switch without allocating, and messages that are formatted again only when their arguments change.
17. Live reload: saving a table file updates bound text at once, in Play Mode and Edit Mode.
18. A Scene view overlay that picks the preview language. Components preview their text live in Edit Mode through `EditModePreview`, and scenes and prefabs always save the text they were authored with.
19. Rename, move and delete entries across every language at once. Moved entries keep working through `@formerly Table.Entry`, at runtime and in generated code.
20. Tables loaded on demand: `@loading OnDemand` tables stay in memory only while bindings or `HoldTable` handles use them, and follow every language switch while they do.
21. Streaming delivery: `@delivery Streaming` tables ship in StreamingAssets and load without blocking the main thread, through UnityWebRequest on Android and WebGL.
22. Fan translations and mods: `FolderTableSource` compiles `.lang` files from a folder at runtime, `RegisterLanguage` adds new languages, and sources asked first patch shipped languages entry by entry.
23. Plural rules and number formats for every CLDR 48 locale, built in without `CultureInfo`, with digits and separators each language can override.
24. Fallback languages loaded only for tables a translation leaves gaps in, so a complete translation costs no extra memory.
25. `ReactiveText` for view models and data binding, and `BindCharacters` for text that updates without allocating, such as TextMeshPro's.
26. Import checks that report problems at their line and column, check every translation against its source text and the plural forms of its language, and suggest the key an orphan most likely meant.
27. *Show source change* finds the source text an outdated translation was made from in git, and marks the words that changed.
28. One canonical layout for table files, written by every tool, with keys in natural order, such as `Line9` before `Line10`.
29. Creating a catalog asks for its source language and culture, and checks the culture against CLDR.
30. Lock-free lookups from any thread, with language switches applied all at once on the main thread, and rapid switches sharing what they load.
31. Missing keys shown as `[Table.Key]` in every build and reported once, and failures that never throw into game code.
32. Unity integration: `UnityHost`, tables packed into builds, the editor reading table files directly, and `EntryReference` and `CatalogReference` Inspector fields.
33. Public helpers for project tools: `NameRules`, `MessageText`, `TranslationExchange` and `CharacterSets`.

### Known issues

1. The remaining samples, the TextMeshPro font coverage check and Google Sheets sync among them, aren't available yet.
2. Undo keeps whole copies of the table files each edit changes, so many edits of one very large table take a lot of memory.
3. Spreadsheet applications take CSV texts starting with = for formulas, so exchange XLSX with spreadsheets; formula errors in an imported CSV are rejected.
4. XLIFF 1.2 can't hold control characters, so entries with one are left out of it and reported; XLIFF 2.0 and XLSX keep them.
5. Tables delivered by custom sources, such as Addressables, have no pseudo version, so pseudo-localization shows their source text.
1. The README and the guides in Documentation/ aren't written yet.
2. Tables delivered by custom sources, such as Addressables, have no pseudo version, so pseudo-localization shows their source text.
3. Undo keeps whole copies of the table files each edit changes, so many edits of one very large table take a lot of memory.
4. Spreadsheet applications take CSV texts starting with = for formulas, so exchange XLSX with spreadsheets; formula errors in an imported CSV are rejected.
5. XLIFF 1.2 can't hold control characters, so entries with one are left out of it and reported; XLIFF 2.0 and XLSX keep them.
6. Character sets can't know text that arguments bring at runtime, such as player names; the window adds printable ASCII or extra characters for it.
7. The Google Sheets sample was checked offline only, since a live check needs a Google account; its requests follow Apps Script's documented web app behavior.
8. The migration converts Smart Format's plain placeholders only; its plural, choose and other formatters are kept as written and marked for converting by hand.
32 changes: 18 additions & 14 deletions Concept/ReactiveLocalizer (design specification).md
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@ Purchase [3fa2c1] = Купить
1. **In the editor,** tables are read straight from the imported assets. There is no build step, and they're always current.
2. **`Embedded` (default):** a build step packs the table inside the build, readable synchronously on every platform.
3. **`Streaming`:** a build step packs the table into StreamingAssets, read on demand by `StreamingTableSource`, which keeps huge content out of memory and, on WebGL, out of the initial download. Files are read on a thread pool thread where StreamingAssets is a folder, and through UnityWebRequest where it's a URL (Android, WebGL), which needs the built-in Unity Web Request module. A list of the packed files sits next to the catalog, so a table a language doesn't have is never requested.
4. **Anything else** (Addressables, a CDN) plugs into the same table-source interface. A source can attach warnings to what it delivers, which reach the host's diagnostics.
4. **`Custom`:** builds don't pack the table, since a table source of the project delivers it, such as the Addressables sample's. The editor still reads its files directly.
5. **Anything else** (Addressables, a CDN) plugs into the same table-source interface. A source can attach warnings to what it delivers, which reach the host's diagnostics. A source that learns only later that it doesn't have a table, as an asynchronous lookup does, declines the request, and the next source is asked with nothing reported.
7. **Mods and downloaded translations** come from `FolderTableSource`: `<Table>.<Language>.lang` files anywhere under a folder, compiled when they're asked for, with their problems reported by file and line. The language sections of the folder's `.catalog` files define new languages, which `localizer.RegisterLanguage(...)` adds at runtime, before or after initialization.

## 9. Reactive layer
Expand Down Expand Up @@ -467,21 +468,22 @@ Each sample is a thin adapter with its own folder and asmdef. Code that integrat

| Sample | Contents |
|---|---|
| Quick Start | The `GlobalLocalizer` access point, which a DI setup can also hand its own instance; saving the chosen language; a template for mapping the OS language; a language picker; a demo scene |
| TextMeshPro | A component with the zero-allocation path, a *Localize* context-menu action that turns the current text into an entry and wires the component, fonts per language, the font coverage check |
| uGUI Text | A component for legacy `Text` |
| UI Toolkit | A data binding declared in UXML (Unity 6), plus a code-only binding for 2022.3 |
| Addressables | `AddressablesTableSource` |
| VContainer | An installer with async initialization |
| R3 | Observables built from `ReactiveText` and bindings |
| Google Sheets | Two-way sync through Apps Script |
| Quick Start | The `GlobalLocalizer` access point, which a DI setup can also hand its own instance, and which other samples extend with table sources and runtime languages; saving the chosen language; a template for mapping the OS language; a language picker; `LocalizedBehaviour`, the base of the UI components; a demo scene |
| TextMeshPro | `LocalizedTextMeshPro` with the zero-allocation path; `LanguageFonts`, a font per language that the Edit Mode preview switches too; the font coverage check in its Inspector; a *Localize* context-menu action that turns the current text into an entry and wires the component |
| UI Toolkit | `LocalizedTextBinding`, a data binding declared in UXML or UI Builder (Unity 6); `BindText` from code and `LocalizedDocument`, which binds a UIDocument's elements by name, on every version |
| uGUI Text | `LocalizedLegacyText` for legacy `Text`, with its own *Localize* action |
| Addressables | `AddressablesTableSource` for `@delivery Custom` tables, and a menu that makes their files Addressables at the addresses it loads |
| VContainer | `LocalizerInstaller`, which registers a catalog's localizer and initializes it asynchronously as the container starts |
| R3 | `LocalizerObservables`: text, messages fed by other observables, the language, and `ReactiveText` as observables |
| Google Sheets | Two-way sync through Apps Script, with a README that sets it up |
| Editor Tool | An `EditorWindow` localized with its own catalog, for tool authors |
| Mods | Fan translations read from a mods folder through the core's `FolderTableSource`, with their languages registered at runtime |
| Unity Localization Migration | Converts locales to languages and string table collections to `.lang` files, converts simple Smart Format placeholders to ICU, and flags complex ones for review |
| Mods | `ModTranslations`: fan translations read from a mods folder through the core's `FolderTableSource`, with their languages registered at runtime, and an example mod |
| Unity Localization Migration | Converts locales to languages and string table collections to `.lang` files, converts simple placeholders to ICU, and flags the rest for review |

1. **UniTask needs no sample:** it can already await `Task` directly.
2. **UI samples find the localizer through Quick Start's access point,** so import Quick Start first.
3. **In Edit Mode,** the access point returns the editor's preview instance, so components show the preview language live.
3. **In Edit Mode,** the access point returns the editor's preview instance, so components show the preview language live. Scenes and prefabs still save the text and font they were authored with.
4. **The migration keeps meaning, not syntax:** a numbered placeholder such as `{0}`, which projects fill with `string.Format`, becomes a named argument such as `{arg0}`, listed for renaming. Smart Format's plural and choose formatters and nested selectors stay as written, marked with a `MIGRATE` comment. Names become PascalCase, and the catalog is named `Localization`.

## 16. Naming reference

Expand All @@ -500,7 +502,9 @@ Each sample is a thin adapter with its own folder and asmdef. Code that integrat
| Runtime languages | `RegisterLanguage` |
| Table sources | `EmbeddedTableSource`, `StreamingTableSource`, `FolderTableSource`, and in samples `AddressablesTableSource` |
| Inspector fields | `EntryReference`, `CatalogReference`, `[EntryCatalog(typeof(...))]` |
| Editor authoring API | `EntryAuthoring`, `CharacterSets` |
| Editor authoring API | `EntryAuthoring`, `CharacterSets`, `TranslationExchange` |
| Message text for tools | `MessageText`, `NameRules` |
| Sample components | `LocalizedBehaviour`, `LocalizedTextMeshPro`, `LocalizedLegacyText`, `LocalizedTextBinding`, `LocalizedDocument` |
| Developer tools (authoring) | `PseudoLocalization`, `CharacterSet` |
| Exchange (authoring) | `ExportBook`, `CsvExchange`, `XlsxExchange`, `XliffExchange`, `LlmHandoff`, `ImportedFile`, `ImportPlanner`, `ImportPlan` |
| Generated code | `LocalizationKeys`, `LocalizationLanguages` |
Expand All @@ -517,7 +521,7 @@ Each sample is a thin adapter with its own folder and asmdef. Code that integrat
2. **Unity lane,** mirroring Wireframes:
1. GameCI tests in Edit and Play Mode on the oldest (2022.3) and newest Unity;
2. compiler warnings from the package fail the run;
3. player builds for WebGL, Android, iOS, macOS and Linux catch problems that only show up in IL2CPP builds or after code stripping.
3. player builds for WebGL, Android, iOS, macOS and Linux catch problems that only show up in IL2CPP builds or after code stripping. The Linux build also compiles the samples for Addressables, Unity Localization and VContainer.
3. **What the tests cover:**
1. reading then writing a file gives byte-identical output, and randomized input (escapes, invisible characters, merge markers) never crashes the parser and always reports its position;
2. plural tests are generated from CLDR's sample numbers for every plural form of every language;
Expand Down
41 changes: 41 additions & 0 deletions UnityPackage/Authoring/MessageText.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using reromanlee.ReactiveLocalizer.Messages;

namespace reromanlee.ReactiveLocalizer.Authoring
{
/// <summary>
/// ICU message text for tools that write table files themselves, such as a migration from another localization
/// system: plain text quoted so it shows as it is, and messages checked the way imports check them.
/// </summary>
public static class MessageText
{
/// <summary>
/// Returns a message that shows <paramref name="text"/> exactly: braces, and apostrophes next to them, go in
/// quotes, so <c>Press {0}</c> stays text instead of becoming an argument.
/// </summary>
public static string Quote(string text) => MessageQuoting.Quote(text);

/// <summary>
/// Returns message text that shows <paramref name="text"/> exactly where a message continues after it, such as
/// before an argument: a final apostrophe is doubled too, so it can't join the brace that follows.
/// </summary>
public static string Quote(string text, bool isFollowedByArgument) => MessageQuoting.Quote(text, false, isFollowedByArgument);

/// <summary>Returns whether <paramref name="message"/> is a valid ICU message, and the first error when it isn't.</summary>
/// <param name="message">The message, with its escapes resolved.</param>
/// <param name="problem">The first error and the index it's at; null for a valid message.</param>
public static bool IsValid(string message, out string problem)
{
ParsedMessage parsed = MessageParser.Parse(message);
for (int i = 0; i < parsed.Issues.Count; i++)
{
if (parsed.Issues[i].Severity == Documents.IssueSeverity.Error)
{
problem = $"at {parsed.Issues[i].Index}: {parsed.Issues[i].Message}";
return false;
}
}
problem = null;
return true;
}
}
}
11 changes: 11 additions & 0 deletions UnityPackage/Authoring/MessageText.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading