Skip to content

tooling: scaffold and re-sync helper for plugin language packs #20508

Description

@berkaysirtas

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 valid orca-plugin.json (manifest v1, pluginApi: 1, engines.orca floor, endonym as name) and an empty locales/<locale>.json.
  • status [--check] [--fix]: compare the pack catalog with en.json (or --source <path> for a tagged catalog): coverage, missing, retired, protected, oversize, placeholder mismatches, catalog limits. --check exits 1 on anything the host would reject; --fix prunes 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

  1. AmethystLiang commented on Sep 15, 2026

    @AmethystLiang
    Contributor

    thanks for reporting. looking into it

  2. added theissue type on Sep 19, 2026
  3. taranttoola-create commented on Sep 26, 2026

    @taranttoola-create

    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.json of 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 chars
    • auto.components.feature.wall.review.animated.visual.ship.styles.90cdcd2ecc — 9,965 chars

    So a pack that starts from en.json and translates it still fails to load until those two are dropped, and nothing says which two. Deterministic --fix pruning 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.screenshotsHint dropped both {{count}} and {{maxSize}}, and components.native-chat.structuredSessionLaunchFailed lost {{value0}}. The host accepts both; they render wrong rather than failing. A status --check diffing 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 the stablyai org (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 plugin orca-russian — a very natural choice — lose real time to it. If init writes 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/_other are 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 --check against a real 14.5k-entry pack on 1.4.212 and paste the output before merge, if that helps.

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

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationenhancementNew feature or request

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions