ci: build and test the localization store on every push - #18
Merged
Conversation
This repo had no CI. Nothing validated keys/*.yaml until the release build
ran `npm run gen` and wrote into the app repos, so a malformed key -- the
common failure when a translation lands -- surfaced far from where it was
introduced.
The workflow does what the repo already does, on a runner:
npm ci install package-lock.json exactly
npm run gen load + validate all 1,224 keys/*.yaml through
gen/store.mjs validate(), then run all four
emitters (android strings.xml, apple
Localizable.xcstrings, windows Resources.resw,
linux .po/.pot/LINGUAS) and assert each wrote
npm test `node gen/generate.mjs --check` against that same
tree: regenerate and require byte equality, which
gates the codegen being deterministic
node -e ...index.js the package's public API -- the platform filter,
alias resolution, every value a string
URNETWORK_ROOT points at a scratch directory rather than its default (`..`,
the sibling app checkouts, which a runner does not have). That keeps the
check inside this repo: `npm test` against the real siblings would make
every PR here a function of four other repos' latest commits, the trade-off
server/test.yml documents where it has no choice. Opting in is four sparse
clones and one env var; left to the maintainers.
No release, no signing, no secrets: package.json's release:* scripts run
`npm publish` and are not reachable from here. gen/verify-lossless.mjs is
also left out -- it requires a --baseline snapshot of the pre-migration app
tree that is not in this repo, and exits 2 without one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MAXFxG1EK4jTxQ1iW73BUr
Contributor
Author
|
Green on the first run: 32549996274, 8 seconds on 78 = 19 android One annotation, fleet-wide rather than specific to this file: |
Ryanmello07
marked this pull request as ready for review
August 22, 2026 05:32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
.github/workflows/build-and-test.yml— this repo's first CI. One job onubuntu-latest, ~1-2 minutes, no secrets.Nothing validates
keys/*.yamltoday. The first thing that runs the store is the release build (build/all/run.sh→npm run gen), which regenerates and writes into../android,../apple,../windows,../linux— so a malformed key surfaces long after, and somewhere else. This runs the same code on every push and PR.npm cipackage-lock.jsoninstalls exactly, and has not drifted frompackage.jsonnpm run gengen/store.mjsvalidate()over all 1,224 keys — snake_case ids, requiredsource/description/en, known locales, one xcstrings key persourceon apple, every declared placeholder present in every localization, exactly the CLDR plural categories a locale has, product names never translated — then all four emitters run (androidstrings.xml, appleLocalizable.xcstrings, windowsResources.resw, linux.po/.pot/LINGUAS), and the step asserts a representative output of each was written non-emptynpm testnode gen/generate.mjs --check— regenerate over the tree the previous step wrote and require byte equality, i.e. the codegen is deterministic (sort order, object key order,Intl.Collator)node … index.jsloadAllKeys()vs{ platform: null }), alias resolution (continue→continue_txt), and the promise that every value is a string (a plural key collapses to itsotherform)Triggers:
pushandpull_requestonmain, plusworkflow_dispatch.permissions: contents: read,timeout-minutes: 10, and acancel-in-progressconcurrency group.What it deliberately does not do
release:patch/release:minor/release:majorrunnpm publishand need an npm token; nothing here reaches them.npm packis not run either, soprepublishOnlynever fires.gen/verify-lossless.mjs. It requires--baseline <dir>, a snapshot of the pre-migration app tree that is not in this repo; run bare — which is all theverify-losslessscript does — it exits 2. Adding it would mean committing or fetching that snapshot.The one thing to decide
npm testisnode gen/generate.mjs --check, and by default it compares the generated files against$URNETWORK_ROOT/{android,apple,windows,linux}/…, i.e. four other repositories' working trees. That is the README's "CI drift gate", and a runner has no siblings, so a barenpm teston a fresh checkout fails 100% of the time (every file reads as drift).This workflow points
URNETWORK_ROOTat a scratch directory, sonpm testgates on the codegen being complete and deterministic and the check stays inside this repo. The reason is the trade-offserver/test.ymlalready writes down where it has no choice: "Tracking each sibling's default branch would make this repo's CI a function of six other repos' latest commits — one unrelated push there turns every open PR here red at once." Here we do have a choice, because the store validates standalone.If you want the cross-repo gate too, it is a second job — four blobless sparse checkouts (only
android/app/app/src/main/res/values*/strings.xml,apple/app/network/Shared/Resources/Localizable.xcstrings,windows/app/src/App/Strings/,linux/app/po/) beside the checkout, then plainnpm testwith the defaultURNETWORK_ROOT. Worth knowing before you say yes: it goes red whenever an app repo lags a store change, which is a normal state between a translation landing here and the next release build regenerating the app trees — so it wants to be non-blocking, or the app repos want a bot that regenerates on push. Say the word and I will add it in the shape you prefer.Two smaller calls, both one-liners:
24, matching the release pipeline'snode v24.14.1gate andurnetwork/extension's CI.package.jsondeclares noengines, andjs-yamlis the only dependency, so nothing narrower is implied.web/build.ymlusesnode-version: latest; if you would rather this repo float with it, that is one line.sitekeys are generated by nothing. 213 keys declareplatforms: [site], butgen/generate.mjsonly hasandroid,apple,windows,linuxtargets, so those keys are validated here but never emitted. Not a CI question — flagging it because the audit fell out of this.Verification
python3 -c "import yaml; yaml.safe_load(...)"andactionlint(with shellcheck) both clean; every npm script, export and output path referenced was checked against this checkout. There is nonodeon the machine this was written on, so the run on this PR is the first real execution — I will report the result here.