Skip to content

fix(settings): settings copy is written in all eleven platform languages, zh-TW reads Traditional, and sms/test answers in the caller's language (#30630) - #30734

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-vo0alu
Nov 6, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-vo0alu

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #30630

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:设置里 7 张"按调用者语言写"的文案表(IP 白名单拒存、登录方式下限、两处 MFA 拒存与开启提示、邮件实时投递状态与测试邮件答复、默认币种锁定、"已不再提供")补齐平台 11 种语言;"发送测试短信"改为按调用者语言答复;设置服务自带的"重置"结果与"未注册处理程序"也按语言答复。
  • 为什么改:这些表只有 4 种(邮件只有 2 种)语言,法语、德语、韩语等用户看到英文;繁体中文(zh-TW)用户被回成简体;测试短信从不读语言,永远英文。
  • 风险与回滚:只改文案与选语言的查表,不改任何判定、错误码或接口;英文原句逐字不变。回滚即撤本 PR。
  • 先看:packages/services/service-sms/src/sms-plugin.ts(测试短信接上语言);packages/services/service-settings/src/settings-service.ts 的 runAction 两处内置答复;packages/plugins/plugin-auth/src/settings-copy-locale.test.ts(逐表 11 语言 pin)。
  • 两分钟验证:pnpm --filter @objectstack/service-sms exec vitest run src/sms-test-recipient.pin.test.ts,预期全绿,其中 zh-TW 的测试短信答复以「測試簡訊已傳送至」开头。
  • 需要你做的:无,席位落地。

What changes

Every module-local settings copy table (sentences composed per request, which the console's translation bundle cannot carry) now has one entry per language on the platform locale table (PLATFORM_LOCALES: en, zh-Hans, zh-Hant, es, ar, fr, pt-BR, ru, de, ja, ko). The four languages the tables served first keep their codes (zh-CN, ja-JP, es-ES); the rest use the table's tags, the shape storage/test uses (PR #30627). Translations are real text in every language, with no English fill.

The seven tables the card names, measured on origin/main (grep for the four-locale key sets and for resolveLocale callers in the three packages):

Package Table Before
plugin-auth allowed-ip-ranges.ts save refusal (now exported as ALLOWED_IP_RANGES_REFUSAL_COPY for its pin) 4
plugin-auth sign-in-method-floor.ts SIGN_IN_FLOOR_COPY 4
plugin-auth mfa-required-enrollment-predicate.ts MFA_REQUIRED_ENROLLMENT_COPY 4
plugin-auth organization-mfa-enrollment-guard.ts refusal + save notice (two tables, one lookup) 4
plugin-email live-transport-status.ts MAIL_DELIVERY_COPY 2 (en, zh-CN)
service-settings localization-currency-lock.ts CURRENCY_LOCK_COPY 4
service-settings localization-locale-options.ts NOT_OFFERED_COPY 4

The lookup stays core's resolveLocale. #30538 is closed and its fix landed: resolveLocale is now resolveLocaleAmong, which reads a code through the platform table's read aliases before the base-language and variant steps. So with a zh-Hant key present, zh-TW / zh-HK / zh-MO reach it and never zh-CN; pt reaches pt-BR; a code naming no table language answers undefined, which every table serves in English. No per-table copy of the reader was added.

  • sms/test (service-sms): the handler now reads the action input's locale (the route passes Accept-Language, else ?locale) and writes every answer from a new eleven-language table, sms-test-copy.ts: the five recipient refusals, missing credential, transport build failure, log-only warning, sent, and failed. A provider's or the engine's own error text passes through verbatim, behind a lead-in outside English. The English sentences are byte-identical to before.
  • The two built-in settings answers (service-settings, SettingsService.runAction): the built-in reset result and "No handler registered for action ..." are written from a new eleven-language table, settings-action-copy.ts, in display.locale. English unchanged.
  • One type per package names the eleven keys (settings-copy-locale.ts in plugin-auth and service-settings, MailDeliveryCopyLocale, SmsTestCopyLocale), so a table missing a language fails tsc. Arabic and Russian counts (MFA grace days, currency usage, reset count) use their plural forms.

Why

A Setup caller in French, German, Korean, Arabic, Russian, Brazilian Portuguese or Traditional Chinese read these sentences in English (or, for zh-TW, in Simplified Chinese), and sms/test ignored the caller's language entirely. The eleven-language rule (ruling F of #29349) holds every surface to the platform table.

Tests

  • New pins, one per table, each asserting: exactly one entry per platform table language; every field present and non-empty in every entry and different from English (no fill); each table tag and each stored alias (zh-TW, zh-HK, zh-CN, zh, ja-JP, es-ES, pt, en-US, fr-CA, de-AT) served its own language; zh-TW never served the Simplified entry; an off-table code (xx, sw-KE) and no code served English.
    • plugin-auth/src/settings-copy-locale.test.ts (5 tables), plus Russian/Arabic plural cells.
    • plugin-email/src/live-transport-status.pin.test.ts (new top-level describe in the existing harness).
    • service-settings/src/settings-copy-locale.test.ts (currency lock, not-offered, built-in action answers).
    • service-sms/src/sms-test-copy.test.ts, including the English sentences pinned byte-identical.
  • Served doors: service-sms/src/sms-test-recipient.pin.test.ts drives the real settings route with an Accept-Language header: zh-TW,zh;q=0.9 answers "測試簡訊已傳送至 …,經由 twilio(id=SM_test)。"; fr, de-DE, ko refusals are in those languages and send nothing; no header and sw-KE stay English. service-settings/src/settings-service.test.ts pins the reset result in zh-TW and ru and the no-handler refusal in de-DE, English for none / sw-KE.
  • Updated pins that encoded the four-language state: the currency-lock coverage pin (was "exactly the bundle's four locales") and its fr-FR row (was English); the email control (fr-FR was English, now sw-KE); the mfa_required and organization-MFA unknown-locale cells (fr-FR → sw-KE, fr-FR now French, zh-TW added); the organization notice's "1 is a digit in every language" cell accepts Arabic's word form for one day ("يوم واحد").

Acceptance notes

  • Overlap with open PR fix(service-settings): a clear goes through an env pin, so the allowed-IP lockout recovery survives removing the variable #30448 (allowed-ip-ranges.ts): it rewrites the recovery sentence in the four languages it had. This PR translates the current one-step recovery into the seven new languages. Whichever lands second owes the four-step recovery in all eleven; a text conflict on the es-ES hunk is possible.
  • zh-Hant-TW / zh-Hans-CN are not read through the table by the shared resolver: resolveLocaleAmong's table step looks the whole code up, misses, and the variant step returns the first zh* key, zh-CN. A caller whose display language is zh-Hant-TW therefore still reads Simplified here and in every other bundle the resolver serves. The resolver lives in packages/spec (outside this card's file surface); reported to the seat, not fixed here.
  • Labels the copy names on screen ("Send test SMS", "Setup → Authentication", the mail provider "None (log only)") come from the settings translation bundle, which ships four languages (i18n phase 3 (platform metadata and built-in emails): extraction and bundle checks read PLATFORM_LOCALES; backfill the seven missing locales #30512's surface). In the seven new languages the copy describes those controls instead of quoting a label the console does not show in that language.
  • The SMS body sent to the phone (ObjectStack SMS test message.) is unchanged: it is the message, not the answer.

Verification

Head b1014f4d (a54649c1 the fix, c3f79fc1 a merge of origin/main at badf24df, then two test-only commits). Readings under scripts/pm/os-verify-lock.sh on a shared box.

Repro on origin/main (base 4bf9a2d)

With the nine edited sources restored to the base (git restore --source=4bf9a2d7, tests from this branch), restored afterwards with git checkout HEAD -- packages and git diff HEAD empty:

  • signInFloorCopy('zh-TW') is the zh-CN entry (Simplified); signInFloorCopy('fr') is English; the table's keys are en,zh-CN,ja-JP,es-ES.
  • sms/test through the served route with Accept-Language: zh-TW,zh;q=0.9 answered Sent test SMS to +8613*******01 via twilio (id=SM_test).
  • The new pins on the base: plugin-auth 22 failed / 27, service-sms 4 failed (served door), service-settings 8 failed / 156, plugin-email 4 failed / 21.
Ablations (scripts/ablation-replace.mjs, anchor 1 → 0, restored to the HEAD blob, git diff HEAD empty; tests import src/)
Ablated Red
sign-in-method-floor.ts: the lookup reads only the four old keys (resolveLocale(locale, ['en','zh-CN','ja-JP','es-ES'])) 2 of 26 in settings-copy-locale.test.ts (tags/aliases served their own language; zh-TW never Simplified)
sms-plugin.ts: the handler ignores the input locale (smsTestCopy(undefined)) 4 of 16 in sms-test-recipient.pin.test.ts (zh-TW sent, fr / de-DE / ko refusals)
Suites and typecheck

After the merge of origin/main: closure build of the four packages (pnpm --filter 'PKG^...' … build) exit 0, then per package typecheck (with check:test-typecheck) and pnpm --filter PKG test:

  • @objectstack/plugin-auth: typecheck exit 0; Test Files 228 passed (228), Tests 4088 passed, 10 skipped (4098).
  • @objectstack/plugin-email: typecheck exit 0; Test Files 48 passed (48), Tests 805 passed (805).
  • @objectstack/service-settings: typecheck exit 0; Test Files 70 passed (70), Tests 1195 passed (1195).
  • @objectstack/service-sms: on b1014f4d typecheck exit 0 (check:test-typecheck: OK); Test Files 9 passed (9), Tests 119 passed (119).
Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 63 commands on b1014f4d; each run with its exit code recorded before any pipe. --ran: 63 derived, 62 run, 1 NOT-MEASURED, 0 UNRUN. NOT MEASURED: pnpm check:dual-build-cjs-loads (exit 3, PREREQUISITE NOT MET: it needs a whole-workspace build; CI runs it). check:test-source-alias first went red on the new sms/test copy pin (it read @objectstack/spec/system from the build); fixed by the anchored source alias in service-sms/vitest.config.ts, then exit 0. Every other command exited 0. pnpm lint exit 0.


Generated by Claude Code

@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/plugin-auth, @objectstack/plugin-email, @objectstack/service-settings, @objectstack/service-sms, touching 58 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/services/service-sms/vitest.config.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/self-hosting.mdx (via COPY (symbol, a top-level const object))
  • content/docs/permissions/sso.mdx (via emailPassword (symbol, a field of const object SIGN_IN_FLOOR_COPY))
  • content/docs/ui/actions.mdx (via runAction (symbol, a method of class SettingsService))
  • content/docs/ui/apps.mdx (via runAction (symbol, a method of class SettingsService))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-1.mdx (via runAction (symbol, a method of class SettingsService))

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.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/services/service-sms/vitest.config.ts) — pages documenting those are invisible to this run
  • 12 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 63 of 221 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 103 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 30 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 447226b222467709ed74ca99eaf808c178656b33 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from c6789dad23a4bf41084c21a98f3be33a8677f780 — the merge of head 953681c5889588fb0521a71584cd2a4f1140e94c into base 447226b222467709ed74ca99eaf808c178656b33, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c6789dad23a4bf41084c21a98f3be33a8677f780 && git checkout c6789dad23a4bf41084c21a98f3be33a8677f780
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 447226b222467709ed74ca99eaf808c178656b33 953681c5889588fb0521a71584cd2a4f1140e94c && git checkout -B drift-repro 447226b222467709ed74ca99eaf808c178656b33 && git merge --no-ff 953681c5889588fb0521a71584cd2a4f1140e94c

node scripts/docs-audit/affected-docs.mjs --json 447226b222467709ed74ca99eaf808c178656b33

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 447226b222467709ed74ca99eaf808c178656b33 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7

席位核对 · ACCEPT

PR #30734 · head b1014f4d · 2026-11-06T00:08Z · card #30630 (claim 6671511018, amendment 6671574234) · os-dev report 6672881213

维护者速读(席位校正)

The body's lines stand as written, read against the diff. No behaviour changes for an English caller: with no locale, or one off the platform table, every sentence is the English one served before.

席位核对

…ent opens with "os "

check:cli-command-ids read the fragment 'os administradores. ...' as a CLI
command literal. The concatenated sentence is byte-identical.

Co-authored-by: Claude <[email protected]>
Claude-Session: https://claude.ai/code/session_016WV4d1JeXeWGVzxc12FsUx
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7

席位核对 · ACCEPT (head moved)

PR #30734 · head 953681c5 · 2026-11-06T00:39Z · supersedes the head b1014f4d record above (6672919956) for this head only.

  • What moved: one commit for the red check:cli-command-ids gate, which read the pt-BR fragment at packages/plugins/plugin-auth/src/sign-in-method-floor.ts:368 opening 'os administradores. Mantenha …' as a CLI command literal. Before: … inclusive ' + 'os administradores. …. After: … inclusive os ' + 'administradores. …. The joined sentence is byte-identical; no other line changes. Seat read the commit diff itself.
  • Gate-derivation gap (dev addendum 6673039440): check:cli-command-ids is an unconditional lint.yml step that dispatch-gates --commands does not derive; noted for the devx lane, not this PR.
  • CI: all 48 check-runs on 953681c5 completed, none failed (ci-failure.mjs --pr 30734: GREEN).
  • Every other line of the earlier record stands. Landing: pr_ready + automerge_enable now.

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 size/xl tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant