Found while implementing objectstack-ai/objectui#4039 (the console language menu now reads this endpoint). Filing only; not fixed there — out of that issue's scope, and the consumer side deliberately routes around it.
What
GetLocalesResponseSchema (packages/spec/src/api/protocol.zod.ts) declares each locale descriptor as:
locales: z.array(z.object({
code: z.string().describe('BCP-47 locale code (e.g., en-US, zh-CN)'),
label: z.string().describe('Display name of the locale'),
isDefault: z.boolean().default(false).describe('Whether this is the default locale'),
}))
The only producer of those descriptors is toLocaleDescriptors (packages/spec/src/system/i18n-resolver.ts):
return codes.map((code) => ({ code, label: code, isDefault: code === defaultLocale }));
label is the code echoed back. Both serving surfaces go through that one helper — the runtime dispatcher's /i18n domain and service-i18n's autonomous route — deliberately, and correctly, so there is no second implementation that behaves differently. The field is therefore never a display name on any surface: GET /api/v1/i18n/locales answers { code: 'th', label: 'th' }, never { code: 'th', label: 'ไทย' }.
Why it is worth writing down
label reads as a display name in the schema, in the generated SDK type, and in the OpenAPI surface. A client that trusts the description renders locale codes to users and only finds out by looking. objectui#4039 hit exactly this and had to make the opposite decision explicitly: the console's language menu names locales from its own built-in metadata plus Intl.DisplayNames and ignores the endpoint's label, because using it would have put th in the menu where ไทย belongs.
This is the declared-but-not-enforced shape ADR-0049 is about, one field wide: the vocabulary exists, no implementation stands behind it.
Observation-class, not a live defect
Nothing a user hits today, as far as I can tell — there is no current consumer of the field. The one client that reached for it (the console) now does not. Recording it so triage can grade it rather than leaving it in a PR comment.
Options, not a decision
- Retire the field — drop
label from the descriptor and the schema; code is the whole payload, and naming a locale is the client's job anyway (that is where Intl.DisplayNames and any built-in native names live). Smallest surface, and consistent with what the one real consumer chose.
- Implement it — have
toLocaleDescriptors produce a real display name. Needs a decision on which language it is named in (its own, or the requester's Accept-Language), and puts CLDR data on the server for something every client can already compute.
No pull for the second that I can see; recording both so the call is made rather than inherited.
Generated by Claude Code
Found while implementing objectstack-ai/objectui#4039 (the console language menu now reads this endpoint). Filing only; not fixed there — out of that issue's scope, and the consumer side deliberately routes around it.
What
GetLocalesResponseSchema(packages/spec/src/api/protocol.zod.ts) declares each locale descriptor as:The only producer of those descriptors is
toLocaleDescriptors(packages/spec/src/system/i18n-resolver.ts):labelis the code echoed back. Both serving surfaces go through that one helper — the runtime dispatcher's/i18ndomain andservice-i18n's autonomous route — deliberately, and correctly, so there is no second implementation that behaves differently. The field is therefore never a display name on any surface:GET /api/v1/i18n/localesanswers{ code: 'th', label: 'th' }, never{ code: 'th', label: 'ไทย' }.Why it is worth writing down
labelreads as a display name in the schema, in the generated SDK type, and in the OpenAPI surface. A client that trusts the description renders locale codes to users and only finds out by looking. objectui#4039 hit exactly this and had to make the opposite decision explicitly: the console's language menu names locales from its own built-in metadata plusIntl.DisplayNamesand ignores the endpoint'slabel, because using it would have putthin the menu whereไทยbelongs.This is the declared-but-not-enforced shape ADR-0049 is about, one field wide: the vocabulary exists, no implementation stands behind it.
Observation-class, not a live defect
Nothing a user hits today, as far as I can tell — there is no current consumer of the field. The one client that reached for it (the console) now does not. Recording it so triage can grade it rather than leaving it in a PR comment.
Options, not a decision
labelfrom the descriptor and the schema;codeis the whole payload, and naming a locale is the client's job anyway (that is whereIntl.DisplayNamesand any built-in native names live). Smallest surface, and consistent with what the one real consumer chose.toLocaleDescriptorsproduce a real display name. Needs a decision on which language it is named in (its own, or the requester'sAccept-Language), and puts CLDR data on the server for something every client can already compute.No pull for the second that I can see; recording both so the call is made rather than inherited.
Generated by Claude Code