Skip to content

ui: 告警与自动化列表的状态筛选把「All」写死,zh-CN 下出现「All / 生效中 / 已暂停」中英混排 #282

Description

@wxrbyte

现象

告警列表与自动化规则列表各有一个「状态筛选」下拉框,三个选项里有两个走译文、一个写死英文,于是 zh-CN 界面下的下拉框是中英混排:

┌─────────────┐
│ All         │   ← 写死的字面量
│ 生效中       │   ← t('alerts.status.active')
│ 已暂停       │   ← t('alerts.status.paused')
└─────────────┘

两处(同一个模板、同一个根因):

文件 行 同一 <select> 里已本地化的兄弟选项
packages/ui/src/components/alert/AlertList.tsx 90 t('alerts.status.active')、t('alerts.status.paused')
packages/ui/src/components/automation/AutomationRulesView.tsx 94 t('automation.status.active')、t('automation.status.paused')
<option value="all">All</option>                                  {/* 写死 */}
<option value="active">{t('alerts.status.active')}</option>       {/* 已本地化 */}
<option value="paused">{t('alerts.status.paused')}</option>       {/* 已本地化 */}

这不是「暂时没翻译」,而是漏用了已经存在的键:两个语言的 common 命名空间里早就有这一条 ——

// packages/i18n/src/locales/en-US/common.ts:33
all: 'All',
// packages/i18n/src/locales/zh-CN/common.ts:36
all: '全部',

复现

新增两个断言文件(各 1 例),用 withI18n(..., 'zh-CN') 以真实 zh-CN 资源渲染,断言下拉框第一项取自 common.all:

const zh = createSyncI18n({ locale: 'zh-CN' });
const expected = zh.t('common.all');
expect(expected).toBe('全部');   // 钉住资源值,避免键被搬走后断言静默通过
...
expect(option?.textContent).toBe(expected);

在未修复的 main 上运行:

(fail) AlertList.test.tsx > renders the status filter "all" option from the shared locale key
  Expected: "全部"      Received: "All"

(fail) AutomationRulesView.statusFilter.test.tsx > renders the status filter "all" option from the shared locale key
  Expected: "全部"      Received: "All"

 0 pass / 2 fail

根因

issue #86「Unify currency formatting onto the i18n locale policy」把货币格式化收敛到了 lib/money.ts → @finagent/i18n,那一轮的迁移遗漏是格式化通路(同一份数值用不同方式渲染,见 #258 / #278)。本条是另一类遗漏:界面文案里残留了字面量,而对应的键在 common 命名空间里早已存在 —— 即「漏用键」,不是「漏迁通路」。

证据是同一个 <select> 内的兄弟选项全部走了 t(...),且 t('common.<key>') 在本仓库是既有约定(packages/ui/src/components 下 20 余处),其中 packages/ui/src/components/alert/AlertForm.tsx:216 就在 AlertList 的同目录:

{t('common.cancel')}

也就是说:同目录、同命名空间、同一种键的用法就在旁边,只有这两行的下拉框第一项被漏掉。

本仓库自带的扫描脚本也独立指向了这两行。bun scripts/i18n-audit.ts 报出 6 个候选,除 4 个品牌名(Folio / Folio Research,属合法不翻译)外,剩下的正是这两处 All:

i18n-audit: scanned 185 files, 6 candidate literals (6 suspicious)
  packages\ui\src\components\alert\AlertList.tsx:90 [jsx-text] "All"
  packages\ui\src\components\automation\AutomationRulesView.tsx:94 [jsx-text] "All"

修复方案

两行,直接用既有键:

-              <option value="all">All</option>
+              <option value="all">{t('common.all')}</option>

两个组件都用裸 useTranslation()(不带命名空间),因此 t('common.all') 直接命中扁平键空间,无需新增 key、无需改 locale 文件、无需改组件签名。

影响面

  • 不新增/不修改任何 i18n 键:bun run i18n:check 修复前后均为 en-US 1487 keys ≡ zh-CN 1487 keys (0 issues)。
  • 改动面:2 个源文件各 1 行,共 2 行。
  • 测试:新增两个独立文件,刻意不改既有的 AutomationRulesView.test.tsx —— 该文件正被 test(automation): RuleCard 补齐 material_update 与 incomplete 状态展示测试 #194 在尾部追加(@@ -154,6 +154,68 @@),避开可减少合并冲突。AlertList.tsx 此前没有任何测试文件,本次顺带补上。
  • 未纳入:i18n-audit 的其余 4 个候选(Folio / Folio Research)是品牌名,按该脚本自己的说明属「合法不翻译」,不是缺陷。

验证

TDD 顺序:先写测试 → 未修复代码上 0 pass / 2 fail → 修复 → 2 pass / 0 fail。

# 修复前
(fail) ... Expected: "全部"  Received: "All"   ×2
 0 pass / 2 fail

# 修复后
 2 pass / 0 fail / 4 expect() calls

回归对照(全部为 main 上可复现的命令):

命令 修复前 修复后
本 PR 新增的两个断言文件 0 pass / 2 fail 2 pass / 0 fail
bun test packages/ui/src/components/automation/AutomationRulesView.test.tsx --isolate 5 pass / 0 fail 5 pass / 0 fail
bun run i18n:check passed(1487 ≡ 1487) passed(1487 ≡ 1487)
bun scripts/i18n-audit.ts 6 候选(含这两处 All) 4 候选(仅剩品牌名)
bun run typecheck 5/5 exit 0 5/5 exit 0

Activity

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

Metadata

Metadata

Assignees

Labels

claimedClaimed by a contributor and currently in progress

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions