You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Default language in Setup offers the languages the deployment supports, instead of four hard-coded ones #30286
Filed on the maintainer's word, verbatim: 「立卡」 (2026-11-05), answering their question 「如果元数据app中只有10种语言,系统管理员可以增加新的语言并且处理国际化吗?」 and the proposal below. Measured on main04f36b63f8.
What a customer sees
A French company sets up the platform. The console has a complete French pack and a user can pick 法语 from their language menu. In Setup, 默认语言 offers only English, 简体中文, 日本語 and Español. Writing fr-FR is refused. So is vi-VN, even after the administrator has added a Vietnamese translation and the language menu offers it. Every new user in that company therefore starts in a language the company did not choose.
Why
packages/services/service-settings/src/manifests/localization.manifest.ts:84: the locale setting's options are a literal list of four, and the option list is the enforcement boundary. Three lists answer the same question differently:
List
Contents on the test deployment
默认语言 options
4, hard-coded
Console's built-in packs
10 (ar de en es fr ja ko pt ru zh)
User's language menu, /api/v1/i18n/locales plus what the renderer can resolve
Grows with translation items, so it gained vi-VN and fr-FR
Expected
默认语言 offers what the deployment supports, from the same source the language menu reads, and the server accepts exactly those values. Adding a translation item makes the language available here too.
A previously saved value that is no longer offered stays readable and is shown as such, not silently replaced.
An option's name reads in its own language, as the language menu does.
Question for the dev to settle in the PR
Some consumers read this setting for things other than console text: the description says number and date formatting, and auth email templates read a locale too. A language with a translation item but no console pack, such as Vietnamese, then shows an English console while dates format in Vietnamese. I think that is acceptable and matches what the user menu already does, but I did not check every reader. List them in the PR, and if one breaks on a locale outside the four, stop and report.
#30206 (first-run checklist) has a row for language. It should use the list this card produces.
The card's fix site, as its own Why names it, is packages/services/service-settings/src/manifests/localization.manifest.ts (read on origin/mainff3f83bb: the locale select's options are the literal four en-US zh-CN ja-JP es-ES, and the option list is the enforcement boundary). Making the offered and accepted set follow the platform locale table (spec(system): export the platform locale table PLATFORM_LOCALES and its read aliases (phase 1 of #29428) #30001, closed) is a service-settings change, which the SKILL.md domain table puts in domain:server (services/*).
Whether Setup's renderer needs any ui/** change depends on how the server serves dynamic options; the seat did not measure that. If it does, that half is a ui sub-card.
Ask: route the card (or its server half) to domain:server. pm:queue stays on.
If Setup's renderer needs a change to draw dynamic options, that half is a domain:ui sub-card, filed by the server seat once it has measured. pm:retriage was removed.
Claim: PM loop round 1
Session: session_01Utp6CZMkHvuDeVYgTD73qF (seat) · dev card thread session_01Utp6CZEhtHfQH5TLEVe3TF
Account: os-bill
Branch: claude/project-thread-g5rn8k (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-30286
Domain: domain:server
Seat: domain:server#4
File surface: packages/services/service-settings/src/manifests/localization.manifest.ts (the locale select and its acceptance path), the server code that serves the deployment's locale list (/api/v1/i18n/locales, the platform locale table from #30001), their tests, one changeset. ⛔ packages/spec/**, docs/adr/**, .claude/**, repo-root AGENTS.md, ui/**, skills/** (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default judgment tier (opus) — dispatch-gates --tier: no path-derived mandate, effort: default
Clause-②: yes (widening) — values refused today (fr-FR, vi-VN on a deployment that supports them) are accepted; the dev measures whether any value accepted today becomes refused and, if so, declares narrowing
Metadata-protocol: none
Responsibility: localization.manifest.ts's locale select hard-codes four options and the option list is the enforcement boundary | none: 默认语言 offers and accepts only en-US / zh-CN / ja-JP / es-ES while the language menu offers what the deployment supports | any administrator setting the organization's default language in Setup
Thread-read: 6652874310
Premise: holds (read on origin/main0e4dfbe2: the literal four options at localization.manifest.tslocale)
Serial constraints cleared: seats 3 and 5 confirmed no overlap; seat 2's #30385 also edits service-settings (auth namespace / settings route), so the dev tells the seat before touching any shared settings-route or validator file.
Dispatch order: /mnt/project-files/server-seat4/dispatch-30286.md (project shared folder). Ruling section: the maintainer's 「立卡」 and the card's Expected list; triage retriage (08:00Z) names the fix site. If the only route is a new value-domain kind or a packages/spec/** schema change, the dev stops and reports and the seat files the spec card. A ui half (Setup's renderer drawing served options) becomes a domain:ui sub-card the seat files after measuring. Fixes #30286 unless a ui half remains (Refs).
Status: done. Draft PR #30446 carries Fixes #30286, head e1c377ef.
What it does:
Setup's default language serves the deployment's locale list.localization.locale offers and accepts what the i18n service's getLocales() reports, the list GET /api/v1/i18n/locales serves to the user's language menu, asked at each save and page read, so a translation item added at runtime is offered at once. Each option is named in its own language (PLATFORM_LOCALES, else Intl.DisplayNames, else the code).
Mechanism, measured first: no dynamic-options precedent exists and the valueDomain vocabulary is closed (bcp47_locale refused by name in spec), so this is a service-level SettingsService.registerOptionSource(namespace, key, source); the save and the OS_* override both ask one helper (optionTableOf), and getNamespace serves the source's options. No packages/spec change.
Stored values:en-US stays accepted where en is offered (the platform table's read aliases). A stored value the deployment no longer offers stays in effect and readable, served as an option named "(no longer offered)" in the reader's language; only a save that writes it again is refused.
Renderer: Setup's SettingsField.tsx draws the served spec.options, so no ui/** half remains. The zh-CN bundle's translated language names were removed so the served self-names show.
Readers of localization.locale: all seven listed in the PR body; none breaks on a locale outside the four (email falls to en-US, SMS to en, i18n t() to the default).
Deviations: Clause-② is yes (narrowing), not the Claim's yes (widening): one of the four is refused on a deployment whose list leaves it out (ja-JP under supportedLocales: ['en', 'zh-CN']). The PR title carries !, the changeset the BREAKING migration and ADR-0087 marker, and the full dogfood ran. The seat agreed before code (10:55Z thread note). Shared files touched with the seat's leave: settings-service.ts (not the setMany env-lock branch seat 2 edits), settings-routes.ts (one line), settings-service-plugin.ts. Follow-up for #30206: read the same served list.
{
"issue": 30286,
"status": "done",
"branch": "claude/project-thread-g5rn8k",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30446",
"session": "session_01Utp6CZEhtHfQH5TLEVe3TF",
"premise_still_valid": true,
"summary": "localization.locale now offers and accepts the i18n service's locale list (the language menu's source) instead of four hard-coded codes, each option named in its own language, read at each use so runtime translation items count. New SettingsService.registerOptionSource drives both the save and the OS_* override through optionTableOf and the served manifest through getNamespace; the source is registered in SettingsServicePlugin.init before the manifests. A stored second spelling (en-US for en) stays accepted; a stored value no longer offered stays in effect and is served named as no longer offered.",
"tests": "At head e1c377ef under the verify lock after a full build: service-settings test 69 files 1170 passed (14 new in localization-locale-options.test.ts, real LiteKernel boot plus real GET/PUT settings routes); typecheck exit 0. Ablation via scripts/ablation-replace.mjs: optionTableOf ignoring the source gives 6 of 14 red; restored, blob equal to HEAD, git diff HEAD empty.",
"gates": "dispatch-gates --commands derived 63 families at e1c377ef; each run with its exit recorded before any pipe: 63 of 63 exit 0 (check-plugin-teardown-shape --self-test answered exit 3 on the shallow clone, exit 0 after fetching its pinned fixture commit). --ran: 63 derived, 63 run, 0 NOT-MEASURED, exit 0. pnpm lint exit 0. pnpm install --frozen-lockfile exit 0; turbo build 119/119. CI check-runs on #30446: not yet reported.",
"dogfood_battery": "e1c377ef · os-verify-lock VERDICT command-exit 0 (held 37m55s) · Test Files 419 passed 3 skipped (422) · Tests 3492 passed 36 skipped (3528) · no failures",
"line_budget": "source +348 -14 (8 files), tests +284 (1 file), changeset +18 (1 file); 10 files",
"files_changed": ".changeset/30286-default-language-follows-deployment.md, packages/services/service-settings/src/{index.ts, localization-locale-options.ts, localization-locale-options.test.ts, manifests/localization.manifest.ts, settings-routes.ts, settings-service-plugin.ts, settings-service.ts, settings-service.types.ts, translations/zh-CN.ts}",
"deviations": "Clause-② yes (narrowing) instead of the Claim's yes (widening), agreed with the seat; shared settings-service/settings-routes/plugin files touched after telling the seat; pins in the new component's own test file, not settings-routes.test.ts",
"mcp_calls": "0 MCP GitHub calls; GitHub reads went through gh api REST (issue, comments, pulls)",
"api_writes": "2 through the relay: pr_create #30446 (draft, no assignee, no labels, read-back identical) and this comment. Plus git pushes to claude/project-thread-g5rn8k (not REST)",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": ["carrier: #30206 · the first-run checklist language row should read the same served list (GET /api/settings/localization locale options); noted, not filed"]
}
MERGED 2026-11-05T15:17Z, merge commit bee98366d; Fixes #30286 closed this card. Seat record on head e1c377ef (BREAKING, not governed, ≤ 3000 lines; contract review PASS 6657571849; landed per pm-dispatch on the maintainer's 13:47Z word).
Activity
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 ·
domain:ui·pm:queue· 2026-11-05T05:58Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:ui#2 · os-bill · session_01Utp6Cb5R6JDdfyvtj3dPR2
Retriage asked ·
domain:ui→domain:server? · 2026-11-05T07:20Z⛔ Not a claim.
packages/services/service-settings/src/manifests/localization.manifest.ts(read onorigin/mainff3f83bb: thelocaleselect'soptionsare the literal fouren-US zh-CN ja-JP es-ES, and the option list is the enforcement boundary). Making the offered and accepted set follow the platform locale table (spec(system): export the platform locale table PLATFORM_LOCALES and its read aliases (phase 1 of #29428) #30001, closed) is aservice-settingschange, which the SKILL.md domain table puts indomain:server(services/*).ui/**change depends on how the server serves dynamic options; the seat did not measure that. If it does, that half is auisub-card.domain:server.pm:queuestays on.objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: retriage ·
domain:ui→domain:server·pm:queue· 2026-11-05T08:00Z⛔ Not a claim.
domain:ui#2's ask. The fix site the card names isservice-settings'slocalization.manifest.ts: itslocaleselect hard-codes four options, and that list is the enforcement boundary. Making it follow the platform locale table (spec(system): export the platform locale table PLATFORM_LOCALES and its read aliases (phase 1 of #29428) #30001) isdomain:serverwork. p2 kept.domain:uisub-card, filed by the server seat once it has measured.pm:retriagewas removed.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF
Claim: PM loop round 1
Session:
session_01Utp6CZMkHvuDeVYgTD73qF(seat) · dev card threadsession_01Utp6CZEhtHfQH5TLEVe3TFAccount:
os-billBranch:
claude/project-thread-g5rn8k(the card thread's designated push branch, the recorded platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-30286Domain:
domain:serverSeat:
domain:server#4File surface:
packages/services/service-settings/src/manifests/localization.manifest.ts(thelocaleselect and its acceptance path), the server code that serves the deployment's locale list (/api/v1/i18n/locales, the platform locale table from #30001), their tests, one changeset. ⛔packages/spec/**,docs/adr/**,.claude/**, repo-rootAGENTS.md,ui/**,skills/**(stop on breach; explain in the report)Container & model:
M,mode:thread,model: default judgment tier (opus) — dispatch-gates --tier: no path-derived mandate,effort: defaultClause-②: yes (widening) — values refused today (
fr-FR,vi-VNon a deployment that supports them) are accepted; the dev measures whether any value accepted today becomes refused and, if so, declares narrowingMetadata-protocol: none
Responsibility:
localization.manifest.ts'slocaleselect hard-codes four options and the option list is the enforcement boundary | none: 默认语言 offers and accepts only en-US / zh-CN / ja-JP / es-ES while the language menu offers what the deployment supports | any administrator setting the organization's default language in SetupThread-read: 6652874310
Premise: holds (read on
origin/main0e4dfbe2: the literal four options atlocalization.manifest.tslocale)Serial constraints cleared: seats 3 and 5 confirmed no overlap; seat 2's #30385 also edits
service-settings(auth namespace / settings route), so the dev tells the seat before touching any shared settings-route or validator file.Dispatch order:
/mnt/project-files/server-seat4/dispatch-30286.md(project shared folder). Ruling section: the maintainer's 「立卡」 and the card's Expected list; triage retriage (08:00Z) names the fix site. If the only route is a new value-domain kind or apackages/spec/**schema change, the dev stops and reports and the seat files the spec card. A ui half (Setup's renderer drawing served options) becomes adomain:uisub-card the seat files after measuring.Fixes #30286unless a ui half remains (Refs).Posted 2026-11-05T09:51Z.
objectstack-fleet commentedon Nov 5, 2026
domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF
os-dev-report
Status: done. Draft PR #30446 carries
Fixes #30286, heade1c377ef.What it does:
localization.localeoffers and accepts what the i18n service'sgetLocales()reports, the listGET /api/v1/i18n/localesserves to the user's language menu, asked at each save and page read, so a translation item added at runtime is offered at once. Each option is named in its own language (PLATFORM_LOCALES, elseIntl.DisplayNames, else the code).valueDomainvocabulary is closed (bcp47_localerefused by name in spec), so this is a service-levelSettingsService.registerOptionSource(namespace, key, source); the save and theOS_*override both ask one helper (optionTableOf), andgetNamespaceserves the source's options. Nopackages/specchange.en-USstays accepted whereenis offered (the platform table's read aliases). A stored value the deployment no longer offers stays in effect and readable, served as an option named "(no longer offered)" in the reader's language; only a save that writes it again is refused.SettingsField.tsxdraws the servedspec.options, so noui/**half remains. The zh-CN bundle's translated language names were removed so the served self-names show.localization.locale: all seven listed in the PR body; none breaks on a locale outside the four (email falls toen-US, SMS toen, i18nt()to the default).Deviations:
Clause-②isyes (narrowing), not the Claim'syes (widening): one of the four is refused on a deployment whose list leaves it out (ja-JPundersupportedLocales: ['en', 'zh-CN']). The PR title carries!, the changeset the BREAKING migration and ADR-0087 marker, and the full dogfood ran. The seat agreed before code (10:55Z thread note). Shared files touched with the seat's leave:settings-service.ts(not thesetManyenv-lock branch seat 2 edits),settings-routes.ts(one line),settings-service-plugin.ts. Follow-up for #30206: read the same served list.{ "issue": 30286, "status": "done", "branch": "claude/project-thread-g5rn8k", "pr": "https://github.com/objectstack-ai/objectstack/pull/30446", "session": "session_01Utp6CZEhtHfQH5TLEVe3TF", "premise_still_valid": true, "summary": "localization.locale now offers and accepts the i18n service's locale list (the language menu's source) instead of four hard-coded codes, each option named in its own language, read at each use so runtime translation items count. New SettingsService.registerOptionSource drives both the save and the OS_* override through optionTableOf and the served manifest through getNamespace; the source is registered in SettingsServicePlugin.init before the manifests. A stored second spelling (en-US for en) stays accepted; a stored value no longer offered stays in effect and is served named as no longer offered.", "tests": "At head e1c377ef under the verify lock after a full build: service-settings test 69 files 1170 passed (14 new in localization-locale-options.test.ts, real LiteKernel boot plus real GET/PUT settings routes); typecheck exit 0. Ablation via scripts/ablation-replace.mjs: optionTableOf ignoring the source gives 6 of 14 red; restored, blob equal to HEAD, git diff HEAD empty.", "gates": "dispatch-gates --commands derived 63 families at e1c377ef; each run with its exit recorded before any pipe: 63 of 63 exit 0 (check-plugin-teardown-shape --self-test answered exit 3 on the shallow clone, exit 0 after fetching its pinned fixture commit). --ran: 63 derived, 63 run, 0 NOT-MEASURED, exit 0. pnpm lint exit 0. pnpm install --frozen-lockfile exit 0; turbo build 119/119. CI check-runs on #30446: not yet reported.", "dogfood_battery": "e1c377ef · os-verify-lock VERDICT command-exit 0 (held 37m55s) · Test Files 419 passed 3 skipped (422) · Tests 3492 passed 36 skipped (3528) · no failures", "line_budget": "source +348 -14 (8 files), tests +284 (1 file), changeset +18 (1 file); 10 files", "files_changed": ".changeset/30286-default-language-follows-deployment.md, packages/services/service-settings/src/{index.ts, localization-locale-options.ts, localization-locale-options.test.ts, manifests/localization.manifest.ts, settings-routes.ts, settings-service-plugin.ts, settings-service.ts, settings-service.types.ts, translations/zh-CN.ts}", "deviations": "Clause-② yes (narrowing) instead of the Claim's yes (widening), agreed with the seat; shared settings-service/settings-routes/plugin files touched after telling the seat; pins in the new component's own test file, not settings-routes.test.ts", "mcp_calls": "0 MCP GitHub calls; GitHub reads went through gh api REST (issue, comments, pulls)", "api_writes": "2 through the relay: pr_create #30446 (draft, no assignee, no labels, read-back identical) and this comment. Plus git pushes to claude/project-thread-g5rn8k (not REST)", "open_questions": [], "security_findings": "none", "out_of_scope_findings": ["carrier: #30206 · the first-run checklist language row should read the same served list (GET /api/settings/localization locale options); noted, not filed"] }Report taken 2026-11-05T11:45Z.
objectstack-fleet commentedon Nov 5, 2026
domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF
Landed · PR #30446 · card closed
bee98366d;Fixes #30286closed this card. Seat record on heade1c377ef(BREAKING, not governed, ≤ 3000 lines; contract review PASS 6657571849; landed per pm-dispatch on the maintainer's 13:47Z word).mainsourcepackages/services/service-settings/src/localization-locale-options.ts:50getLocales(): readonly string[];main.The card's label
pm:dispatchedand theos-billassignee come off with this comment.Posted 2026-11-05T15:21Z.