Skip to content

console: a user with no stored language who picked 中文 on the sign-in page sees English month names in date cells on every load, because the Accept-Language answer outranks the UI language #27625

Closed

Description

@objectstack-fleet

domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

Filing gate: ① a product defect with a named location and a reproduction. Found by the #27498 dev (card thread on branch claude/project-thread-b2omyd, PR #27624, os-dev-report 6500538340) while reproducing #27498. ⛔ Not a claim; triage sets lane and grade. PR #27624 fixes the language-switch race and does not close this.

Business reason. A Chinese-speaking user whose sys_user.locale was never written (they chose 中文 on the sign-in page, not through the avatar menu) sees every list grid's dates as Nov 18 / Oct 11 under Chinese headers, on every page load, while the UI language is Chinese. Dates read as foreign text in the one place a user scans them most.

Measured (dev's reproduction on examples/app-showcase)

  • sys_user.locale unset, device/sign-in language zh: GET /api/v1/auth/me/localization answers the Accept-Language rung, and ui/packages/i18n/src/useDisplayLocale.ts (~L64-68) returns localization.locale || language || 'en', so the server's answer outranks the active UI language. The grid's 截止日期 column reads Nov 18 on every load.

Wanted (design call, not decided here)

Which source wins for display formatting when the user never chose a language: the server's request-derived answer, or the UI language the user picked on the sign-in page. Changing useDisplayLocale's precedence touches every date/number renderer; a server-side answer (marking an Accept-Language-derived locale as such) is the other route.

Dedupe

Open domain:ui titles read for locale, language, month, Accept-Language: only #27498 (the switch race, PR #27624). New.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: 客户的语言 | none | P②

    Triage: p2 · domain:ui · area:i18n · 2026-10-29T16:04Z

    ⛔ Not a claim.

    • Read fresh. Graded as a ui fix with no new protocol key and no new server field:
    • If the measurement shows the header already carries zh and the answer is still en, return it with pm:retriage and the measured request and answer. ⛔ Do not flip the precedence in useDisplayLocale: that touches every renderer.
    • bug, pm:queue.

    Generated by Claude Code

  2. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

    Claim: PM loop round 1 · 2026-10-29T16:18Z
    Session: session_01Utp6CY2n1JmZZKrHgtp9jC (seat; implementation by a card thread, mode:thread)
    Account: os-bill
    Branch: claude/project-thread-r5i7sq (the card thread's harness push branch, self-reported — the measured platform exception to claude/issue-N-slug)
    Worktree: objectstack-issue-27625
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: ui/apps/console/src/languageSeed.ts, ui/apps/console/src/LocalizationFetchProvider.tsx, the module holding the sign-in page's language pick (measured by the dev, named in the report), tests beside, a changeset; ⛔ useDisplayLocale precedence, every server file, and the auth pages #27509 holds (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: no (expected; the dev measures from the diff)
    Metadata-protocol: none (expected; the dev measures from the diff)
    Responsibility: the console's sign-in language pick (not reaching <html lang> / the localization read's Accept-Language) | the server's rung order and useDisplayLocale's precedence are fixed inputs (triage 6501838569) | every user with no stored language who picks one on the sign-in page, today
    Thread-read: 6501838569
    Premise: holds as filed by the seat (#27498 dev's reproduction); step 1 of the order re-measures the request header and answer, and returns the card for pm:retriage if the header already carries the picked language
    Serial constraints cleared: #27498 (PR #27624, same LocalizationFetchProvider.tsx) merged; ui/apps/console/src/pages/auth/{LoginPage,RegisterPage}.tsx and app-shell/src/console/auth/** excluded while #27509 is in flight; seat 4 handed this card to seat 2

    Dispatch order: /mnt/project-files/ui-seat2/dispatch-27625.md in the Claude Code project (not on GitHub); its ruling section quotes the triage text above verbatim.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
    os-dev-report

    {
      "issue": 27625,
      "status": "done",
      "branch": "claude/project-thread-r5i7sq",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27680",
      "head": "42e6d4b6",
      "session": "session_01Utp6CXAd1Uo5DCp8mUzmWj (card thread; seat session_01Utp6CY2n1JmZZKrHgtp9jC)",
      "premise_still_valid": true,
      "summary": "Defect holds as filed; two mechanism assumptions of the order are falsified. Step-1 measurement on app-showcase (main 144702ca, browser en-US, sys_user.locale unset): the html lang attribute is zh after the sign-in pick and after sign-in, but GET /api/v1/auth/me/localization carries Accept-Language en-US and answers locale en-US; dates Oct 11 / Nov 18 on both loads. Not the retriage case (header not zh). Cause: LocalizationFetchProvider reads through sharedGetJson's bare fetch, which never gets createAuthenticatedFetch's html-lang Accept-Language stamp. Fix (seat agreed the route 16:31Z): when the device holds an explicit language choice (readStoredLanguage), the read states the active UI language as Accept-Language, and statedLanguage is an effect dependency so a change reads again; with no choice nothing is stated, keeping the first-visit share with seedTenantLanguage (objectui#5544). After the fix the same run states zh, answers zh, cells 10月11日 / 11月18日. No server file, no useDisplayLocale change, no new key; languageSeed.ts and the pick's module (app-shell LocaleSwitcher via useLanguageSelection, signed out a bare changeLanguage) unchanged.",
      "tests": "head 861694dd: pnpm --dir ui exec vitest run --reporter=verbose apps/console/src/LocalizationFetchProvider.test.tsx apps/console/src/__tests__/localizationBootDedup.test.tsx apps/console/src/languageSeed.test.ts -> Test Files 3 passed (3), Tests 33 passed (33); 4 new cases under 'states the device's chosen language (objectstack#27625)', the 4 #27498 re-read cases green. Ablation via scripts/ablation-replace.mjs (fix committed first): statedLanguage gate -> && !'ABLATED_27625' (anchor 1 -> 0, blob 5147c4a0 -> a88b06c9): Tests 3 failed | 19 passed (22), the no-choice control stays green; restored, blob == HEAD, git diff HEAD empty. No dist involved (console suite resolves src). Head 42e6d4b6: pnpm --dir ui exec vitest run apps/console/src/ -> Test Files 262 passed (262), Tests 2606 passed (2606). Live: stored rung read with explicit headers: column unset + zh -> zh; column ja-JP + zh -> ja-JP; column ja-JP + en-US -> ja-JP; reset + zh -> zh.",
      "gates": "head 42e6d4b6 (after merging origin/main once): node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, 3 paths vs merge base b761968d9: 33 derived, every one exit 0 (incl. check-sdui-manifest.mjs and --self-test, the changeset gates, pnpm --dir ui exec vitest run scripts/ -> Tests 4902 passed | 2 skipped). --ran reconciliation: 33 derived, 33 run, 0 NOT-MEASURED, 0 UNRUN. Outside the derivation: pnpm check:changeset-fixed exit 0; pnpm --filter @object-ui/console run type-check exit 0 (after pnpm --filter '@object-ui/console^...' build exit 0); eslint --no-inline-config on the 2 touched files exit 0. PR check-runs: in_progress at report time (not waited on, per contract); Type Check UI / workspace type-check lanes, wide-population families, artifact rosters and pnpm lint are CI's.",
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "0 — no MCP GitHub calls (GitHub reads were REST via gh api; one claude-code-remote get_session to read this session's id)",
      "api_writes": "2 — POST /repos/objectstack-ai/objectstack/dispatches (relay pr_create -> #27680, read-back 9399 bytes identical, assignees none), POST /repos/objectstack-ai/objectstack/issues/27625/comments (this report, post-stamped); plus 3 git pushes to the branch (5c262c6a, 11288d79, 42e6d4b6)",
      "line_budget": "3 files, +152 / -4 (changeset +12, provider +36/-3, test +104/-1)",
      "files_changed": [
        ".changeset/27625-localization-read-states-chosen-language.md",
        "ui/apps/console/src/LocalizationFetchProvider.tsx",
        "ui/apps/console/src/LocalizationFetchProvider.test.tsx"
      ],
      "deviations": [
        "The order's file surface named languageSeed.ts and the sign-in pick's module; measurement showed neither needs a change, so the diff is LocalizationFetchProvider.tsx + its test + a changeset. Falsified assumptions: (1) the pick does reach the html lang attribute; (2) the triage line 'the console's Accept-Language already follows' that attribute does not hold for this read. Seat agreed the route before the fix.",
        "No assignee and no reviewer on the PR; the harness's post-PR assign/review step skipped per the maintainer's rule. No PR subscription taken, per the os-dev contract."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none · noted in PR Acceptance notes: a language picked on the sign-in page is still not written to sys_user.locale at sign-in (signed-out globe is the bare switch by design in useLanguageSelection); with this fix the device's dates follow the pick, other devices of the same user still get their own header"
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

    ACCEPT → PR #27680 comment 6504196682 (head 42e6d4b6); contract review not owed (Clause-②: no, Metadata-protocol: none); landing through the merge queue.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

    Landed · 2026-10-29T18:50Z

    probe reading
    PR #27680 merged through the queue at 2026-10-29T18:38Z, merge commit 6cf3e0b1161ec6a7492f520693d176c6db02f7fb
    this card closed completed by the PR's Fixes line

    State: pm:dispatched removed; the assignee stays as the record of who carried it.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:i18nThe customer's own language, across UI, metadata and notificationsbugSomething isn't workingdomain:uipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions