Skip to content

Add i18n tools to support Crowdin translation sync - #125

Merged
microbit-matt-hillsdon merged 16 commits into
mainfrom
i18n-tools
Sep 7, 2026
Merged

Add i18n tools to support Crowdin translation sync#125
microbit-matt-hillsdon merged 16 commits into
mainfrom
i18n-tools

Conversation

@microbit-matt-hillsdon

@microbit-matt-hillsdon microbit-matt-hillsdon commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

This should significantly improve diffs for new strings by only updating the English source file while keeping the English backfill at build/dev time and stop us pulling the whole zip from Crowdin (which is slow to build, N x M languages and files, including many that aren't ready to be shipped) in order to sync a few files.

Used in this repo too.

Fixes #124

A CLI and config-file replacement for the translation scripts copied
between the micro:bit apps and this repo. It tidies and compiles
react-intl catalogs and talks to Crowdin one file at a time:

- tidy: sort, prune, and check translations keep their placeholders,
  now by parsing the ICU message rather than a regex that skipped plurals
- compile: formatjs AST output with English backfill and package
  catalogs merged, byte-for-byte what `formatjs compile --ast` produced
- download: per-file, per-language translation builds, no project zip
- upload: replace English sources after showing the change, optionally
  keeping translations for corrections
- status: per-language progress for the repo's files
- new-strings: ml-trainer's word counter for translation estimates

Translated catalogs hold only real translations; English is backfilled at
compile time so diffs show translation changes alone.

Ships TypeScript run directly by Node 24's type stripping, hence the
erasable-syntax-only tsconfig and explicit .ts imports.
Replaces bin/i18n-packages.cjs, tidy-lang.cjs and update-translations.cjs
with an i18n.config.mjs driving the shared tool. CI now checks the
catalogs are tidy.

The translated catalogs are tidied into their new sparse form: only the
strings each locale actually has, without descriptions, and en-US holding
only what differs from en (nothing, for these packages). Apps backfill
from English when they compile.
Uploading on merge to main gets new strings to translators without a
manual step; the weekly download opens or refreshes a pull request so
translations are in the packages before the next release.

Both need the org Crowdin token granted to this repo, and the download
needs the repository setting allowing Actions to create pull requests.
Crowdin's lol locale replaces every string with a marker, so comparing its
arguments and tags against English only ever reports noise.
A translation missing {name} or <link>, or with an apostrophe escaping
one, shows users literal markup. English is the safer rendering, so the
download leaves such strings out and names them for fixing in Crowdin
rather than failing.
data-microbit-org's playground survey has a Welsh translation but no file
in Crowdin, so a catalog can now list its own languages, or none to mark
it as compiled-only.
pxt-microbit-ml keeps its Node project in simx/ but its translated files
across the repo, so the CLI runs from simx with --config ../i18n.config.mjs
and paths must be relative to the config, not the working directory.
data-microbit-org's playground survey is in Crowdin with translation
disabled, which the option covers as well as a file that is not there.
Keep the push-to-main trigger out until a dry run from the Actions tab has
shown the upload does the right thing; the trigger to restore is noted in
the workflow.
{lang} now expands to Crowdin's casing (ui.pt-BR.json) so file names match
the ids the apps and Crowdin already use, with {lang:lower} for the stubs
and pyright repos, which keep lowercase. Compile still reads lowercase
package catalogs so apps work against ui packages published before the
rename.
ui.pt-br.json becomes ui.pt-BR.json and so on, matching the ids the apps
pass around and Crowdin's own. Apps compiling against a version of these
packages published with lowercase names keep working: the tool falls back
to them.
Finding a file used to list the whole branch recursively, thousands of
entries once the website content is counted. Now each directory in the
path is resolved with a name-filtered listing of its parent, and the file
with one of its directory, cached per run.

The client retries three times on 429 and 5xx responses, and directory
builds are polled with a delay doubling from one second to ten.
All eight repos share one Crowdin token; starting them ten minutes apart
keeps them off it at the same moment.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 5, 2026

Copy link
Copy Markdown

Deploying ui with  Cloudflare Pages  Cloudflare Pages

Latest commit: 0e68095
Status: ✅  Deploy successful!
Preview URL: https://e1ded263.ui-2wg.pages.dev
Branch Preview URL: https://i18n-tools.ui-2wg.pages.dev

View logs

Running the TypeScript sources directly only worked because the workspace
and npm-link consumers reach them through symlinks, which Node resolves to
real paths. An installed copy fails at the first import with
ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING.

tsc emits dist/ on prepare, so npm install in this repo and npm publish
both build it, with .ts import specifiers rewritten to .js so the sources
still run under vitest as they are. bin and exports point at dist.
download and status reported a catalog not yet in Crowdin by aborting the
whole run, so the carousel, uploaded to Crowdin only this week, stopped
ui-patterns downloading. Each is now one counted failure.

upload treated crowdinFile as a bare name, but this repo's are
ui/ui.en.json and so on within packages/. It now takes the full Crowdin
path, creating any missing directories, which the dry run names.

Also: a catalog's own languages list can add languages the config does not
have, not only narrow it, and --language accepts the union; the directory
build poll throws on timeout instead of downloading an unfinished build;
new-strings reads the base file relative to the config directory; the CLI
rejects flags that do not belong to the command.

The README also documents the microbit-i18n GitHub App the download
workflows use.
Pushes and pull requests made with GITHUB_TOKEN never start workflows, so
the weekly sync pull request got no CI. A token from the microbit-i18n
GitHub App, narrowed to contents and pull requests, does. Files are staged
before the change check so a newly added language's file counts as a
change; it did not before.
@microbit-matt-hillsdon

Copy link
Copy Markdown
Contributor Author

Merging to help test this but more validation will be done before it's used in anger.

@microbit-matt-hillsdon
microbit-matt-hillsdon merged commit e52946a into main Sep 7, 2026
3 checks passed
@microbit-matt-hillsdon
microbit-matt-hillsdon deleted the i18n-tools branch September 7, 2026 12:07
@microbit-matt-hillsdon

Copy link
Copy Markdown
Contributor Author

(The token/app aren't yet configured so nothing automatic is going to happen as yet)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Check i18n:compile is clean in the app builds

1 participant