Skip to content

docs(spec,i18n): GET /i18n/locales stops declaring label a display name - #7757

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-7634-locale-label-declared-enforced
Aug 11, 2026
Merged

docs(spec,i18n): GET /i18n/locales stops declaring label a display name#7757
os-zhuang merged 1 commit into
mainfrom
claude/issue-7634-locale-label-declared-enforced

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #7634

GetLocalesResponseSchema described each locale descriptor's label as "Display name of the locale", and no producer has ever written one. The sole producer is toLocaleDescriptors (packages/spec/src/system/i18n-resolver.ts:1118) — shared deliberately by the runtime dispatcher's /i18n domain and service-i18n's autonomous route, so there is no second implementation to diverge — and it sets label to the code. GET /api/v1/i18n/locales answers code: 'th', label: 'th', never label: 'ไทย'. Declared-not-enforced (ADR-0049), one field wide, and the describe is what carries the claim into the generated JSON Schema, the OpenAPI surface and the SDK type.

The premise, measured first

The card's fork was conditional: describe-only fix if no consumer treats label as a human display name; stop and report if one does. Both halves were measured, and no consumer reads the field.

This repo — quoted-exact-name greps over toLocaleDescriptors / LocaleDescriptor / GetLocalesResponse / i18n/locales, plus a co-occurrence sweep for label in any file mentioning locales. Every read of the body takes code or isDefault:

Site Reads
packages/runtime/src/http-dispatcher.test.ts (4 sites) l.code
packages/runtime/src/domain-handler-registry.test.ts:130 l.code
packages/runtime/src/i18n-success-envelope.conformance.test.ts:162 l.isDefault
packages/spec/src/system/i18n-resolver.test.ts:1624 l.code / isDefault
packages/client/src/i18n-wire-dialect.test.ts:46 fixture sets label: 'zh-CN' (the code); asserts only array length

Positive control: the same grep method finds real isDefault and code reads across the repo, so the absence of label reads is a measurement rather than a missed pattern.

objectui — verified directly against a read-only clone at 3b7d1cc, not taken on the filer's word. The one real consumer is apps/console/src/loadLocales.ts, which reads entry?.code and says so in its header: "that helper sets label to the code itself, so the descriptor's label is not a display name and the switcher names locales for itself." packages/app-shell/src/layout/LocaleSwitcher.tsx names locales from a built-in native-name table plus Intl.DisplayNames, and its header gives the reason — "a server label would put th in the menu where ไทย belongs".

So the premise holds, and the narrowest fix applies. No consumer changes; runtime behaviour is byte-identical.

What changed

  • packages/spec/src/api/protocol.zod.tslabel's describe now states the convention it ships: "Locale label. Equals code on every serving surface today — the client names locales for its UI (i18n: GET /i18n/locales declares a locale label as "Display name" but every producer sets it to the code #7634)", with a schema-level comment recording why display naming stays a client concern (Intl.DisplayNames is in every runtime that matters, and which language to name a locale in — its own, or the requester's Accept-Language — is the caller's choice).
  • packages/spec/src/system/i18n-resolver.tsLocaleDescriptor.label's JSDoc said "Display name. Falls back to the code"; it now says the field is the code. The toLocaleDescriptors header records the no-pull measurement.
  • packages/spec/src/system/i18n-resolver.test.ts — one pin asserting the substance: producer output equals the declared shape, on both sides.

Deliberately not done

Both are contract actions nobody has ruled, and they stay open on #7634:

  • Serving real display names — a capability addition with no measured pull, putting CLDR data behind an endpoint for something every client already computes. Startup-focus: a declared surface with no pull is not built out.
  • Retiring the field — removing a shipped response field is a heavier contract action, explicitly off the table on this card.

Tests

The pin is two-sided, and reverse verification was run in both directions with the expected direction predicted first — the halves move independently, which is the point:

  • Producer half broken (label: NAME(${code})) → RED on the value assertion: expected 'NAME(en)' to be 'en'. Describe assertions stayed green.
  • Declaration half broken (describe restored to "Display name of the locale") → RED on the describe assertion: Expected: /display name/i · Received: "Display name of the locale". The value assertion stayed green.

Restored via git checkout HEAD -- ... against a checkpoint commit; tree verified clean at HEAD afterwards. No git stash.

Gates run locally, scoped to this card's surface:

Gate Result
@objectstack/spec full suite 377 files / 9884 passed
@objectstack/spec typecheck (+ scripts, + test-typecheck) pass
check:generated (13 artifacts) all up to date
check:authorable-surface, check:api-surface, check:docs pass (inside check:generated)
pnpm check:i18n OK, 9 packages in sync — EXIT=0 read explicitly, not via a pipe
check:nul-bytes OK — 7115 files, no raw control bytes
runtime i18n conformance + domain-handler-registry 55 passed
service-i18n full suite 62 passed
client i18n wire-dialect 4 passed

check:i18n needed the built CLI on its first run (pnpm exec turbo run build --filter=@objectstack/cli) — a fresh-worktree prerequisite, not drift; green after building.

Generated-artifact closure: the new describe reaches packages/spec/json-schema/api/GetLocalesResponse.json and the bundled objectstack.json, both of which are gitignored build outputs. content/docs/references/api/protocol.mdx renders only the array-level describe for this schema, so no committed generated file changes — check:generated reports all 13 artifacts current.

Changeset: patch on @objectstack/spec. User-visible because the describe ships in the JSON Schema, OpenAPI and SDK type surfaces; patch because no runtime behaviour, no type shape and no field moves.


Generated by Claude Code

…y name

`GetLocalesResponseSchema` described each locale descriptor's `label` as
"Display name of the locale" while the sole producer — `toLocaleDescriptors`
in `system/i18n-resolver.ts`, shared deliberately by the runtime dispatcher's
`/i18n` domain and service-i18n's autonomous route — sets it to the code.
`GET /api/v1/i18n/locales` answers `{ code: 'th', label: 'th' }`, never
`{ code: 'th', label: 'ไทย' }`. Declared not enforced (ADR-0049), one field
wide, and the describe carries the claim into the JSON Schema, the OpenAPI
surface and the SDK type.

Measured before choosing the fix: no consumer anywhere reads `label`. In this
repo every read of the body takes `code` or `isDefault`; the one wire fixture
spelling `label` sets it to the code and asserts only the array length. In
objectui the one real consumer, `apps/console/src/loadLocales.ts`, reads
`entry?.code` and documents that the descriptor's label is not a display name
(objectui#4039). With nothing consuming the field, the honest declaration is
the whole fix — runtime behaviour is unchanged.

The declaration now states the convention it ships, and `i18n-resolver.test.ts`
pins both sides of it: a producer that starts inventing display names and a
describe that starts promising them each go red separately. Serving real
display names (CLDR data on the server for something every client computes)
and retiring the field are both left open on #7634 — neither has a ruling.

Fixes #7634
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 11, 2026 1:05pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/permissions/system-context.mdx (via packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:system tests tooling labels Aug 11, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 11, 2026 13:54
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 7a8476f Aug 11, 2026
27 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-7634-locale-label-declared-enforced branch August 11, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:system size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

i18n: GET /i18n/locales declares a locale label as "Display name" but every producer sets it to the code

1 participant