@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.
Files changed (126) hide show
  1. package/.claude-plugin/marketplace.json +15 -13
  2. package/.claude-plugin/plugin.json +13 -7
  3. package/AGENTS.md +5 -5
  4. package/README.md +212 -212
  5. package/agents/android-to-hmos-00-orchestrator.md +318 -318
  6. package/agents/d2h.md +157 -157
  7. package/dist/bin/install-opencode.js +30 -30
  8. package/docs/environment-check.md +86 -86
  9. package/install-opencode.sh +160 -160
  10. package/opencode/agents.json +16 -16
  11. package/opencode/vscode-d2h-package.json +7 -7
  12. package/opencode/vscode-d2h-plugin.mjs +29 -29
  13. package/package.json +54 -54
  14. package/schemas/android-source-manifest.schema.json +25 -25
  15. package/schemas/checkpoint-provenance.schema.json +67 -67
  16. package/schemas/final-acceptance.schema.json +29 -29
  17. package/schemas/managed-evidence-index.schema.json +15 -15
  18. package/schemas/managed-evidence.schema.json +114 -114
  19. package/schemas/migration-config.schema.json +51 -51
  20. package/schemas/migration-report-index.schema.json +39 -39
  21. package/schemas/migration-report-item.schema.json +51 -51
  22. package/schemas/migration-report-summary.schema.json +25 -25
  23. package/schemas/migration-status.schema.json +202 -202
  24. package/schemas/preflight.schema.json +60 -60
  25. package/schemas/source-order-audit.schema.json +16 -16
  26. package/schemas/source-provenance-event.schema.json +19 -19
  27. package/schemas/spec-app-shard.schema.json +45 -45
  28. package/schemas/spec-fact-corrections-shard.schema.json +13 -13
  29. package/schemas/spec-features-shard.schema.json +43 -43
  30. package/schemas/spec-index.schema.json +26 -26
  31. package/schemas/spec-interactions-shard.schema.json +40 -40
  32. package/schemas/spec-page.schema.json +98 -98
  33. package/schemas/spec-pages-index.schema.json +1 -1
  34. package/schemas/spec-unresolved-shard.schema.json +1 -1
  35. package/schemas/task-envelope.schema.json +109 -109
  36. package/schemas/task-plan.schema.json +341 -341
  37. package/scripts/check-environment.mjs +320 -320
  38. package/skills/android2hmos_resources_convert/SKILL.md +180 -180
  39. package/skills/android2hmos_resources_convert/references/image-conversion-rules.md +230 -230
  40. package/skills/android2hmos_resources_convert/references/svg-fix-patterns.md +175 -175
  41. package/skills/android2hmos_resources_convert/references/xml-drawable-to-svg-rules.md +513 -513
  42. package/skills/arkts-app-identity/SKILL.md +238 -238
  43. package/skills/arkts-i18n/SKILL.md +496 -496
  44. package/skills/arkts-i18n/evals/evals.json +84 -84
  45. package/skills/arkts-i18n/references/code-examples.md +302 -302
  46. package/skills/arkts-i18n/references/common-pitfalls.md +391 -391
  47. package/skills/arkts-i18n/references/dynamic-language-switch.md +604 -604
  48. package/skills/arkts-i18n/references/hardcoded-string-scanner.md +348 -348
  49. package/skills/arkts-i18n/references/language-codes.md +104 -104
  50. package/skills/arkts-i18n/references/resource-file-structure.md +775 -775
  51. package/skills/arkts-i18n/references/static-vs-dynamic.md +242 -242
  52. package/skills/arkts-i18n/references/v1-compat.md +244 -244
  53. package/skills/arkts-i18n/scripts/audit_i18n_completeness.sh +174 -174
  54. package/skills/arkts-icon-sizing/SKILL.md +211 -211
  55. package/skills/arkts-icon-sizing/scripts/icon_audit.py +131 -131
  56. package/skills/arkts-icon-sizing/scripts/icon_autofix.py +88 -88
  57. package/skills/arkts-icon-sizing/scripts/icon_dims.py +179 -179
  58. package/skills/arkts-icon-sizing/scripts/icon_fix.py +119 -119
  59. package/skills/arkts-mvvm-architecture/SKILL.md +613 -613
  60. package/skills/d2h-hypium-forge/SKILL.md +309 -309
  61. package/skills/d2h-hypium-forge/references/functional-spec-template.md +140 -140
  62. package/skills/d2h-hypium-forge/references/official-api.md +135 -135
  63. package/skills/d2h-hypium-forge/references/pitfalls.md +98 -98
  64. package/skills/d2h-hypium-forge/references/testability-template.md +71 -71
  65. package/skills/d2h-hypium-forge/scripts/_common_template.py +659 -659
  66. package/skills/d2h-hypium-forge/scripts/analyze_results.py +165 -165
  67. package/skills/d2h-hypium-forge/scripts/assert_audit.py +107 -107
  68. package/skills/d2h-hypium-forge/scripts/collect_runtime_pages.py +169 -169
  69. package/skills/d2h-hypium-forge/scripts/forge_report.py +325 -325
  70. package/skills/d2h-hypium-forge/scripts/inject_resource_ids.py +114 -114
  71. package/skills/d2h-hypium-forge/scripts/lint_asserts.py +80 -80
  72. package/skills/d2h-hypium-forge/scripts/lint_locate.py +60 -60
  73. package/skills/d2h-hypium-forge/scripts/merge_appgraph.py +249 -249
  74. package/skills/d2h-hypium-forge/scripts/precondition.py +140 -140
  75. package/skills/d2h-hypium-forge/scripts/preinject_ids.py +232 -232
  76. package/skills/d2h-hypium-forge/scripts/probe.py +197 -197
  77. package/skills/d2h-hypium-forge/scripts/run_direct.py +823 -823
  78. package/skills/d2h-hypium-forge/scripts/run_parallel.py +121 -121
  79. package/skills/d2h-hypium-forge/scripts/scan_features.py +255 -255
  80. package/skills/d2h-hypium-forge/scripts/upgrade_asserts.py +66 -66
  81. package/skills/d2h-hypium-forge/scripts/validate_scenarios.py +84 -84
  82. package/skills/d2h-page-develop/SKILL.md +112 -112
  83. package/skills/d2h-page-develop/agents/openai.yaml +7 -7
  84. package/skills/d2h-page-spec-generate/SKILL.md +206 -206
  85. package/skills/d2h-page-spec-generate/scripts/build_batch_report.py +227 -227
  86. package/skills/d2h-utg-ai-generate/SKILL.md +128 -128
  87. package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +76 -76
  88. package/skills/d2h-utg-ai-generate/references/extraction-guide.md +34 -34
  89. package/skills/d2h-utg-ai-generate/references/facts-contract.md +96 -96
  90. package/skills/d2h-utg-ai-generate/references/page-schema.json +61 -61
  91. package/skills/d2h-utg-ai-generate/references/utg-schema.json +89 -89
  92. package/skills/d2h-utg-ai-generate/scripts/build_page_json.py +314 -314
  93. package/skills/d2h-utg-ai-generate/scripts/build_utg.py +123 -123
  94. package/skills/d2h-utg-ai-generate/scripts/capture_from_page_json.ts +280 -280
  95. package/skills/d2h-utg-ai-generate/scripts/utg_common.py +524 -524
  96. package/skills/d2h-utg-ai-generate/scripts/utg_html.py +489 -489
  97. package/skills/d2h-utg-ai-generate/scripts/validate_utg.py +69 -69
  98. package/skills/d2h-utg-ai-generate/scripts/vendor/D3-LICENSE +13 -13
  99. package/skills/d2h-utg-ai-generate/scripts/vendor/d3-7.9.0.min.js +2 -2
  100. package/skills/d2h-utg-ai-generate/tests/test_pipeline.py +369 -369
  101. package/skills/d2h-utg-static-generate/SKILL.md +73 -73
  102. package/skills/d2h-utg-static-generate/references/discoveries.md +38 -38
  103. package/skills/d2h-utg-static-generate/scripts/generate.py +499 -499
  104. package/skills/d2h-utg-static-generate/tests/test_static.py +335 -335
  105. package/skills/d2h-utg-static-generate/tests/verify-offline-html.py +60 -60
  106. package/skills/d2h-utg-static-generate/tests/verify-produced-fixture.py +131 -131
  107. package/skills/harmonyos-migration-playbook/SKILL.md +83 -83
  108. package/skills/harmonyos-migration-playbook/agents/openai.yaml +7 -7
  109. package/skills/harmonyos-migration-playbook/references/arkts-compile.md +24 -24
  110. package/skills/harmonyos-migration-playbook/references/harmony-runtime.md +53 -53
  111. package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +45 -45
  112. package/skills/harmonyos-migration-playbook/references/protocol-e2e.md +23 -23
  113. package/skills/harmonyos-migration-playbook/references/ui-automation.md +53 -53
  114. package/skills/harmonyos-migration-playbook/references/windows-environment.md +38 -38
  115. package/skills/maintaining-migration-report/SKILL.md +144 -144
  116. package/skills/maintaining-migration-report/references/final-acceptance.md +48 -48
  117. package/skills/maintaining-migration-report/references/source-provenance.md +66 -66
  118. package/skills/managing-d2h-toolchain/SKILL.md +24 -24
  119. package/skills/managing-d2h-toolchain/environment-requirements.json +16 -16
  120. package/skills/managing-d2h-toolchain/scripts/manage-toolchain.mjs +98 -98
  121. package/skills/managing-d2h-toolchain/toolchain.json +16 -16
  122. package/skills/native-library-substitution/SKILL.md +384 -384
  123. package/skills/native-library-substitution/references/native-library-substitution.json +56905 -56905
  124. package/skills/native-library-substitution/references/native-library-substitution.md +163 -163
  125. package/skills/preparing-migration-workspace/SKILL.md +131 -131
  126. package/skills/reviewing-migration-process/SKILL.md +62 -62
@@ -1,302 +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`
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`