@hagicode/hagi18n 0.1.0-dev.2.1.05e19ba → 0.1.0-dev.21.1.eb540d4
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 +21 -0
- package/README.md +45 -36
- package/README_cn.md +45 -36
- package/dist/cli.js +32 -15
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +75 -4
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/locale-name.d.ts +1 -0
- package/dist/locale-name.js +26 -0
- package/dist/locale-name.js.map +1 -0
- package/dist/locale-toolkit.d.ts +7 -1
- package/dist/locale-toolkit.js +115 -30
- package/dist/locale-toolkit.js.map +1 -1
- package/package.json +14 -4
- package/skills/hagi18n/references/config-and-layout.md +18 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HagiCode
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Hagi18n
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@hagicode/hagi18n)
|
|
4
|
+
[](https://www.npmjs.com/package/@hagicode/hagi18n)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://nodejs.org/)
|
|
7
|
+
[](https://github.com/HagiCode-org/hagi18n/actions/workflows/ci.yml)
|
|
8
|
+
|
|
9
|
+
`@hagicode/hagi18n` is a reusable YAML locale maintenance toolkit for HagiCode projects. It runs as an independent package and CLI.
|
|
4
10
|
|
|
5
11
|
It supports:
|
|
6
12
|
|
|
@@ -17,14 +23,14 @@ Use it when an AI agent needs to:
|
|
|
17
23
|
|
|
18
24
|
- run or explain `hagi18n audit`, `doctor`, `sync`, or `prune`
|
|
19
25
|
- add or review a `hagi18n.yaml` file
|
|
20
|
-
- inspect or modify the source
|
|
26
|
+
- inspect or modify the package source
|
|
21
27
|
- validate locale maintenance workflows against a consumer repository
|
|
22
28
|
|
|
23
29
|
The skill includes focused references for command usage, configuration, and package development.
|
|
24
30
|
|
|
25
31
|
## Requirements
|
|
26
32
|
|
|
27
|
-
- Node.js
|
|
33
|
+
- Node.js 24 or newer
|
|
28
34
|
- npm for package management
|
|
29
35
|
|
|
30
36
|
## Installation
|
|
@@ -74,8 +80,16 @@ Example:
|
|
|
74
80
|
localesRoot: src/locales
|
|
75
81
|
repoRoot: .
|
|
76
82
|
baseLocale: en-US
|
|
83
|
+
auditBaseLocales:
|
|
84
|
+
- en-US
|
|
85
|
+
- ja-JP
|
|
77
86
|
targetLocales:
|
|
78
87
|
- zh-CN
|
|
88
|
+
auditIdenticalAllowlist:
|
|
89
|
+
common.yml:
|
|
90
|
+
- brand.name
|
|
91
|
+
pages/home.yml:
|
|
92
|
+
- hero.cta
|
|
79
93
|
doctor:
|
|
80
94
|
excludedDirectories:
|
|
81
95
|
- .git
|
|
@@ -99,6 +113,12 @@ Precedence is:
|
|
|
99
113
|
|
|
100
114
|
Relative paths in `hagi18n.yaml` are resolved from the config file directory.
|
|
101
115
|
|
|
116
|
+
`baseLocale` remains the single structural baseline for mutation workflows like `sync` and `prune`. `auditBaseLocales` is audit-only, ordered, and additive: the first resolved locale becomes the structural audit baseline and any additional locales are used only for exact duplicate-value detection during `audit`, `report`, and `doctor`.
|
|
117
|
+
|
|
118
|
+
`auditIdenticalAllowlist` exempts specific translation keys whose value is intentionally identical to the baseline, such as a brand name or a technical identifier. Each key is a locale-relative YAML file path written with forward slashes, and each value is a list of dot-separated scalar key paths in that file. An entry is exact: it suppresses only the matching `baselineValueMatches` entry, applies to every target locale, and does not disable any other check. It does not accept globs, patterns, or value-based rules. Use it only for terms that are meant to stay identical to English.
|
|
119
|
+
|
|
120
|
+
This is different from `doctor.allowlist`. `doctor.allowlist` exempts repository-scan findings by rule id and source file path, and it does not apply to audit output. Across config layers, a higher-precedence list for a file replaces the lower-precedence list for that same file, and files defined only in the lower layer keep their lists.
|
|
121
|
+
|
|
102
122
|
## CLI Commands
|
|
103
123
|
|
|
104
124
|
```bash
|
|
@@ -113,31 +133,35 @@ hagi18n prune
|
|
|
113
133
|
Examples:
|
|
114
134
|
|
|
115
135
|
```bash
|
|
116
|
-
hagi18n audit --locales-root src/locales --base-locale en-US
|
|
136
|
+
hagi18n audit --locales-root src/locales --base-locale en-US --base-locale ja-JP
|
|
117
137
|
hagi18n audit --config hagi18n.yaml --json
|
|
118
138
|
hagi18n report --config hagi18n.yaml
|
|
119
|
-
hagi18n doctor --config hagi18n.yaml
|
|
139
|
+
hagi18n doctor --config hagi18n.yaml --base-locale en-US --base-locale zh-CN
|
|
120
140
|
hagi18n sync --from en-US --to zh-CN
|
|
121
141
|
hagi18n sync --from en-US --to zh-CN --write
|
|
122
142
|
hagi18n prune --from en-US --to zh-CN --write
|
|
123
143
|
```
|
|
124
144
|
|
|
145
|
+
`audit`, `report`, and `doctor` accept repeated `--base-locale` values. Repeated baselines are normalized, deduped, and preserved in order. `sync` and `prune` still use exactly one mutation baseline through `--from`.
|
|
146
|
+
|
|
125
147
|
`sync` and `prune` are dry-run by default. Pass `--write` to mutate files.
|
|
126
148
|
|
|
127
149
|
## Option Reference
|
|
128
150
|
|
|
129
|
-
| Option
|
|
130
|
-
|
|
|
131
|
-
| `--config <path>`
|
|
132
|
-
| `--locales-root <path>`
|
|
133
|
-
|
|
|
134
|
-
|
|
|
135
|
-
| `--locale <locale>`
|
|
136
|
-
| `--
|
|
137
|
-
| `--
|
|
138
|
-
| `--
|
|
139
|
-
| `--
|
|
140
|
-
| `--
|
|
151
|
+
| Option | Commands | Description |
|
|
152
|
+
| ------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
153
|
+
| `--config <path>` | all except `info` | Load defaults from a config file |
|
|
154
|
+
| `--locales-root <path>` | audit, report, doctor, sync, prune | Locale root directory |
|
|
155
|
+
| `auditBaseLocales` | `hagi18n.yaml` | Ordered audit-only baselines; first resolved locale stays structural |
|
|
156
|
+
| `auditIdenticalAllowlist` | `hagi18n.yaml` | Exact file and key paths whose baseline-identical values are exempt from audit `baselineValueMatches` |
|
|
157
|
+
| `--base-locale <locale>` | audit, report, doctor | Audit baseline locale; can be repeated |
|
|
158
|
+
| `--from <locale>` | sync, prune | Base locale for mutation commands |
|
|
159
|
+
| `--locale <locale>` | audit, report, doctor | Limit output to one or more locales |
|
|
160
|
+
| `--to <locale>` | sync, prune | Limit mutations to one or more target locales |
|
|
161
|
+
| `--repo-root <path>` | doctor | Repository root for repository scanning |
|
|
162
|
+
| `--json` | audit, doctor, sync, prune | Print JSON instead of text |
|
|
163
|
+
| `--dry-run` | sync, prune | Preview mutations without writing files |
|
|
164
|
+
| `--write` | sync, prune | Apply mutations to disk |
|
|
141
165
|
|
|
142
166
|
## JSON Output and Exit Codes
|
|
143
167
|
|
|
@@ -147,7 +171,7 @@ hagi18n prune --from en-US --to zh-CN --write
|
|
|
147
171
|
- `sync` and `prune` return exit code `1` only for parse or processing errors. Planned or applied mutations are reported in the summary.
|
|
148
172
|
- Command parsing failures return exit code `1` from the current `commander` integration.
|
|
149
173
|
|
|
150
|
-
The JSON payload is the same structured summary returned by the TypeScript API.
|
|
174
|
+
The JSON payload is the same structured summary returned by the TypeScript API. For multi-baseline audits it now includes additive `baselineLocales` at the summary level and `baselineValueMatches` for each target locale result.
|
|
151
175
|
|
|
152
176
|
## TypeScript API
|
|
153
177
|
|
|
@@ -163,7 +187,7 @@ import {
|
|
|
163
187
|
|
|
164
188
|
const audit = await auditLocaleTree({
|
|
165
189
|
localesRoot: "src/locales",
|
|
166
|
-
|
|
190
|
+
auditBaseLocales: ["en-US", "ja-JP"]
|
|
167
191
|
});
|
|
168
192
|
|
|
169
193
|
console.log(formatAuditSummary(audit));
|
|
@@ -177,21 +201,6 @@ Main exports include:
|
|
|
177
201
|
- text formatters such as `formatAuditSummary`, `formatDoctorSummary`, and `formatMutationSummary`
|
|
178
202
|
- package metadata helpers such as `getPackageMetadata` and `createRuntimeInfo`
|
|
179
203
|
|
|
180
|
-
##
|
|
181
|
-
|
|
182
|
-
This package preserves the maintenance model from the Web repository:
|
|
204
|
+
## License
|
|
183
205
|
|
|
184
|
-
|
|
185
|
-
- `repos/web/buildTools/i18n-locale-cli.mjs` -> `src/cli.ts`
|
|
186
|
-
|
|
187
|
-
The Web project remains the behavior reference, but it does not need runtime changes to use this package later.
|
|
188
|
-
|
|
189
|
-
## Development
|
|
190
|
-
|
|
191
|
-
Run commands from `repos/hagi18n/`:
|
|
192
|
-
|
|
193
|
-
```bash
|
|
194
|
-
npm install
|
|
195
|
-
npm test
|
|
196
|
-
npm run build
|
|
197
|
-
```
|
|
206
|
+
MIT. See [LICENSE](./LICENSE).
|
package/README_cn.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Hagi18n
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@hagicode/hagi18n)
|
|
4
|
+
[](https://www.npmjs.com/package/@hagicode/hagi18n)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://nodejs.org/)
|
|
7
|
+
[](https://github.com/HagiCode-org/hagi18n/actions/workflows/ci.yml)
|
|
8
|
+
|
|
9
|
+
`@hagicode/hagi18n` 是一个可复用的 YAML 语言包维护工具,可作为独立 npm 包和 CLI 使用。
|
|
4
10
|
|
|
5
11
|
它提供:
|
|
6
12
|
|
|
@@ -17,14 +23,14 @@
|
|
|
17
23
|
|
|
18
24
|
- 执行或解释 `hagi18n audit`、`doctor`、`sync`、`prune`
|
|
19
25
|
- 新增或审查 `hagi18n.yaml`
|
|
20
|
-
-
|
|
26
|
+
- 检查或修改该包的源码
|
|
21
27
|
- 在消费方仓库中验证语言包维护流程
|
|
22
28
|
|
|
23
29
|
skill 内还附带了命令使用、配置结构和包开发流程的拆分参考文档。
|
|
24
30
|
|
|
25
31
|
## 前提
|
|
26
32
|
|
|
27
|
-
- Node.js
|
|
33
|
+
- Node.js 24 或更高版本
|
|
28
34
|
- 使用 npm 管理依赖
|
|
29
35
|
|
|
30
36
|
## 安装
|
|
@@ -69,8 +75,16 @@ CLI 默认会在当前工作目录查找 `hagi18n.yaml`。也可以显式传入
|
|
|
69
75
|
localesRoot: src/locales
|
|
70
76
|
repoRoot: .
|
|
71
77
|
baseLocale: en-US
|
|
78
|
+
auditBaseLocales:
|
|
79
|
+
- en-US
|
|
80
|
+
- ja-JP
|
|
72
81
|
targetLocales:
|
|
73
82
|
- zh-CN
|
|
83
|
+
auditIdenticalAllowlist:
|
|
84
|
+
common.yml:
|
|
85
|
+
- brand.name
|
|
86
|
+
pages/home.yml:
|
|
87
|
+
- hero.cta
|
|
74
88
|
doctor:
|
|
75
89
|
excludedDirectories:
|
|
76
90
|
- .git
|
|
@@ -94,6 +108,12 @@ doctor:
|
|
|
94
108
|
|
|
95
109
|
`hagi18n.yaml` 中的相对路径会相对于配置文件所在目录解析,因此不同项目可以用它自定义 `baseLocale`、`localesRoot`、`repoRoot` 和默认目标语言目录。
|
|
96
110
|
|
|
111
|
+
`baseLocale` 仍然是 `sync` / `prune` 这类变更命令使用的单一结构基线。`auditBaseLocales` 是仅用于审计的有序附加配置:第一个解析后的语言仍作为结构比较基线,其余语言只参与 `audit`、`report`、`doctor` 中的精确重复值检测。
|
|
112
|
+
|
|
113
|
+
`auditIdenticalAllowlist` 用于豁免那些有意与基线语言保持一致的译文键,例如品牌名或技术标识符。每个键是相对于语言目录的 YAML 文件路径(使用正斜杠),每个值是该文件中以点号分隔的标量键路径列表。该配置是精确匹配:它只抑制对应的 `baselineValueMatches` 条目,适用于所有目标语言,不会关闭其他任何检查,也不支持 glob、模式或基于值的规则。仅对确实应与英文保持相同的术语使用它。
|
|
114
|
+
|
|
115
|
+
它与 `doctor.allowlist` 不同。`doctor.allowlist` 按规则 ID 和源文件路径豁免仓库扫描结果,并且不作用于审计输出。在配置层之间,同一文件的高优先级列表会替换低优先级列表;只在低优先级层中定义的文件则保留其列表。
|
|
116
|
+
|
|
97
117
|
## CLI 命令
|
|
98
118
|
|
|
99
119
|
```bash
|
|
@@ -108,31 +128,35 @@ hagi18n prune
|
|
|
108
128
|
示例:
|
|
109
129
|
|
|
110
130
|
```bash
|
|
111
|
-
hagi18n audit --locales-root src/locales --base-locale en-US
|
|
131
|
+
hagi18n audit --locales-root src/locales --base-locale en-US --base-locale ja-JP
|
|
112
132
|
hagi18n audit --config hagi18n.yaml --json
|
|
113
133
|
hagi18n report --config hagi18n.yaml
|
|
114
|
-
hagi18n doctor --config hagi18n.yaml
|
|
134
|
+
hagi18n doctor --config hagi18n.yaml --base-locale en-US --base-locale zh-CN
|
|
115
135
|
hagi18n sync --from en-US --to zh-CN
|
|
116
136
|
hagi18n sync --from en-US --to zh-CN --write
|
|
117
137
|
hagi18n prune --from en-US --to zh-CN --write
|
|
118
138
|
```
|
|
119
139
|
|
|
140
|
+
`audit`、`report`、`doctor` 现在支持重复传入 `--base-locale`。这些基线会按顺序规范化并去重。`sync` 和 `prune` 仍然只通过 `--from` 使用单一变更基线。
|
|
141
|
+
|
|
120
142
|
`sync` 和 `prune` 默认不会写盘,只有传入 `--write` 才会真正修改文件。
|
|
121
143
|
|
|
122
144
|
## 参数说明
|
|
123
145
|
|
|
124
|
-
| 参数
|
|
125
|
-
|
|
|
126
|
-
| `--config <path>`
|
|
127
|
-
| `--locales-root <path>`
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
| `--locale <locale>`
|
|
131
|
-
| `--
|
|
132
|
-
| `--
|
|
133
|
-
| `--
|
|
134
|
-
| `--
|
|
135
|
-
| `--
|
|
146
|
+
| 参数 | 命令 | 说明 |
|
|
147
|
+
| ------------------------- | ---------------------------------- | ----------------------------------------------------------------------- |
|
|
148
|
+
| `--config <path>` | `info` 之外所有命令 | 加载配置文件 |
|
|
149
|
+
| `--locales-root <path>` | audit, report, doctor, sync, prune | 语言包根目录 |
|
|
150
|
+
| `auditBaseLocales` | `hagi18n.yaml` | 有序的审计专用基线列表;第一个解析结果仍为结构基线 |
|
|
151
|
+
| `auditIdenticalAllowlist` | `hagi18n.yaml` | 精确的文件与键路径,其与基线相同的值豁免审计中的 `baselineValueMatches` |
|
|
152
|
+
| `--base-locale <locale>` | audit, report, doctor | 审计基线语言;可重复传入 |
|
|
153
|
+
| `--from <locale>` | sync, prune | 变更时使用的基准语言 |
|
|
154
|
+
| `--locale <locale>` | audit, report, doctor | 限定一个或多个目标语言 |
|
|
155
|
+
| `--to <locale>` | sync, prune | 限定一个或多个目标语言 |
|
|
156
|
+
| `--repo-root <path>` | doctor | 仓库扫描根目录 |
|
|
157
|
+
| `--json` | audit, doctor, sync, prune | 输出 JSON |
|
|
158
|
+
| `--dry-run` | sync, prune | 仅预览,不写盘 |
|
|
159
|
+
| `--write` | sync, prune | 真正写入文件 |
|
|
136
160
|
|
|
137
161
|
## JSON 输出与退出码
|
|
138
162
|
|
|
@@ -141,7 +165,7 @@ hagi18n prune --from en-US --to zh-CN --write
|
|
|
141
165
|
- `doctor`:审计或仓库扫描存在问题时返回 `1`
|
|
142
166
|
- `sync` / `prune`:只有解析或处理错误时返回 `1`,正常的预览或写入结果都会输出摘要
|
|
143
167
|
|
|
144
|
-
JSON 输出与 TypeScript API 返回的结构化 summary
|
|
168
|
+
JSON 输出与 TypeScript API 返回的结构化 summary 保持一致。多基线审计时,summary 会新增 `baselineLocales`,每个目标语言结果会新增 `baselineValueMatches`。
|
|
145
169
|
|
|
146
170
|
## TypeScript API
|
|
147
171
|
|
|
@@ -157,7 +181,7 @@ import {
|
|
|
157
181
|
|
|
158
182
|
const audit = await auditLocaleTree({
|
|
159
183
|
localesRoot: "src/locales",
|
|
160
|
-
|
|
184
|
+
auditBaseLocales: ["en-US", "ja-JP"]
|
|
161
185
|
});
|
|
162
186
|
|
|
163
187
|
console.log(formatAuditSummary(audit));
|
|
@@ -171,21 +195,6 @@ console.log(formatAuditSummary(audit));
|
|
|
171
195
|
- 文本格式化函数:`formatAuditSummary`、`formatDoctorSummary`、`formatMutationSummary`
|
|
172
196
|
- 元数据函数:`getPackageMetadata`、`createRuntimeInfo`
|
|
173
197
|
|
|
174
|
-
##
|
|
175
|
-
|
|
176
|
-
此包保留了 Web 项目的维护模型:
|
|
198
|
+
## 许可证
|
|
177
199
|
|
|
178
|
-
|
|
179
|
-
- `repos/web/buildTools/i18n-locale-cli.mjs` -> `src/cli.ts`
|
|
180
|
-
|
|
181
|
-
也就是说,这次抽象保留了 Web 的参考行为,但不会要求 `repos/web` 立即修改运行时代码。
|
|
182
|
-
|
|
183
|
-
## 开发
|
|
184
|
-
|
|
185
|
-
在 `repos/hagi18n/` 目录执行:
|
|
186
|
-
|
|
187
|
-
```bash
|
|
188
|
-
npm install
|
|
189
|
-
npm test
|
|
190
|
-
npm run build
|
|
191
|
-
```
|
|
200
|
+
MIT。参见 [LICENSE](./LICENSE)。
|
package/dist/cli.js
CHANGED
|
@@ -7,13 +7,25 @@ function collectValues(value, previous = []) {
|
|
|
7
7
|
previous.push(value);
|
|
8
8
|
return previous;
|
|
9
9
|
}
|
|
10
|
-
function
|
|
10
|
+
function toLocaleArray(value) {
|
|
11
|
+
if (value === undefined) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return Array.isArray(value) ? value : [value];
|
|
15
|
+
}
|
|
16
|
+
function getPrimaryBaseLocale(options) {
|
|
17
|
+
return toLocaleArray(options.baseLocale)?.[0] ?? options.from;
|
|
18
|
+
}
|
|
19
|
+
function toToolkitOptions(options, { includeAuditBaseLocales = false } = {}) {
|
|
11
20
|
const targetLocales = [...(options.locale ?? []), ...(options.to ?? [])];
|
|
12
21
|
return {
|
|
13
22
|
configPath: options.config,
|
|
14
23
|
localesRoot: options.localesRoot,
|
|
15
24
|
repoRoot: options.repoRoot,
|
|
16
|
-
baseLocale: options
|
|
25
|
+
baseLocale: getPrimaryBaseLocale(options),
|
|
26
|
+
auditBaseLocales: includeAuditBaseLocales
|
|
27
|
+
? toLocaleArray(options.baseLocale)
|
|
28
|
+
: undefined,
|
|
17
29
|
targetLocales: targetLocales.length > 0 ? targetLocales : undefined
|
|
18
30
|
};
|
|
19
31
|
}
|
|
@@ -30,15 +42,20 @@ function printSummary(summary, { json = false, formatter }) {
|
|
|
30
42
|
}
|
|
31
43
|
process.stdout.write(`${formatter(summary)}\n`);
|
|
32
44
|
}
|
|
33
|
-
function applySharedOptions(command, { includeRepoRoot = false } = {}) {
|
|
45
|
+
function applySharedOptions(command, { includeRepoRoot = false, auditBaseLocaleMode = "none" } = {}) {
|
|
34
46
|
command
|
|
35
47
|
.addOption(new Option("--config <path>", "load defaults from a hagi18n.yaml file"))
|
|
36
48
|
.addOption(new Option("--locales-root <path>", "locale root directory"))
|
|
37
|
-
.addOption(new Option("--base-locale <locale>", "base locale to compare against"))
|
|
38
49
|
.addOption(new Option("--from <locale>", "source locale used as the structure baseline"))
|
|
39
50
|
.addOption(new Option("--locale <locale>", "limit the command to one or more locales").argParser(collectValues))
|
|
40
51
|
.addOption(new Option("--to <locale>", "target locale; can be repeated").argParser(collectValues))
|
|
41
52
|
.addOption(new Option("--json", "print machine-readable JSON output"));
|
|
53
|
+
if (auditBaseLocaleMode === "single") {
|
|
54
|
+
command.addOption(new Option("--base-locale <locale>", "base locale to compare against"));
|
|
55
|
+
}
|
|
56
|
+
if (auditBaseLocaleMode === "repeat") {
|
|
57
|
+
command.addOption(new Option("--base-locale <locale>", "audit baseline locale; can be repeated, first resolved locale stays structural").argParser(collectValues));
|
|
58
|
+
}
|
|
42
59
|
if (includeRepoRoot) {
|
|
43
60
|
command.addOption(new Option("--repo-root <path>", "repository root for doctor scanning"));
|
|
44
61
|
}
|
|
@@ -66,37 +83,37 @@ export function createCli() {
|
|
|
66
83
|
});
|
|
67
84
|
applySharedOptions(program
|
|
68
85
|
.command("audit")
|
|
69
|
-
.description("audit YAML locale files for drift against
|
|
86
|
+
.description("audit YAML locale files for drift against one or more baseline locales")
|
|
70
87
|
.action(async (options) => {
|
|
71
|
-
const summary = await auditLocaleTree(toToolkitOptions(options));
|
|
88
|
+
const summary = await auditLocaleTree(toToolkitOptions(options, { includeAuditBaseLocales: true }));
|
|
72
89
|
printSummary(summary, {
|
|
73
90
|
json: options.json,
|
|
74
91
|
formatter: formatAuditSummary
|
|
75
92
|
});
|
|
76
93
|
process.exitCode = summary.hasIssues ? 1 : 0;
|
|
77
|
-
}));
|
|
94
|
+
}), { auditBaseLocaleMode: "repeat" });
|
|
78
95
|
applySharedOptions(program
|
|
79
96
|
.command("report")
|
|
80
|
-
.description("run audit and print JSON output")
|
|
97
|
+
.description("run multi-baseline audit and print JSON output")
|
|
81
98
|
.action(async (options) => {
|
|
82
|
-
const summary = await auditLocaleTree(toToolkitOptions(options));
|
|
99
|
+
const summary = await auditLocaleTree(toToolkitOptions(options, { includeAuditBaseLocales: true }));
|
|
83
100
|
printSummary(summary, {
|
|
84
101
|
json: true,
|
|
85
102
|
formatter: formatAuditSummary
|
|
86
103
|
});
|
|
87
104
|
process.exitCode = summary.hasIssues ? 1 : 0;
|
|
88
|
-
}));
|
|
105
|
+
}), { auditBaseLocaleMode: "repeat" });
|
|
89
106
|
applySharedOptions(program
|
|
90
107
|
.command("doctor")
|
|
91
|
-
.description("
|
|
108
|
+
.description("run multi-baseline locale audit and scan the repository for legacy locale references")
|
|
92
109
|
.action(async (options) => {
|
|
93
|
-
const summary = await doctorLocaleTree(toToolkitOptions(options));
|
|
110
|
+
const summary = await doctorLocaleTree(toToolkitOptions(options, { includeAuditBaseLocales: true }));
|
|
94
111
|
printSummary(summary, {
|
|
95
112
|
json: options.json,
|
|
96
113
|
formatter: formatDoctorSummary
|
|
97
114
|
});
|
|
98
115
|
process.exitCode = summary.hasIssues ? 1 : 0;
|
|
99
|
-
}), { includeRepoRoot: true });
|
|
116
|
+
}), { includeRepoRoot: true, auditBaseLocaleMode: "repeat" });
|
|
100
117
|
applyMutationOptions(program
|
|
101
118
|
.command("sync")
|
|
102
119
|
.description("add missing files and keys from the base locale")
|
|
@@ -128,9 +145,9 @@ export function createCli() {
|
|
|
128
145
|
program.addHelpText("after", `
|
|
129
146
|
|
|
130
147
|
Examples:
|
|
131
|
-
hagi18n audit --locales-root src/locales --base-locale en-US
|
|
148
|
+
hagi18n audit --locales-root src/locales --base-locale en-US --base-locale ja-JP
|
|
132
149
|
hagi18n report --config hagi18n.yaml
|
|
133
|
-
hagi18n doctor --repo-root . --locales-root src/locales
|
|
150
|
+
hagi18n doctor --repo-root . --locales-root src/locales --base-locale en-US --base-locale zh-CN
|
|
134
151
|
hagi18n sync --from en-US --to zh-CN --dry-run
|
|
135
152
|
hagi18n prune --from en-US --to zh-CN --write
|
|
136
153
|
`);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,eAAe,EACf,cAAc,EAIf,MAAM,YAAY,CAAC;AAoBpB,SAAS,aAAa,CAAC,KAAa,EAAE,WAAqB,EAAE;IAC3D,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -20,7 +20,9 @@ export interface Hagi18nConfig {
|
|
|
20
20
|
localesRoot?: string;
|
|
21
21
|
repoRoot?: string;
|
|
22
22
|
baseLocale?: string;
|
|
23
|
+
auditBaseLocales?: string[];
|
|
23
24
|
targetLocales?: string[];
|
|
25
|
+
auditIdenticalAllowlist?: Record<string, string[]>;
|
|
24
26
|
doctor?: Hagi18nDoctorConfig;
|
|
25
27
|
}
|
|
26
28
|
export interface ResolvedDoctorScanRule extends DoctorScanRuleConfig {
|
|
@@ -39,7 +41,9 @@ export interface ResolvedHagi18nConfig {
|
|
|
39
41
|
localesRoot: string;
|
|
40
42
|
repoRoot: string;
|
|
41
43
|
baseLocale: string;
|
|
44
|
+
auditBaseLocales: string[];
|
|
42
45
|
targetLocales: string[];
|
|
46
|
+
auditIdenticalAllowlist: Record<string, string[]>;
|
|
43
47
|
doctor: ResolvedHagi18nDoctorConfig;
|
|
44
48
|
}
|
|
45
49
|
export interface LoadHagi18nConfigOptions {
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { access, readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import yaml from "js-yaml";
|
|
4
|
+
import { normalizeLocaleName } from "./locale-name.js";
|
|
4
5
|
export const DEFAULT_BASE_LOCALE = "en-US";
|
|
5
6
|
export const DEFAULT_CONFIG_FILE_NAMES = ["hagi18n.yaml", "hagi18n.yml"];
|
|
6
7
|
export const DEFAULT_DOCTOR_EXCLUDED_DIRECTORIES = [
|
|
@@ -94,6 +95,26 @@ function dedupeStrings(values) {
|
|
|
94
95
|
}
|
|
95
96
|
return output;
|
|
96
97
|
}
|
|
98
|
+
function normalizeLocaleValue(value) {
|
|
99
|
+
if (value === undefined) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
return normalizeLocaleName(value) ?? value.trim();
|
|
103
|
+
}
|
|
104
|
+
function normalizeLocaleValues(values) {
|
|
105
|
+
if (!values) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const output = [];
|
|
109
|
+
for (const value of values) {
|
|
110
|
+
const normalizedValue = normalizeLocaleName(value) ?? value.trim();
|
|
111
|
+
if (!normalizedValue || output.includes(normalizedValue)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
output.push(normalizedValue);
|
|
115
|
+
}
|
|
116
|
+
return output;
|
|
117
|
+
}
|
|
97
118
|
function normalizeAllowlist(value, fieldName) {
|
|
98
119
|
if (value === undefined) {
|
|
99
120
|
return undefined;
|
|
@@ -107,6 +128,33 @@ function normalizeAllowlist(value, fieldName) {
|
|
|
107
128
|
}
|
|
108
129
|
return output;
|
|
109
130
|
}
|
|
131
|
+
function groupIdenticalAllowlist(entries) {
|
|
132
|
+
const grouped = new Map();
|
|
133
|
+
for (const [rawFilePath, keyPaths] of entries) {
|
|
134
|
+
const filePath = rawFilePath.trim().replace(/\\/g, "/");
|
|
135
|
+
if (!filePath) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
grouped.set(filePath, dedupeStrings([...(grouped.get(filePath) ?? []), ...keyPaths]));
|
|
139
|
+
}
|
|
140
|
+
return Object.fromEntries(grouped);
|
|
141
|
+
}
|
|
142
|
+
function normalizeIdenticalAllowlist(value, fieldName) {
|
|
143
|
+
if (value === undefined) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
if (!isPlainObject(value)) {
|
|
147
|
+
throw new Error(`${fieldName} must be a mapping of locale-relative file paths to scalar key path arrays.`);
|
|
148
|
+
}
|
|
149
|
+
const entries = Object.entries(value).map(([filePath, keyPaths]) => {
|
|
150
|
+
ensureString(filePath, `${fieldName} file path`);
|
|
151
|
+
return [
|
|
152
|
+
filePath,
|
|
153
|
+
ensureStringArray(keyPaths, `${fieldName}.${filePath}`) ?? []
|
|
154
|
+
];
|
|
155
|
+
});
|
|
156
|
+
return groupIdenticalAllowlist(entries);
|
|
157
|
+
}
|
|
110
158
|
function normalizeScanRules(value, fieldName) {
|
|
111
159
|
if (value === undefined) {
|
|
112
160
|
return undefined;
|
|
@@ -154,7 +202,9 @@ function parseConfigDocument(document, configPath) {
|
|
|
154
202
|
localesRoot: ensureString(document.localesRoot, "localesRoot"),
|
|
155
203
|
repoRoot: ensureString(document.repoRoot, "repoRoot"),
|
|
156
204
|
baseLocale: ensureString(document.baseLocale, "baseLocale"),
|
|
205
|
+
auditBaseLocales: ensureStringArray(document.auditBaseLocales, "auditBaseLocales"),
|
|
157
206
|
targetLocales: ensureStringArray(document.targetLocales, "targetLocales"),
|
|
207
|
+
auditIdenticalAllowlist: normalizeIdenticalAllowlist(document.auditIdenticalAllowlist, "auditIdenticalAllowlist"),
|
|
158
208
|
doctor: parseDoctorConfig(document.doctor)
|
|
159
209
|
};
|
|
160
210
|
}
|
|
@@ -228,9 +278,11 @@ function resolveConfigLayer(config, { cwd, configPath }) {
|
|
|
228
278
|
return {
|
|
229
279
|
localesRoot: resolveStringPath(config.localesRoot, baseDirectory),
|
|
230
280
|
repoRoot: resolveStringPath(config.repoRoot, baseDirectory),
|
|
231
|
-
baseLocale: config.baseLocale
|
|
232
|
-
|
|
233
|
-
|
|
281
|
+
baseLocale: normalizeLocaleValue(config.baseLocale),
|
|
282
|
+
auditBaseLocales: normalizeLocaleValues(config.auditBaseLocales),
|
|
283
|
+
targetLocales: normalizeLocaleValues(config.targetLocales),
|
|
284
|
+
auditIdenticalAllowlist: config.auditIdenticalAllowlist
|
|
285
|
+
? groupIdenticalAllowlist(Object.entries(config.auditIdenticalAllowlist))
|
|
234
286
|
: undefined,
|
|
235
287
|
doctor: {
|
|
236
288
|
excludedDirectories: config.doctor?.excludedDirectories
|
|
@@ -264,6 +316,11 @@ function mergeAllowlists(baseAllowlist, overrideAllowlist) {
|
|
|
264
316
|
}
|
|
265
317
|
return merged;
|
|
266
318
|
}
|
|
319
|
+
function mergeIdenticalAllowlists(baseAllowlist, overrideAllowlist) {
|
|
320
|
+
// Replace per file: a higher-precedence list for a file wins outright,
|
|
321
|
+
// and files defined only in the lower layer keep their lists.
|
|
322
|
+
return { ...baseAllowlist, ...overrideAllowlist };
|
|
323
|
+
}
|
|
267
324
|
function mergeResolvedDoctorConfig(baseConfig, overrideConfig) {
|
|
268
325
|
if (!overrideConfig) {
|
|
269
326
|
return {
|
|
@@ -297,7 +354,9 @@ function createDefaultResolvedConfig(cwd) {
|
|
|
297
354
|
localesRoot: path.resolve(cwd, "src/locales"),
|
|
298
355
|
repoRoot: cwd,
|
|
299
356
|
baseLocale: DEFAULT_BASE_LOCALE,
|
|
357
|
+
auditBaseLocales: [DEFAULT_BASE_LOCALE],
|
|
300
358
|
targetLocales: [],
|
|
359
|
+
auditIdenticalAllowlist: {},
|
|
301
360
|
doctor: {
|
|
302
361
|
excludedDirectories: [...DEFAULT_DOCTOR_EXCLUDED_DIRECTORIES],
|
|
303
362
|
textFileExtensions: [...DEFAULT_DOCTOR_TEXT_FILE_EXTENSIONS],
|
|
@@ -319,22 +378,34 @@ export async function resolveHagi18nConfig(options = {}) {
|
|
|
319
378
|
localesRoot: options.localesRoot,
|
|
320
379
|
repoRoot: options.repoRoot,
|
|
321
380
|
baseLocale: options.baseLocale,
|
|
381
|
+
auditBaseLocales: options.auditBaseLocales,
|
|
322
382
|
targetLocales: options.targetLocales,
|
|
383
|
+
auditIdenticalAllowlist: options.auditIdenticalAllowlist,
|
|
323
384
|
doctor: options.doctor
|
|
324
385
|
}, {
|
|
325
386
|
cwd,
|
|
326
387
|
configPath: null
|
|
327
388
|
});
|
|
328
389
|
const mergedDoctor = mergeResolvedDoctorConfig(mergeResolvedDoctorConfig(defaults.doctor, fileLayer.doctor), overrideLayer.doctor);
|
|
390
|
+
const mergedBaseLocale = overrideLayer.baseLocale ?? fileLayer.baseLocale ?? defaults.baseLocale;
|
|
391
|
+
const mergedAuditBaseLocales = overrideLayer.auditBaseLocales && overrideLayer.auditBaseLocales.length > 0
|
|
392
|
+
? overrideLayer.auditBaseLocales
|
|
393
|
+
: overrideLayer.baseLocale
|
|
394
|
+
? [mergedBaseLocale]
|
|
395
|
+
: fileLayer.auditBaseLocales && fileLayer.auditBaseLocales.length > 0
|
|
396
|
+
? fileLayer.auditBaseLocales
|
|
397
|
+
: [mergedBaseLocale];
|
|
329
398
|
return {
|
|
330
399
|
cwd,
|
|
331
400
|
configPath: loaded.configPath,
|
|
332
401
|
localesRoot: overrideLayer.localesRoot ?? fileLayer.localesRoot ?? defaults.localesRoot,
|
|
333
402
|
repoRoot: overrideLayer.repoRoot ?? fileLayer.repoRoot ?? defaults.repoRoot,
|
|
334
|
-
baseLocale:
|
|
403
|
+
baseLocale: mergedBaseLocale,
|
|
404
|
+
auditBaseLocales: mergedAuditBaseLocales,
|
|
335
405
|
targetLocales: overrideLayer.targetLocales ??
|
|
336
406
|
fileLayer.targetLocales ??
|
|
337
407
|
defaults.targetLocales,
|
|
408
|
+
auditIdenticalAllowlist: mergeIdenticalAllowlists(mergeIdenticalAllowlists(defaults.auditIdenticalAllowlist, fileLayer.auditIdenticalAllowlist), overrideLayer.auditIdenticalAllowlist),
|
|
338
409
|
doctor: mergedDoctor
|
|
339
410
|
};
|
|
340
411
|
}
|