@harmonyos-arkts/d2h 0.1.10 → 0.1.12
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.
- package/.claude-plugin/marketplace.json +15 -13
- package/.claude-plugin/plugin.json +13 -7
- package/AGENTS.md +5 -5
- package/README.md +212 -212
- package/agents/android-to-hmos-00-orchestrator.md +318 -318
- package/agents/d2h.md +157 -157
- package/dist/bin/install-opencode.js +30 -30
- package/docs/environment-check.md +86 -86
- package/install-opencode.sh +160 -160
- package/opencode/agents.json +16 -16
- package/opencode/vscode-d2h-package.json +7 -7
- package/opencode/vscode-d2h-plugin.mjs +29 -29
- package/package.json +54 -54
- package/schemas/android-source-manifest.schema.json +25 -25
- package/schemas/checkpoint-provenance.schema.json +67 -67
- package/schemas/final-acceptance.schema.json +29 -29
- package/schemas/managed-evidence-index.schema.json +15 -15
- package/schemas/managed-evidence.schema.json +114 -114
- package/schemas/migration-config.schema.json +51 -51
- package/schemas/migration-report-index.schema.json +39 -39
- package/schemas/migration-report-item.schema.json +51 -51
- package/schemas/migration-report-summary.schema.json +25 -25
- package/schemas/migration-status.schema.json +202 -202
- package/schemas/preflight.schema.json +60 -60
- package/schemas/source-order-audit.schema.json +16 -16
- package/schemas/source-provenance-event.schema.json +19 -19
- package/schemas/spec-app-shard.schema.json +45 -45
- package/schemas/spec-fact-corrections-shard.schema.json +13 -13
- package/schemas/spec-features-shard.schema.json +43 -43
- package/schemas/spec-index.schema.json +26 -26
- package/schemas/spec-interactions-shard.schema.json +40 -40
- package/schemas/spec-page.schema.json +98 -98
- package/schemas/spec-pages-index.schema.json +1 -1
- package/schemas/spec-unresolved-shard.schema.json +1 -1
- package/schemas/task-envelope.schema.json +109 -109
- package/schemas/task-plan.schema.json +341 -341
- package/scripts/check-environment.mjs +320 -320
- package/skills/android2hmos_resources_convert/SKILL.md +180 -180
- package/skills/android2hmos_resources_convert/references/image-conversion-rules.md +230 -230
- package/skills/android2hmos_resources_convert/references/svg-fix-patterns.md +175 -175
- package/skills/android2hmos_resources_convert/references/xml-drawable-to-svg-rules.md +513 -513
- package/skills/arkts-app-identity/SKILL.md +238 -238
- package/skills/arkts-i18n/SKILL.md +496 -496
- package/skills/arkts-i18n/evals/evals.json +84 -84
- package/skills/arkts-i18n/references/code-examples.md +302 -302
- package/skills/arkts-i18n/references/common-pitfalls.md +391 -391
- package/skills/arkts-i18n/references/dynamic-language-switch.md +604 -604
- package/skills/arkts-i18n/references/hardcoded-string-scanner.md +348 -348
- package/skills/arkts-i18n/references/language-codes.md +104 -104
- package/skills/arkts-i18n/references/resource-file-structure.md +775 -775
- package/skills/arkts-i18n/references/static-vs-dynamic.md +242 -242
- package/skills/arkts-i18n/references/v1-compat.md +244 -244
- package/skills/arkts-i18n/scripts/audit_i18n_completeness.sh +174 -174
- package/skills/arkts-icon-sizing/SKILL.md +211 -211
- package/skills/arkts-icon-sizing/scripts/icon_audit.py +131 -131
- package/skills/arkts-icon-sizing/scripts/icon_autofix.py +88 -88
- package/skills/arkts-icon-sizing/scripts/icon_dims.py +179 -179
- package/skills/arkts-icon-sizing/scripts/icon_fix.py +119 -119
- package/skills/arkts-mvvm-architecture/SKILL.md +613 -613
- package/skills/d2h-hypium-forge/SKILL.md +309 -309
- package/skills/d2h-hypium-forge/references/functional-spec-template.md +140 -140
- package/skills/d2h-hypium-forge/references/official-api.md +135 -135
- package/skills/d2h-hypium-forge/references/pitfalls.md +98 -98
- package/skills/d2h-hypium-forge/references/testability-template.md +71 -71
- package/skills/d2h-hypium-forge/scripts/_common_template.py +659 -659
- package/skills/d2h-hypium-forge/scripts/analyze_results.py +165 -165
- package/skills/d2h-hypium-forge/scripts/assert_audit.py +107 -107
- package/skills/d2h-hypium-forge/scripts/collect_runtime_pages.py +169 -169
- package/skills/d2h-hypium-forge/scripts/forge_report.py +325 -325
- package/skills/d2h-hypium-forge/scripts/inject_resource_ids.py +114 -114
- package/skills/d2h-hypium-forge/scripts/lint_asserts.py +80 -80
- package/skills/d2h-hypium-forge/scripts/lint_locate.py +60 -60
- package/skills/d2h-hypium-forge/scripts/merge_appgraph.py +249 -249
- package/skills/d2h-hypium-forge/scripts/precondition.py +140 -140
- package/skills/d2h-hypium-forge/scripts/preinject_ids.py +232 -232
- package/skills/d2h-hypium-forge/scripts/probe.py +197 -197
- package/skills/d2h-hypium-forge/scripts/run_direct.py +823 -823
- package/skills/d2h-hypium-forge/scripts/run_parallel.py +121 -121
- package/skills/d2h-hypium-forge/scripts/scan_features.py +255 -255
- package/skills/d2h-hypium-forge/scripts/upgrade_asserts.py +66 -66
- package/skills/d2h-hypium-forge/scripts/validate_scenarios.py +84 -84
- package/skills/d2h-page-develop/SKILL.md +112 -112
- package/skills/d2h-page-develop/agents/openai.yaml +7 -7
- package/skills/d2h-page-spec-generate/SKILL.md +206 -206
- package/skills/d2h-page-spec-generate/scripts/build_batch_report.py +227 -227
- package/skills/d2h-utg-ai-generate/SKILL.md +128 -128
- package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +76 -76
- package/skills/d2h-utg-ai-generate/references/extraction-guide.md +34 -34
- package/skills/d2h-utg-ai-generate/references/facts-contract.md +96 -96
- package/skills/d2h-utg-ai-generate/references/page-schema.json +61 -61
- package/skills/d2h-utg-ai-generate/references/utg-schema.json +89 -89
- package/skills/d2h-utg-ai-generate/scripts/build_page_json.py +314 -314
- package/skills/d2h-utg-ai-generate/scripts/build_utg.py +123 -123
- package/skills/d2h-utg-ai-generate/scripts/capture_from_page_json.ts +280 -280
- package/skills/d2h-utg-ai-generate/scripts/utg_common.py +524 -524
- package/skills/d2h-utg-ai-generate/scripts/utg_html.py +489 -489
- package/skills/d2h-utg-ai-generate/scripts/validate_utg.py +69 -69
- package/skills/d2h-utg-ai-generate/scripts/vendor/D3-LICENSE +13 -13
- package/skills/d2h-utg-ai-generate/scripts/vendor/d3-7.9.0.min.js +2 -2
- package/skills/d2h-utg-ai-generate/tests/test_pipeline.py +369 -369
- package/skills/d2h-utg-static-generate/SKILL.md +73 -73
- package/skills/d2h-utg-static-generate/references/discoveries.md +38 -38
- package/skills/d2h-utg-static-generate/scripts/generate.py +499 -499
- package/skills/d2h-utg-static-generate/tests/test_static.py +335 -335
- package/skills/d2h-utg-static-generate/tests/verify-offline-html.py +60 -60
- package/skills/d2h-utg-static-generate/tests/verify-produced-fixture.py +131 -131
- package/skills/harmonyos-migration-playbook/SKILL.md +83 -83
- package/skills/harmonyos-migration-playbook/agents/openai.yaml +7 -7
- package/skills/harmonyos-migration-playbook/references/arkts-compile.md +24 -24
- package/skills/harmonyos-migration-playbook/references/harmony-runtime.md +53 -53
- package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +45 -45
- package/skills/harmonyos-migration-playbook/references/protocol-e2e.md +23 -23
- package/skills/harmonyos-migration-playbook/references/ui-automation.md +53 -53
- package/skills/harmonyos-migration-playbook/references/windows-environment.md +38 -38
- package/skills/maintaining-migration-report/SKILL.md +144 -144
- package/skills/maintaining-migration-report/references/final-acceptance.md +48 -48
- package/skills/maintaining-migration-report/references/source-provenance.md +66 -66
- package/skills/managing-d2h-toolchain/SKILL.md +24 -24
- package/skills/managing-d2h-toolchain/environment-requirements.json +16 -16
- package/skills/managing-d2h-toolchain/scripts/manage-toolchain.mjs +98 -98
- package/skills/managing-d2h-toolchain/toolchain.json +16 -16
- package/skills/native-library-substitution/SKILL.md +384 -384
- package/skills/native-library-substitution/references/native-library-substitution.json +56905 -56905
- package/skills/native-library-substitution/references/native-library-substitution.md +163 -163
- package/skills/preparing-migration-workspace/SKILL.md +131 -131
- package/skills/reviewing-migration-process/SKILL.md +62 -62
|
@@ -1,391 +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
|
-
| 图片不切换 | 文件名不一致 | 不同语言目录图片文件名相同 |
|
|
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
|
+
| 图片不切换 | 文件名不一致 | 不同语言目录图片文件名相同 |
|