@harmonyos-arkts/d2h 0.0.0-stage → 0.1.1

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 (74) hide show
  1. package/.claude-plugin/marketplace.json +13 -0
  2. package/.claude-plugin/plugin.json +7 -0
  3. package/README.md +177 -2
  4. package/agents/android-to-hmos-00-orchestrator.md +312 -0
  5. package/agents/d2h.md +168 -0
  6. package/bin/install-opencode.mjs +57 -0
  7. package/install-opencode.sh +160 -0
  8. package/opencode/agents.json +16 -0
  9. package/package.json +43 -4
  10. package/schemas/android-source-manifest.schema.json +25 -0
  11. package/schemas/checkpoint-provenance.schema.json +67 -0
  12. package/schemas/final-acceptance.schema.json +29 -0
  13. package/schemas/managed-evidence-index.schema.json +15 -0
  14. package/schemas/managed-evidence.schema.json +114 -0
  15. package/schemas/migration-config.schema.json +51 -0
  16. package/schemas/migration-report-index.schema.json +39 -0
  17. package/schemas/migration-report-item.schema.json +51 -0
  18. package/schemas/migration-report-summary.schema.json +25 -0
  19. package/schemas/migration-status.schema.json +202 -0
  20. package/schemas/preflight.schema.json +60 -0
  21. package/schemas/source-order-audit.schema.json +16 -0
  22. package/schemas/source-provenance-event.schema.json +19 -0
  23. package/schemas/spec-app-shard.schema.json +45 -0
  24. package/schemas/spec-fact-corrections-shard.schema.json +13 -0
  25. package/schemas/spec-features-shard.schema.json +43 -0
  26. package/schemas/spec-index.schema.json +26 -0
  27. package/schemas/spec-interactions-shard.schema.json +40 -0
  28. package/schemas/spec-page.schema.json +98 -0
  29. package/schemas/spec-pages-index.schema.json +1 -0
  30. package/schemas/spec-unresolved-shard.schema.json +1 -0
  31. package/schemas/task-envelope.schema.json +55 -0
  32. package/schemas/task-plan.schema.json +123 -0
  33. package/skills/android2hmos_resources_convert/SKILL.md +162 -0
  34. package/skills/android2hmos_resources_convert/references/image-conversion-rules.md +230 -0
  35. package/skills/android2hmos_resources_convert/references/svg-fix-patterns.md +175 -0
  36. package/skills/android2hmos_resources_convert/references/xml-drawable-to-svg-rules.md +513 -0
  37. package/skills/appgraph-rule-audit/SKILL.md +58 -0
  38. package/skills/appgraph-rule-audit/references/audit-contract.md +47 -0
  39. package/skills/appgraph-rule-audit/references/recommendation-schema.md +41 -0
  40. package/skills/appgraph-rule-audit/schemas/rule-opportunities.schema.json +125 -0
  41. package/skills/arkts-app-identity/SKILL.md +238 -0
  42. package/skills/arkts-i18n/SKILL.md +496 -0
  43. package/skills/arkts-i18n/evals/evals.json +84 -0
  44. package/skills/arkts-i18n/references/code-examples.md +302 -0
  45. package/skills/arkts-i18n/references/common-pitfalls.md +391 -0
  46. package/skills/arkts-i18n/references/dynamic-language-switch.md +604 -0
  47. package/skills/arkts-i18n/references/hardcoded-string-scanner.md +348 -0
  48. package/skills/arkts-i18n/references/language-codes.md +104 -0
  49. package/skills/arkts-i18n/references/resource-file-structure.md +775 -0
  50. package/skills/arkts-i18n/references/static-vs-dynamic.md +242 -0
  51. package/skills/arkts-i18n/references/v1-compat.md +244 -0
  52. package/skills/arkts-i18n/scripts/audit_i18n_completeness.sh +174 -0
  53. package/skills/arkts-icon-sizing/SKILL.md +211 -0
  54. package/skills/arkts-icon-sizing/scripts/icon_audit.py +131 -0
  55. package/skills/arkts-icon-sizing/scripts/icon_autofix.py +88 -0
  56. package/skills/arkts-icon-sizing/scripts/icon_dims.py +179 -0
  57. package/skills/arkts-icon-sizing/scripts/icon_fix.py +119 -0
  58. package/skills/arkts-mvvm-architecture/SKILL.md +613 -0
  59. package/skills/harmonyos-migration-playbook/SKILL.md +56 -0
  60. package/skills/harmonyos-migration-playbook/agents/openai.yaml +7 -0
  61. package/skills/harmonyos-migration-playbook/references/arkts-compile.md +24 -0
  62. package/skills/harmonyos-migration-playbook/references/harmony-runtime.md +53 -0
  63. package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +45 -0
  64. package/skills/harmonyos-migration-playbook/references/protocol-e2e.md +23 -0
  65. package/skills/harmonyos-migration-playbook/references/ui-automation.md +52 -0
  66. package/skills/harmonyos-migration-playbook/references/windows-environment.md +38 -0
  67. package/skills/maintaining-migration-report/SKILL.md +155 -0
  68. package/skills/native-library-substitution/SKILL.md +385 -0
  69. package/skills/native-library-substitution/references/native-library-substitution.json +56906 -0
  70. package/skills/native-library-substitution/references/native-library-substitution.md +163 -0
  71. package/skills/preparing-migration-workspace/SKILL.md +124 -0
  72. package/skills/preparing-migration-workspace/toolchain.json +43 -0
  73. package/skills/reviewing-migration-process/SKILL.md +62 -0
  74. package/src/install-opencode.mjs +110 -0
@@ -0,0 +1,302 @@
1
+ # 代码仓验证示例(V2)
2
+
3
+ > 基于代码仓实际实现的完整代码示例。**本项目锁 V2**:所有 struct 用 `@ComponentV2`,状态用 `@Local / @Param / @ObservedV2`。i18n API(`resourceManager.getStringByNameSync` / `$r()` 等)与装饰器版本无关。
4
+ >
5
+ > V1 老项目兼容写法见 [`v1-compat.md`](./v1-compat.md)。
6
+
7
+ ---
8
+
9
+ ## ✅ 已验证:获取字符串资源
10
+
11
+ 基于 `entry/src/main/ets/components/dialogs/ColumnsDialog.ets:9-22`
12
+
13
+ ```typescript
14
+ import { common } from '@kit.AbilityKit'
15
+ import { hilog } from '@kit.PerformanceAnalysisKit'
16
+
17
+ const DOMAIN = 0xFF00
18
+ const TAG = 'ColumnsDialog'
19
+
20
+ @CustomDialog
21
+ export struct ColumnsDialog {
22
+ dialogController: CustomDialogController
23
+
24
+ aboutToAppear(): void {
25
+ try {
26
+ const context = getContext(this) as common.UIAbilityContext
27
+ const resourceManager = context.resourceManager
28
+
29
+ // ✅ 已验证:同步获取字符串资源
30
+ const str = resourceManager.getStringByNameSync('dialog_column')
31
+ hilog.info(DOMAIN, TAG, `Loaded string: ${str}`)
32
+ } catch (err) {
33
+ const error = err as Error
34
+ hilog.error(DOMAIN, TAG, `Failed to load string: ${error.message}`)
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ **关键点**:
41
+ 1. ✅ 使用 `getContext(this)` 获取 context
42
+ 2. ✅ 使用 `resourceManager.getStringByNameSync()` 同步获取字符串
43
+ 3. ✅ 使用 try-catch 处理错误
44
+
45
+ ---
46
+
47
+ ## ✅ 已验证:语言环境检测
48
+
49
+ > 基于代码仓 `components/dialogs/ColumnsDialog.ets:7-16` 的实际实现
50
+
51
+ **适用场景**:
52
+ - 需要根据语言执行不同逻辑(如复数处理)
53
+ - 不依赖未知 API 的可靠方案
54
+ - 判断当前加载的是中文还是英文资源
55
+
56
+ **完整实现**:
57
+
58
+ ```typescript
59
+ @ComponentV2
60
+ export struct ColumnsDialog {
61
+ /**
62
+ * 检测当前语言环境是否为中文
63
+ * @returns true 表示中文环境,false 表示非中文环境
64
+ */
65
+ private isChineseLocale(): boolean {
66
+ const context = getContext(this)
67
+ const resourceManager = context.resourceManager
68
+ try {
69
+ // 获取任意包含中文的字符串
70
+ const str = resourceManager.getStringByNameSync('dialog_column')
71
+ // 检测中文字符(Unicode 范围:0x4e00 - 0x9fa5)
72
+ return /[\u4e00-\u9fa5]/.test(str)
73
+ } catch {
74
+ return false // 默认非中文环境
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ **原理解释**:
81
+ - `[\u4e00-\u9fa5]` 匹配中文常用汉字的 Unicode 范围
82
+ - 如果字符串包含中文字符,说明加载了中文资源
83
+ - 无需依赖未知 API,可靠性高
84
+
85
+ **使用示例**:
86
+
87
+ ```typescript
88
+ if (this.isChineseLocale()) {
89
+ // 中文环境:无需复数处理
90
+ label = `${count} 列`
91
+ } else {
92
+ // 英文环境:需要复数处理
93
+ label = count === 1 ? '1 column' : `${count} columns`
94
+ }
95
+ ```
96
+
97
+ **注意事项**:
98
+ - ✅ 选择有明显中文特征的字符串(如 'dialog_column'、'app_name')
99
+ - ✅ 提供 try-catch 错误处理
100
+ - ✅ 提供默认返回值(false)
101
+ - ⚠️ 字符串需要包含中文才能准确判断
102
+
103
+ ---
104
+
105
+ ## ✅ 已验证:复数处理完整实现
106
+
107
+ > 基于代码仓 `components/dialogs/ColumnsDialog.ets:18-31` 的实际实现
108
+
109
+ **适用场景**:
110
+ - 需要显示带数字的文本(如 "3 列"、"3 columns")
111
+ - 英文环境需要复数处理
112
+ - 需要动态替换占位符
113
+
114
+ **完整代码示例**:
115
+
116
+ ```typescript
117
+ @ComponentV2
118
+ export struct ColumnsDialog {
119
+ @Param @Once currentColumns: number = 3
120
+
121
+ /**
122
+ * 获取列数标签(带复数处理)
123
+ * @param num 列数
124
+ * @returns 格式化后的字符串
125
+ */
126
+ private getColumnLabel(num: number): string {
127
+ const context = getContext(this)
128
+ const resourceManager = context.resourceManager
129
+
130
+ try {
131
+ // 1. 获取字符串资源(包含占位符 %d)
132
+ let str = resourceManager.getStringByNameSync('dialog_column')
133
+ // str = "%d 列" (中文) 或 "%d column" (英文)
134
+
135
+ // 2. 替换占位符
136
+ str = str.replace('%d', num.toString())
137
+
138
+ // 3. 英文复数处理(大于1时添加 's')
139
+ if (num > 1 && !this.isChineseLocale() && !str.endsWith('s')) {
140
+ str = str + 's' // "2 column" → "2 columns"
141
+ }
142
+
143
+ return str
144
+ } catch {
145
+ // 4. 兜底逻辑:确保始终有返回值
146
+ return num === 1 ? '1 column' : `${num} columns`
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ **处理流程**:
153
+ 1. **获取字符串资源**:从 string.json 加载(含占位符 `%d`)
154
+ 2. **替换占位符**:`str.replace('%d', value)` 替换为实际数字
155
+ 3. **判断复数**:`num > 1 && !isChineseLocale() && !str.endsWith('s')`
156
+ 4. **添加复数标记**:英文环境且 num > 1 时添加 's'
157
+ 5. **兜底逻辑**:确保异常时也有返回值
158
+
159
+ **关键点**:
160
+ - ✅ 占位符替换:`str.replace('%d', value.toString())`
161
+ - ✅ 复数判断:`num > 1 && !isChineseLocale() && !str.endsWith('s')`
162
+ - ✅ 兜底逻辑:`catch { return num === 1 ? '1 column' : `${num} columns` }`
163
+
164
+ **支持的占位符**:
165
+
166
+ | 占位符 | 类型 | 示例 string.json | 替换结果 |
167
+ |--------|------|-----------------|----------|
168
+ | `%d` | 整数 | `"%d 列"` | `"3 列"` |
169
+ | `%s` | 字符串 | `"文件:%s"` | `"文件:photo.jpg"` |
170
+ | `%f` | 浮点数 | `"大小:%f MB"` | `"大小:1.5 MB"` |
171
+
172
+ **注意事项**:
173
+ - ⚠️ 仅支持规则复数(+s),不规则复数需特殊处理(如 child → children)
174
+ - ⚠️ 中文字符串无需复数处理
175
+ - ✅ 始终提供兜底逻辑,避免崩溃
176
+ - ✅ 使用 `toString()` 转换数字为字符串
177
+
178
+ ---
179
+
180
+ ## ✅ 已验证:完整业务场景示例(ColumnsDialog)
181
+
182
+ > 基于代码仓 `components/dialogs/ColumnsDialog.ets` 的完整实现
183
+
184
+ **业务场景**:让用户选择网格列数(1-20),需要:
185
+ - 显示格式化文本("3 列" 或 "3 columns")
186
+ - 根据语言环境处理复数
187
+ - 提供友好的用户界面
188
+
189
+ **完整代码**:
190
+
191
+ ```typescript
192
+ @ComponentV2
193
+ export struct ColumnsDialog {
194
+ @Param @Once currentColumns: number = 3
195
+ @Event onColumnsChange: (columns: number) => void = () => {}
196
+ @Event onCancel: () => void = () => {}
197
+
198
+ // ✅ 技术点1:语言环境检测
199
+ private isChineseLocale(): boolean {
200
+ const context = getContext(this)
201
+ const resourceManager = context.resourceManager
202
+ try {
203
+ const str = resourceManager.getStringByNameSync('dialog_column')
204
+ return /[\u4e00-\u9fa5]/.test(str)
205
+ } catch {
206
+ return false
207
+ }
208
+ }
209
+
210
+ // ✅ 技术点2:复数处理 + 占位符替换
211
+ private getColumnLabel(num: number): string {
212
+ const context = getContext(this)
213
+ const resourceManager = context.resourceManager
214
+ try {
215
+ let str = resourceManager.getStringByNameSync('dialog_column')
216
+ str = str.replace('%d', num.toString())
217
+
218
+ // 英文复数处理
219
+ if (num > 1 && !this.isChineseLocale() && !str.endsWith('s')) {
220
+ str = str + 's'
221
+ }
222
+ return str
223
+ } catch {
224
+ // 兜底逻辑
225
+ return num === 1 ? '1 column' : `${num} columns`
226
+ }
227
+ }
228
+
229
+ @Builder
230
+ columnOption(num: number) {
231
+ Row() {
232
+ Text(this.currentColumns === num ? '✓ ' : ' ')
233
+ .fontSize(14)
234
+ .fontColor('#007DFF')
235
+ .width(24)
236
+ Text(this.getColumnLabel(num))
237
+ .fontSize(14)
238
+ .fontColor(this.currentColumns === num ? '#007DFF' : '#333333')
239
+ }
240
+ .width('100%')
241
+ .padding({ left: 12, right: 12, top: 10, bottom: 10 })
242
+ .backgroundColor(this.currentColumns === num ? '#E6F0FF' : '#FFFFFF')
243
+ .borderRadius(8)
244
+ .margin({ bottom: 4 })
245
+ .onClick(() => {
246
+ this.onColumnsChange(num)
247
+ })
248
+ }
249
+
250
+ build() {
251
+ Column() {
252
+ // ✅ 技术点3:静态资源引用
253
+ Text($r('app.string.dialog_columns_title'))
254
+ .fontSize(18)
255
+ .fontWeight(FontWeight.Bold)
256
+ .margin({ top: 20, bottom: 16 })
257
+
258
+ Scroll() {
259
+ Column() {
260
+ // 动态生成 1-20 的列选项
261
+ ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
262
+ (num: number) => {
263
+ this.columnOption(num)
264
+ }
265
+ )
266
+ }
267
+ .width('100%')
268
+ }
269
+ .constraintSize({ maxHeight: 360 })
270
+ .scrollBar(BarState.Auto)
271
+
272
+ Button($r('app.string.dialog_cancel'))
273
+ .width('100%')
274
+ .height(40)
275
+ .backgroundColor('#F0F0F0')
276
+ .fontColor('#333333')
277
+ .margin({ top: 16 })
278
+ .onClick(() => this.onCancel())
279
+ }
280
+ .width(280)
281
+ .padding(20)
282
+ .backgroundColor('#FFFFFF')
283
+ .borderRadius(16)
284
+ }
285
+ }
286
+ ```
287
+
288
+ **技术要点总结**:
289
+
290
+ | 技术点 | 代码位置 | 说明 |
291
+ |--------|---------|------|
292
+ | 语言环境检测 | `isChineseLocale():7-16` | 通过中文字符判断语言 |
293
+ | 复数处理 | `getColumnLabel():18-31` | 占位符替换 + 复数规则 |
294
+ | 占位符替换 | `getColumnLabel():23` | `str.replace('%d', value)` |
295
+ | 静态引用 | `build():56` | `$r('app.string.xxx')` |
296
+ | 动态获取 | `getColumnLabel():22` | `resourceManager.getStringByNameSync()` |
297
+ | 错误处理 | `getColumnLabel():28-30` | try-catch + 兜底逻辑 |
298
+
299
+ **文件位置**:
300
+ - 实现代码:`entry/src/main/ets/components/dialogs/ColumnsDialog.ets`
301
+ - 中文资源:`entry/src/main/resources/base/element/string.json`
302
+ - 英文资源:`entry/src/main/resources/en_US/element/string.json`
@@ -0,0 +1,391 @@
1
+ # 国际化避坑指南(V2)
2
+
3
+ > 常见国际化错误与正确做法对照表。**本项目锁 V2**:所有代码示例使用 `@ComponentV2 / @Local / @ObservedV2 / @Trace / AppStorageV2`。i18n API(`resourceManager` / `$r()` 等)与装饰器版本无关。
4
+ >
5
+ > V1 老项目兼容写法见 [`v1-compat.md`](./v1-compat.md)。
6
+
7
+ ---
8
+
9
+ ## ⚠️ 文档说明
10
+
11
+ - ✅ **已验证项**:基于代码仓实际使用或官方文档
12
+ - ⚠️ **未验证项**:需要查证官方文档确认
13
+ - 💡 **建议**:使用前查阅 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/i18n-overview-V5
14
+
15
+ ---
16
+
17
+ ## 目录命名错误
18
+
19
+ ### 错误 vs 正确
20
+
21
+ | 错误写法 | 正确写法 | 说明 |
22
+ |---------|---------|------|
23
+ | `chinese` | `zh` | 使用 ISO 639-1 |
24
+ | `china` | `zh_CN` | 使用 ISO 639-1 + ISO 3166-1 |
25
+ | `Chinese` | `zh-Hans` | 小写,或带区域码 |
26
+ | `zh-CN` | `zh_CN` | 下划线而非连字符(部分版本接受连字符) |
27
+ | `en-uk` | `en_GB` | 英国英文用 `en_GB` |
28
+
29
+ ### 完整语言代码参考
30
+
31
+ | 语言 | 代码 | 区域变体 |
32
+ |------|------|---------|
33
+ | 中文 | `zh` | `zh_CN`, `zh_HK`, `zh_TW`, `zh_Hans`, `zh_Hant` |
34
+ | 英文 | `en` | `en_US`, `en_GB`, `en_AU`, `en_CA` |
35
+ | 日文 | `ja` | - |
36
+ | 韩文 | `ko` | - |
37
+ | 德文 | `de` | `de_DE`, `de_AT`, `de_CH` |
38
+ | 法文 | `fr` | `fr_FR`, `fr_CA`, `fr_CH` |
39
+ | 西班牙文 | `es` | `es_ES`, `es_MX`, `es_AR` |
40
+ | 葡萄牙文 | `pt` | `pt_BR`, `pt_PT` |
41
+ | 俄文 | `ru` | - |
42
+ | 阿拉伯文 | `ar` | - |
43
+
44
+ ---
45
+
46
+ ## string.json 格式错误
47
+
48
+ ### 错误 1:value 使用错误引号
49
+
50
+ ```json
51
+ // 错误 — value 被双引号包裹
52
+ {
53
+ "string": [
54
+ { "name": "app_name", "value": "\"图库\"" }
55
+ ]
56
+ }
57
+
58
+ // 正确 — value 直接是字符串
59
+ {
60
+ "string": [
61
+ { "name": "app_name", "value": "图库" }
62
+ ]
63
+ }
64
+ ```
65
+
66
+ ### 错误 2:缺少必要字段
67
+
68
+ ```json
69
+ // 错误 — 缺少 name 或 value
70
+ {
71
+ "string": [
72
+ { "value": "图库" },
73
+ { "name": "app_name" }
74
+ ]
75
+ }
76
+
77
+ // 正确
78
+ {
79
+ "string": [
80
+ { "name": "app_name", "value": "图库" }
81
+ ]
82
+ }
83
+ ```
84
+
85
+ ### 错误 3:JSON 语法错误
86
+
87
+ ```json
88
+ // 错误 — 尾随逗号
89
+ {
90
+ "string": [
91
+ { "name": "a", "value": "A" },
92
+ { "name": "b", "value": "B" }, // ← 尾随逗号
93
+ ]
94
+ }
95
+
96
+ // 正确
97
+ {
98
+ "string": [
99
+ { "name": "a", "value": "A" },
100
+ { "name": "b", "value": "B" }
101
+ ]
102
+ }
103
+ ```
104
+
105
+ ---
106
+
107
+ ## 硬编码字符串
108
+
109
+ ### 错误 vs 正确
110
+
111
+ | 错误写法 | 正确写法 |
112
+ |---------|---------|
113
+ | `Text('确认')` | `Text($r('app.string.confirm'))` |
114
+ | `Button('取消')` | `Button($r('app.string.cancel'))` |
115
+ | `'共 ' + count + ' 个文件'` | `$r('app.string.file_count', count)` |
116
+ | `promptAction.showToast('操作成功')` | `promptAction.showToast($r('app.string.success'))` |
117
+
118
+ ### 容易硬编码的地方
119
+
120
+ 1. **Button 文字**
121
+ 2. **Dialog 标题和内容**
122
+ 3. **Toast 消息**
123
+ 4. **Placeholder 文案**
124
+ 5. **Error 消息**
125
+ 6. **Menu 项**
126
+ 7. **Tab 标签**
127
+
128
+ ---
129
+
130
+ ## 动态切换不生效
131
+
132
+ ### 原因 1:未声明 supportedLanguages
133
+
134
+ ```json5
135
+ // module.json5
136
+ {
137
+ "module": {
138
+ "supportedLanguages": ["zh", "en", "ja"] // ← 必须声明
139
+ }
140
+ }
141
+ ```
142
+
143
+ ### 原因 2:UI 未触发重建
144
+
145
+ ```typescript
146
+ // 错误 — 切换后 UI 不更新
147
+ async switchLanguage(lang: string) {
148
+ await resMgr.setPreferredLanguage([lang])
149
+ // 直接返回,UI 不会更新
150
+ }
151
+
152
+ // 正确 — 触发状态更新
153
+ async switchLanguage(lang: string) {
154
+ await resMgr.setPreferredLanguage([lang])
155
+ this.refreshKey++ // 触发重建
156
+ this.currentLang = lang
157
+ }
158
+ ```
159
+
160
+ ### 原因 3:组件未持有响应式 locale 状态
161
+
162
+ ```typescript
163
+ // 错误 — 组件不响应状态变化
164
+ @Entry
165
+ @ComponentV2
166
+ struct Page {
167
+ build() {
168
+ Column() {
169
+ Text($r('app.string.hello')) // 不会自动更新
170
+ }
171
+ }
172
+ }
173
+
174
+ // 正确 — 使用 @Local + LocaleModel 包装
175
+ @ObservedV2
176
+ class LocaleModel {
177
+ @Trace currentLanguage: string = 'zh'
178
+ @Trace refreshKey: number = 0
179
+ }
180
+
181
+ @Entry
182
+ @ComponentV2
183
+ struct Page {
184
+ @Local locale: LocaleModel = AppStorageV2.connect(
185
+ LocaleModel, 'locale', () => new LocaleModel()
186
+ )!
187
+
188
+ build() {
189
+ Column() {
190
+ Text($r('app.string.hello'))
191
+ .id('page_' + this.locale.refreshKey) // refreshKey 改即重建
192
+ }
193
+ }
194
+ }
195
+ ```
196
+
197
+ > V2 关键点:`@Local` 持有 `@ObservedV2` 实例 → 实例的 `@Trace` 字段变化自动刷新该组件。无需 V1 的 `@StorageLink('refreshKey')` 单字段绑定。
198
+
199
+ ---
200
+
201
+ ## 图片国际化问题
202
+
203
+ ### 错误:文件名不一致
204
+
205
+ ```
206
+ base/media/
207
+ └── logo.png ✓
208
+
209
+ en/media/
210
+ └── logo_en.png ✗ 应该是 logo.png
211
+
212
+ ja/media/
213
+ └── logo_jp.png ✗ 应该是 logo.png
214
+ ```
215
+
216
+ ### 正确做法
217
+
218
+ ```
219
+ base/media/
220
+ └── logo.png ✓
221
+
222
+ en/media/
223
+ └── logo.png ✓(同名文件,内容不同)
224
+
225
+ ja/media/
226
+ └── logo.png ✓(同名文件,内容不同)
227
+ ```
228
+
229
+ ---
230
+
231
+ ## 插值参数使用错误
232
+
233
+ > ⚠️ **验证状态**:`IntlString.format()` API 需要查证官方文档
234
+ >
235
+ > **推荐方案**:使用字符串模板 ` \`...${variable}...\` ` 或 `resourceManager.getStringByName()`
236
+
237
+ ### 错误 1:参数顺序错误
238
+
239
+ ```typescript
240
+ // string.json: { "name": "info", "value": "文件:%s,大小:%s" }
241
+
242
+ // ⚠️ 以下 API 需要查证 - IntlString.format 可能不存在
243
+ // 错误 — 参数顺序颠倒
244
+ // IntlString.format($r('app.string.info'), size, name)
245
+
246
+ // ✅ 推荐 — 使用字符串模板
247
+ const name = 'photo.jpg'
248
+ const size = '1.5MB'
249
+ const message = `文件:${name},大小:${size}`
250
+
251
+ // ⚠️ 或者 — 如果 resourceManager 支持参数
252
+ // resourceManager.getStringByName('info', name, size)
253
+ ```
254
+
255
+ ### 错误 2:参数类型不匹配
256
+
257
+ ```typescript
258
+ // string.json: { "name": "count", "value": "数量:%d" }
259
+
260
+ // ⚠️ 以下 API 需要查证
261
+ // 错误 — 传字符串
262
+ // IntlString.format($r('app.string.count'), '10')
263
+
264
+ // ✅ 推荐 — 使用字符串模板
265
+ const count = 10
266
+ const message = `数量:${count}`
267
+ ```
268
+
269
+ ### 错误 3:参数数量不匹配
270
+
271
+ ```typescript
272
+ // string.json: { "name": "info", "value": "%s 大小:%s" }
273
+
274
+ // ⚠️ 以下 API 需要查证
275
+ // 错误 — 只传一个参数
276
+ // IntlString.format($r('app.string.info'), name)
277
+
278
+ // ✅ 推荐 — 使用字符串模板
279
+ const name = 'file.txt'
280
+ const size = '2KB'
281
+ const message = `${name} 大小:${size}`
282
+ ```
283
+
284
+ ---
285
+
286
+ ## RTL 语言注意事项
287
+
288
+ 阿拉伯文、希伯来文等 RTL(从右到左)语言需要额外处理:
289
+
290
+ ### 1. 布局适配
291
+
292
+ ```typescript
293
+ // 检测 RTL
294
+ import { i18n } from '@kit.LocalizationKit'
295
+
296
+ const isRTL = i18n.isRTL()
297
+ ```
298
+
299
+ ### 2. 布局方向
300
+
301
+ ```typescript
302
+ Column() {
303
+ // RTL 语言下自动反转
304
+ }
305
+ .layoutDirection(isRTL ? LayoutDirection.RTL : LayoutDirection.LTR)
306
+ ```
307
+
308
+ ### 3. 图片翻转
309
+
310
+ ```typescript
311
+ Image($r('app.media.arrow'))
312
+ .rotate({ angle: isRTL ? 180 : 0 }) // 箭头方向需反转
313
+ ```
314
+
315
+ ---
316
+
317
+ ## module.json5 supportedLanguages 陷阱
318
+
319
+ ### 问题:声明了但不生效
320
+
321
+ ```json5
322
+ // module.json5
323
+ {
324
+ "module": {
325
+ "supportedLanguages": ["zh", "en"]
326
+ }
327
+ }
328
+ ```
329
+
330
+ 可能原因:
331
+ 1. **语言代码不匹配**:`"zh-Hans"` vs `"zh"`
332
+ 2. **应用未重启**:切换语言后需要重启应用
333
+ 3. **资源文件缺失**:声明了但对应目录不存在
334
+
335
+ ### 正确声明
336
+
337
+ ```json5
338
+ {
339
+ "module": {
340
+ "supportedLanguages": [
341
+ "zh", // 中文(默认)
342
+ "zh-Hans", // 简体中文(API 12+)
343
+ "zh-Hant", // 繁体中文(API 12+)
344
+ "en", // 英文
345
+ "ja" // 日文
346
+ ]
347
+ }
348
+ }
349
+ ```
350
+
351
+ ---
352
+
353
+ ## 调试技巧
354
+
355
+ ### 1. 打印当前语言
356
+
357
+ ```typescript
358
+ import { resourceManager } from '@kit.LocalizationKit'
359
+
360
+ const langs = await context.resourceManager.getPreferredLanguage()
361
+ hilog.info(0x0000, 'I18N', 'Current language: %{public}s', langs[0])
362
+ ```
363
+
364
+ ### 2. 检查资源是否加载
365
+
366
+ ```typescript
367
+ const resMgr = context.resourceManager
368
+ const str = await resMgr.getStringByName('app_name')
369
+ hilog.info(0x0000, 'I18N', 'String value: %{public}s', str)
370
+ ```
371
+
372
+ ### 3. 检查资源目录是否存在
373
+
374
+ ```typescript
375
+ // 在 DevEco Studio 中
376
+ // File > Project Structure > Module > Resources
377
+ // 查看 supportedLanguages 是否与实际目录匹配
378
+ ```
379
+
380
+ ---
381
+
382
+ ## 错误代码速查
383
+
384
+ | 错误现象 | 可能原因 | 解决方案 |
385
+ |---------|---------|---------|
386
+ | 切换语言后 UI 不更新 | 未触发重建 | 增加 `@Local refreshKey` 或 `@ObservedV2 LocaleModel` 的 `@Trace refreshKey` 字段并改变 ID |
387
+ | `$r()` 引用报错 | string.json 中 key 不存在 | 检查 key 拼写 |
388
+ | setPreferredLanguage 不生效 | 未声明 supportedLanguages | 在 module.json5 中声明 |
389
+ | 英文资源不加载 | 目录名错误 | 使用 `en` 而非 `en_US` |
390
+ | 日文显示乱码 | 文件编码错误 | 确保文件是 UTF-8 编码 |
391
+ | 图片不切换 | 文件名不一致 | 不同语言目录图片文件名相同 |