deveco_hmigbot 0.21.5

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 (101) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +50 -0
  3. package/agents/hmigbot-worker.md +61 -0
  4. package/agents/hmigbot.md +22 -0
  5. package/agents/workflow-subagent.md +55 -0
  6. package/commands/hmigbot.md +17 -0
  7. package/dist/index.js +1 -0
  8. package/manifest.json +19 -0
  9. package/package.json +29 -0
  10. package/skills/migrate-core/FILES.md +26 -0
  11. package/skills/migrate-core/SKILL.md +484 -0
  12. package/skills/migrate-core/references/README.md +64 -0
  13. package/skills/migrate-core/references/flow/arkts-vector-gate.md +55 -0
  14. package/skills/migrate-core/references/flow/build-error-patterns.md +52 -0
  15. package/skills/migrate-core/references/flow/conventions-template.md +244 -0
  16. package/skills/migrate-core/references/flow/navigation-migration.md +42 -0
  17. package/skills/migrate-core/references/flow/platform-api-guards.md +59 -0
  18. package/skills/migrate-core/references/flow/platform-model-gaps.md +43 -0
  19. package/skills/migrate-core/references/flow/resource-conversion.md +46 -0
  20. package/skills/migrate-core/references/flow/ui-layout-semantics.md +124 -0
  21. package/skills/migrate-core/references/flow/unit-breakdown.md +42 -0
  22. package/skills/migrate-core/references/host-capabilities.md +24 -0
  23. package/skills/migrate-core/references/topics/app-identity.md +214 -0
  24. package/skills/migrate-core/references/topics/env-doctor.md +245 -0
  25. package/skills/migrate-core/references/topics/i18n/README.md +458 -0
  26. package/skills/migrate-core/references/topics/i18n/references/code-examples.md +304 -0
  27. package/skills/migrate-core/references/topics/i18n/references/common-pitfalls.md +354 -0
  28. package/skills/migrate-core/references/topics/i18n/references/dynamic-language-switch.md +464 -0
  29. package/skills/migrate-core/references/topics/i18n/references/language-codes.md +104 -0
  30. package/skills/migrate-core/references/topics/icon-sizing.md +98 -0
  31. package/skills/migrate-core/references/topics/library-migration/README.md +234 -0
  32. package/skills/migrate-core/references/topics/library-migration/closed-source-sdk.md +128 -0
  33. package/skills/migrate-core/references/topics/library-migration/download-api-decision.md +84 -0
  34. package/skills/migrate-core/references/topics/library-migration/library-mapping-table.md +100 -0
  35. package/skills/migrate-core/references/topics/library-migration/napi-compile-guide.md +84 -0
  36. package/skills/migrate-core/references/topics/library-migration/ohpm-search-guide.md +73 -0
  37. package/skills/migrate-core/references/topics/library-migration/stdlib-mapping-table.md +34 -0
  38. package/skills/migrate-core/references/topics/resources/aar-decompile.md +25 -0
  39. package/skills/migrate-core/references/topics/resources/conversion-rules.md +625 -0
  40. package/skills/migrate-core/references/topics/resources/dependency-analysis-rules.md +328 -0
  41. package/skills/migrate-core/references/topics/resources/material-design-icons.md +173 -0
  42. package/skills/migrate-core/references/topics/resources/svg-fix-patterns.md +175 -0
  43. package/skills/migrate-core/references/topics/resources/xml-drawable-to-svg-rules.md +513 -0
  44. package/skills/migrate-core/references/topics/system-capabilities/README.md +331 -0
  45. package/skills/migrate-core/references/topics/system-capabilities/avplayer-guide.md +161 -0
  46. package/skills/migrate-core/references/topics/system-capabilities/background-tasks.md +403 -0
  47. package/skills/migrate-core/references/topics/system-capabilities/browser-intent.md +121 -0
  48. package/skills/migrate-core/references/topics/system-capabilities/camera-picker.md +118 -0
  49. package/skills/migrate-core/references/topics/system-capabilities/document-picker.md +246 -0
  50. package/skills/migrate-core/references/topics/system-capabilities/file-utils.md +131 -0
  51. package/skills/migrate-core/references/topics/system-capabilities/permission-helper.md +112 -0
  52. package/skills/migrate-core/references/topics/system-capabilities/photo-access-helper.md +208 -0
  53. package/skills/migrate-core/references/topics/system-capabilities/print-management.md +213 -0
  54. package/skills/migrate-core/references/topics/system-capabilities/share-panel.md +177 -0
  55. package/skills/migrate-core/references/topics/system-capabilities/system-settings.md +322 -0
  56. package/skills/migrate-core/references/topics/system-capabilities/telephony-dial.md +49 -0
  57. package/skills/migrate-core/references/topics/system-capabilities/video-playback.md +42 -0
  58. package/skills/migrate-core/references/topics/system-capabilities/webview-patterns.md +38 -0
  59. package/skills/migrate-core/references/topics/ui-alignment/README.md +344 -0
  60. package/skills/migrate-core/references/topics/ui-alignment/references/dark-mode.md +47 -0
  61. package/skills/migrate-core/references/topics/ui-alignment/references/layout-mapping.md +301 -0
  62. package/skills/migrate-core/references/topics/ui-alignment/references/visual-patterns.md +411 -0
  63. package/skills/migrate-core/scripts/closure/check-anchors.mjs +186 -0
  64. package/skills/migrate-core/scripts/closure/check-api-guards.mjs +175 -0
  65. package/skills/migrate-core/scripts/closure/check-consumers.mjs +301 -0
  66. package/skills/migrate-core/scripts/closure/check-permissions.mjs +165 -0
  67. package/skills/migrate-core/scripts/closure/check-resources.mjs +130 -0
  68. package/skills/migrate-core/scripts/closure/check-routes.mjs +527 -0
  69. package/skills/migrate-core/scripts/closure/check-safearea.mjs +122 -0
  70. package/skills/migrate-core/scripts/closure/check-stubs.mjs +69 -0
  71. package/skills/migrate-core/scripts/closure/closure-suite.mjs +256 -0
  72. package/skills/migrate-core/scripts/closure/idioms.json +105 -0
  73. package/skills/migrate-core/scripts/convert/convert-resources.mjs +437 -0
  74. package/skills/migrate-core/scripts/feasibility/feasibility.mjs +235 -0
  75. package/skills/migrate-core/scripts/feasibility/tables/cross-platform.json +11 -0
  76. package/skills/migrate-core/scripts/feasibility/tables/deprecated-api.json +10 -0
  77. package/skills/migrate-core/scripts/feasibility/tables/imported-arkts-core.json +425 -0
  78. package/skills/migrate-core/scripts/feasibility/tables/lib-equivalence.json +206 -0
  79. package/skills/migrate-core/scripts/feasibility/tables/system-capabilities.json +22 -0
  80. package/skills/migrate-core/scripts/front.mjs +107 -0
  81. package/skills/migrate-core/scripts/interface/ark-extract.mjs +172 -0
  82. package/skills/migrate-core/scripts/interface/interface.mjs +152 -0
  83. package/skills/migrate-core/scripts/ledger/ledger.mjs +383 -0
  84. package/skills/migrate-core/scripts/ledger/parse-cards.mjs +98 -0
  85. package/skills/migrate-core/scripts/lib/literals.mjs +37 -0
  86. package/skills/migrate-core/scripts/lib/scan.mjs +315 -0
  87. package/skills/migrate-core/scripts/smoke/align-sdk.mjs +118 -0
  88. package/skills/migrate-core/scripts/smoke/ensure-sign.mjs +53 -0
  89. package/skills/migrate-core/scripts/smoke/smoke.mjs +238 -0
  90. package/skills/migrate-core/scripts/smoke/verdict.mjs +31 -0
  91. package/skills/migrate-core/scripts/smoke/walk.mjs +480 -0
  92. package/skills/migrate-core/scripts/transpile/mapping.json +76 -0
  93. package/skills/migrate-core/scripts/transpile/transpile-layout.mjs +404 -0
  94. package/skills/migrate-core/scripts/vectors/run-arkts-vectors.mjs +107 -0
  95. package/skills/migrate-core/scripts/vectors/setup-arkts-test.mjs +90 -0
  96. package/skills/migrate-core/scripts/wire/extractors.mjs +258 -0
  97. package/skills/migrate-core/scripts/wire/wire-routes.mjs +507 -0
  98. package/skills/migrate-core/templates/acceptance.js +365 -0
  99. package/skills/migrate-core/templates/explore.js +86 -0
  100. package/skills/migrate-core/templates/implement.js +211 -0
  101. package/skills/migrate-core/templates/mig_slices.js +491 -0
@@ -0,0 +1,304 @@
1
+ # 代码仓验证示例(V2)
2
+
3
+ > ⚠ 本文样例中 `getContext(this)` 为已废弃写法:组件内一律改 `this.getUIContext().getHostContext()`;Ability 内用 `this.context`。
4
+
5
+
6
+ > 基于代码仓实际实现的完整代码示例。**本项目锁 V2**:所有 struct 用 `@ComponentV2`,状态用 `@Local / @Param / @ObservedV2`。i18n API(`resourceManager.getStringByNameSync` / `$r()` 等)与装饰器版本无关。
7
+ >
8
+
9
+ ---
10
+
11
+ ## ✅ 已验证:获取字符串资源
12
+
13
+ 基于 `entry/src/main/ets/components/dialogs/ColumnsDialog.ets:9-22`
14
+
15
+ ```typescript
16
+ import { common } from '@kit.AbilityKit'
17
+ import { hilog } from '@kit.PerformanceAnalysisKit'
18
+
19
+ const DOMAIN = 0xFF00
20
+ const TAG = 'ColumnsDialog'
21
+
22
+ @CustomDialog
23
+ export struct ColumnsDialog {
24
+ dialogController: CustomDialogController
25
+
26
+ aboutToAppear(): void {
27
+ try {
28
+ const context = getContext(this) as common.UIAbilityContext
29
+ const resourceManager = context.resourceManager
30
+
31
+ // ✅ 已验证:同步获取字符串资源
32
+ const str = resourceManager.getStringByNameSync('dialog_column')
33
+ hilog.info(DOMAIN, TAG, `Loaded string: ${str}`)
34
+ } catch (err) {
35
+ const error = err as Error
36
+ hilog.error(DOMAIN, TAG, `Failed to load string: ${error.message}`)
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ **关键点**:
43
+ 1. ✅ 使用 `getContext(this)` 获取 context
44
+ 2. ✅ 使用 `resourceManager.getStringByNameSync()` 同步获取字符串
45
+ 3. ✅ 使用 try-catch 处理错误
46
+
47
+ ---
48
+
49
+ ## ✅ 已验证:语言环境检测
50
+
51
+ > 基于代码仓 `components/dialogs/ColumnsDialog.ets:7-16` 的实际实现
52
+
53
+ **适用场景**:
54
+ - 需要根据语言执行不同逻辑(如复数处理)
55
+ - 不依赖未知 API 的可靠方案
56
+ - 判断当前加载的是中文还是英文资源
57
+
58
+ **完整实现**:
59
+
60
+ ```typescript
61
+ @ComponentV2
62
+ export struct ColumnsDialog {
63
+ /**
64
+ * 检测当前语言环境是否为中文
65
+ * @returns true 表示中文环境,false 表示非中文环境
66
+ */
67
+ private isChineseLocale(): boolean {
68
+ const context = getContext(this)
69
+ const resourceManager = context.resourceManager
70
+ try {
71
+ // 获取任意包含中文的字符串
72
+ const str = resourceManager.getStringByNameSync('dialog_column')
73
+ // 检测中文字符(Unicode 范围:0x4e00 - 0x9fa5)
74
+ return /[\u4e00-\u9fa5]/.test(str)
75
+ } catch {
76
+ return false // 默认非中文环境
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ **原理解释**:
83
+ - `[\u4e00-\u9fa5]` 匹配中文常用汉字的 Unicode 范围
84
+ - 如果字符串包含中文字符,说明加载了中文资源
85
+ - 无需依赖未知 API,可靠性高
86
+
87
+ **使用示例**:
88
+
89
+ ```typescript
90
+ if (this.isChineseLocale()) {
91
+ // 中文环境:无需复数处理
92
+ label = `${count} 列`
93
+ } else {
94
+ // 英文环境:需要复数处理
95
+ label = count === 1 ? '1 column' : `${count} columns`
96
+ }
97
+ ```
98
+
99
+ **注意事项**:
100
+ - ✅ 选择有明显中文特征的字符串(如 'dialog_column'、'app_name')
101
+ - ✅ 提供 try-catch 错误处理
102
+ - ✅ 提供默认返回值(false)
103
+ - ⚠️ 字符串需要包含中文才能准确判断
104
+
105
+ ---
106
+
107
+ ## ✅ 已验证:复数处理完整实现
108
+
109
+ > 基于代码仓 `components/dialogs/ColumnsDialog.ets:18-31` 的实际实现
110
+
111
+ **适用场景**:
112
+ - 需要显示带数字的文本(如 "3 列"、"3 columns")
113
+ - 英文环境需要复数处理
114
+ - 需要动态替换占位符
115
+
116
+ **完整代码示例**:
117
+
118
+ ```typescript
119
+ @ComponentV2
120
+ export struct ColumnsDialog {
121
+ @Param @Once currentColumns: number = 3
122
+
123
+ /**
124
+ * 获取列数标签(带复数处理)
125
+ * @param num 列数
126
+ * @returns 格式化后的字符串
127
+ */
128
+ private getColumnLabel(num: number): string {
129
+ const context = getContext(this)
130
+ const resourceManager = context.resourceManager
131
+
132
+ try {
133
+ // 1. 获取字符串资源(包含占位符 %d)
134
+ let str = resourceManager.getStringByNameSync('dialog_column')
135
+ // str = "%d 列" (中文) 或 "%d column" (英文)
136
+
137
+ // 2. 替换占位符
138
+ str = str.replace('%d', num.toString())
139
+
140
+ // 3. 英文复数处理(大于1时添加 's')
141
+ if (num > 1 && !this.isChineseLocale() && !str.endsWith('s')) {
142
+ str = str + 's' // "2 column" → "2 columns"
143
+ }
144
+
145
+ return str
146
+ } catch {
147
+ // 4. 兜底逻辑:确保始终有返回值
148
+ return num === 1 ? '1 column' : `${num} columns`
149
+ }
150
+ }
151
+ }
152
+ ```
153
+
154
+ **处理流程**:
155
+ 1. **获取字符串资源**:从 string.json 加载(含占位符 `%d`)
156
+ 2. **替换占位符**:`str.replace('%d', value)` 替换为实际数字
157
+ 3. **判断复数**:`num > 1 && !isChineseLocale() && !str.endsWith('s')`
158
+ 4. **添加复数标记**:英文环境且 num > 1 时添加 's'
159
+ 5. **兜底逻辑**:确保异常时也有返回值
160
+
161
+ **关键点**:
162
+ - ✅ 占位符替换:`str.replace('%d', value.toString())`
163
+ - ✅ 复数判断:`num > 1 && !isChineseLocale() && !str.endsWith('s')`
164
+ - ✅ 兜底逻辑:`catch { return num === 1 ? '1 column' : `${num} columns` }`
165
+
166
+ **支持的占位符**:
167
+
168
+ | 占位符 | 类型 | 示例 string.json | 替换结果 |
169
+ |--------|------|-----------------|----------|
170
+ | `%d` | 整数 | `"%d 列"` | `"3 列"` |
171
+ | `%s` | 字符串 | `"文件:%s"` | `"文件:photo.jpg"` |
172
+ | `%f` | 浮点数 | `"大小:%f MB"` | `"大小:1.5 MB"` |
173
+
174
+ **注意事项**:
175
+ - ⚠️ 仅支持规则复数(+s),不规则复数需特殊处理(如 child → children)
176
+ - ⚠️ 中文字符串无需复数处理
177
+ - ✅ 始终提供兜底逻辑,避免崩溃
178
+ - ✅ 使用 `toString()` 转换数字为字符串
179
+
180
+ ---
181
+
182
+ ## ✅ 已验证:完整业务场景示例(ColumnsDialog)
183
+
184
+ > 基于代码仓 `components/dialogs/ColumnsDialog.ets` 的完整实现
185
+
186
+ **业务场景**:让用户选择网格列数(1-20),需要:
187
+ - 显示格式化文本("3 列" 或 "3 columns")
188
+ - 根据语言环境处理复数
189
+ - 提供友好的用户界面
190
+
191
+ **完整代码**:
192
+
193
+ ```typescript
194
+ @ComponentV2
195
+ export struct ColumnsDialog {
196
+ @Param @Once currentColumns: number = 3
197
+ @Event onColumnsChange: (columns: number) => void = () => {}
198
+ @Event onCancel: () => void = () => {}
199
+
200
+ // ✅ 技术点1:语言环境检测
201
+ private isChineseLocale(): boolean {
202
+ const context = getContext(this)
203
+ const resourceManager = context.resourceManager
204
+ try {
205
+ const str = resourceManager.getStringByNameSync('dialog_column')
206
+ return /[\u4e00-\u9fa5]/.test(str)
207
+ } catch {
208
+ return false
209
+ }
210
+ }
211
+
212
+ // ✅ 技术点2:复数处理 + 占位符替换
213
+ private getColumnLabel(num: number): string {
214
+ const context = getContext(this)
215
+ const resourceManager = context.resourceManager
216
+ try {
217
+ let str = resourceManager.getStringByNameSync('dialog_column')
218
+ str = str.replace('%d', num.toString())
219
+
220
+ // 英文复数处理
221
+ if (num > 1 && !this.isChineseLocale() && !str.endsWith('s')) {
222
+ str = str + 's'
223
+ }
224
+ return str
225
+ } catch {
226
+ // 兜底逻辑
227
+ return num === 1 ? '1 column' : `${num} columns`
228
+ }
229
+ }
230
+
231
+ @Builder
232
+ columnOption(num: number) {
233
+ Row() {
234
+ Text(this.currentColumns === num ? '✓ ' : ' ')
235
+ .fontSize(14)
236
+ .fontColor('#007DFF')
237
+ .width(24)
238
+ Text(this.getColumnLabel(num))
239
+ .fontSize(14)
240
+ .fontColor(this.currentColumns === num ? '#007DFF' : '#333333')
241
+ }
242
+ .width('100%')
243
+ .padding({ left: 12, right: 12, top: 10, bottom: 10 })
244
+ .backgroundColor(this.currentColumns === num ? '#E6F0FF' : '#FFFFFF')
245
+ .borderRadius(8)
246
+ .margin({ bottom: 4 })
247
+ .onClick(() => {
248
+ this.onColumnsChange(num)
249
+ })
250
+ }
251
+
252
+ build() {
253
+ Column() {
254
+ // ✅ 技术点3:静态资源引用
255
+ Text($r('app.string.dialog_columns_title'))
256
+ .fontSize(18)
257
+ .fontWeight(FontWeight.Bold)
258
+ .margin({ top: 20, bottom: 16 })
259
+
260
+ Scroll() {
261
+ Column() {
262
+ // 动态生成 1-20 的列选项
263
+ ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
264
+ (num: number) => {
265
+ this.columnOption(num)
266
+ }
267
+ )
268
+ }
269
+ .width('100%')
270
+ }
271
+ .constraintSize({ maxHeight: 360 })
272
+ .scrollBar(BarState.Auto)
273
+
274
+ Button($r('app.string.dialog_cancel'))
275
+ .width('100%')
276
+ .height(40)
277
+ .backgroundColor('#F0F0F0')
278
+ .fontColor('#333333')
279
+ .margin({ top: 16 })
280
+ .onClick(() => this.onCancel())
281
+ }
282
+ .width(280)
283
+ .padding(20)
284
+ .backgroundColor('#FFFFFF')
285
+ .borderRadius(16)
286
+ }
287
+ }
288
+ ```
289
+
290
+ **技术要点总结**:
291
+
292
+ | 技术点 | 代码位置 | 说明 |
293
+ |--------|---------|------|
294
+ | 语言环境检测 | `isChineseLocale():7-16` | 通过中文字符判断语言 |
295
+ | 复数处理 | `getColumnLabel():18-31` | 占位符替换 + 复数规则 |
296
+ | 占位符替换 | `getColumnLabel():23` | `str.replace('%d', value)` |
297
+ | 静态引用 | `build():56` | `$r('app.string.xxx')` |
298
+ | 动态获取 | `getColumnLabel():22` | `resourceManager.getStringByNameSync()` |
299
+ | 错误处理 | `getColumnLabel():28-30` | try-catch + 兜底逻辑 |
300
+
301
+ **文件位置**:
302
+ - 实现代码:`entry/src/main/ets/components/dialogs/ColumnsDialog.ets`
303
+ - 中文资源:`entry/src/main/resources/base/element/string.json`
304
+ - 英文资源:`entry/src/main/resources/en_US/element/string.json`
@@ -0,0 +1,354 @@
1
+ # 国际化避坑指南(V2)
2
+
3
+ > 常见国际化错误与正确做法对照表。**本项目锁 V2**:所有代码示例使用 `@ComponentV2 / @Local / @ObservedV2 / @Trace / AppStorageV2`。i18n API(`resourceManager` / `$r()` 等)与装饰器版本无关。
4
+ >
5
+
6
+ ---
7
+
8
+ ## ⚠️ 文档说明
9
+
10
+ - ✅ **已验证项**:基于代码仓实际使用或官方文档
11
+ - 💡 **建议**:使用前查阅 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/i18n-overview-V5
12
+
13
+ ---
14
+
15
+ ## 目录命名错误
16
+
17
+ ### 错误 vs 正确
18
+
19
+ | 错误写法 | 正确写法 | 说明 |
20
+ |---------|---------|------|
21
+ | `chinese` | `zh` | 使用 ISO 639-1 |
22
+ | `china` | `zh_CN` | 使用 ISO 639-1 + ISO 3166-1 |
23
+ | `Chinese` | `zh-Hans` | 小写,或带区域码 |
24
+ | `zh-CN` | `zh_CN` | 下划线而非连字符(部分版本接受连字符) |
25
+ | `en-uk` | `en_GB` | 英国英文用 `en_GB` |
26
+
27
+ ### 完整语言代码参考
28
+
29
+ | 语言 | 代码 | 区域变体 |
30
+ |------|------|---------|
31
+ | 中文 | `zh` | `zh_CN`, `zh_HK`, `zh_TW`, `zh_Hans`, `zh_Hant` |
32
+ | 英文 | `en` | `en_US`, `en_GB`, `en_AU`, `en_CA` |
33
+ | 日文 | `ja` | - |
34
+ | 韩文 | `ko` | - |
35
+ | 德文 | `de` | `de_DE`, `de_AT`, `de_CH` |
36
+ | 法文 | `fr` | `fr_FR`, `fr_CA`, `fr_CH` |
37
+ | 西班牙文 | `es` | `es_ES`, `es_MX`, `es_AR` |
38
+ | 葡萄牙文 | `pt` | `pt_BR`, `pt_PT` |
39
+ | 俄文 | `ru` | - |
40
+ | 阿拉伯文 | `ar` | - |
41
+
42
+ ---
43
+
44
+ ## string.json 格式错误
45
+
46
+ ### 错误 1:value 使用错误引号
47
+
48
+ ```json
49
+ // 错误 — value 被双引号包裹
50
+ {
51
+ "string": [
52
+ { "name": "app_name", "value": "\"图库\"" }
53
+ ]
54
+ }
55
+
56
+ // 正确 — value 直接是字符串
57
+ {
58
+ "string": [
59
+ { "name": "app_name", "value": "图库" }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ ### 错误 2:缺少必要字段
65
+
66
+ ```json
67
+ // 错误 — 缺少 name 或 value
68
+ {
69
+ "string": [
70
+ { "value": "图库" },
71
+ { "name": "app_name" }
72
+ ]
73
+ }
74
+
75
+ // 正确
76
+ {
77
+ "string": [
78
+ { "name": "app_name", "value": "图库" }
79
+ ]
80
+ }
81
+ ```
82
+
83
+ ### 错误 3:JSON 语法错误
84
+
85
+ ```json
86
+ // 错误 — 尾随逗号
87
+ {
88
+ "string": [
89
+ { "name": "a", "value": "A" },
90
+ { "name": "b", "value": "B" }, // ← 尾随逗号
91
+ ]
92
+ }
93
+
94
+ // 正确
95
+ {
96
+ "string": [
97
+ { "name": "a", "value": "A" },
98
+ { "name": "b", "value": "B" }
99
+ ]
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## 硬编码字符串
106
+
107
+ ### 错误 vs 正确
108
+
109
+ | 错误写法 | 正确写法 |
110
+ |---------|---------|
111
+ | `Text('确认')` | `Text($r('app.string.confirm'))` |
112
+ | `Button('取消')` | `Button($r('app.string.cancel'))` |
113
+ | `'共 ' + count + ' 个文件'` | `$r('app.string.file_count', count)` |
114
+ | `promptAction.showToast('操作成功')` | `promptAction.showToast($r('app.string.success'))` |
115
+
116
+ ### 容易硬编码的地方
117
+
118
+ 1. **Button 文字**
119
+ 2. **Dialog 标题和内容**
120
+ 3. **Toast 消息**
121
+ 4. **Placeholder 文案**
122
+ 5. **Error 消息**
123
+ 6. **Menu 项**
124
+ 7. **Tab 标签**
125
+
126
+ ---
127
+
128
+ ## 动态切换不生效
129
+
130
+ ### 原因 1:该语言没有资源目录
131
+
132
+ module.json5 **没有** `supportedLanguages` 字段(hvigor 模块 schema 无此项,写了校验报错)。应用支持哪些语言 = `resources/` 下有哪些 locale 目录(`base/` 必有;`zh_CN/`、`en_US/`、`zh_HK/`、`ja_JP/` … 按平台限定符命名);`i18n.System.setAppPreferredLanguage(lang)` 设到没有资源目录的语言时,`$r` 回退 `base/`。
133
+
134
+ ### 原因 2:UI 未触发重建
135
+
136
+ ```typescript
137
+ import { i18n } from '@kit.LocalizationKit'
138
+ // 错误 — 切换后 UI 不更新
139
+ async switchLanguage(lang: string) {
140
+ i18n.System.setAppPreferredLanguage(lang)
141
+ // 直接返回,UI 不会更新
142
+ }
143
+
144
+ // 正确 — 触发状态更新
145
+ async switchLanguage(lang: string) {
146
+ i18n.System.setAppPreferredLanguage(lang)
147
+ this.refreshKey++ // 触发重建
148
+ this.currentLang = lang
149
+ }
150
+ ```
151
+
152
+ ### 原因 3:组件未持有响应式 locale 状态
153
+
154
+ ```typescript
155
+ // 错误 — 组件不响应状态变化
156
+ @Entry
157
+ @ComponentV2
158
+ struct Page {
159
+ build() {
160
+ Column() {
161
+ Text($r('app.string.hello')) // 不会自动更新
162
+ }
163
+ }
164
+ }
165
+
166
+ // 正确 — 使用 @Local + LocaleModel 包装
167
+ @ObservedV2
168
+ class LocaleModel {
169
+ @Trace currentLanguage: string = 'zh'
170
+ @Trace refreshKey: number = 0
171
+ }
172
+
173
+ @Entry
174
+ @ComponentV2
175
+ struct Page {
176
+ @Local locale: LocaleModel = AppStorageV2.connect(
177
+ LocaleModel, 'locale', () => new LocaleModel()
178
+ )!
179
+
180
+ build() {
181
+ Column() {
182
+ Text($r('app.string.hello'))
183
+ .id('page_' + this.locale.refreshKey) // refreshKey 改即重建
184
+ }
185
+ }
186
+ }
187
+ ```
188
+
189
+ > V2 关键点:`@Local` 持有 `@ObservedV2` 实例 → 实例的 `@Trace` 字段变化自动刷新该组件。无需 V1 的 `@StorageLink('refreshKey')` 单字段绑定。
190
+
191
+ ---
192
+
193
+ ## 图片国际化问题
194
+
195
+ ### 错误:文件名不一致
196
+
197
+ ```
198
+ base/media/
199
+ └── logo.png ✓
200
+
201
+ en/media/
202
+ └── logo_en.png ✗ 应该是 logo.png
203
+
204
+ ja/media/
205
+ └── logo_jp.png ✗ 应该是 logo.png
206
+ ```
207
+
208
+ ### 正确做法
209
+
210
+ ```
211
+ base/media/
212
+ └── logo.png ✓
213
+
214
+ en/media/
215
+ └── logo.png ✓(同名文件,内容不同)
216
+
217
+ ja/media/
218
+ └── logo.png ✓(同名文件,内容不同)
219
+ ```
220
+
221
+ ---
222
+
223
+ ## 插值参数使用错误
224
+
225
+ > ⚠️ **验证状态**:`IntlString.format()` API 需要查证官方文档
226
+ >
227
+ > **推荐方案**:使用字符串模板 ` \`...${variable}...\` ` 或 `resourceManager.getStringByName()`
228
+
229
+ ### 错误 1:参数顺序错误
230
+
231
+ ```typescript
232
+ // string.json: { "name": "info", "value": "文件:%s,大小:%s" }
233
+
234
+ // ⚠️ 以下 API 需要查证 - IntlString.format 可能不存在
235
+ // 错误 — 参数顺序颠倒
236
+ // IntlString.format($r('app.string.info'), size, name)
237
+
238
+ // ✅ 推荐 — 使用字符串模板
239
+ const name = 'photo.jpg'
240
+ const size = '1.5MB'
241
+ const message = `文件:${name},大小:${size}`
242
+
243
+ // ⚠️ 或者 — 如果 resourceManager 支持参数
244
+ // resourceManager.getStringByName('info', name, size)
245
+ ```
246
+
247
+ ### 错误 2:参数类型不匹配
248
+
249
+ ```typescript
250
+ // string.json: { "name": "count", "value": "数量:%d" }
251
+
252
+ // ⚠️ 以下 API 需要查证
253
+ // 错误 — 传字符串
254
+ // IntlString.format($r('app.string.count'), '10')
255
+
256
+ // ✅ 推荐 — 使用字符串模板
257
+ const count = 10
258
+ const message = `数量:${count}`
259
+ ```
260
+
261
+ ### 错误 3:参数数量不匹配
262
+
263
+ ```typescript
264
+ // string.json: { "name": "info", "value": "%s 大小:%s" }
265
+
266
+ // ⚠️ 以下 API 需要查证
267
+ // 错误 — 只传一个参数
268
+ // IntlString.format($r('app.string.info'), name)
269
+
270
+ // ✅ 推荐 — 使用字符串模板
271
+ const name = 'file.txt'
272
+ const size = '2KB'
273
+ const message = `${name} 大小:${size}`
274
+ ```
275
+
276
+ ---
277
+
278
+ ## RTL 语言注意事项
279
+
280
+ 阿拉伯文、希伯来文等 RTL(从右到左)语言需要额外处理:
281
+
282
+ ### 1. 布局适配
283
+
284
+ ```typescript
285
+ // 检测 RTL
286
+ import { i18n } from '@kit.LocalizationKit'
287
+
288
+ const isRTL = i18n.isRTL()
289
+ ```
290
+
291
+ ### 2. 布局方向
292
+
293
+ ```typescript
294
+ Column() {
295
+ // RTL 语言下自动反转
296
+ }
297
+ .layoutDirection(isRTL ? LayoutDirection.RTL : LayoutDirection.LTR)
298
+ ```
299
+
300
+ ### 3. 图片翻转
301
+
302
+ ```typescript
303
+ Image($r('app.media.arrow'))
304
+ .rotate({ angle: isRTL ? 180 : 0 }) // 箭头方向需反转
305
+ ```
306
+
307
+ ---
308
+
309
+ ## module.json5 没有 supportedLanguages
310
+
311
+ module.json5 **没有** `supportedLanguages` 字段(hvigor 模块 schema 无此项,写了校验报错)。应用支持哪些语言 = `resources/` 下有哪些 locale 目录(`base/` 必有;`zh_CN/`、`en_US/`、`zh_HK/`、`ja_JP/` … 按平台限定符命名);`i18n.System.setAppPreferredLanguage(lang)` 设到没有资源目录的语言时,`$r` 回退 `base/`。
312
+
313
+ ---
314
+
315
+ ## 调试技巧
316
+
317
+ ### 1. 打印当前语言
318
+
319
+ ```typescript
320
+ import { i18n } from '@kit.LocalizationKit'
321
+
322
+ const lang = i18n.System.getAppPreferredLanguage() // 应用当前生效语言(同步返回,如 'zh-Hans')
323
+ const prefs = i18n.System.getPreferredLanguageList() // 系统偏好语言列表
324
+ hilog.info(0x0000, 'I18N', 'Current language: %{public}s / prefs: %{public}s', lang, prefs.join(','))
325
+ ```
326
+
327
+ ### 2. 检查资源是否加载
328
+
329
+ ```typescript
330
+ const resMgr = context.resourceManager
331
+ const str = await resMgr.getStringByName('app_name')
332
+ hilog.info(0x0000, 'I18N', 'String value: %{public}s', str)
333
+ ```
334
+
335
+ ### 3. 检查资源目录是否存在
336
+
337
+ ```typescript
338
+ // 在 DevEco Studio 中
339
+ // File > Project Structure > Module > Resources
340
+ // 查看 resources/ 下的 locale 目录是否齐全(没有 supportedLanguages 字段可查)
341
+ ```
342
+
343
+ ---
344
+
345
+ ## 错误代码速查
346
+
347
+ | 错误现象 | 可能原因 | 解决方案 |
348
+ |---------|---------|---------|
349
+ | 切换语言后 UI 不更新 | 未触发重建 | 增加 `@Local refreshKey` 或 `@ObservedV2 LocaleModel` 的 `@Trace refreshKey` 字段并改变 ID |
350
+ | `$r()` 引用报错 | string.json 中 key 不存在 | 检查 key 拼写 |
351
+ | 切换后 `$r` 仍是旧语言 | 该语言没有 `resources/<locale>/` 目录(回退 base)或页面未重建 | 补 locale 目录;refreshKey 触发重建 |
352
+ | 英文资源不加载 | 目录名错误 | 使用 `en` 而非 `en_US` |
353
+ | 日文显示乱码 | 文件编码错误 | 确保文件是 UTF-8 编码 |
354
+ | 图片不切换 | 文件名不一致 | 不同语言目录图片文件名相同 |