deveco_hmigbot 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +26 -0
- package/README.md +50 -0
- package/agents/hmigbot-worker.md +61 -0
- package/agents/hmigbot.md +22 -0
- package/agents/workflow-subagent.md +55 -0
- package/commands/hmigbot.md +17 -0
- package/dist/index.js +1 -0
- package/manifest.json +19 -0
- package/package.json +29 -0
- package/skills/migrate-core/FILES.md +26 -0
- package/skills/migrate-core/SKILL.md +484 -0
- package/skills/migrate-core/references/README.md +64 -0
- package/skills/migrate-core/references/flow/arkts-vector-gate.md +55 -0
- package/skills/migrate-core/references/flow/build-error-patterns.md +52 -0
- package/skills/migrate-core/references/flow/conventions-template.md +244 -0
- package/skills/migrate-core/references/flow/navigation-migration.md +42 -0
- package/skills/migrate-core/references/flow/platform-api-guards.md +59 -0
- package/skills/migrate-core/references/flow/platform-model-gaps.md +43 -0
- package/skills/migrate-core/references/flow/resource-conversion.md +46 -0
- package/skills/migrate-core/references/flow/ui-layout-semantics.md +124 -0
- package/skills/migrate-core/references/flow/unit-breakdown.md +42 -0
- package/skills/migrate-core/references/host-capabilities.md +24 -0
- package/skills/migrate-core/references/topics/app-identity.md +214 -0
- package/skills/migrate-core/references/topics/env-doctor.md +245 -0
- package/skills/migrate-core/references/topics/i18n/README.md +458 -0
- package/skills/migrate-core/references/topics/i18n/references/code-examples.md +304 -0
- package/skills/migrate-core/references/topics/i18n/references/common-pitfalls.md +354 -0
- package/skills/migrate-core/references/topics/i18n/references/dynamic-language-switch.md +464 -0
- package/skills/migrate-core/references/topics/i18n/references/language-codes.md +104 -0
- package/skills/migrate-core/references/topics/icon-sizing.md +98 -0
- package/skills/migrate-core/references/topics/library-migration/README.md +234 -0
- package/skills/migrate-core/references/topics/library-migration/closed-source-sdk.md +128 -0
- package/skills/migrate-core/references/topics/library-migration/download-api-decision.md +84 -0
- package/skills/migrate-core/references/topics/library-migration/library-mapping-table.md +100 -0
- package/skills/migrate-core/references/topics/library-migration/napi-compile-guide.md +84 -0
- package/skills/migrate-core/references/topics/library-migration/ohpm-search-guide.md +73 -0
- package/skills/migrate-core/references/topics/library-migration/stdlib-mapping-table.md +34 -0
- package/skills/migrate-core/references/topics/resources/aar-decompile.md +25 -0
- package/skills/migrate-core/references/topics/resources/conversion-rules.md +625 -0
- package/skills/migrate-core/references/topics/resources/dependency-analysis-rules.md +328 -0
- package/skills/migrate-core/references/topics/resources/material-design-icons.md +173 -0
- package/skills/migrate-core/references/topics/resources/svg-fix-patterns.md +175 -0
- package/skills/migrate-core/references/topics/resources/xml-drawable-to-svg-rules.md +513 -0
- package/skills/migrate-core/references/topics/system-capabilities/README.md +331 -0
- package/skills/migrate-core/references/topics/system-capabilities/avplayer-guide.md +161 -0
- package/skills/migrate-core/references/topics/system-capabilities/background-tasks.md +403 -0
- package/skills/migrate-core/references/topics/system-capabilities/browser-intent.md +121 -0
- package/skills/migrate-core/references/topics/system-capabilities/camera-picker.md +118 -0
- package/skills/migrate-core/references/topics/system-capabilities/document-picker.md +246 -0
- package/skills/migrate-core/references/topics/system-capabilities/file-utils.md +131 -0
- package/skills/migrate-core/references/topics/system-capabilities/permission-helper.md +112 -0
- package/skills/migrate-core/references/topics/system-capabilities/photo-access-helper.md +208 -0
- package/skills/migrate-core/references/topics/system-capabilities/print-management.md +213 -0
- package/skills/migrate-core/references/topics/system-capabilities/share-panel.md +177 -0
- package/skills/migrate-core/references/topics/system-capabilities/system-settings.md +322 -0
- package/skills/migrate-core/references/topics/system-capabilities/telephony-dial.md +49 -0
- package/skills/migrate-core/references/topics/system-capabilities/video-playback.md +42 -0
- package/skills/migrate-core/references/topics/system-capabilities/webview-patterns.md +38 -0
- package/skills/migrate-core/references/topics/ui-alignment/README.md +344 -0
- package/skills/migrate-core/references/topics/ui-alignment/references/dark-mode.md +47 -0
- package/skills/migrate-core/references/topics/ui-alignment/references/layout-mapping.md +301 -0
- package/skills/migrate-core/references/topics/ui-alignment/references/visual-patterns.md +411 -0
- package/skills/migrate-core/scripts/closure/check-anchors.mjs +186 -0
- package/skills/migrate-core/scripts/closure/check-api-guards.mjs +175 -0
- package/skills/migrate-core/scripts/closure/check-consumers.mjs +301 -0
- package/skills/migrate-core/scripts/closure/check-permissions.mjs +165 -0
- package/skills/migrate-core/scripts/closure/check-resources.mjs +130 -0
- package/skills/migrate-core/scripts/closure/check-routes.mjs +527 -0
- package/skills/migrate-core/scripts/closure/check-safearea.mjs +122 -0
- package/skills/migrate-core/scripts/closure/check-stubs.mjs +69 -0
- package/skills/migrate-core/scripts/closure/closure-suite.mjs +256 -0
- package/skills/migrate-core/scripts/closure/idioms.json +105 -0
- package/skills/migrate-core/scripts/convert/convert-resources.mjs +437 -0
- package/skills/migrate-core/scripts/feasibility/feasibility.mjs +235 -0
- package/skills/migrate-core/scripts/feasibility/tables/cross-platform.json +11 -0
- package/skills/migrate-core/scripts/feasibility/tables/deprecated-api.json +10 -0
- package/skills/migrate-core/scripts/feasibility/tables/imported-arkts-core.json +425 -0
- package/skills/migrate-core/scripts/feasibility/tables/lib-equivalence.json +206 -0
- package/skills/migrate-core/scripts/feasibility/tables/system-capabilities.json +22 -0
- package/skills/migrate-core/scripts/front.mjs +107 -0
- package/skills/migrate-core/scripts/interface/ark-extract.mjs +172 -0
- package/skills/migrate-core/scripts/interface/interface.mjs +152 -0
- package/skills/migrate-core/scripts/ledger/ledger.mjs +383 -0
- package/skills/migrate-core/scripts/ledger/parse-cards.mjs +98 -0
- package/skills/migrate-core/scripts/lib/literals.mjs +37 -0
- package/skills/migrate-core/scripts/lib/scan.mjs +315 -0
- package/skills/migrate-core/scripts/smoke/align-sdk.mjs +118 -0
- package/skills/migrate-core/scripts/smoke/ensure-sign.mjs +53 -0
- package/skills/migrate-core/scripts/smoke/smoke.mjs +238 -0
- package/skills/migrate-core/scripts/smoke/verdict.mjs +31 -0
- package/skills/migrate-core/scripts/smoke/walk.mjs +480 -0
- package/skills/migrate-core/scripts/transpile/mapping.json +76 -0
- package/skills/migrate-core/scripts/transpile/transpile-layout.mjs +404 -0
- package/skills/migrate-core/scripts/vectors/run-arkts-vectors.mjs +107 -0
- package/skills/migrate-core/scripts/vectors/setup-arkts-test.mjs +90 -0
- package/skills/migrate-core/scripts/wire/extractors.mjs +258 -0
- package/skills/migrate-core/scripts/wire/wire-routes.mjs +507 -0
- package/skills/migrate-core/templates/acceptance.js +365 -0
- package/skills/migrate-core/templates/explore.js +86 -0
- package/skills/migrate-core/templates/implement.js +211 -0
- package/skills/migrate-core/templates/mig_slices.js +491 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
# 动态语言切换实现(V2)
|
|
2
|
+
|
|
3
|
+
> ⚠ 本文样例中 `getContext(this)` 为已废弃写法:组件内一律改 `this.getUIContext().getHostContext()`;Ability 内用 `this.context`。
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
> 完整的运行时语言切换代码模板,包括状态管理和 UI 刷新。**本项目锁 V2**:组件用 `@ComponentV2`,本地状态用 `@Local`,全局 locale / refreshKey 用 `@ObservedV2 LocaleModel + AppStorageV2.connect`。i18n API 与装饰器版本无关。
|
|
7
|
+
>
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ⚠️ API 验证状态
|
|
12
|
+
|
|
13
|
+
| API | 状态 | 说明 |
|
|
14
|
+
|-----|------|------|
|
|
15
|
+
| `i18n.System.setAppPreferredLanguage(lang)` | ✅ SDK 有声明 | `@ohos.i18n` System,同步、返回 void;设 `'default'` 跟随系统(冷启动生效) |
|
|
16
|
+
| `i18n.System.getAppPreferredLanguage()` | ✅ SDK 有声明 | 同步返回 string(如 `zh-Hans`) |
|
|
17
|
+
| V2 `@Local` / `AppStorageV2.connect` 触发 UI 刷新 | ✅ 已验证 | ArkTS V2 标准机制(API 12+) |
|
|
18
|
+
| `onConfigurationUpdate(config)` | ✅ SDK 有声明 | UIAbility 唯一的配置变化回调;`config.language` 有值即语言变了 |
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 基本实现模式
|
|
24
|
+
|
|
25
|
+
### 简单实现(单页面,V2)
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { i18n } from '@kit.LocalizationKit'
|
|
29
|
+
import { common } from '@kit.AbilityKit'
|
|
30
|
+
import { hilog } from '@kit.PerformanceAnalysisKit'
|
|
31
|
+
|
|
32
|
+
const DOMAIN = 0x0001
|
|
33
|
+
const TAG = 'I18nDemo'
|
|
34
|
+
|
|
35
|
+
@Entry
|
|
36
|
+
@ComponentV2
|
|
37
|
+
struct LanguageSwitchDemo {
|
|
38
|
+
private context = getContext(this) as common.UIAbilityContext
|
|
39
|
+
@Local private currentLanguage: string = 'zh'
|
|
40
|
+
@Local private refreshKey: number = 0
|
|
41
|
+
|
|
42
|
+
aboutToAppear(): void {
|
|
43
|
+
this.loadCurrentLanguage()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async loadCurrentLanguage(): Promise<void> {
|
|
47
|
+
try {
|
|
48
|
+
this.currentLanguage = i18n.System.getAppPreferredLanguage() || 'zh' // 同步返回当前生效语言
|
|
49
|
+
hilog.info(DOMAIN, TAG, `Current language: ${this.currentLanguage}`)
|
|
50
|
+
} catch (e) {
|
|
51
|
+
hilog.error(DOMAIN, TAG, `Failed to get language: ${e.message}`)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async switchLanguage(lang: string): Promise<void> {
|
|
56
|
+
try {
|
|
57
|
+
hilog.info(DOMAIN, TAG, `Switching to: ${lang}`)
|
|
58
|
+
|
|
59
|
+
i18n.System.setAppPreferredLanguage(lang) // 同步;之后加载的资源按此语言取,已渲染页面靠 refreshKey 重建
|
|
60
|
+
|
|
61
|
+
this.currentLanguage = lang
|
|
62
|
+
this.refreshKey++
|
|
63
|
+
|
|
64
|
+
hilog.info(DOMAIN, TAG, `Language switched to: ${lang}`)
|
|
65
|
+
} catch (e) {
|
|
66
|
+
hilog.error(DOMAIN, TAG, `Failed to switch language: ${e.message}`)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
build() {
|
|
71
|
+
Column({ space: 20 }) {
|
|
72
|
+
Text($r('app.string.app_name'))
|
|
73
|
+
.fontSize(24)
|
|
74
|
+
.fontWeight(FontWeight.Bold)
|
|
75
|
+
|
|
76
|
+
Text($r('app.string.welcome'))
|
|
77
|
+
.fontSize(16)
|
|
78
|
+
|
|
79
|
+
Text($r('app.string.settings_language'))
|
|
80
|
+
.fontSize(14)
|
|
81
|
+
.fontColor('#666666')
|
|
82
|
+
|
|
83
|
+
Row({ space: 12 }) {
|
|
84
|
+
Button('中文')
|
|
85
|
+
.onClick(() => this.switchLanguage('zh'))
|
|
86
|
+
.backgroundColor(this.currentLanguage === 'zh' ? '#007AFF' : '#CCCCCC')
|
|
87
|
+
|
|
88
|
+
Button('English')
|
|
89
|
+
.onClick(() => this.switchLanguage('en'))
|
|
90
|
+
.backgroundColor(this.currentLanguage === 'en' ? '#007AFF' : '#CCCCCC')
|
|
91
|
+
|
|
92
|
+
Button('日本語')
|
|
93
|
+
.onClick(() => this.switchLanguage('ja'))
|
|
94
|
+
.backgroundColor(this.currentLanguage === 'ja' ? '#007AFF' : '#CCCCCC')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
Text(`Current: ${this.currentLanguage}`)
|
|
98
|
+
.fontSize(12)
|
|
99
|
+
.fontColor('#999999')
|
|
100
|
+
}
|
|
101
|
+
.width('100%')
|
|
102
|
+
.height('100%')
|
|
103
|
+
.justifyContent(FlexAlign.Center)
|
|
104
|
+
.id('lang_container_' + this.refreshKey)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 完整实现(设置页面 + 全局状态,V2)
|
|
112
|
+
|
|
113
|
+
### 1. 全局 LocaleModel(@ObservedV2 + AppStorageV2)
|
|
114
|
+
|
|
115
|
+
V2 推荐**单一 `@ObservedV2` 类封装所有 locale 相关全局状态**,避免 V1 散落的 `@StorageLink('currentLanguage')` / `@StorageLink('languageRefreshKey')` 多 key 写法(拼写易错、无类型保护)。
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { i18n } from '@kit.LocalizationKit'
|
|
119
|
+
// common/LocaleModel.ets
|
|
120
|
+
import { common } from '@kit.AbilityKit'
|
|
121
|
+
import { AppStorageV2 } from '@kit.ArkUI'
|
|
122
|
+
import { hilog } from '@kit.PerformanceAnalysisKit'
|
|
123
|
+
|
|
124
|
+
const DOMAIN = 0x0000
|
|
125
|
+
const TAG = 'LocaleModel'
|
|
126
|
+
|
|
127
|
+
@ObservedV2
|
|
128
|
+
export class LocaleModel {
|
|
129
|
+
@Trace currentLanguage: string = 'zh'
|
|
130
|
+
@Trace refreshKey: number = 0
|
|
131
|
+
|
|
132
|
+
async init(context: common.UIAbilityContext): Promise<void> {
|
|
133
|
+
try {
|
|
134
|
+
this.currentLanguage = i18n.System.getAppPreferredLanguage() || 'zh'
|
|
135
|
+
} catch (e) {
|
|
136
|
+
this.currentLanguage = 'zh'
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async setLanguage(context: common.UIAbilityContext, lang: string): Promise<void> {
|
|
141
|
+
try {
|
|
142
|
+
i18n.System.setAppPreferredLanguage(lang) // 同步;之后加载的资源按此语言取,已渲染页面靠 refreshKey 重建
|
|
143
|
+
this.currentLanguage = lang
|
|
144
|
+
this.refreshKey++ // @Trace 字段变化 → 所有 connect 同 key 的组件自动刷新
|
|
145
|
+
} catch (e) {
|
|
146
|
+
hilog.error(DOMAIN, TAG, `Failed to set language: ${e.message}`)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 全局单例(推荐在 EntryAbility.onCreate 中预热一次;任何位置 connect 同 key 都返回同一实例)
|
|
152
|
+
export const localeStore = AppStorageV2.connect(
|
|
153
|
+
LocaleModel,
|
|
154
|
+
'locale',
|
|
155
|
+
() => new LocaleModel()
|
|
156
|
+
)!
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 2. 设置页面(V2)
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// pages/SettingsPage.ets
|
|
163
|
+
import { LocaleModel, localeStore } from '../common/LocaleModel'
|
|
164
|
+
import { common } from '@kit.AbilityKit'
|
|
165
|
+
|
|
166
|
+
@Entry
|
|
167
|
+
@ComponentV2
|
|
168
|
+
struct SettingsPage {
|
|
169
|
+
@Local locale: LocaleModel = AppStorageV2.connect(
|
|
170
|
+
LocaleModel,
|
|
171
|
+
'locale',
|
|
172
|
+
() => new LocaleModel()
|
|
173
|
+
)!
|
|
174
|
+
|
|
175
|
+
@Builder
|
|
176
|
+
LanguageItem(lang: string, label: string) {
|
|
177
|
+
Row() {
|
|
178
|
+
Text(label)
|
|
179
|
+
.fontSize(16)
|
|
180
|
+
|
|
181
|
+
if (this.locale.currentLanguage === lang) {
|
|
182
|
+
Text('✓')
|
|
183
|
+
.fontSize(16)
|
|
184
|
+
.fontColor('#007AFF')
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
.width('100%')
|
|
188
|
+
.padding(16)
|
|
189
|
+
.backgroundColor(this.locale.currentLanguage === lang ? '#F0F8FF' : '#FFFFFF')
|
|
190
|
+
.onClick(() => {
|
|
191
|
+
const context = getContext(this) as common.UIAbilityContext
|
|
192
|
+
this.locale.setLanguage(context, lang)
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
build() {
|
|
197
|
+
Column() {
|
|
198
|
+
Text($r('app.string.settings_title'))
|
|
199
|
+
.fontSize(20)
|
|
200
|
+
.fontWeight(FontWeight.Bold)
|
|
201
|
+
.padding(16)
|
|
202
|
+
|
|
203
|
+
List() {
|
|
204
|
+
ListItem() {
|
|
205
|
+
this.LanguageItem('zh', '简体中文')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
ListItem() {
|
|
209
|
+
this.LanguageItem('zh-Hans', '简体中文 (简体)')
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
ListItem() {
|
|
213
|
+
this.LanguageItem('zh-Hant', '繁体中文 (繁体)')
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
ListItem() {
|
|
217
|
+
this.LanguageItem('en', 'English')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
ListItem() {
|
|
221
|
+
this.LanguageItem('ja', '日本語')
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
.width('100%')
|
|
226
|
+
.height('100%')
|
|
227
|
+
.id('settings_' + this.locale.refreshKey)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 语言切换后刷新页面的方法(V2)
|
|
235
|
+
|
|
236
|
+
### 方法 1:@Local refreshKey + ID 变化
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { i18n } from '@kit.LocalizationKit'
|
|
240
|
+
@Local private refreshKey: number = 0
|
|
241
|
+
|
|
242
|
+
async switchLanguage(lang: string) {
|
|
243
|
+
await i18n.System.setAppPreferredLanguage([lang])
|
|
244
|
+
this.refreshKey++
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
build() {
|
|
248
|
+
Column() {
|
|
249
|
+
Text($r('app.string.hello'))
|
|
250
|
+
}
|
|
251
|
+
.id('page_' + this.refreshKey) // ID 变化触发重建
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### 方法 2:router.replaceUrl 重新加载
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import { i18n } from '@kit.LocalizationKit'
|
|
259
|
+
import { router } from '@kit.ArkUI'
|
|
260
|
+
|
|
261
|
+
async switchLanguage(lang: string) {
|
|
262
|
+
await i18n.System.setAppPreferredLanguage([lang])
|
|
263
|
+
router.replaceUrl({ url: 'pages/SettingsPage' })
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### 方法 3:Navigation 模式(V2)
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
@Local private navPathStack: NavPathStack = new NavPathStack()
|
|
271
|
+
@Local private refreshKey: number = 0
|
|
272
|
+
|
|
273
|
+
build() {
|
|
274
|
+
Navigation(this.navPathStack) {
|
|
275
|
+
// 内容
|
|
276
|
+
}
|
|
277
|
+
.id('nav_' + this.refreshKey)
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 方法 4:@Monitor 触发副作用(V2 新增)
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
@ComponentV2
|
|
285
|
+
struct PageWithMonitor {
|
|
286
|
+
@Local locale: LocaleModel = AppStorageV2.connect(
|
|
287
|
+
LocaleModel, 'locale', () => new LocaleModel()
|
|
288
|
+
)!
|
|
289
|
+
|
|
290
|
+
@Monitor('locale.currentLanguage')
|
|
291
|
+
onLanguageChange(monitor: IMonitor): void {
|
|
292
|
+
// currentLanguage 改变时执行副作用:埋点、缓存清理等
|
|
293
|
+
console.info('Language changed to:', this.locale.currentLanguage)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
build() {
|
|
297
|
+
Text($r('app.string.hello'))
|
|
298
|
+
.id('page_' + this.locale.refreshKey)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## 获取支持的语言列表
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
|
|
309
|
+
interface LanguageInfo {
|
|
310
|
+
language: string // 如 'zh'
|
|
311
|
+
region?: string // 如 'CN'
|
|
312
|
+
displayName: string // 如 '简体中文'
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function getSupportedLanguages(): Promise<LanguageInfo[]> {
|
|
316
|
+
// 这是一个示例,实际 API 可能不同
|
|
317
|
+
const supported = ['zh', 'zh-Hans', 'zh-Hant', 'en', 'ja', 'ko']
|
|
318
|
+
const displayNames: Record<string, string> = {
|
|
319
|
+
'zh': '中文',
|
|
320
|
+
'zh-Hans': '简体中文',
|
|
321
|
+
'zh-Hant': '繁体中文',
|
|
322
|
+
'en': 'English',
|
|
323
|
+
'ja': '日本語',
|
|
324
|
+
'ko': '한국어'
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return supported.map(lang => ({
|
|
328
|
+
language: lang,
|
|
329
|
+
displayName: displayNames[lang] || lang
|
|
330
|
+
}))
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## 监听系统语言变化(V2)
|
|
337
|
+
|
|
338
|
+
### 在 UIAbility 中处理(写入 AppStorageV2)
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// EntryAbility.ets
|
|
342
|
+
import { AbilityConstant, Configuration, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'
|
|
343
|
+
import { hilog } from '@kit.PerformanceAnalysisKit'
|
|
344
|
+
import { AppStorageV2 } from '@kit.ArkUI'
|
|
345
|
+
import { i18n } from '@kit.LocalizationKit'
|
|
346
|
+
import { LocaleModel } from '../common/LocaleModel'
|
|
347
|
+
|
|
348
|
+
export default class EntryAbility extends UIAbility {
|
|
349
|
+
// UIAbility 只有 onConfigurationUpdate 一个配置变化回调(没有单独的语言回调):语言变了在这里刷新
|
|
350
|
+
onConfigurationUpdate(configuration: Configuration): void {
|
|
351
|
+
if (configuration.language === undefined) return
|
|
352
|
+
hilog.info(0x0000, 'EntryAbility', `Language changed to: ${configuration.language}`)
|
|
353
|
+
this.refreshLocale()
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private refreshLocale(): void {
|
|
357
|
+
// 1. 拿到全局 LocaleModel(与组件 connect 同一实例)
|
|
358
|
+
const locale = AppStorageV2.connect(LocaleModel, 'locale', () => new LocaleModel())!
|
|
359
|
+
// 2. 重新获取当前语言(同步返回当前生效语言,如 'zh-Hans')
|
|
360
|
+
locale.currentLanguage = i18n.System.getAppPreferredLanguage() || 'zh'
|
|
361
|
+
locale.refreshKey++ // 通知所有页面刷新
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## 完整页面刷新机制(V2)
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
import { i18n } from '@kit.LocalizationKit'
|
|
372
|
+
// 确保语言切换后所有页面都刷新的完整 V2 模式
|
|
373
|
+
|
|
374
|
+
// 1. 全局 LocaleModel
|
|
375
|
+
@ObservedV2
|
|
376
|
+
class LocaleModel {
|
|
377
|
+
@Trace currentLanguage: string = 'zh'
|
|
378
|
+
@Trace refreshKey: number = 0
|
|
379
|
+
|
|
380
|
+
async switchLanguage(context: common.UIAbilityContext, lang: string): Promise<void> {
|
|
381
|
+
// 1. 切换语言
|
|
382
|
+
i18n.System.setAppPreferredLanguage(lang) // 同步;之后加载的资源按此语言取,已渲染页面靠 refreshKey 重建
|
|
383
|
+
|
|
384
|
+
// 2. 更新状态:所有 connect 'locale' 的组件 @Trace 字段变化即刷新
|
|
385
|
+
this.currentLanguage = lang
|
|
386
|
+
this.refreshKey++
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// 2. 任意页面用 @Local + AppStorageV2.connect 引用单例
|
|
391
|
+
@Entry
|
|
392
|
+
@ComponentV2
|
|
393
|
+
struct AnyPage {
|
|
394
|
+
@Local locale: LocaleModel = AppStorageV2.connect(
|
|
395
|
+
LocaleModel, 'locale', () => new LocaleModel()
|
|
396
|
+
)!
|
|
397
|
+
|
|
398
|
+
build() {
|
|
399
|
+
Column() {
|
|
400
|
+
Text($r('app.string.any_string'))
|
|
401
|
+
.fontSize(16)
|
|
402
|
+
}
|
|
403
|
+
.id('page_' + this.locale.refreshKey)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## 注意事项
|
|
411
|
+
|
|
412
|
+
1. **语言代码大小写**:`zh-Hans` vs `zh-hans` — 必须完全匹配
|
|
413
|
+
2. **切换后需要时间生效**:异步操作,避免连续快速切换
|
|
414
|
+
3. **并非所有页面都需要重建**:只有持有 `LocaleModel`(`@Local locale = AppStorageV2.connect(LocaleModel, 'locale', ...)`)并在 `build()` 中读取 `locale.refreshKey` 的组件才会重建
|
|
415
|
+
4. **性能考虑**:避免高频调用,建议在语言切换按钮上做防抖
|
|
416
|
+
5. **V1/V2 不混用**:同一 struct 内不要混用 `@State` 和 `@Local`,会编译报错
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## 持久化用户语言偏好(V2 推荐 PersistenceV2)
|
|
421
|
+
|
|
422
|
+
V1 用 `PersistentStorage.persistProp('currentLanguage', 'zh')` + `@StorageLink`;V2 一站式:
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
// common/LocaleModel.ets
|
|
426
|
+
@ObservedV2
|
|
427
|
+
export class LocaleModel {
|
|
428
|
+
@Trace currentLanguage: string = 'zh'
|
|
429
|
+
@Trace refreshKey: number = 0
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// 在任意位置 connect(自动持久化到磁盘 + UI 响应式)
|
|
433
|
+
@ComponentV2
|
|
434
|
+
struct App {
|
|
435
|
+
@Local locale: LocaleModel = PersistenceV2.globalConnect({
|
|
436
|
+
type: LocaleModel,
|
|
437
|
+
key: 'app_locale',
|
|
438
|
+
defaultCreator: () => new LocaleModel()
|
|
439
|
+
})!
|
|
440
|
+
|
|
441
|
+
build() {
|
|
442
|
+
Column() {
|
|
443
|
+
Text(`当前: ${this.locale.currentLanguage}`)
|
|
444
|
+
Button('切换 EN').onClick(() => {
|
|
445
|
+
this.locale.currentLanguage = 'en' // 自动落盘 + 触发所有引用刷新
|
|
446
|
+
})
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## V1 → V2 迁移速查(仅本文涉及部分)
|
|
455
|
+
|
|
456
|
+
| V1 | V2 | 备注 |
|
|
457
|
+
|---|---|---|
|
|
458
|
+
| `@Component` | `@ComponentV2` | struct 装饰器 |
|
|
459
|
+
| `@State refreshKey: number = 0` | `@Local refreshKey: number = 0` | 必须初始化 |
|
|
460
|
+
| `@StorageLink('currentLanguage') lang: string = 'zh'` | `@Local locale: LocaleModel = AppStorageV2.connect(LocaleModel, 'locale', () => new LocaleModel())!` 然后 `this.locale.currentLanguage` | 先抽 @ObservedV2 类 |
|
|
461
|
+
| `AppStorage.setOrCreate('refreshKey', n+1)` | `localeStore.refreshKey++` | 直接改 @Trace 字段 |
|
|
462
|
+
| `PersistentStorage.persistProp('lang', 'zh')` | `PersistenceV2.globalConnect({ type: LocaleModel, key, defaultCreator })` | V2 一站式 |
|
|
463
|
+
| `@Watch('lang') onLangChange()` | `@Monitor('lang') onLangChange(m: IMonitor)` | 方法装饰器 + IMonitor |
|
|
464
|
+
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# 语言代码对照表
|
|
2
|
+
|
|
3
|
+
> HarmonyOS 支持的语言代码规范,包括 ISO 标准和使用示例
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 两种命名方案
|
|
8
|
+
|
|
9
|
+
### 方案 1:仅使用语言代码(推荐用于简单应用)
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
resources/
|
|
13
|
+
├── base/ # 默认资源(fallback)
|
|
14
|
+
├── en/ # 英文
|
|
15
|
+
├── zh/ # 中文
|
|
16
|
+
├── ja/ # 日文
|
|
17
|
+
└── ko/ # 韩文
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**优点**:
|
|
21
|
+
- 简单直观
|
|
22
|
+
- 适配所有区域(en 匹配 en_US, en_GB, en_AU 等)
|
|
23
|
+
|
|
24
|
+
**缺点**:
|
|
25
|
+
- 无法区分 zh_CN(简体)和 zh_TW(繁体)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
### 方案 2:使用语言+区域代码(推荐用于多区域应用)
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
resources/
|
|
33
|
+
├── base/
|
|
34
|
+
├── en_US/ # 美国英语
|
|
35
|
+
├── en_GB/ # 英国英语
|
|
36
|
+
├── zh_CN/ # 简体中文
|
|
37
|
+
├── zh_TW/ # 繁体中文(台湾)
|
|
38
|
+
├── zh_HK/ # 繁体中文(香港)
|
|
39
|
+
└── ja/
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**优点**:
|
|
43
|
+
- 更精确的区域划分
|
|
44
|
+
- 可以针对不同地区提供不同翻译
|
|
45
|
+
|
|
46
|
+
**缺点**:
|
|
47
|
+
- 目录较多
|
|
48
|
+
- 需要维护更多资源文件
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 推荐实践
|
|
53
|
+
|
|
54
|
+
| 场景 | 推荐方案 | 示例 |
|
|
55
|
+
|------|---------|------|
|
|
56
|
+
| 单一市场应用 | 方案 1 | 中国应用:`zh`, `en` |
|
|
57
|
+
| 多区域应用 | 方案 2 | 国际化应用:`zh_CN`, `zh_TW`, `en_US`, `en_GB` |
|
|
58
|
+
| 简单国际化 | 方案 1 | `en`, `zh`, `ja` |
|
|
59
|
+
| 专业国际化 | 方案 2 | 所有主要语言+区域 |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 语言代码对照表
|
|
64
|
+
|
|
65
|
+
| 语言 | 语言代码 | 区域代码 | 完整代码 | 说明 |
|
|
66
|
+
|------|---------|---------|---------|------|
|
|
67
|
+
| 中文 | `zh` | - | `zh` | 通用中文 |
|
|
68
|
+
| 简体中文 | `zh` | `CN` | `zh_CN` 或 `zh-Hans` | 中国大陆 |
|
|
69
|
+
| 繁体中文 | `zh` | `TW` | `zh_TW` 或 `zh-Hant` | 台湾 |
|
|
70
|
+
| 繁体中文 | `zh` | `HK` | `zh_HK` | 香港 |
|
|
71
|
+
| 英文 | `en` | - | `en` | 通用英文 |
|
|
72
|
+
| 美国英文 | `en` | `US` | `en_US` | 美国 |
|
|
73
|
+
| 英国英文 | `en` | `GB` | `en_GB` | 英国 |
|
|
74
|
+
| 日文 | `ja` | - | `ja` | 日本 |
|
|
75
|
+
| 韩文 | `ko` | - | `ko` | 韩国 |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## API 12+ 格式注意
|
|
80
|
+
|
|
81
|
+
HarmonyOS 支持两种格式:
|
|
82
|
+
|
|
83
|
+
| 格式 | 示例 | 说明 |
|
|
84
|
+
|------|------|------|
|
|
85
|
+
| 下划线格式 | `zh_CN`, `en_US` | 传统格式 |
|
|
86
|
+
| 连字符格式 | `zh-Hans`, `zh-Hant` | Unicode 格式,API 12+ 推荐 |
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 当前代码仓实践
|
|
91
|
+
|
|
92
|
+
**代码仓使用**:`en_US/`(方案 2)
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
entry/src/main/resources/
|
|
96
|
+
├── base/
|
|
97
|
+
├── en_US/ # 美国英语
|
|
98
|
+
└── dark/ # 深色模式
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**分析**:
|
|
102
|
+
- ✅ 使用了精确的区域划分
|
|
103
|
+
- ⚠️ 只有英文翻译,缺少其他语言
|
|
104
|
+
- 💡 建议:如需支持更多语言,可按相同模式添加
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<!-- 原技能 arkts-icon-sizing,已吸收为 migrate-core 语料专题,不再单独注册 -->
|
|
2
|
+
> 检测并修复 Android→HarmonyOS UI 迁移中,因丢失 `.width()/.height()` 而导致渲染尺寸错误或比例 失真的 ArkUI/HarmonyOS `Image` 组件。转换器保留了 `.objectFit()` 却丢掉了尺寸,使 `resources/base/media` 里的位图图标按**原始像素尺寸**渲染(通常大 3 倍,非方形的还会被拉伸)。当用户反馈图标/图片在鸿蒙页面 (主页、我的、弹窗、列表项——任何位置)上「太大 / 太小 / 被拉伸 / 被压扁 / 比例异常 / 图标变形 / 显示太大 / icons look off」,或要求「统一检查/修复各页图标比例」,或刚做完 a2h / ArkUI UI 迁移(大量 Android ImageView 用了 wrap_content)时触发。内置「审计→测量→修复」三步流水线:用源位图的像素尺寸
|
|
3
|
+
|
|
4
|
+
# ArkUI 图标尺寸修复(Android→HarmonyOS 迁移修复)
|
|
5
|
+
|
|
6
|
+
## 问题与成因
|
|
7
|
+
|
|
8
|
+
在 Android 中,`android:layout_width/height="wrap_content"` 的 `ImageView` 会按 drawable 的**标称 dp**
|
|
9
|
+
尺寸渲染。一张只放在 `*-xxhdpi`(3 倍密度)目录的位图,磁盘上可能是 `90×90 px`,显示为 `30×30 dp`。
|
|
10
|
+
|
|
11
|
+
Android→HarmonyOS UI 转换器常常复刻了 `ImageView` 的 `.objectFit(...)`,却**丢掉了 `.width()/.height()`**
|
|
12
|
+
(因为 `wrap_content` 没有固定数值可抄)。位图被原样拷进 HarmonyOS 的 `resources/base/media`——而**该目录无密度
|
|
13
|
+
限定符**——于是 ArkUI 按 `Image` 的**字面像素尺寸(当成 vp)**渲染。那张 `90×90 px` 的图标现在显示为 `90×90 vp`,
|
|
14
|
+
即**大了 3 倍**。更糟的是,非方形图标(`45×23` 的电池、`44×26` 的箭头)在没有显式尺寸时保持原始像素框,一旦
|
|
15
|
+
某根轴被布局约束触碰,宽高比就明显失真——这就是用户看到的「比例异常 / 图标变形」。
|
|
16
|
+
|
|
17
|
+
修复办法是把每个欠约束的 `Image` 还原成它的**标称 vp 尺寸**:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
标称 vp = 源位图像素 ÷ 源密度倍数
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
宽和高**分别独立**相除,这正是还原正确宽高比的关键。已经带显式 `.width()/.height()` 的图标是正常的——**不要**
|
|
24
|
+
碰它们,也**不要**用「把位图挪进带密度限定的目录」这种全局手段去修(那会把本来正确的图标缩小)。缺陷只针对
|
|
25
|
+
**那一部分丢了尺寸的 `Image`**。
|
|
26
|
+
|
|
27
|
+
## 何时使用
|
|
28
|
+
|
|
29
|
+
- 用户反馈图标/图片在鸿蒙页面上过大、过小、被拉伸或被压扁——尤其刚做完 a2h / ArkUI UI 迁移之后。
|
|
30
|
+
- 用户要求「统一检查/修复图标比例」。
|
|
31
|
+
- 做迁移 QA,想横扫每个页面排查这一类缺陷。
|
|
32
|
+
|
|
33
|
+
不属于本专题:文本截断、大字体布局溢出、深色模式图标变色、生成全新 UI。那些是不同的问题。
|
|
34
|
+
|
|
35
|
+
## 检测 → 测量 → 修复(三步,全是确定性判断,手工或 grep 即可完成)
|
|
36
|
+
|
|
37
|
+
> **先提交或暂存你的工作。** 修复会就地改源文件;干净的 git 状态让一次坏的运行可以秒回滚。
|
|
38
|
+
|
|
39
|
+
### 1. 审计 — 找出每一个欠约束的 Image
|
|
40
|
+
|
|
41
|
+
在 `*/src/main/ets` 下 grep 所有 `Image(` 调用,逐处看修饰链。被标记 = 修饰链**无 width 且无 height**(`NO-DIMS`)
|
|
42
|
+
或**只有其中一个且无 `aspectRatio`**(`ONE-DIM-NO-ASPECT`)。`width('100%') + height('100%')` 视为有意的满铺,不标记。
|
|
43
|
+
|
|
44
|
+
分两份清单:**静态**欠约束图标(`$r('app.media.xxx')`,可按下面公式直接修)与**动态源**图片
|
|
45
|
+
(`Image(this.foo())`、三元、`@Builder` 参数——需手工解析,见第 4 步)。`ImageFit.Fill` 用法单列复核:
|
|
46
|
+
`Fill` 会拉伸填满,通常是**可拉伸背景图形**(`shape_*_bg` 弹窗横幅)的有意为之,除非真有图标用了 `Fill`,否则放着别动。
|
|
47
|
+
|
|
48
|
+
### 2. 测量 — 还原每个图标的正确 vp 尺寸
|
|
49
|
+
|
|
50
|
+
对每个被标记的静态图标:取 HarmonyOS `resources/base/media` 里该文件(webp/png/jpg/svg)的像素尺寸
|
|
51
|
+
(`sips -g pixelWidth -g pixelHeight <file>` / `file <file>` / SVG 读 `viewBox`),再到 **Android 源**里找同名资源所在的
|
|
52
|
+
密度目录,按倍数表相除:ldpi .75,mdpi 1,hdpi 1.5,xhdpi 2,xxhdpi 3,xxxhdpi 4。多模块工程的共享资源在
|
|
53
|
+
`base`/`common` 模块的 `res/`,一起查。找不到 Android 源时按工程主密度桶兜底(多数 app 只发 xxhdpi=3),并在结论里标注"假定 3×"。
|
|
54
|
+
|
|
55
|
+
**落地前核对**:密度来源是真实目录(如 `mipmap-xxhdpi`),算出的 vp 尺寸对一个图标而言合理(小图标个/十位数,插图更大)。
|
|
56
|
+
|
|
57
|
+
没有 raster 的项(动态引用、纯 SVG `<shape>` 转换或矢量图)不在本步——转第 4 步手工处理。
|
|
58
|
+
|
|
59
|
+
### 3. 修复 — 插入尺寸
|
|
60
|
+
|
|
61
|
+
逐处在 `Image(...)` 开头之后插入:
|
|
62
|
+
- **NO-DIMS** → `.width(W)` `.height(H)`(还原的标称 vp;固定尺寸、真实比例)。
|
|
63
|
+
- **ONE-DIM** → 仅 `.aspectRatio(W/H)`——保留有意的那一维(如满宽横幅的 `.width('100%')`),只修比例。
|
|
64
|
+
|
|
65
|
+
动态图片只报告,绝不批量改。
|
|
66
|
+
|
|
67
|
+
### 4. 手工解析动态源图片
|
|
68
|
+
|
|
69
|
+
`Image(this.vipEditRes())` 或 `Image(cond ? $r('app.media.a') : $r('app.media.b'))` 读不出尺寸。对每一处:
|
|
70
|
+
|
|
71
|
+
1. 读代码找出它能解析到的具体资源名(顺着方法/三元跟踪)。勾选类开关通常指向两张同尺寸资源。
|
|
72
|
+
2. 按第 2 步量这些资源。
|
|
73
|
+
3. 手工给 `Image(...)` 加 `.width(W).height(H)`(若已设一维则用 `.aspectRatio()`)。`@Builder tabItem(icon: Resource)`
|
|
74
|
+
会让所有调用方共享一个尺寸——在 Builder 的 `Image` 上设一次即可。
|
|
75
|
+
|
|
76
|
+
### 5. 校验
|
|
77
|
+
|
|
78
|
+
重跑第 1 步的 grep:静态被标记数应为 0。然后**编译工程**(`hvigorw assembleHap ...`)——插入的都是普通
|
|
79
|
+
`.width(n)/.height(n)/.aspectRatio(n)`(vp 数值),编译通过即确认语法无误。只应剩下有意跳过的动态项(已手工修)和 `Fill` 背景。
|
|
80
|
+
|
|
81
|
+
## 决策规则与边界
|
|
82
|
+
|
|
83
|
+
- **密度是逐图的,不是全局的。** 每个图标按它在 Android 源里的真实密度目录算。
|
|
84
|
+
多数 app 只发一个桶(xxhdpi=3),但别假设——逐图核实目录。倍数表:ldpi .75,mdpi 1,hdpi 1.5,xhdpi 2,
|
|
85
|
+
xxhdpi 3,xxxhdpi 4。
|
|
86
|
+
- **方形 SVG 不会失真。** 方形 viewBox 的 `<shape>`/矢量图按标称 vp 渲染、比例正确——它不会「比例错」,只可能
|
|
87
|
+
「尺寸错」。若这种 SVG 渲染得过大(如 `100×100` viewBox 显示成 100vp,而本意是小图标),查 **Android drawable 的
|
|
88
|
+
`android:width/height`** 取本意 dp 并显式设上。否则优先级低。
|
|
89
|
+
- **`shape_*_bg` 上的 `ImageFit.Fill`** 是可拉伸横幅背景——放着别动。只在真图标的盒子比例 ≠ 图标比例时才管 `Fill`。
|
|
90
|
+
- **别碰已带尺寸的图标。** 重点就是欠约束的那部分;全局改资源密度会破坏本来正确的图标。
|
|
91
|
+
- **多模块 Android 工程**把共享资源放在 `base`/`common` 模块——每个 `*/src/main/res` 根都要查,密度才找得到。
|
|
92
|
+
|
|
93
|
+
## 与其他专题的关系
|
|
94
|
+
|
|
95
|
+
- 图片被裁切/容器溢出类问题方向相反(倾向去掉固定尺寸换 `%`/`aspectRatio`),不归本专题。
|
|
96
|
+
- 资源/资产侧转换(res/ → resources/、SVG 坐标/格式修复)归 `scripts/convert/convert-resources.mjs`(规则见 `resources/`);本专题修 `.ets` 消费侧。
|
|
97
|
+
- UI 迁移对齐 / SymbolGlyph 图标体系见 `ui-alignment/README.md`。
|
|
98
|
+
- 不确定某 API 是否存在/版本兼容时:grep 本机 SDK `.d.ts` 声明为准。
|