Skip to content

ui: 8 个组件内联 toLocale* 渲染日期/时间,输出跟随宿主 OS 区域而非应用语言 - #293

Open
wxrbyte wants to merge 1 commit into
helsome:mainfrom
wxrbyte:codex/fix-i18n-date-locale-policy
Open

wxrbyte wants to merge 1 commit into
helsome:mainfrom
wxrbyte:codex/fix-i18n-date-locale-policy

Conversation

@wxrbyte

@wxrbyte wxrbyte commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

问题与修复

Closes #292

UI 包里有 8 处把区域参数直接交给 Date.prototype.toLocale*(toLocaleString() / toLocaleDateString() / toLocaleTimeString([], …)),输出因此由宿主 OS 的日期模式决定,与 @finagent/i18n 声明的契约相反(packages/i18n/src/format.ts:4-7:「Every money/percent/date/relative-time value must render through this layer so the UI locale controls presentation」;packages/ui/src/lib/money.ts:5-7:「the OS locale no longer participates」)。

宿主语言与应用语言一致时同样出错 —— 应用语言 zh-CN、宿主中文 Windows 时内联调用走宿主 yyyy/M/d 模式,渲染成 2024/11/21,而统一层对该语言给出 2024年11月21日;宿主语言不同时连语言都会串。

修复:每个调用点改用 options 逐项等价的统一层 formatter,一行改动 + 一行 import。

# 文件:行(3a17eca6) 修复前 修复后
1 components/primitives/DataFreshness.tsx:15 new Date(updatedAtMs).toLocaleTimeString([], { hour:'2-digit', minute:'2-digit', second:'2-digit' }) formatMarketTime(updatedAtMs)
2 components/research/EvidenceList.tsx:26 new Date(ref.fetchedAt).toLocaleTimeString() formatMarketTime(ref.fetchedAt)
3 components/research/WhatChangedSection.tsx:53 new Date(previousReport.generatedAt).toLocaleDateString() formatDate(previousReport.generatedAt)
4 components/research/ResearchReportView.tsx:86 new Date(report.generatedAt).toLocaleString() formatDateTime(report.generatedAt)
5 components/settings/ConnectionCard.tsx:323 new Date(lastCheck).toLocaleString() formatDateTime(lastCheck)
6 components/settings/EvaluationSettingsTab.tsx:39 new Date(updatedAt).toLocaleDateString(undefined, { year:'numeric', month:'short', day:'numeric' }) formatDate(updatedAt)
7 components/thesis/ThesisImpactList.tsx:40 new Date(impact.evaluatedAt).toLocaleDateString() formatDate(impact.evaluatedAt)
8 components/discover/DiscoverView.tsx:471 new Date(run.createdAt).toLocaleString() formatDateTime(run.createdAt)

选 formatDate / formatDateTime / formatMarketTime 而非在各组件里补 i18nCurrentLocale():8 处的 options 都能在统一层找到逐项等价实现(3/6/7 对应 format.ts:114-122 的 formatDate;4/5/8 对应 :124-134 的 formatDateTime;1/2 对应 :136-144 的 formatMarketTime,其 docstring 正是 "Clock time only (market-data freshness lines)")。不新增任何 API,不改显示形状,也不涉及秒/毫秒换算(DataFreshness 入参与 ProviderHealth.lastCheck 均为 epoch ms)。

可复现测试报告

环境:Windows 10.0.26200.9457(x64)、Bun 1.4.2、main 3a17eca6。

新增守卫 packages/ui/src/i18n/localeRendering.test.tsx(6 个用例,逐站点断言「应用语言决定输出」;宿主渲染结果与两个受支持语言都相同时会 throw,避免空断言通过),并在 packages/ui/src/components/discover/DiscoverView.test.tsx 追加 1 个同型用例。

修复前(把 8 个源文件还原为 3a17eca6 的原始字节后重跑):

$ bun test packages/ui/src/i18n/localeRendering.test.tsx
 0 pass / 6 fail

  DataFreshness        Expected: "09:20:00 AM"         Received: "Longbridge · Updated 09:20:00"
  EvidenceList         Expected: "09:20:00 AM"         Received: "claimQuote · 09:20:00"
  WhatChangedSection   Expected: "2024年11月21日"       Received: "What ChangedSince 2024/11/21No material changes."
  ResearchReportView   Expected: "2024年11月21日 09:20" Received: "… · 2024/11/21 09:20:00Evidence…"
  ThesisImpactList     Expected: "2024年11月21日"       Received: "Weakened2024/11/21weakened by a downgrade"
  ConnectionCard       Expected: "2024年11月21日 09:20" Received: "…Portfolio ✓2024/11/21 09:20:00"

$ bun test packages/ui/src/components/discover/DiscoverView.test.tsx
 12 pass / 1 fail
  Expected to contain: "2023年11月14日 22:13"
  Received: "…Strong Momentum2023/11/14 22:13:20 · 1 candidateReopen"

修复后:localeRendering 6 pass / 0 fail;DiscoverView 13 pass / 0 fail。

受影响目录回归(bun test --isolate,逐文件):

文件 修复前 修复后
components/primitives/DataFreshness.test.tsx 3 pass / 0 fail 3 pass / 0 fail
components/research/WhatChangedSection.test.tsx 6 pass / 0 fail 6 pass / 0 fail
components/evaluation/evaluation.test.tsx 5 pass / 0 fail 5 pass / 0 fail
i18n/localeRendering.test.tsx 0 pass / 6 fail 6 pass / 0 fail
components/discover/DiscoverView.test.tsx 12 pass / 1 fail 13 pass / 0 fail
合计 — 33 pass / 0 fail

其余校验:

  • bun run i18n:check ⇒ key parity: en-US 1487 keys ≡ zh-CN 1487 keys (0 issues)(零新增 key)。
  • bun run typecheck(packages/ui ⇒ tsc --noEmit)exit 0。
  • 未运行:apps/electron 的 e2e 套件(本机无法跑,见 issue e2e: 平台专属的 Electron 二进制路径与 POSIX shell 清理,让 harness 在 Windows 上无法运行 #223);全量 bun test --isolate 未跑,本次只跑改动工作区的聚焦套件。
  • 上游 PR checks(.github/workflows/pr.yml)当前 state=disabled_manually,新建 PR 不会产生 check run,因此以上均为本地等价命令结果,非 CI 结论。

UI 变化

文本内容随应用语言变化,布局不变:

  • DataFreshness:Longbridge · Updated 09:20:00 → 应用语言为 en-US 时 09:20:00 AM
  • WhatChangedSection:2024/11/21 → 2024年11月21日(zh-CN)
  • DiscoverView 历史运行行:2023/11/14 22:13:20 → 2023年11月14日 22:13(zh-CN)

范围

…t locale

Eight UI components handed the locale argument straight to the runtime
(toLocaleString / toLocaleDateString / toLocaleTimeString), so their output
was decided by the host OS date pattern instead of the active UI locale. This
is wrong even when both agree: a zh-CN app on a Chinese Windows renders
2024/11/21 where the unified layer renders 2024年11月21日.

format.ts already states that every date/time value must render through it
("so the UI locale controls presentation"), and lib/money.ts states "the OS
locale no longer participates" (issue helsome#86). Each call site now uses the
formatter whose options match the inline ones item by item
(formatDate / formatDateTime / formatMarketTime): no visible shape change, and
no epoch unit conversion is involved.

Add packages/ui/src/i18n/localeRendering.test.tsx (6 cases) as a guard, and one
matching case in DiscoverView.test.tsx. Pre-fix: localeRendering 0 pass / 6
fail and DiscoverView 12 pass / 1 fail. Post-fix: 6 pass / 0 fail and
13 pass / 0 fail. The three pre-existing suites in the affected directories
report 3/0, 6/0 and 5/0 both with and without the fix (A/B checked by restoring
the main bytes), so there is no behavioural regression.

Closes helsome#292

@helsome helsome left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

生产日期格式化方向合理,但本轮独立验证发现新增测试清理问题:精确 HEAD 的相关非隔离组合测试为6 pass / 27 fail / 1 error;--isolate 后仍33 pass / 1 error、exit 1,localeRendering 测试销毁 DOM 后仍有异步 React 更新。实际 main 对应既有四组测试26/0、exit 0。请在恢复全局 DOM 前完整卸载 root 并处理待执行更新,确保相关组合和全 UI 测试无未捕获错误。此外当前已合 #297,与 EvaluationSettingsTab.tsx 存在冲突,更新时保留错误翻译解析和本 PR 日期格式化两者。

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ui: 8 个组件内联 toLocale* 渲染日期/时间,输出跟随宿主 OS 区域而非应用语言

2 participants