跳到內容

Verification

本頁內容尚未翻譯。

Run the gates per locale, in this order. The localization checks come first because they are cheap and they catch the errors that a later full-suite run would bury under unrelated output.

Most of these accept --locale=<code>, which scopes the run to one tree. A scoped run is the right default for a translation lane: a full scan reports every locale’s existing debt alongside the new work. check-locales and check-locale-markers have no scope flag and always scan every authored locale.

Terminal window
# Localization safety
bun run check-locales # key parity, all authored locales
bun run check-locale-placeholders --locale=<code> # {placeholder} parity against en-US
bun run check-locale-lengths # Discord 45/100 code-point caps
bun run check-locale-markers # protocol keys, templates, collisions
bun run check-locale-links --locale=<code> # project routes and heading fragments
bun run find-stale-translations --locale=<code> # untranslated English strings
bun run check-intent-packs --locale=<code> --requests=<file.json> # natural requests reach tools
# Add --export to write the review list to scripts/maintenance/stale-translations.json.
# Repository gates
bun run check-seed-catalogs # i18n map shape and catalog invariants
bun run check # TypeScript strict
bun run lint:ci # Biome, no writes
bun run test # unit, isolated-unit, and DB lanes

Then run the docs build, which is a separate package:

Terminal window
cd apps/docs && bun run build

It runs astro build and then scripts/checkLlmsOutput.ts, which verifies that llms.txt and its siblings stay English-only and that hreflang matches the files on disk. A fallback route that advertises an alternate fails the build rather than warning.

GateFails onExit behavior
check-localesA key missing from every localeExit 1 is fatal; parity gaps in one locale are advisory exit 2
check-locale-placeholdersAn English placeholder absent from the translationExit 1; a placeholder the translation adds is an advisory warning
check-locale-lengthsModal titles or input labels over 45 code points, or command, option, choice, and placeholder text over 100Exit 1
check-locale-markersA protocol key absent from an authored locale, a template placeholder mismatch, a missing literal anchor, or two keys rendering the same titleExit 1
check-locale-linksA project-owned docs route or heading fragment that resolves to nothingExit 1
find-stale-translationsValues that are byte-identical to English, plus English-looking text in a non-Latin script, checked against each locale’s expected scriptExit 0 with a report; it exits 1 only for an unauthored locale, so it never passes vacuously
check-intent-packsA deliberate target or explicit_memory pack that is empty for the locale, fewer than three requests for a target, or a request that does not reach its expected toolsExit 1
check-seed-catalogsi18n map shape, persona uniqueness, unpaired sample dialogues, sprite validityExit 1
checkAny type error, including a pt-br key that is not a LocaleCodeExit 1
testBehavior regressionsExit 1

Two gates are known to report pre-existing debt that is not caused by a new locale:

  • check-locales reports the Japanese parity gap as an advisory exit 2. Nothing is missing everywhere, so the run still proves the required invariant.
  • check-locale-links --locale=ja reports two Japanese heading-fragment drifts, in src/locales/ja/providers.ts and src/locales/ja/commands/setup.ts. Japanese catch-up owns them.

Report a gate failure with its exact output rather than describing it. A gate that was not run is not a passed gate.

Passing every localization gate does not make bun run test pass, because several locale checks live only in the unit lane:

  • Panel line width. tests/unit/discord/panelProseWidth.test.ts holds every authored panel line to 65 characters, or 40 beside a thumbnail. check-locale-lengths measures Discord’s hard caps, not this layout budget, so a translation that runs longer than English passes one and fails the other. The static scan measures the bare string; the payload walks run once per authored locale and measure the rendered line, including a -# or > marker and the thumbnail budget. Shorten the wording. Add a line break only where the builder applies the marker per line (withLinePrefix), because a string rendered as `-# ${text}` loses the subtext style after its first line.
  • Fixtures that list the authored locales. The personal language picker test asserts every endonym in tests/unit/discord/personalConfigRoutes.test.ts, and tests/unit/db/personaNamingCatalog.test.ts asserts each persona’s namingConfig per language. Both are exact on purpose, so they fail when a locale lands. Add the new locale’s reviewed values instead of loosening the assertion.
  • Docs publication. tests/unit/docs/docsLocaleConfig.test.ts fails when a docsTree flag and the directories under docs/ disagree. The docs build also fails when the root landing page does not link to every published locale. Flipping docsTree is the change these checks expect, so they stay green only when the tree and the flag land together.

Boot the bot and confirm command registration succeeds.

An invalid Discord locale folder name fails registration for every command in every language, and nothing above catches it. The localization checks read the tree; only registration proves Discord accepted the key.

Boot also asserts the protocol-key map. Two protocol keys rendering to the same title in any loaded locale is a startup failure, on the same grounds as an invalid locale code: the runtime cannot recover from an ambiguous title, and startup is the only place the ambiguity is still visible.

A protocol key’s translation is frozen after that locale’s first release.

Editing a released protocol value orphans every marker embed already posted in that locale’s servers. sliceMessagesAtResetMarker reports no marker rather than an error, so the user’s /reset or /refresh quietly stops applying with no log line and no failing test.

The review checklist in UI Strings has to carry this, because the ship-unreviewed-then-review-later flow is exactly the sequence that triggers it. Review every protocol value before the locale’s first release, and after release treat the value as data rather than as prose.

A locale addition needs no migration. The descriptions JSONB columns already exist (migration 081), and the legacy per-language columns are gone (migration 082). Adding a locale’s descriptions is a seed-only change.

Run the migration gates only if the change touches schema or seed writers:

Terminal window
bun run check-migrations # numbering uniqueness and up/down pairing, working tree only
bun run check-seed-catalogs

check-migrations cannot see a colliding number on another branch, because Git does not conflict on two differently named files. A cross-branch scan is part of the procedure for any new migration.

The root bun run check does not cover apps/docs. Its routeData.ts and docsRouting.ts reach @astrojs/starlight and astro:content, which resolve only under apps/docs/node_modules, so the root tsconfig cannot include them. bun run build in apps/docs is the only type-level check those files get today, and it doubles as the hreflang and llms.txt verifier.

Adding astro check to the docs package is the open improvement. It needs @astrojs/check plus typescript as docs-only dev dependencies, and it is not installed yet, so do not claim a docs type gate that does not exist.

A completed locale lane reports:

  • The gate commands that ran, with their exit codes and any finding text verbatim.
  • Any key authored differently from the English meaning, and why.
  • Which protocol values were reviewed and frozen.
  • Any fragment whose translated heading changes the anchor.
  • Any third-party localization candidate that was left unverified.
  • Any check failure outside the lane’s allowed scope, with the file paths involved.

A locale is ready to publish when every gate above passes, the bot boots and registers commands, the protocol values are reviewed, and either the translated tree and README exist or they are deliberately deferred with docsTree: false and the root switcher row still disabled.