Repository navigation
Commit 6427e2c
fix(platform-objects): translate the ja-JP and es-ES object labels, options and help that shipped as English copies (#20530)
Fixes #20493
Clause-②: no
## What changed
A ja-JP or es-ES console read English on Setup surfaces: the Invite user
dialog's role list carried **Delegated Admin** beside the translated
roles, and every Setup record page read the same copied leaves. The keys
were present in
`packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts`
and `es-ES.objects.generated.ts`, but their values were byte copies of
the `en` source that `os i18n extract --fill=default` seeds. This PR
applies the zh-CN mechanism of PR #20490 (squash `9bf5e67af`) to both
locales, one PR, one ledger per locale.
- **ja-JP: 340 copied leaves translated**, 43 kept as written by design.
- **es-ES: 338 copied leaves translated**, 54 kept as written by design.
- Leaf values only; the structure of both bundles is untouched. `pnpm
i18n:extract` re-read both bundles byte-identical.
- **Two ledgers**, one per locale, of the shape
`objects-zh-cn-echo-decisions.test.ts` built (`type Verdict =
'translate' | 'echo'`, a reason on every row, a departure on every echo,
`undeclaredEchoes` proved on a synthetic row):
`objects-ja-jp-echo-decisions.test.ts` and
`objects-es-es-echo-decisions.test.ts`. No new mechanism.
- **Provenance companions** `ja-JP.source-hashes.generated.ts` and
`es-ES.source-hashes.generated.ts` regenerated by `pnpm i18n:extract`,
never by hand: −340 and −338 rows, 0 added. The rows that remain are
set-equal to each ledger's declared echoes (43 and 54).
- **One sibling ledger followed:**
`object-lifecycle-panel-echo-decisions.test.ts` pinned
`sys_oauth_resource.fields.access_token_ttl.label` as an unauthored fill
in ja-JP and es-ES. Both are now authored (アクセストークン TTL, TTL del token
de acceso), so the test now asserts all three locales authored with the
`TTL` token kept and no provenance row. The other eight sibling ledgers
already asked for a non-empty table (`> 0`) since PR #20490 and needed
no edit.
- zh-CN, `*.metadata-forms.generated.ts`, the hand-authored `ja-JP.ts` /
`es-ES.ts` and objectui are not touched.
**The coverage-ratchet half is out of scope, per triage (5877552039,
「新增门禁默认否」).** Teaching `i18n-coverage-baseline` to count a copied source
string as untranslated widens an existing gate; if the maintainer wants
that ratchet, it is a separate gate card. For the same reason neither
ledger asserts that no other leaf of its locale is a copy.
## Pinned strings
| leaf | ja-JP | es-ES | pin |
|---|---|---|---|
| `sys_member.fields.role.options.delegated_admin` | 委任管理者 |
Administrador delegado | exact word, equal to the console's |
| `sys_invitation.fields.role.options.delegated_admin` | 委任管理者 |
Administrador delegado | equal to the `sys_member` options, one word per
role |
| `sys_team.fields.member_count.label` | メンバー数 | Número de miembros |
not the `en` source, no provenance row |
The console's words were read at objectui `main` `797a30f`, read-only:
`packages/i18n/src/locales/ja.ts`, key
`organization.roles.delegatedAdmin` = 委任管理者;
`packages/i18n/src/locales/es.ts`, same key = Administrador delegado.
The role test also asserts the four role words are distinct, identical
on both objects, Japanese-script in ja-JP and none equal to its `en`
source in es-ES.
## Copy count, before and after, by key class
A string leaf counts as a copy when it is byte-equal to the `en` leaf at
the same path, over every leaf of `en.objects.generated.ts` and the
locale's bundle. All three bundles have 1529 leaves. "Before" is
measured at base `397572ed5`; the card's `b810ddb6f` counts are
reproduced exactly (H1 holds).
| key class | ja-JP before | ja-JP after | es-ES before | es-ES after |
|---|---:|---:|---:|---:|
| `label` (object, field, view, action param) | 210 | 29 | 217 | 38 |
| `options.*` | 20 | 10 | 22 | 12 |
| `pluralLabel` | 12 | 0 | 12 | 0 |
| `help` | 121 | 0 | 121 | 0 |
| `description` | 12 | 0 | 12 | 0 |
| `placeholder` | 4 | 4 | 4 | 4 |
| `emptyState.title` / `.message` | 4 | 0 | 4 | 0 |
| **total** | **383** | **43** | **392** | **54** |
| `label` + `options.*` | 230 | 39 | 239 | 50 |
`help` / `description` / `placeholder` / `emptyState` were decided as
the same class as `label` (H5), as zh-CN did: the extractor files each
under the same `source` kind as its element's label
(`packages/cli/src/utils/i18n-extract.ts`). All four placeholders in
both locales are typed literals and stay as written.
## Kept as written by design — ja-JP (43)
| leaves | value(s) | reason |
|---|---|---|
| `sys_account._actions.link_social.params.provider.options.{google,
github, microsoft, apple, facebook, gitlab, discord}` (7) | Google,
GitHub, Microsoft, Apple, Facebook, GitLab, Discord | Sign-in provider
brands. The bundle renders no brand name in kana: the authored SSO empty
state keeps Okta, Entra and Auth0 verbatim. |
| `fields.id.label` on the 23 objects zh-CN lists (`sys_oauth_*` ×7,
`sys_sso_provider`, the eight `sys_scim_*`, `sys_email_template`,
`sys_metadata`, `sys_metadata_history`, `sys_view_definition`,
`sys_metadata_audit`, `sys_secret`, `sys_setting_audit`) (23) | ID | The
bundle keeps ID verbatim in every label that carries it (ユーザー ID, 組織 ID,
クライアント ID); a bare ID has no noun to render. |
| `sys_oauth_application.fields.jwks.label`, `.jwks_uri.label` (2) |
JWKS, JWKS URI | Protocol names: `sys_jwks` is authored 署名キー (JWKS), and
URIs stay URI (リダイレクト URI). Their `help` leaves are translated. |
|
`sys_oauth_application._actions.create_oauth_application.params.type.options.web`
(1) | Web | The bundle already renders the sibling
`sys_oauth_application.fields.type.options.web` as Web; the dialog and
the record field read one word. |
|
`sys_sso_provider._actions.register_saml_provider.params.entryPoint.label`
(1) | IdP SSO URL | Protocol initialisms only; the sibling SAML params
are authored IdP エンティティ ID and IdP 署名証明書. |
| `sys_sso_provider._actions.register_sso_provider.params.{scopes,
mapEmail, mapName}.placeholder` (3) | openid email profile, email, name
| Typed literals: OIDC scope and claim names; the authored help on the
same params keeps them (既定値は「email」). |
|
`sys_sso_provider._actions.register_saml_provider.params.identifierFormat.placeholder`
(1) | the SAML NameID emailAddress URN | Typed literal: an identifier,
not prose. |
| `sys_email.fields.message_id.label` (1) | Message-ID | RFC 5322 header
name; the authored help keeps it (トランスポートが割り当てる RFC-5322 Message-ID). |
| `sys_email.fields.cc_addresses.label`, `.bcc_addresses.label` (2) |
Cc, Bcc | Header abbreviations Japanese mail clients print in Latin
letters, with no kana or kanji form in use. The prose labels on the same
record are authored (送信元, 宛先, and 返信先 for Reply-To in this PR). |
| `sys_setting_audit.fields.source.options.{ui, api}` (2) | UI, API |
Initialisms the bundle keeps (API キー, UI には表示されません;
`sys_metadata.fields.source.options.api` is authored API). |
Where ja-JP departs from zh-CN: the result dialog's `Client ID` /
`Client Secret` were never copies here (authored クライアント ID /
クライアントシークレット); `Web` and `Cc` / `Bcc` are ja-JP decisions of their own.
## Kept as written by design — es-ES (54)
| leaves | value(s) | reason |
|---|---|---|
| the 7 sign-in provider options | Google … Discord | Brands; the
authored SSO empty state keeps Okta, Entra and Auth0 verbatim. |
| `fields.id.label` on the same 23 objects (23) | ID | The bundle keeps
ID verbatim in every label that carries it (ID de usuario, ID de
organización, ID de cliente). |
| `sys_oauth_application.fields.jwks.label`, `.jwks_uri.label` (2) |
JWKS, JWKS URI | `sys_jwks` is authored Clave de firma (JWKS); URIs stay
URI (URI de redirección). |
| `...create_oauth_application.params.type.options.web` (1) | Web | The
sibling `fields.type.options.web` is authored Web. |
| the 3 OIDC placeholders + the SAML NameID URN placeholder (4) | openid
email profile, email, name, URN | Typed literals; the authored help
keeps them (Valor predeterminado: «email»). |
| `sys_email.fields.message_id.label` (1) | Message-ID | Authored help:
Message-ID RFC-5322 asignado por el transporte. |
| `sys_email.fields.cc_addresses.label` (1) | Cc | The Spanish copy
abbreviation (con copia) is Cc too; the blind-copy sibling is authored
Cco. |
| `sys_setting_audit.fields.source.options.{ui, api}` (2) | UI, API |
Initialisms the bundle keeps (nunca se muestra en la UI; `sys_metadata`
source option `api` is authored API). |
| `sys_email.fields.error.label`, `sys_job_run.fields.error.label` (2) |
Error | Spanish spells it the same (el error); the bundle writes Último
error on `sys_job_queue`. |
| `sys_notification.fields.actor_id.label`,
`sys_metadata_audit.fields.actor.label`,
`sys_setting_audit.fields.actor_id.label` (3) | Actor | Spanish spells
it the same (el actor); authored help reads Último actor que escribió
esta fila, and the messaging bundle authors its actor label Actor. |
| `sys_setting.fields.scope.options.global`,
`sys_setting_audit.fields.scope.options.global` (2) | Global | Spanish
spells it the same, beside the authored sibling options Inquilino and
Usuario. |
| `sys_email_template.fields.variables_json.label` (1) | Variables
(JSON) | Spanish spells it the same; the authored description of the
same object says (asunto + contenido + variables). |
| `sys_oauth_access_token.fields.token.label`,
`sys_oauth_refresh_token.fields.token.label` (2) | Token | The bundle's
own word for the concept (Token de sesión, Token de acceso). |
| `sys_metadata.fields.checksum.label`,
`sys_metadata_history.fields.checksum.label` (2) | Checksum | The
bundle's own word (Checksum anterior). |
| `sys_organization.fields.slug.label` (1) | Slug | The bundle's own
word (Cambiar slug); the console says the same (Ese slug ya está en
uso). |
Where es-ES departs from zh-CN: `Client ID` / `Client Secret` in the
result dialog and `IdP SSO URL` were never copies here (authored); the
14 Spanish-as-written rows above are es-ES's own class.
## Terminology (H4: one term, one rendering)
- **ja-JP** takes the bundle's already-authored words: 作成日時 / 更新日時 /
有効期限 / 失効日時 / ローテーション日時, クライアント ID, スコープ, クレーム (claim, as in マッピング:
メールクレーム), プロビジョニング (as in IdP プロビジョニング), 投影 and 付与 (as
`sys_scim_projection_grant.display_title` authors them), リース and 冪等キー
(`sys_job_queue`), トピック / 重大度 / 情報・警告・重大 / 操作者 (the messaging bundle),
ポジション (the security bundle), 管理者 for `Managed By` (as `sys_metadata`),
所属組織 for My Memberships (the account nav's label, `ja-JP.ts`
`nav_account_memberships`), 配属 for Placement (objectui's word).
Decommission is 廃止; tombstone is トゥームストーン, the service-storage ja-JP
objects bundle's existing rendering.
- **es-ES** likewise: Creado el / Actualizado el / Caduca el / Revocado
el / Rotado el, ID de cliente, Ámbitos, reclamo (claim, as in Mapeo:
reclamo de correo), aprovisionamiento (as in Aprovisionada por IdP),
concesión (as in Tipos de concesión), arrendamiento (`sys_job_queue`'s
lease), Tema / Severidad / Información · Advertencia · Crítica (the
messaging bundle), Puesto (the security bundle), principal for Parent
(the bundle's word throughout: Unidad de negocio principal, Objeto
principal), Mis organizaciones (`es-ES.ts` `nav_account_memberships`),
Asignación for Placement (objectui's word). Decommission is retirada;
tombstone is lápida.
## Verification
Measured at head `d616c8cc3`, a true merge of `origin/main` `fb386074f`
(parents `8f3e7cae3`, `fb386074f`). The CLI closure was rebuilt after
the merge.
- **H2, `pnpm check:i18n`:**
- exit 0 with both bundles restored to their `397572ed5` bytes;
- exit 1 after the leaf edits alone, `platform-objects DRIFTED (2)`: the
two provenance companions (zh-CN's run read `DRIFTED (1)`, one locale);
- exit 0 after `pnpm i18n:extract`, which changed only the two
companions (−340 / −338 rows, 0 added). Remaining rows vs remaining
copies: 43 = 43 and 54 = 54, set difference 0. The mechanism carried
over unchanged; the only per-locale difference is which leaves each
locale keeps.
- exit 0 again at `d616c8cc3` (derived gate run below).
- **`pnpm --filter @objectstack/platform-objects exec vitest run
--maxWorkers=2`:** 58 files, 942 tests passed (lock verdict command-exit
0), at `d616c8cc3` and before the merge.
- **`pnpm --filter @objectstack/platform-objects typecheck`:** exit 0.
`tsc -p tsconfig.test.json --listFilesOnly` lists both new ledgers (2
hits).
- **Reverse verification.** Five mutation legs through
`scripts/ablation-replace.mjs` (anchor hit counts declared, blob change
proved, restore proved blob == HEAD and `git diff HEAD` empty each
time), each red as predicted:
1. ja-JP both `delegated_admin: "委任管理者"` → `"Delegated Admin"`: 2 failed
/ 9 passed (verdict row; role test "expected 'Delegated Admin' to match"
the Japanese-script pattern).
2. es-ES both `delegated_admin: "Administrador delegado"` → `"Delegated
Admin"`: 2 failed / 8 passed (verdict row; "es-ES role delegated_admin
reads its en source").
3. ja-JP declared echo `google: "Google"` → `"グーグル"`: 1 failed / 10
passed ("is a declared echo and must stay the en source").
4. es-ES declared echo `global: "Global"` (×2) → `"Mundial"`: 1 failed /
9 passed.
5. ja-JP `委任管理者` (×2) → `委託管理者`, still Japanese: 1 failed / 10 passed,
only the console-word pin ("expected '委託管理者' to be '委任管理者'").
6. Sibling leg: `object-lifecycle-panel-echo-decisions.test.ts` restored
to its `397572ed5` bytes against the new bundles: 1 failed / 34 passed
("ja-JP sys_oauth_resource.fields.access_token_ttl.label: expected
'アクセストークン TTL' to be 'Access Token TTL'"); restored to HEAD, blob equal.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at `d616c8cc3` printed 61 commands.
All 61 were run, each exit code written to a file before any pipe.
- 60 exited 0 on their first run.
- `pnpm check:dual-build-cjs-loads` first answered PREREQUISITE NOT MET
(exit 3: `dist/` missing for packages outside the CLI closure). Not a
measurement. Rerun after a workspace build (71 of 71 tasks cached): exit
0.
- `--ran` reconciliation: 61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN
(exit 0).
- **`pnpm check:i18n-coverage`:** exit 0, "621 baselined untranslated
string(s), none new". The ratchet is unchanged, as expected.
- **ESLint, narrowed and declared:** `eslint --no-inline-config --format
json` over the 7 changed `.ts` files: 7 files linted (each confirmed not
ignored by the repo config's own `isPathIgnored`), 0 errors, 0 warnings.
`eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`, stated in its own comment), so this diff cannot
move a verdict on any untouched file. The repo-wide `pnpm lint` is CI's.
- **H6, size:** the derivation reads 2787 changed lines (+1416 / −1371)
over 8 files, under the 5000 human-merge threshold.
- **NOT MEASURED locally, declared to CI:** the 6 workflow-valued
families, the 11 wide-population families and the path-scheduled CI jobs
the derivation names. The Invite dialog was not re-driven in a browser
(NOT MEASURED: browser rendering; reason: bundle leaves are read
directly, as the card measured them).
## Acceptance notes
- es-ES renders `Parent` as principal throughout the bundle, so the
pre-existing `sys_business_unit.fields.parent_business_unit_id.label`
and `sys_user.fields.primary_business_unit_id.label` both read Unidad de
negocio principal. This PR follows the bundle's word for `Parent
Organization` (Organización principal) and leaves the pre-existing pair
alone. Carrier: none.
- The ja-JP bundle renders `Locked Until` two ways already (ロック解除日時 on
`sys_user`, ロック期限 on `sys_job_queue`). This PR uses ロック解除日時 for
`sys_two_factor`, the same lockout concept as `sys_user`.
- ja-JP `Managed By` is 管理者, `sys_metadata`'s word, so
`sys_email_template` reads 管理者 as the label and as the `admin` option.
管理元 would disambiguate; one line changes it if the maintainer prefers
it.
- A few authored (not copied) ja-JP / es-ES leaves no longer match their
moved `en` source, for example `sys_two_factor.fields.backup_codes.help`
(still "JSON-serialized backup codes" where `en` now says an opaque
ciphertext) and `sys_notification.description`. Authored leaves are
legacy-trusted by the provenance design and outside this card's copy
class. Carrier: none.
- The eight sibling ledgers' comments name only zh-CN ("since #20462
zh-CN's objects slice holds only its declared English-by-design
leaves"); the `> 0` floors they guard hold for all three locales now.
Comment-only drift, not edited here.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01N8TPEsoJxPsdSdNKGnNGEN)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent c7ad16f commit 6427e2c
8 files changed
Lines changed: 1416 additions & 1371 deletions
File tree
- .changeset
- packages/platform-objects/src/apps/translations
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
Lines changed: 338 additions & 338 deletions
Large diffs are not rendered by default.
Lines changed: 0 additions & 338 deletions
Large diffs are not rendered by default.
Lines changed: 340 additions & 340 deletions
Large diffs are not rendered by default.
Lines changed: 0 additions & 340 deletions
Large diffs are not rendered by default.
Lines changed: 8 additions & 15 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1291 | 1291 | | |
1292 | 1292 | | |
1293 | 1293 | | |
1294 | | - | |
| 1294 | + | |
1295 | 1295 | | |
1296 | 1296 | | |
1297 | 1297 | | |
1298 | | - | |
1299 | | - | |
1300 | | - | |
| 1298 | + | |
| 1299 | + | |
| 1300 | + | |
| 1301 | + | |
1301 | 1302 | | |
1302 | 1303 | | |
1303 | 1304 | | |
1304 | 1305 | | |
1305 | 1306 | | |
1306 | | - | |
1307 | | - | |
1308 | | - | |
1309 | | - | |
1310 | | - | |
1311 | | - | |
1312 | | - | |
1313 | | - | |
1314 | | - | |
1315 | | - | |
1316 | | - | |
| 1307 | + | |
| 1308 | + | |
| 1309 | + | |
1317 | 1310 | | |
1318 | 1311 | | |
1319 | 1312 | | |
| |||
Lines changed: 357 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 338 additions & 0 deletions
Large diffs are not rendered by default.
0 commit comments