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
21 changes: 21 additions & 0 deletions .github/RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Release checklist

Continuous integration checks what a machine without a display or a device can: the pure assemblies under .NET, every test on the oldest and newest supported Unity, and player builds for WebGL, Android, iOS, macOS and Linux. The rest is checked by hand before each release, most important first.

### Before merging the release pull request

1. Every check of the pull request passes: the fast lane, the tests on both Unity versions, and every player build.
2. The Quick Start sample runs in Windows, Android and WebGL builds: every language switch shows at once, the saved language comes back after a restart, Pirate's missing hint falls back to English, and a development build lists Pseudo while a release build doesn't.
3. The timings in the test results artifacts are no worse than the previous release's, and when the runtime changed, the IL2CPP numbers in `Documentation/PERFORMANCE.md` are measured again on the same machine.
4. In the Editor, on both Unity versions, the table window adds, edits, renames, moves and deletes entries, and Undo takes each back; the Inspector field searches and creates entries; the Preview Language overlay previews every language in Edit Mode while scenes and prefabs save their authored text; and Validate and the build gate report a broken file, an orphan and a missing key.
5. An exchange round trip works: XLSX through Excel or LibreOffice, CSV, XLIFF 2.0 through a translation tool, and an LLM handoff through a chat, each imported through the preview.
6. Every sample imports and compiles with its package on both Unity versions, and runs: TextMeshPro with fonts per language and the coverage check, UI Toolkit with the UXML binding on Unity 6, uGUI Text, Addressables loading a custom table in a build, VContainer starting the localizer, R3, Mods with the example mod in a build, the Editor Tool window, the Unity Localization migration, and the Google Sheets sync pushing to and pulling from a test spreadsheet.
7. A Profiler capture of an Android development build switching languages shows no allocations in steady frames and no new spikes.
8. *Write Agent Guide* writes a section that matches the release, into a project with and without `AGENTS.md`.
9. `package.json` has the new version, `CHANGELOG.md` has its section under the version instead of *Unreleased*, and the README and `Documentation/` describe the release; a release that changes the file format updates `Documentation/FORMAT.md`, and a major release adds a section to `Documentation/MIGRATION.md`.

### After merging

1. Tag the squashed commit on `main` with the version alone, such as `1.0.0`.
2. Export the signed tarball of the package from the Package Manager.
3. Draft the GitHub release from the tag, named like `v1.0.0 (signed)`, with the changelog section as its notes and the tarball attached.
Binary file added .github/table-window.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 59 additions & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ permissions:
contents: read
# The test runner reports its results as a check run.
checks: write
# The gate looks for the marker an earlier run left on the same files.
actions: read

# A Personal license needs UNITY_LICENSE, UNITY_EMAIL and UNITY_PASSWORD; a Pro license UNITY_SERIAL instead of the first.
env:
Expand All @@ -34,8 +36,38 @@ env:
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}

jobs:
# A squash merge makes a new commit, but its files are exactly the ones the pull request's last run tested, as long as
# main didn't move in between. A push whose file tree already passed skips the jobs below, and the run still succeeds;
# any other push, such as one combining two pull requests, runs them all.
gate:
name: Look for a run that passed on the same files
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
is-verified: ${{ steps.lookup.outputs.is-verified }}
steps:
- name: Look for the marker of a passed run
id: lookup
if: github.event_name == 'push'
env:
GH_TOKEN: ${{ github.token }}
TREE: ${{ github.event.head_commit.tree_id }}
run: |
if [ -z "$TREE" ]; then
exit 0
fi
# Only runs of this repository count, never runs of pull requests from forks.
query='[.artifacts[] | select((.expired | not) and .workflow_run.head_repository_id == .workflow_run.repository_id)] | first | .workflow_run.id // empty'
run=$(gh api "repos/$GITHUB_REPOSITORY/actions/artifacts?name=passed-files-$TREE" --jq "$query") || run=""
if [ -n "$run" ]; then
echo "::notice::Run $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$run already passed on these exact files, so this run skips its jobs."
echo "is-verified=true" >> "$GITHUB_OUTPUT"
fi

tests:
name: Tests on ${{ matrix.unityVersion }}
needs: gate
if: needs.gate.outputs.is-verified != 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
Expand Down Expand Up @@ -102,6 +134,8 @@ jobs:

builds:
name: ${{ matrix.targetPlatform }} build on ${{ matrix.unityVersion }}
needs: gate
if: needs.gate.outputs.is-verified != 'true'
runs-on: ubuntu-latest
timeout-minutes: 90
strategy:
Expand Down Expand Up @@ -173,3 +207,28 @@ jobs:
projectPath: UnityProject
buildMethod: reromanlee.ReactiveLocalizer.ContinuousIntegration.PlayerBuild.Build
versioning: None

# Leaves the marker the gate looks for, named after the tested file tree, once every job passed.
passed:
name: Mark the files as passed
needs: [tests, builds]
runs-on: ubuntu-latest
timeout-minutes: 5
# A missing marker only means main runs everything again, so it never fails the run.
continue-on-error: true
steps:
- name: Find the tree of the tested commit
id: tree
env:
GH_TOKEN: ${{ github.token }}
run: |
tree=$(gh api "repos/$GITHUB_REPOSITORY/git/commits/$GITHUB_SHA" --jq .tree.sha)
echo "$tree" > passed-files.txt
echo "tree=$tree" >> "$GITHUB_OUTPUT"

- name: Upload the marker
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: passed-files-${{ steps.tree.outputs.tree }}
path: passed-files.txt
retention-days: 30
63 changes: 31 additions & 32 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# ReactiveLocalizer (Unreleased)
# ReactiveLocalizer 1.0.0

### What's new

Expand All @@ -12,37 +12,36 @@
8. Exchange with translators: an XLSX workbook, CSV files or XLIFF 1.2 and 2.0 files, exported with a choice of tables, languages, and all entries or only missing or outdated ones. Keys are written `Shop.Purchase`, so renaming a file or sheet changes nothing.
9. Imports that change translations only, never keys: a preview lists every new, updated, outdated, confirmed and rejected text before anything is written, each compiled like the files, and the whole import is one step Undo can take back. The source text in the file becomes the fingerprint, so a translation of an older text arrives outdated.
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. 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`.
11. An agent guide, `Tools/ReactiveLocalizer/Write Agent Guide`: a section of the project's `AGENTS.md` or `CLAUDE.md` telling coding agents where the catalogs and generated keys are, how to add and rename entries, and what never to edit by hand, updated in place when written again.
12. 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.
13. 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.
14. 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.
15. 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.
16. `@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.
17. Bindings that update text on every language switch without allocating, and messages that are formatted again only when their arguments change.
18. Live reload: saving a table file updates bound text at once, in Play Mode and Edit Mode.
19. 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.
20. Rename, move and delete entries across every language at once. Moved entries keep working through `@formerly Table.Entry`, at runtime and in generated code.
21. 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.
22. Streaming delivery: `@delivery Streaming` tables ship in StreamingAssets and load without blocking the main thread, through UnityWebRequest on Android and WebGL.
23. 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.
24. Plural rules and number formats for every CLDR 48 locale, built in without `CultureInfo`, with digits and separators each language can override.
25. Fallback languages loaded only for tables a translation leaves gaps in, so a complete translation costs no extra memory.
26. `ReactiveText` for view models and data binding, and `BindCharacters` for text that updates without allocating, such as TextMeshPro's.
27. 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.
28. *Show source change* finds the source text an outdated translation was made from in git, and marks the words that changed.
29. One canonical layout for table files, written by every tool, with keys in natural order, such as `Line9` before `Line10`.
30. Creating a catalog asks for its source language and culture, and checks the culture against CLDR.
31. Lock-free lookups from any thread, with language switches applied all at once on the main thread, and rapid switches sharing what they load.
32. Missing keys shown as `[Table.Key]` in every build and reported once, and failures that never throw into game code.
33. Unity integration: `UnityHost`, tables packed into builds, the editor reading table files directly, and `EntryReference` and `CatalogReference` Inspector fields.
34. Guides in `Documentation/`: usage, every feature, the exact grammar of `.lang` and `.catalog` files, migrating from Unity Localization, and performance measured in the Editor and in an IL2CPP build by performance tests that run with every other test.
35. Public helpers for project tools: `NameRules`, `MessageText`, `TranslationExchange` and `CharacterSets`.

### Known issues

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.
1. Right-to-left text isn't shaped: a language's `Direction` is only a hint, and shaping comes from the text renderer, such as UI Toolkit's advanced text generator, or RTLTMPro for TextMeshPro.
2. Spreadsheet applications take CSV texts starting with `=`, `+`, `-` or `@` for formulas, so exchange XLSX with spreadsheets; formula errors in an imported CSV are rejected.
3. 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.
4. 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.
5. Fingerprints are six hexadecimal digits, so about one change of a source text in 16.7 million keeps its fingerprint and leaves its translations marked current.
Loading
Loading