@harmonyos-arkts/d2h 0.1.11 → 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,348 +1,348 @@
|
|
|
1
|
-
# 硬编码字符串扫描与迁移工具
|
|
2
|
-
|
|
3
|
-
> 辅助扫描项目中硬编码字符串并迁移到 resources 的工具和脚本
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 扫描工具汇总
|
|
8
|
-
|
|
9
|
-
### 1. rg(ripgrep)快速扫描
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
# 安装:Windows 使用 winget install Andersonby.rg 或 choco install ripgrep
|
|
13
|
-
|
|
14
|
-
# 扫描 Text() 硬编码
|
|
15
|
-
rg "Text\(['\"]" --type ets -n -o
|
|
16
|
-
|
|
17
|
-
# 扫描 Button() 硬编码
|
|
18
|
-
rg "Button\(['\"]" --type ets -n -o
|
|
19
|
-
|
|
20
|
-
# 扫描 showToast() 硬编码
|
|
21
|
-
rg "showToast\(['\"]" --type ets -n -o
|
|
22
|
-
|
|
23
|
-
# 扫描 AlertDialog 硬编码
|
|
24
|
-
rg "AlertDialog" --type ets -n -o
|
|
25
|
-
|
|
26
|
-
# 扫描 promptAction 硬编码
|
|
27
|
-
rg "promptAction\." --type ets -n
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### 2. PowerShell 扫描脚本
|
|
31
|
-
|
|
32
|
-
```powershell
|
|
33
|
-
# 扫描所有 .ets 文件中的硬编码字符串
|
|
34
|
-
Get-ChildItem -Recurse -Filter "*.ets" | ForEach-Object {
|
|
35
|
-
$content = Get-Content $_.FullName -Raw
|
|
36
|
-
# 匹配单引号字符串
|
|
37
|
-
if ($content -match "Text\(['\']([^'\)]+)['\']") {
|
|
38
|
-
Write-Host "$($_.FullName): $($matches[1])"
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### 3. Node.js 扫描脚本
|
|
44
|
-
|
|
45
|
-
```javascript
|
|
46
|
-
// scan-hardcoded-strings.js
|
|
47
|
-
const fs = require('fs');
|
|
48
|
-
const path = require('path');
|
|
49
|
-
|
|
50
|
-
const patterns = [
|
|
51
|
-
/Text\(['"]([^'"]+)['"]\)/g,
|
|
52
|
-
/Button\(['"]([^'"]+)['"]\)/g,
|
|
53
|
-
/\.title\(['"]([^'"]+)['"]\)/g,
|
|
54
|
-
/\.placeholder\(['"]([^'"]+)['"]\)/g,
|
|
55
|
-
/\.promptText\(['"]([^'"]+)['"]\)/g,
|
|
56
|
-
/showToast\(['"]([^'"]+)['"]\)/g,
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
function scanFile(filePath) {
|
|
60
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
61
|
-
const results = [];
|
|
62
|
-
|
|
63
|
-
for (const pattern of patterns) {
|
|
64
|
-
let match;
|
|
65
|
-
while ((match = pattern.exec(content)) !== null) {
|
|
66
|
-
const str = match[1];
|
|
67
|
-
// 过滤
|
|
68
|
-
if (str.length > 1 && /[\u4e00-\u9fa5a-zA-Z]/.test(str)) {
|
|
69
|
-
results.push({
|
|
70
|
-
file: filePath,
|
|
71
|
-
line: content.substring(0, match.index).split('\n').length,
|
|
72
|
-
pattern: match[0],
|
|
73
|
-
value: str
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return results;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function scanDirectory(dir) {
|
|
82
|
-
const results = [];
|
|
83
|
-
const files = fs.readdirSync(dir, { withFileTypes: true });
|
|
84
|
-
|
|
85
|
-
for (const file of files) {
|
|
86
|
-
const fullPath = path.join(dir, file.name);
|
|
87
|
-
if (file.isDirectory() && !file.name.startsWith('.')) {
|
|
88
|
-
results.push(...scanDirectory(fullPath));
|
|
89
|
-
} else if (file.name.endsWith('.ets')) {
|
|
90
|
-
results.push(...scanFile(fullPath));
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return results;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const results = scanDirectory('./entry/src/main/ets');
|
|
97
|
-
console.log(JSON.stringify(results, null, 2));
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## 过滤规则
|
|
103
|
-
|
|
104
|
-
### 必须排除的模式
|
|
105
|
-
|
|
106
|
-
```javascript
|
|
107
|
-
// 排除 $r() 引用
|
|
108
|
-
const filtered = results.filter(r => !r.value.includes('$r'));
|
|
109
|
-
|
|
110
|
-
// 排除纯数字
|
|
111
|
-
const filtered = results.filter(r => !/^\d+$/.test(r.value));
|
|
112
|
-
|
|
113
|
-
// 排除单字符
|
|
114
|
-
const filtered = results.filter(r => r.value.length > 1);
|
|
115
|
-
|
|
116
|
-
// 排除 URL
|
|
117
|
-
const filtered = results.filter(r => !/^https?:\/\//.test(r.value));
|
|
118
|
-
|
|
119
|
-
// 排除技术术语
|
|
120
|
-
const filtered = results.filter(r => !/^<[^>]+>$/.test(r.value)); // HTML 标签
|
|
121
|
-
const filtered = results.filter(r => !/^\{[^}]+\}$/.test(r.value)); // 变量引用
|
|
122
|
-
|
|
123
|
-
// 排除正则表达式
|
|
124
|
-
const filtered = results.filter(r => !/^[\[\]\{\}\(\)\.\?\+\*\^\\]$/.test(r.value));
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
## key 生成算法
|
|
130
|
-
|
|
131
|
-
```javascript
|
|
132
|
-
function generateKey(str) {
|
|
133
|
-
// 1. 转小写
|
|
134
|
-
let key = str.toLowerCase();
|
|
135
|
-
|
|
136
|
-
// 2. 移除特殊字符
|
|
137
|
-
key = key.replace(/[^a-z0-9\u4e00-\u9fa5]/g, '_');
|
|
138
|
-
|
|
139
|
-
// 3. 移除连续下划线
|
|
140
|
-
key = key.replace(/_+/g, '_');
|
|
141
|
-
|
|
142
|
-
// 4. 移除首尾下划线
|
|
143
|
-
key = key.replace(/^_|_$/g, '');
|
|
144
|
-
|
|
145
|
-
// 5. 截断过长 key
|
|
146
|
-
if (key.length > 30) {
|
|
147
|
-
key = key.substring(0, 30);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return key || 'unknown';
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 示例
|
|
154
|
-
console.log(generateKey('确认')); // confirm
|
|
155
|
-
console.log(generateKey('确定要删除吗?')); // 确定要删除吗
|
|
156
|
-
console.log(generateKey('Search Files')); // search_files
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
## 迁移报告模板
|
|
162
|
-
|
|
163
|
-
```markdown
|
|
164
|
-
# 硬编码字符串迁移报告
|
|
165
|
-
|
|
166
|
-
## 扫描时间
|
|
167
|
-
2024-xx-xx
|
|
168
|
-
|
|
169
|
-
## 扫描范围
|
|
170
|
-
- entry/src/main/ets/**/*.ets
|
|
171
|
-
|
|
172
|
-
## 发现数量
|
|
173
|
-
- 总计:XX 个硬编码字符串
|
|
174
|
-
- 已迁移:XX 个
|
|
175
|
-
- 待处理:XX 个
|
|
176
|
-
|
|
177
|
-
## 按文件分布
|
|
178
|
-
| 文件 | 数量 |
|
|
179
|
-
|------|------|
|
|
180
|
-
| page1.ets | 10 |
|
|
181
|
-
| page2.ets | 5 |
|
|
182
|
-
| dialog1.ets | 3 |
|
|
183
|
-
|
|
184
|
-
## 待迁移列表
|
|
185
|
-
|
|
186
|
-
| # | 原文本 | 建议 key | 优先级 |
|
|
187
|
-
|---|--------|---------|--------|
|
|
188
|
-
| 1 | 确认 | confirm | P0 |
|
|
189
|
-
| 2 | 取消 | cancel | P0 |
|
|
190
|
-
| 3 | 保存成功 | toast_save_success | P1 |
|
|
191
|
-
|
|
192
|
-
## 已完成迁移
|
|
193
|
-
|
|
194
|
-
| # | 原文本 | key | 文件 |
|
|
195
|
-
|---|--------|-----|------|
|
|
196
|
-
| 1 | 设置 | title_settings | SettingsPage.ets |
|
|
197
|
-
| 2 | 搜索 | hint_search | SearchBar.ets |
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
---
|
|
201
|
-
|
|
202
|
-
## 批量替换脚本
|
|
203
|
-
|
|
204
|
-
```javascript
|
|
205
|
-
// migrate-to-resources.js
|
|
206
|
-
const fs = require('fs');
|
|
207
|
-
const path = require('path');
|
|
208
|
-
|
|
209
|
-
// 迁移映射表(从扫描结果生成)
|
|
210
|
-
const migrationMap = {
|
|
211
|
-
'确认': 'app.string.confirm',
|
|
212
|
-
'取消': 'app.string.cancel',
|
|
213
|
-
'保存': 'app.string.save',
|
|
214
|
-
'删除': 'app.string.delete',
|
|
215
|
-
'设置': 'app.string.title_settings',
|
|
216
|
-
'搜索': 'app.string.hint_search',
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
function migrateFile(filePath) {
|
|
220
|
-
let content = fs.readFileSync(filePath, 'utf-8');
|
|
221
|
-
let modified = false;
|
|
222
|
-
|
|
223
|
-
for (const [oldStr, newRef] of Object.entries(migrationMap)) {
|
|
224
|
-
// 替换 Text('xxx')
|
|
225
|
-
const textPattern = new RegExp(`Text\\(['"]${escapeRegExp(oldStr)}['"]\\)`, 'g');
|
|
226
|
-
if (textPattern.test(content)) {
|
|
227
|
-
content = content.replace(textPattern, `Text(\$r('${newRef}'))`);
|
|
228
|
-
modified = true;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// 替换 Button('xxx')
|
|
232
|
-
const buttonPattern = new RegExp(`Button\\(['"]${escapeRegExp(oldStr)}['"]\\)`, 'g');
|
|
233
|
-
if (buttonPattern.test(content)) {
|
|
234
|
-
content = content.replace(buttonPattern, `Button(\$r('${newRef}'))`);
|
|
235
|
-
modified = true;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (modified) {
|
|
240
|
-
fs.writeFileSync(filePath, content);
|
|
241
|
-
console.log(`Migrated: ${filePath}`);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function escapeRegExp(string) {
|
|
246
|
-
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function migrateDirectory(dir) {
|
|
250
|
-
const files = fs.readdirSync(dir, { withFileTypes: true });
|
|
251
|
-
|
|
252
|
-
for (const file of files) {
|
|
253
|
-
const fullPath = path.join(dir, file.name);
|
|
254
|
-
if (file.isDirectory() && !file.name.startsWith('.')) {
|
|
255
|
-
migrateDirectory(fullPath);
|
|
256
|
-
} else if (file.name.endsWith('.ets')) {
|
|
257
|
-
migrateFile(fullPath);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
migrateDirectory('./entry/src/main/ets');
|
|
263
|
-
console.log('Migration complete!');
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
---
|
|
267
|
-
|
|
268
|
-
## string.json 更新脚本
|
|
269
|
-
|
|
270
|
-
```javascript
|
|
271
|
-
// update-string-json.js
|
|
272
|
-
const fs = require('fs');
|
|
273
|
-
|
|
274
|
-
// 新增的 key-value 对
|
|
275
|
-
const newEntries = [
|
|
276
|
-
{ name: 'confirm', value: 'Confirm' },
|
|
277
|
-
{ name: 'cancel', value: 'Cancel' },
|
|
278
|
-
{ name: 'save', value: 'Save' },
|
|
279
|
-
{ name: 'delete', value: 'Delete' },
|
|
280
|
-
];
|
|
281
|
-
|
|
282
|
-
function updateStringJson(filePath) {
|
|
283
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
284
|
-
const json = JSON.parse(content);
|
|
285
|
-
|
|
286
|
-
// 检查是否已存在
|
|
287
|
-
const existingNames = json.string.map(s => s.name);
|
|
288
|
-
const toAdd = newEntries.filter(e => !existingNames.includes(e.name));
|
|
289
|
-
|
|
290
|
-
if (toAdd.length > 0) {
|
|
291
|
-
json.string.push(...toAdd);
|
|
292
|
-
fs.writeFileSync(filePath, JSON.stringify(json, null, 2));
|
|
293
|
-
console.log(`Updated: ${filePath} (+${toAdd.length} entries)`);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// 更新 base 和 en_US
|
|
298
|
-
updateStringJson('./entry/src/main/resources/base/element/string.json');
|
|
299
|
-
updateStringJson('./entry/src/main/resources/en_US/element/string.json');
|
|
300
|
-
|
|
301
|
-
console.log('Done!');
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
---
|
|
305
|
-
|
|
306
|
-
## 使用流程
|
|
307
|
-
|
|
308
|
-
### 完整迁移流程
|
|
309
|
-
|
|
310
|
-
```
|
|
311
|
-
1. 扫描阶段
|
|
312
|
-
├── 运行 scan-hardcoded-strings.js
|
|
313
|
-
├── 生成迁移报告
|
|
314
|
-
└── 人工审核
|
|
315
|
-
|
|
316
|
-
2. 准备阶段
|
|
317
|
-
├── 创建 migrationMap
|
|
318
|
-
└── 验证 key 不冲突
|
|
319
|
-
|
|
320
|
-
3. 迁移阶段
|
|
321
|
-
├── 运行 update-string-json.js(更新资源文件)
|
|
322
|
-
├── 运行 migrate-to-resources.js(替换代码)
|
|
323
|
-
└── 人工检查修改
|
|
324
|
-
|
|
325
|
-
4. 验证阶段
|
|
326
|
-
├── 编译项目
|
|
327
|
-
├── 运行应用检查
|
|
328
|
-
└── 如有问题,回滚修改
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### 推荐工具链
|
|
332
|
-
|
|
333
|
-
| 阶段 | 工具 | 说明 |
|
|
334
|
-
|------|------|------|
|
|
335
|
-
| 扫描 | rg / grep | 快速定位 |
|
|
336
|
-
| 分析 | Node.js 脚本 | 生成报告 |
|
|
337
|
-
| 迁移 | VS Code 多文件替换 | 人工确认 |
|
|
338
|
-
| 验证 | DevEco Studio 编译 | 检查错误 |
|
|
339
|
-
|
|
340
|
-
---
|
|
341
|
-
|
|
342
|
-
## 注意事项
|
|
343
|
-
|
|
344
|
-
1. **先备份**:迁移前先 commit 或备份
|
|
345
|
-
2. **小步快跑**:分批次迁移,每次只处理 10-20 个
|
|
346
|
-
3. **编译验证**:每批次迁移后立即编译检查
|
|
347
|
-
4. **人工审核**:自动化替换后需要人工复核
|
|
348
|
-
5. **key 冲突**:避免重复 key,如已存在则复用
|
|
1
|
+
# 硬编码字符串扫描与迁移工具
|
|
2
|
+
|
|
3
|
+
> 辅助扫描项目中硬编码字符串并迁移到 resources 的工具和脚本
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 扫描工具汇总
|
|
8
|
+
|
|
9
|
+
### 1. rg(ripgrep)快速扫描
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# 安装:Windows 使用 winget install Andersonby.rg 或 choco install ripgrep
|
|
13
|
+
|
|
14
|
+
# 扫描 Text() 硬编码
|
|
15
|
+
rg "Text\(['\"]" --type ets -n -o
|
|
16
|
+
|
|
17
|
+
# 扫描 Button() 硬编码
|
|
18
|
+
rg "Button\(['\"]" --type ets -n -o
|
|
19
|
+
|
|
20
|
+
# 扫描 showToast() 硬编码
|
|
21
|
+
rg "showToast\(['\"]" --type ets -n -o
|
|
22
|
+
|
|
23
|
+
# 扫描 AlertDialog 硬编码
|
|
24
|
+
rg "AlertDialog" --type ets -n -o
|
|
25
|
+
|
|
26
|
+
# 扫描 promptAction 硬编码
|
|
27
|
+
rg "promptAction\." --type ets -n
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 2. PowerShell 扫描脚本
|
|
31
|
+
|
|
32
|
+
```powershell
|
|
33
|
+
# 扫描所有 .ets 文件中的硬编码字符串
|
|
34
|
+
Get-ChildItem -Recurse -Filter "*.ets" | ForEach-Object {
|
|
35
|
+
$content = Get-Content $_.FullName -Raw
|
|
36
|
+
# 匹配单引号字符串
|
|
37
|
+
if ($content -match "Text\(['\']([^'\)]+)['\']") {
|
|
38
|
+
Write-Host "$($_.FullName): $($matches[1])"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 3. Node.js 扫描脚本
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
// scan-hardcoded-strings.js
|
|
47
|
+
const fs = require('fs');
|
|
48
|
+
const path = require('path');
|
|
49
|
+
|
|
50
|
+
const patterns = [
|
|
51
|
+
/Text\(['"]([^'"]+)['"]\)/g,
|
|
52
|
+
/Button\(['"]([^'"]+)['"]\)/g,
|
|
53
|
+
/\.title\(['"]([^'"]+)['"]\)/g,
|
|
54
|
+
/\.placeholder\(['"]([^'"]+)['"]\)/g,
|
|
55
|
+
/\.promptText\(['"]([^'"]+)['"]\)/g,
|
|
56
|
+
/showToast\(['"]([^'"]+)['"]\)/g,
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
function scanFile(filePath) {
|
|
60
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
61
|
+
const results = [];
|
|
62
|
+
|
|
63
|
+
for (const pattern of patterns) {
|
|
64
|
+
let match;
|
|
65
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
66
|
+
const str = match[1];
|
|
67
|
+
// 过滤
|
|
68
|
+
if (str.length > 1 && /[\u4e00-\u9fa5a-zA-Z]/.test(str)) {
|
|
69
|
+
results.push({
|
|
70
|
+
file: filePath,
|
|
71
|
+
line: content.substring(0, match.index).split('\n').length,
|
|
72
|
+
pattern: match[0],
|
|
73
|
+
value: str
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return results;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function scanDirectory(dir) {
|
|
82
|
+
const results = [];
|
|
83
|
+
const files = fs.readdirSync(dir, { withFileTypes: true });
|
|
84
|
+
|
|
85
|
+
for (const file of files) {
|
|
86
|
+
const fullPath = path.join(dir, file.name);
|
|
87
|
+
if (file.isDirectory() && !file.name.startsWith('.')) {
|
|
88
|
+
results.push(...scanDirectory(fullPath));
|
|
89
|
+
} else if (file.name.endsWith('.ets')) {
|
|
90
|
+
results.push(...scanFile(fullPath));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return results;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const results = scanDirectory('./entry/src/main/ets');
|
|
97
|
+
console.log(JSON.stringify(results, null, 2));
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 过滤规则
|
|
103
|
+
|
|
104
|
+
### 必须排除的模式
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
// 排除 $r() 引用
|
|
108
|
+
const filtered = results.filter(r => !r.value.includes('$r'));
|
|
109
|
+
|
|
110
|
+
// 排除纯数字
|
|
111
|
+
const filtered = results.filter(r => !/^\d+$/.test(r.value));
|
|
112
|
+
|
|
113
|
+
// 排除单字符
|
|
114
|
+
const filtered = results.filter(r => r.value.length > 1);
|
|
115
|
+
|
|
116
|
+
// 排除 URL
|
|
117
|
+
const filtered = results.filter(r => !/^https?:\/\//.test(r.value));
|
|
118
|
+
|
|
119
|
+
// 排除技术术语
|
|
120
|
+
const filtered = results.filter(r => !/^<[^>]+>$/.test(r.value)); // HTML 标签
|
|
121
|
+
const filtered = results.filter(r => !/^\{[^}]+\}$/.test(r.value)); // 变量引用
|
|
122
|
+
|
|
123
|
+
// 排除正则表达式
|
|
124
|
+
const filtered = results.filter(r => !/^[\[\]\{\}\(\)\.\?\+\*\^\\]$/.test(r.value));
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## key 生成算法
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
function generateKey(str) {
|
|
133
|
+
// 1. 转小写
|
|
134
|
+
let key = str.toLowerCase();
|
|
135
|
+
|
|
136
|
+
// 2. 移除特殊字符
|
|
137
|
+
key = key.replace(/[^a-z0-9\u4e00-\u9fa5]/g, '_');
|
|
138
|
+
|
|
139
|
+
// 3. 移除连续下划线
|
|
140
|
+
key = key.replace(/_+/g, '_');
|
|
141
|
+
|
|
142
|
+
// 4. 移除首尾下划线
|
|
143
|
+
key = key.replace(/^_|_$/g, '');
|
|
144
|
+
|
|
145
|
+
// 5. 截断过长 key
|
|
146
|
+
if (key.length > 30) {
|
|
147
|
+
key = key.substring(0, 30);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return key || 'unknown';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 示例
|
|
154
|
+
console.log(generateKey('确认')); // confirm
|
|
155
|
+
console.log(generateKey('确定要删除吗?')); // 确定要删除吗
|
|
156
|
+
console.log(generateKey('Search Files')); // search_files
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 迁移报告模板
|
|
162
|
+
|
|
163
|
+
```markdown
|
|
164
|
+
# 硬编码字符串迁移报告
|
|
165
|
+
|
|
166
|
+
## 扫描时间
|
|
167
|
+
2024-xx-xx
|
|
168
|
+
|
|
169
|
+
## 扫描范围
|
|
170
|
+
- entry/src/main/ets/**/*.ets
|
|
171
|
+
|
|
172
|
+
## 发现数量
|
|
173
|
+
- 总计:XX 个硬编码字符串
|
|
174
|
+
- 已迁移:XX 个
|
|
175
|
+
- 待处理:XX 个
|
|
176
|
+
|
|
177
|
+
## 按文件分布
|
|
178
|
+
| 文件 | 数量 |
|
|
179
|
+
|------|------|
|
|
180
|
+
| page1.ets | 10 |
|
|
181
|
+
| page2.ets | 5 |
|
|
182
|
+
| dialog1.ets | 3 |
|
|
183
|
+
|
|
184
|
+
## 待迁移列表
|
|
185
|
+
|
|
186
|
+
| # | 原文本 | 建议 key | 优先级 |
|
|
187
|
+
|---|--------|---------|--------|
|
|
188
|
+
| 1 | 确认 | confirm | P0 |
|
|
189
|
+
| 2 | 取消 | cancel | P0 |
|
|
190
|
+
| 3 | 保存成功 | toast_save_success | P1 |
|
|
191
|
+
|
|
192
|
+
## 已完成迁移
|
|
193
|
+
|
|
194
|
+
| # | 原文本 | key | 文件 |
|
|
195
|
+
|---|--------|-----|------|
|
|
196
|
+
| 1 | 设置 | title_settings | SettingsPage.ets |
|
|
197
|
+
| 2 | 搜索 | hint_search | SearchBar.ets |
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 批量替换脚本
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
// migrate-to-resources.js
|
|
206
|
+
const fs = require('fs');
|
|
207
|
+
const path = require('path');
|
|
208
|
+
|
|
209
|
+
// 迁移映射表(从扫描结果生成)
|
|
210
|
+
const migrationMap = {
|
|
211
|
+
'确认': 'app.string.confirm',
|
|
212
|
+
'取消': 'app.string.cancel',
|
|
213
|
+
'保存': 'app.string.save',
|
|
214
|
+
'删除': 'app.string.delete',
|
|
215
|
+
'设置': 'app.string.title_settings',
|
|
216
|
+
'搜索': 'app.string.hint_search',
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
function migrateFile(filePath) {
|
|
220
|
+
let content = fs.readFileSync(filePath, 'utf-8');
|
|
221
|
+
let modified = false;
|
|
222
|
+
|
|
223
|
+
for (const [oldStr, newRef] of Object.entries(migrationMap)) {
|
|
224
|
+
// 替换 Text('xxx')
|
|
225
|
+
const textPattern = new RegExp(`Text\\(['"]${escapeRegExp(oldStr)}['"]\\)`, 'g');
|
|
226
|
+
if (textPattern.test(content)) {
|
|
227
|
+
content = content.replace(textPattern, `Text(\$r('${newRef}'))`);
|
|
228
|
+
modified = true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 替换 Button('xxx')
|
|
232
|
+
const buttonPattern = new RegExp(`Button\\(['"]${escapeRegExp(oldStr)}['"]\\)`, 'g');
|
|
233
|
+
if (buttonPattern.test(content)) {
|
|
234
|
+
content = content.replace(buttonPattern, `Button(\$r('${newRef}'))`);
|
|
235
|
+
modified = true;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (modified) {
|
|
240
|
+
fs.writeFileSync(filePath, content);
|
|
241
|
+
console.log(`Migrated: ${filePath}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function escapeRegExp(string) {
|
|
246
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function migrateDirectory(dir) {
|
|
250
|
+
const files = fs.readdirSync(dir, { withFileTypes: true });
|
|
251
|
+
|
|
252
|
+
for (const file of files) {
|
|
253
|
+
const fullPath = path.join(dir, file.name);
|
|
254
|
+
if (file.isDirectory() && !file.name.startsWith('.')) {
|
|
255
|
+
migrateDirectory(fullPath);
|
|
256
|
+
} else if (file.name.endsWith('.ets')) {
|
|
257
|
+
migrateFile(fullPath);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
migrateDirectory('./entry/src/main/ets');
|
|
263
|
+
console.log('Migration complete!');
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## string.json 更新脚本
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
// update-string-json.js
|
|
272
|
+
const fs = require('fs');
|
|
273
|
+
|
|
274
|
+
// 新增的 key-value 对
|
|
275
|
+
const newEntries = [
|
|
276
|
+
{ name: 'confirm', value: 'Confirm' },
|
|
277
|
+
{ name: 'cancel', value: 'Cancel' },
|
|
278
|
+
{ name: 'save', value: 'Save' },
|
|
279
|
+
{ name: 'delete', value: 'Delete' },
|
|
280
|
+
];
|
|
281
|
+
|
|
282
|
+
function updateStringJson(filePath) {
|
|
283
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
284
|
+
const json = JSON.parse(content);
|
|
285
|
+
|
|
286
|
+
// 检查是否已存在
|
|
287
|
+
const existingNames = json.string.map(s => s.name);
|
|
288
|
+
const toAdd = newEntries.filter(e => !existingNames.includes(e.name));
|
|
289
|
+
|
|
290
|
+
if (toAdd.length > 0) {
|
|
291
|
+
json.string.push(...toAdd);
|
|
292
|
+
fs.writeFileSync(filePath, JSON.stringify(json, null, 2));
|
|
293
|
+
console.log(`Updated: ${filePath} (+${toAdd.length} entries)`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// 更新 base 和 en_US
|
|
298
|
+
updateStringJson('./entry/src/main/resources/base/element/string.json');
|
|
299
|
+
updateStringJson('./entry/src/main/resources/en_US/element/string.json');
|
|
300
|
+
|
|
301
|
+
console.log('Done!');
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## 使用流程
|
|
307
|
+
|
|
308
|
+
### 完整迁移流程
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
1. 扫描阶段
|
|
312
|
+
├── 运行 scan-hardcoded-strings.js
|
|
313
|
+
├── 生成迁移报告
|
|
314
|
+
└── 人工审核
|
|
315
|
+
|
|
316
|
+
2. 准备阶段
|
|
317
|
+
├── 创建 migrationMap
|
|
318
|
+
└── 验证 key 不冲突
|
|
319
|
+
|
|
320
|
+
3. 迁移阶段
|
|
321
|
+
├── 运行 update-string-json.js(更新资源文件)
|
|
322
|
+
├── 运行 migrate-to-resources.js(替换代码)
|
|
323
|
+
└── 人工检查修改
|
|
324
|
+
|
|
325
|
+
4. 验证阶段
|
|
326
|
+
├── 编译项目
|
|
327
|
+
├── 运行应用检查
|
|
328
|
+
└── 如有问题,回滚修改
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 推荐工具链
|
|
332
|
+
|
|
333
|
+
| 阶段 | 工具 | 说明 |
|
|
334
|
+
|------|------|------|
|
|
335
|
+
| 扫描 | rg / grep | 快速定位 |
|
|
336
|
+
| 分析 | Node.js 脚本 | 生成报告 |
|
|
337
|
+
| 迁移 | VS Code 多文件替换 | 人工确认 |
|
|
338
|
+
| 验证 | DevEco Studio 编译 | 检查错误 |
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## 注意事项
|
|
343
|
+
|
|
344
|
+
1. **先备份**:迁移前先 commit 或备份
|
|
345
|
+
2. **小步快跑**:分批次迁移,每次只处理 10-20 个
|
|
346
|
+
3. **编译验证**:每批次迁移后立即编译检查
|
|
347
|
+
4. **人工审核**:自动化替换后需要人工复核
|
|
348
|
+
5. **key 冲突**:避免重复 key,如已存在则复用
|