@captain_z/zsk-skills 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +263 -0
  2. package/bundles.yaml +104 -0
  3. package/design-handoff/.gitkeep +0 -0
  4. package/design-handoff/figma-to-code/SKILL.md +265 -0
  5. package/design-handoff/ue-mcp/SKILL.md +184 -0
  6. package/frontend/.gitkeep +0 -0
  7. package/frontend/a11y-web/SKILL.md +169 -0
  8. package/frontend/api-contract-ts/SKILL.md +275 -0
  9. package/frontend/css-bem/SKILL.md +268 -0
  10. package/frontend/design-frontend/SKILL.md +163 -0
  11. package/frontend/dor-dod-frontend/SKILL.md +114 -0
  12. package/frontend/feature-tasks-frontend/SKILL.md +246 -0
  13. package/frontend/i18n/SKILL.md +296 -0
  14. package/frontend/nfr-web/SKILL.md +258 -0
  15. package/frontend/performance-web/SKILL.md +299 -0
  16. package/frontend/react-components/SKILL.md +211 -0
  17. package/frontend/react-naming/SKILL.md +224 -0
  18. package/frontend/review-frontend/SKILL.md +126 -0
  19. package/frontend/security-web/SKILL.md +286 -0
  20. package/frontend/spec-frontend/SKILL.md +141 -0
  21. package/frontend/testing-web/SKILL.md +252 -0
  22. package/frontend/typescript/SKILL.md +219 -0
  23. package/meta/.gitkeep +0 -0
  24. package/meta/philosophy/SKILL.md +221 -0
  25. package/package.json +24 -0
  26. package/quality/.gitkeep +0 -0
  27. package/quality/a11y-principles/SKILL.md +155 -0
  28. package/quality/code-hygiene/SKILL.md +126 -0
  29. package/quality/release/SKILL.md +209 -0
  30. package/quality/security-owasp/SKILL.md +157 -0
  31. package/quality/testing-pyramid/SKILL.md +173 -0
  32. package/sdlc/.gitkeep +0 -0
  33. package/sdlc/archive/SKILL.md +267 -0
  34. package/sdlc/bugfix/SKILL.md +181 -0
  35. package/sdlc/bugfix-tasks/SKILL.md +232 -0
  36. package/sdlc/coding/SKILL.md +177 -0
  37. package/sdlc/design/SKILL.md +299 -0
  38. package/sdlc/dor-dod/SKILL.md +120 -0
  39. package/sdlc/feature/SKILL.md +162 -0
  40. package/sdlc/proposal/SKILL.md +271 -0
  41. package/sdlc/refactor/SKILL.md +220 -0
  42. package/sdlc/refactor-tasks/SKILL.md +265 -0
  43. package/sdlc/reviewing/SKILL.md +197 -0
  44. package/sdlc/spec/SKILL.md +235 -0
  45. package/sdlc/task/SKILL.md +116 -0
  46. package/sdlc/task-evidence/SKILL.md +178 -0
  47. package/sdlc/task-structure/SKILL.md +153 -0
  48. package/sdlc/task-tracking/SKILL.md +192 -0
  49. package/sdlc/verify/SKILL.md +181 -0
  50. package/system/.gitkeep +0 -0
  51. package/system/adr/SKILL.md +169 -0
  52. package/system/architecture/SKILL.md +182 -0
  53. package/system/glossary/SKILL.md +141 -0
  54. package/system/nfr-baseline/SKILL.md +156 -0
  55. package/system/project-constraints-template/SKILL.md +241 -0
@@ -0,0 +1,246 @@
1
+ ---
2
+ name: zsk:feature-tasks-frontend
3
+ description: Frontend component/page feature task template — 7-category
4
+ mandatory (事实源对齐 / Props 契约 / UE-MCP 刷新 / UE 状态 / 测试 / 视觉回归 / 质量门禁). Enforces
5
+ TDD-first, contract-driven, evidence-before-assertion.
6
+ category: resource
7
+ domain: frontend
8
+ tier: optional
9
+ change_type: feature
10
+ related:
11
+ - ../../sdlc/task-structure/SKILL.md
12
+ - ../../sdlc/task-evidence/SKILL.md
13
+ - ../../sdlc/task-tracking/SKILL.md
14
+ - ../dor-dod-frontend/SKILL.md
15
+ - ../spec-frontend/SKILL.md
16
+ - ../testing-web/SKILL.md
17
+ - ../i18n/SKILL.md
18
+ triggers:
19
+ - frontend feature tasks
20
+ - component tasks template
21
+ - 7 category tasks
22
+ - Props UE state visual regression
23
+ ---
24
+
25
+ # Feature Tasks · Frontend(7 类强制任务)
26
+
27
+ > **用途**:前端**新功能**(组件 / 页面)的任务列表模板,直接复制到 `docs/{module}/tasks.md`
28
+ > **适用**:新组件 / 新页面 / 新能力 / 新接入点(有 UI 的变更)
29
+ > **非组件类 Feature**(接口 / 纯数据 / 构建工具):用 [`zsk:task-structure`](../../sdlc/task-structure/SKILL.md) 通用框架,不走本模板
30
+
31
+ ## 核心纪律
32
+
33
+ - **测试驱动优先**:能 TDD 就 TDD(`superpowers:test-driven-development`)
34
+ - **契约驱动**:Props / Event / API 类型引自 Spec,不自造
35
+ - **强制 7 类任务**:组件类需求至少覆盖下述 7 个类别
36
+ - **证据先于断言**:每任务完成必须有验证链接
37
+
38
+ ## 7 类强制任务
39
+
40
+ | # | 类别 | 目标 | 典型工作量 |
41
+ | --- | --- | --- | --- |
42
+ | 1 | 事实源对齐 | Spec / Design / API / UE 契约已冻结 | XS |
43
+ | 2 | Props / API 契约实现 | 类型引入 + Props + Event 回调 | S |
44
+ | 3 | UE / MCP 上下文刷新 | Figma 读取结果落盘到 ue-mcp.md | XS |
45
+ | 4 | UE 状态实现 | loading / empty / error / hover / focus / selected | M |
46
+ | 5 | 测试实现(TDD) | 单元 + 集成 + a11y | M |
47
+ | 6 | 视觉回归 | Figma 对照 + 差异记录 | S |
48
+ | 7 | 质量门禁准备 | lint / type-check / 覆盖率 / 三语 | XS |
49
+
50
+ ## 完整模板(复制到 `docs/{module}/tasks.md`)
51
+
52
+ ```md
53
+ # Tasks: {模块} — {需求名}
54
+
55
+ > 变更类型:Feature · 领域:Frontend(组件类)
56
+ > 模块唯一标识:{module-id}
57
+ > 关联文档:proposal.md / spec.md / design.md / api-contract.md / ue-mcp.md
58
+ > 责任人:@{handle}
59
+ > 日期:{YYYY-MM-DD}
60
+
61
+ ## 任务概述
62
+
63
+ {1–2 句话描述本轮迭代做什么、不做什么}
64
+
65
+ ## DoR(启动前门禁)
66
+
67
+ 见 `zsk:dor-dod` + `zsk:dor-dod-frontend` 清单,全勾方可启动。
68
+
69
+ - [ ] (逐项勾选)
70
+
71
+ ---
72
+
73
+ ## 任务列表
74
+
75
+ ### 1. 事实源对齐(XS / 0.5 人天)
76
+
77
+ 状态:`TODO`
78
+ 负责人:@{handle}
79
+ 覆盖 FR:无(前置对齐任务)
80
+
81
+ 子任务:
82
+ - [ ] 1.1 Spec / Design 冻结核对(FR/NFR/US/AC 编号齐全)
83
+ - [ ] 1.2 `{{config.paths.api_contracts}}` 下类型已同步后端
84
+ - [ ] 1.3 `ue-mcp.md` 已登记(有 Figma 输入时)
85
+ - [ ] 1.4 本轮涉及的 ADR 已 Accepted
86
+
87
+ 交付物:核对记录(本任务本身的勾选)
88
+ 验证:Spec / Design / api-contract / ue-mcp 文件 diff 链接
89
+
90
+ ---
91
+
92
+ ### 2. Props / API 契约实现(S / 1 人天)
93
+
94
+ 状态:`TODO`
95
+ 负责人:@{handle}
96
+ 覆盖 FR:FR-1, FR-2(示例,引自 spec.md)
97
+
98
+ 子任务:
99
+ - [ ] 2.1 从后端生成的 types 引入(`import type { XxxResponse } from '{{config.paths.api_contracts}}/...'`)
100
+ - [ ] 2.2 Props 接口声明(对照 spec.md `Component Public Contract`)
101
+ - [ ] 2.3 Event 回调实现(`onChange` / `onSelect` / ...)
102
+ - [ ] 2.4 service 层封装(引用 `{{config.paths.services_entry}}`,不新建 request 实例)
103
+
104
+ 交付物:组件 Props / Event 类型 · service 调用代码
105
+ 验证:单元测试覆盖类型边界 · 手动触发 event,log 捕获回调参数
106
+
107
+ ---
108
+
109
+ ### 3. UE / MCP 上下文刷新(XS / 0.5 人天)
110
+
111
+ 状态:`TODO`
112
+ 负责人:@{handle}
113
+ 覆盖 FR:NFR-4(i18n)、NFR-5(兼容性)
114
+
115
+ 子任务:
116
+ - [ ] 3.1 Figma MCP 读取当前 snapshot
117
+ - [ ] 3.2 结果落盘到 `docs/{module}/ue-mcp.md`(人读摘要)
118
+ - [ ] 3.3 `{{config.paths.design_assets}}/{module}/{snapshot}/description.md` 归档(完整快照)
119
+
120
+ 交付物:刷新后的 `ue-mcp.md` · description.md 快照
121
+ 验证:快照文件存在性检查
122
+
123
+ ---
124
+
125
+ ### 4. UE 状态实现(M / 2 人天)
126
+
127
+ 状态:`TODO`
128
+ 负责人:@{handle}
129
+ 覆盖 FR:FR-3, FR-4(示例)
130
+
131
+ 子任务:
132
+ - [ ] 4.1 loading / empty / error 三态(对照 spec.md UE 状态矩阵)
133
+ - [ ] 4.2 hover / focus-visible / selected / disabled
134
+ - [ ] 4.3 组件特有状态(search-hit / lazy-loading / ...)
135
+ - [ ] 4.4 键盘交互(Tab / Enter / Space / Esc / 方向键)
136
+
137
+ 交付物:所有状态的 JSX 实现 · 状态对应样式
138
+ 验证:逐一触发每个状态并截图 · 对照 Figma 状态矩阵
139
+
140
+ ---
141
+
142
+ ### 5. 测试实现(M / 2 人天,TDD 优先)
143
+
144
+ 状态:`TODO`
145
+ 负责人:@{handle}
146
+ 覆盖 FR:全体 P0 FR 的验收路径
147
+
148
+ 子任务:
149
+ - [ ] 5.1 单元测试(Props / hooks / utils / 归一化函数)
150
+ - [ ] 5.2 集成测试(组件 + service + 路由)
151
+ - [ ] 5.3 a11y 测试(axe-core + 键盘矩阵回归)
152
+ - [ ] 5.4 AC 到测试的映射(每条 AC 至少一条测试)
153
+
154
+ 交付物:`__tests__/` 目录下的测试文件 · 覆盖率报告
155
+ 验证:`test` 全绿 + 覆盖率 ≥ `{{config.quality.testing.unit_coverage_min}}%`;新增代码 `{{config.quality.testing.new_code_coverage}}%`
156
+
157
+ 详细工具链见 [`zsk:testing-web`](../testing-web/SKILL.md)
158
+
159
+ ---
160
+
161
+ ### 6. 视觉回归(S / 1 人天)
162
+
163
+ 状态:`TODO`
164
+ 负责人:@{handle}
165
+ 覆盖 FR:NFR-2(a11y)、所有视觉 FR
166
+
167
+ 子任务:
168
+ - [ ] 6.1 Figma 关键节点截图(参考 `zsk:figma-to-code` 的还原判定)
169
+ - [ ] 6.2 实现截图
170
+ - [ ] 6.3 差异记录到 `docs/{module}/archive/{YYYY-MM-DD}-visual-regression/`
171
+ - [ ] 6.4 差异 > 还原判定阈值 → 修复再截 / 在 design.md ADR 解释保留
172
+
173
+ 交付物:before (Figma) / after (实现) 截图对照 · 差异记录
174
+ 验证:对照文件存在 + 差异在阈值内
175
+
176
+ ---
177
+
178
+ ### 7. 质量门禁准备(XS / 0.5 人天)
179
+
180
+ 状态:`TODO`
181
+ 负责人:@{handle}
182
+ 覆盖 FR:兜底 — FR 覆盖矩阵全绿
183
+
184
+ 子任务:
185
+ - [ ] 7.1 `lint` / `type-check` 零 error(`{{config.scripts.lint}}` / `{{config.scripts.type_check}}`)
186
+ - [ ] 7.2 `test` 覆盖率达标(`{{config.scripts.test}}`)
187
+ - [ ] 7.3 i18n 多语(语言清单 `{{config.i18n.languages}}`)同 commit 同步
188
+ - [ ] 7.4 依赖无高危(按栈扫描)
189
+ - [ ] 7.5 包体积增量 < 10%(或有 ADR 豁免)
190
+
191
+ 交付物:门禁证据(见 [`zsk:task-evidence`](../../sdlc/task-evidence/SKILL.md) 的校验记录模板)
192
+ 验证:整表勾选完整,每行有链接
193
+
194
+ ---
195
+
196
+ ## 任务依赖
197
+
198
+ \`\`\`mermaid
199
+ graph LR
200
+ T1[1.事实源对齐] --> T2[2.Props/API]
201
+ T1 --> T3[3.UE/MCP 刷新]
202
+ T2 --> T4[4.UE 状态]
203
+ T3 --> T4
204
+ T2 --> T5[5.测试]
205
+ T4 --> T6[6.视觉回归]
206
+ T6 --> T7[7.质量门禁]
207
+ \`\`\`
208
+
209
+ ## 阻塞记录 / 里程碑
210
+
211
+ 见 [`zsk:task-tracking`](../../sdlc/task-tracking/SKILL.md) 模板。
212
+
213
+ ## 总工作量
214
+
215
+ - T-shirt:`M`
216
+ - 人天:`7.0`
217
+ - 工期:`{按并行度压缩的日历天}`
218
+
219
+ ## 证据记录
220
+
221
+ DoR / DoD / FR 覆盖矩阵 / AC 勾选 / 校验记录 → 见 [`zsk:task-evidence`](../../sdlc/task-evidence/SKILL.md) 的模板。
222
+
223
+ ---
224
+
225
+ ## DoD(关闭前门禁)
226
+
227
+ 见 `zsk:dor-dod` + `zsk:dor-dod-frontend` 清单,全勾方可合并。
228
+ ```
229
+
230
+ ## Skip 规则
231
+
232
+ | 场景 | 可跳过的任务 |
233
+ | --- | --- |
234
+ | 文案微调 | 3(UE/MCP)、6(视觉回归)可简化 |
235
+ | 仅接入现有组件 | 4(UE 状态)可简化(只做新增状态) |
236
+ | 后端已稳定上线 | 2 可部分提前(类型先同步,Props 跟 UE 一起做) |
237
+
238
+ **不能省**:1(事实源对齐)、5(测试)、7(质量门禁)。
239
+
240
+ ## 反模式(禁止)
241
+
242
+ - ❌ 跳过事实源对齐直接写代码(Spec / Design 未冻结 → 必返工)
243
+ - ❌ 测试放到最后一把梭哈(TDD 精神丧失)
244
+ - ❌ 视觉回归只截 after 不截 before
245
+ - ❌ 质量门禁"事后补证据 PR"
246
+ - ❌ 任务 > 2 人天不拆(blocker 影响扩大)
@@ -0,0 +1,296 @@
1
+ ---
2
+ name: zsk:i18n
3
+ description: Frontend module i18n implementation — unified entry (i18n.t with
4
+ mandatory fallback), key namespace structure (namespace.module.leaf_key),
5
+ three-language sync rule (commit-atomic), ICU MessageFormat for plural/select,
6
+ Intl.* for date/number/currency, RTL declaration, React Trans component for
7
+ JSX interpolation. Replaces standards/i18n for frontend scope.
8
+ category: standard
9
+ domain: frontend
10
+ tier: optional
11
+ related:
12
+ - ../react-naming/SKILL.md
13
+ - ../a11y-web/SKILL.md
14
+ - ../../system/nfr-baseline/SKILL.md
15
+ triggers:
16
+ - i18n implementation
17
+ - key namespace
18
+ - fallback baseline
19
+ - ICU plural
20
+ - three language sync
21
+ - Intl.DateTimeFormat
22
+ - RTL
23
+ - Trans component
24
+ ---
25
+
26
+ # i18n · 国际化实现(Web)
27
+
28
+ > **范围**:前端 i18n 的**具体实现** — 入口 / key 命名 / 三语同步 / ICU / Intl.* / JSX 插值 / RTL
29
+ > **前置纪律**:国际化**开发当下完成**,禁止"先中文、最后补翻译"
30
+ > **基线语言**:`{{config.i18n.languages}}` 的第一个(通常 `zh_CN`)是事实源
31
+
32
+ ## 1. 统一入口(强制)
33
+
34
+ ### 标准调用
35
+
36
+ ```ts
37
+ import { i18n } from '{{config.i18n.entry}}';
38
+
39
+ i18n.t('{{config.i18n.namespace}}.order.empty', '暂无订单');
40
+ i18n.t('{{config.i18n.namespace}}.order.total', '共{total}条', { total });
41
+ ```
42
+
43
+ ### 签名
44
+
45
+ ```ts
46
+ i18n.t(key: string, fallback: string, values?: Record<string, unknown>): string
47
+ ```
48
+
49
+ - `key`:必填,遵循命名规则(见下节)
50
+ - `fallback`:**必填**,**必须等于基线语言文案**
51
+ - `values`:可选,占位符替换 / ICU 参数
52
+
53
+ ### 禁止
54
+
55
+ - ❌ 缺 fallback:`i18n.t('order.empty')`
56
+ - ❌ fallback 非基线语言:`i18n.t('order.empty', 'No data')`(基线是中文时)
57
+ - ❌ 非标准签名:`i18n.t({ k: 'x', d: 'y' })`
58
+ - ❌ 跳过统一入口直接调底层 SDK
59
+ - ❌ FC 组件函数体硬编码基线语言文案(`<div>暂无订单</div>` 不经 i18n)
60
+
61
+ ### 全局 i18n 工具边界
62
+
63
+ `{{config.i18n.entry}}` **只允许**:
64
+
65
+ - `i18n.t`
66
+ - `i18n.getI18nStringToObj`
67
+ - `i18n.getValueByI18n`
68
+
69
+ **禁止**掺入:领域化命名 / 业务映射表 / UI 副作用 / 业务逻辑。
70
+
71
+ ## 2. Key 命名规则
72
+
73
+ ### 结构
74
+
75
+ ```
76
+ {{config.i18n.namespace}}.<module>.<leaf_key>
77
+ ```
78
+
79
+ | 段 | 约定 | 示例 |
80
+ | --- | --- | --- |
81
+ | namespace | 项目配置决定 | `app` / `admin` |
82
+ | module | lowerCamelCase | `order` / `userProfile` |
83
+ | leaf_key | snake_case,语义优先 | `empty` / `load_failed` / `btn_confirm` |
84
+
85
+ ### 示例
86
+
87
+ ```
88
+ app.order.list.empty
89
+ app.order.list.error.network
90
+ app.userProfile.avatar.alt
91
+ app.common.btn.confirm
92
+ ```
93
+
94
+ ### 硬规则
95
+
96
+ - 全小写 + `.` 分隔;不含中文、空格、下划线在层级分隔位
97
+ - **一旦发布不可重命名**;必须调整时:
98
+ 1. 新旧 key 同时存在一段时间
99
+ 2. 全量迁移所有语言文件 + 所有调用点
100
+ 3. 迁移完成移除旧 key(独立 commit)
101
+ - key 语义优先于位置(不要 `page1.btn3`,用 `order.detail.submit`)
102
+
103
+ ### 与 react-naming 对齐
104
+
105
+ 见 [`react-naming`](../react-naming/SKILL.md) 的 i18n key 命名条目。
106
+
107
+ ## 3. 语言文件规范
108
+
109
+ ### 基线 + 非基线
110
+
111
+ - **基线语言**(通常 `zh_CN`):事实源,不允许空字符串占位
112
+ - **非基线语言**:不得缺 key;暂时无法翻译**必须占位 + 任务追踪**
113
+
114
+ ### JSON 规则
115
+
116
+ - 必须合法:禁止注释、禁止重复 key
117
+ - 按模块分组并字母序排序,方便 diff
118
+ - 所有语言文件 key 结构**完全一致**
119
+
120
+ ### 三语同步(硬门禁)
121
+
122
+ **同一 PR 内**必须同时提交所有语言文件(见 `{{config.i18n.languages}}`):
123
+
124
+ - 新增 key → 每种语言都加
125
+ - 删除 key → 每种语言都删
126
+ - 改文案 → 对应语言改(fallback 同步改)
127
+
128
+ CI 必查:语言文件 key 数量一致 + key 路径一致。
129
+
130
+ ## 4. ICU MessageFormat
131
+
132
+ 涉及**复数 / 性别 / 枚举**文案时使用 ICU,不得用字符串拼接。
133
+
134
+ ### 复数(plural)
135
+
136
+ ```ts
137
+ i18n.t(
138
+ 'app.order.count',
139
+ '{count, plural, =0 {无订单} one {# 个订单} other {# 个订单}}',
140
+ { count },
141
+ );
142
+ ```
143
+
144
+ ### 选择(select)
145
+
146
+ ```ts
147
+ i18n.t(
148
+ 'app.user.status',
149
+ '{status, select, active {活跃} inactive {禁用} other {未知}}',
150
+ { status },
151
+ );
152
+ ```
153
+
154
+ ### 硬规则
155
+
156
+ - 所有语言文件**同步使用 ICU 结构**(不得某语言退化为拼接)
157
+ - `other` 分支必填(兜底)
158
+ - 不允许在 TS 代码里手写 `if (count === 0) ... else if (count === 1) ...` 后调用 `i18n.t`
159
+
160
+ ## 5. Intl.* · 日期 / 数字 / 货币
161
+
162
+ ### 禁止硬编码
163
+
164
+ - ❌ `YYYY-MM-DD` 硬格式化
165
+ - ❌ `toLocaleString()` 不传 locale(不同环境行为不一致)
166
+ - ❌ `${price.toFixed(2)}元` 假定货币 / 区域
167
+
168
+ ### 正确用法
169
+
170
+ ```ts
171
+ // 日期
172
+ new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short' }).format(date);
173
+
174
+ // 数字
175
+ new Intl.NumberFormat(locale, { minimumFractionDigits: 2 }).format(value);
176
+
177
+ // 货币
178
+ new Intl.NumberFormat(locale, { style: 'currency', currency: 'CNY' }).format(price);
179
+
180
+ // 相对时间
181
+ new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(-1, 'day'); // "1 天前"
182
+ ```
183
+
184
+ `locale` 从 i18n 运行时读取,不在组件内硬写。
185
+
186
+ ### 时区
187
+
188
+ - 默认跟随浏览器
189
+ - 业务必须指定(如"始终 UTC+8")→ 在 `spec.md` NFR 声明,并使用 `timeZone` 选项
190
+
191
+ ## 6. React JSX 插值
192
+
193
+ ### 简单占位
194
+
195
+ ```tsx
196
+ <div>{i18n.t('app.order.total', '共{total}条', { total })}</div>
197
+ ```
198
+
199
+ ### 复杂插值(Trans 组件)
200
+
201
+ 文本中需要嵌入 JSX(链接、加粗、图标)时:
202
+
203
+ ```tsx
204
+ <Trans
205
+ i18nKey="app.order.read_terms"
206
+ defaults="请阅读<link>使用条款</link>后提交"
207
+ components={{ link: <a href="/terms" /> }}
208
+ />
209
+ ```
210
+
211
+ 优于字符串拼接 + `dangerouslySetInnerHTML`。
212
+
213
+ ### 禁止
214
+
215
+ - ❌ 字符串拼接构造可见文案(`'共' + total + '条'`)
216
+ - ❌ 用 `dangerouslySetInnerHTML` 注入翻译文本(XSS 风险)
217
+
218
+ ## 7. 可访问属性的文案
219
+
220
+ JSX 里**所有**对用户可见或被读屏读出的字符串都要 i18n:
221
+
222
+ | 属性 | 示例 |
223
+ | --- | --- |
224
+ | `aria-label` | `aria-label={i18n.t('app.modal.close', '关闭')}` |
225
+ | `alt` | `alt={i18n.t('app.avatar.alt', '用户头像')}` |
226
+ | `title` | `title={i18n.t(...)}` |
227
+ | `placeholder` | `placeholder={i18n.t(...)}` |
228
+ | 错误消息 | `form.setError(...)` 的消息 |
229
+ | Toast / 通知 | `notify({ message: i18n.t(...) })` |
230
+
231
+ 与 [`a11y-web`](../a11y-web/SKILL.md) 的 aria 实现配合。
232
+
233
+ ## 8. RTL(右到左)
234
+
235
+ ### 当前模块不支持 RTL
236
+
237
+ 在 `spec.md` NFR 声明"暂不支持 RTL(语言清单不含 ar / he)"。
238
+
239
+ ### 支持 RTL 时
240
+
241
+ 1. 使用 **CSS 逻辑属性**:
242
+ - `margin-inline-start` / `padding-inline-end`(不用 `left/right`)
243
+ - `inset-inline-start` / `border-inline-*`
244
+ 2. 图标方向适配(箭头、翻页、返回)
245
+ 3. `dir="rtl"` 通过 `<html dir>` 或容器属性注入
246
+ 4. 测试:Playwright 切换 locale 验证布局镜像
247
+
248
+ ## 9. 组件实践
249
+
250
+ ### Hook 封装(减少 import)
251
+
252
+ ```ts
253
+ // useT.ts
254
+ export function useT() {
255
+ return { t: i18n.t };
256
+ }
257
+
258
+ // 组件
259
+ const { t } = useT();
260
+ <button>{t('app.common.btn.confirm', '确认')}</button>
261
+ ```
262
+
263
+ 不是硬性要求,项目内一致即可。
264
+
265
+ ### 避免在渲染时做重活
266
+
267
+ `Intl.DateTimeFormat` 实例化有开销。高频场景**缓存实例**:
268
+
269
+ ```ts
270
+ const dateFormatter = useMemo(
271
+ () => new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }),
272
+ [locale],
273
+ );
274
+ ```
275
+
276
+ ## 反模式(禁止)
277
+
278
+ - ❌ "先中文、最后补翻译"
279
+ - ❌ 字符串拼接构造可见文案
280
+ - ❌ 缺 fallback 或 fallback ≠ 基线文案
281
+ - ❌ 三语不同 commit 提交
282
+ - ❌ 复数 / 选择用 if / switch 而非 ICU
283
+ - ❌ 日期 / 数字硬编码
284
+ - ❌ 把业务逻辑塞进 i18n 工具
285
+ - ❌ `aria-label` / `alt` / `placeholder` 硬编码
286
+
287
+ ## 质量门禁
288
+
289
+ - [ ] 所有可见文案 / a11y 属性走 `i18n.t`
290
+ - [ ] 每次 `i18n.t` 调用有合法 fallback
291
+ - [ ] Key 结构符合 `{{config.i18n.namespace}}.<module>.<leaf_key>`
292
+ - [ ] 三语(`{{config.i18n.languages}}`)同 commit 同步
293
+ - [ ] 复数 / 选择用 ICU
294
+ - [ ] 日期 / 数字用 `Intl.*`
295
+ - [ ] RTL 支持在 `spec.md` 有声明
296
+ - [ ] CI 通过语言文件一致性检查