Repository navigation
tooling: scaffold and re-sync helper for plugin language packs #20508
Description
Problem or use case
In #9207, #10592 and #11577 the maintainers pointed community translators at plugin language packs (contributes.languagePacks) instead of new built-in locales, and named what is still missing on the authoring side:
there's no authoring guide, no scaffolding to generate a starter catalog from
en.json, no re-sync story when English strings change, and the glossary/repair tooling we use for the built-in locales isn't exposed to pack authors.
Community pack authors are currently rediscovering the runtime rules the hard way (see #17913 and the Russian pack threads in #9207): a single key under the protected auto.components.settings.plugin* prefix rejects the whole catalog, two English values exceed the 8192-character limit, dotted keys are refused, placeholder drift is only caught by the repo's verify-localization-catalog.mjs --plugin-catalog, and every author writes their own script to diff their catalog against the en.json of the Orca version they target.
Proposed solution
Add a zero-dependency contributor script, config/scripts/scaffold-language-pack.mjs, plus a docs page:
init: write a validorca-plugin.json(manifest v1,pluginApi: 1,engines.orcafloor, endonym asname) and an emptylocales/<locale>.json.status [--check] [--fix]: compare the pack catalog withen.json(or--source <path>for a tagged catalog): coverage, missing, retired, protected, oversize, placeholder mismatches, catalog limits.--checkexits 1 on anything the host would reject;--fixprunes retired/protected/oversize entries deterministically.export-missing --out <file>: nested JSON of the English source for keys still to translate (protected, oversize and policy-preserved English values excluded).merge --from <file> [--overwrite]: fold translated values back in, rejecting placeholder mismatches and copied English, never overwriting existing work unless asked.
The script reuses collectStringLeaves / shouldPreserveEnglishValue from locale-translation-policy.mjs and the interpolation check from verify-localization-catalog.mjs. Tests pin the duplicated protected-chrome allowlist and catalog limits against the TypeScript sources so they cannot rot, and prove the generated catalog passes parsePluginLanguagePackArtifact.
No machine translation is included, so the contract in localization-package-contract.test.mjs is unaffected.
I have a PR in progress for this.
Activity
AmethystLiang commentedon Sep 15, 2026
thanks for reporting. looking into it
taranttoola-create commentedon Sep 26, 2026
Field report from a pack author — every item in the list is real. Numbers below are from a re-sync against the 14,733-key
en.jsonof 1.4.212 (pack:taranttoola-create.russian, 14,551 entries, submitted in stablyai/orca-plugins#9).Protected prefix: 180 keys, and the failure is invisible to the user. Confirmed that one protected key rejects the whole catalog. We path-filter exactly 180
auto.components.settings.plugin*keys — those are the only 180 of 14,733 we cannot carry. Worth stressing that the host reports nothing: the language silently turns off, with no dialog naming the offending key.Oversize values are real, and they aren't translations. In 1.4.212 exactly two English values exceed 8192 chars, both raw CSS blobs shipped as catalog strings:
auto.components.feature.wall.review.animated.visual.notes.styles.db6691aa0a— 9,691 charsauto.components.feature.wall.review.animated.visual.ship.styles.90cdcd2ecc— 9,965 charsSo a pack that starts from
en.jsonand translates it still fails to load until those two are dropped, and nothing says which two. Deterministic--fixpruning is the right shape.Placeholder drift bit us in strings we had already shipped. Two entries had silently lost placeholders in an earlier sync:
SidebarFeedbackImageAttachments.screenshotsHintdropped both{{count}}and{{maxSize}}, andcomponents.native-chat.structuredSessionLaunchFailedlost{{value0}}. The host accepts both; they render wrong rather than failing. Astatus --checkdiffing interpolation against the English source would have caught them at sync time — in my experience this is the highest-value part of the proposal.One trap missing from the list, and it costs installs rather than translations. Since 1.4.196 the host refuses any plugin whose id starts with
orca-unless it resolves to thestablyaiorg (reserved plugin identity … must resolve to the stablyai organization). That is a manifest rule, so it fails before the catalog is parsed, and the UI renders it as the generic "Plugin installation failed. Check the source and try again." Authors naming the pluginorca-russian— a very natural choice — lose real time to it. Ifinitwrites the manifest, it should reject or warn on that prefix.One limit tooling can't fix, for expectation-setting. Russian needs four plural forms; only
_one/_otherare reachable host-side because the call sites pick the suffix in JS (#12106). Our 19 plural families are filled correctly and still read "5 запусков" for 2. Reporting plural families as a coverage gap would send Russian, Polish, Czech and Arabic authors chasing a number they cannot move — informational, not a deficit, seems right.Happy to run
scaffold-language-pack.mjs status --checkagainst a real 14.5k-entry pack on 1.4.212 and paste the output before merge, if that helps.