@i18n-micro/cli 0.0.0-stage → 2.0.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 +21 -0
- package/README.md +29 -2
- package/bin/i18n-micro.mjs +3 -0
- package/dist/chunks/check-BaP_H-mS.mjs +175 -0
- package/dist/chunks/clean-Do9z6D5j.mjs +68 -0
- package/dist/chunks/clean-bQVjG_2M.mjs +1 -0
- package/dist/chunks/export-C2J-pagB.mjs +219 -0
- package/dist/chunks/export-usBaNIpp.mjs +1 -0
- package/dist/chunks/extract-CJ8UfrVJ.mjs +30 -0
- package/dist/chunks/format-6CtMgO-M.mjs +26 -0
- package/dist/chunks/http-whB7a5ye.mjs +100 -0
- package/dist/chunks/import-CnL8U6Za.mjs +99 -0
- package/dist/chunks/info-rYas4Yw3.mjs +32 -0
- package/dist/chunks/json-BV_apCMu.mjs +439 -0
- package/dist/chunks/lint-BaEIQGMl.mjs +125 -0
- package/dist/chunks/nuxt-dKxG087V.mjs +366 -0
- package/dist/chunks/package-f9OTcDxl.mjs +3 -0
- package/dist/chunks/project-DgWgr7pF.mjs +206 -0
- package/dist/chunks/protect-D1HIuxmJ.mjs +36 -0
- package/dist/chunks/pseudo-oXBaONgF.mjs +32 -0
- package/dist/chunks/rename-uJnBX86A.mjs +117 -0
- package/dist/chunks/runner-wHXdhRdA.mjs +200 -0
- package/dist/chunks/search-Dj_Ynq_x.mjs +67 -0
- package/dist/chunks/source-Bx2lHSW7.mjs +744 -0
- package/dist/chunks/spec-BwZlT28L.mjs +507 -0
- package/dist/chunks/stats-BU-SmrVR.mjs +156 -0
- package/dist/chunks/sync-BY2EFEda.mjs +1 -0
- package/dist/chunks/sync-C0NVDabK.mjs +56 -0
- package/dist/chunks/sync-remote-y3ZATbF2.mjs +148 -0
- package/dist/chunks/text-to-i18n-DweavFLm.mjs +404 -0
- package/dist/chunks/translate-CmnmgAHX.mjs +543 -0
- package/dist/cli.mjs +4 -0
- package/dist/index.mjs +3 -0
- package/dist/src/cli.d.ts +1 -0
- package/dist/src/commands/check.d.ts +28 -0
- package/dist/src/commands/clean.d.ts +26 -0
- package/dist/src/commands/export.d.ts +10 -0
- package/dist/src/commands/extract.d.ts +6 -0
- package/dist/src/commands/format.d.ts +8 -0
- package/dist/src/commands/import.d.ts +8 -0
- package/dist/src/commands/info.d.ts +5 -0
- package/dist/src/commands/lint.d.ts +16 -0
- package/dist/src/commands/pseudo.d.ts +9 -0
- package/dist/src/commands/rename.d.ts +9 -0
- package/dist/src/commands/search.d.ts +20 -0
- package/dist/src/commands/stats.d.ts +20 -0
- package/dist/src/commands/sync-remote.d.ts +30 -0
- package/dist/src/commands/sync.d.ts +20 -0
- package/dist/src/commands/text-to-i18n.d.ts +10 -0
- package/dist/src/commands/translate.d.ts +14 -0
- package/dist/src/csv.d.ts +3 -0
- package/dist/src/errors.d.ts +22 -0
- package/dist/src/fs.d.ts +45 -0
- package/dist/src/glossary.d.ts +12 -0
- package/dist/src/help.d.ts +3 -0
- package/dist/src/http.d.ts +10 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/json.d.ts +34 -0
- package/dist/src/lint-rules.d.ts +9 -0
- package/dist/src/log.d.ts +88 -0
- package/dist/src/nuxt.d.ts +66 -0
- package/dist/src/po.d.ts +14 -0
- package/dist/src/project.d.ts +64 -0
- package/dist/src/runner.d.ts +6 -0
- package/dist/src/source.d.ts +109 -0
- package/dist/src/spec.d.ts +57 -0
- package/dist/src/text-to-i18n.d.ts +46 -0
- package/dist/src/translate/ai.d.ts +2 -0
- package/dist/src/translate/drivers.d.ts +26 -0
- package/dist/src/translate/engine.d.ts +33 -0
- package/dist/src/translate/protect.d.ts +11 -0
- package/package.json +71 -3
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
// src/spec.ts
|
|
2
|
+
var COMMAND_GROUPS = [
|
|
3
|
+
"Inspect",
|
|
4
|
+
"Translation workflow",
|
|
5
|
+
"Refactoring",
|
|
6
|
+
"Exchange"
|
|
7
|
+
];
|
|
8
|
+
/** Translation services of `translate`. A test keeps this equal to the drivers. */
|
|
9
|
+
var SERVICES = [
|
|
10
|
+
"ai",
|
|
11
|
+
"azure",
|
|
12
|
+
"deepl",
|
|
13
|
+
"google",
|
|
14
|
+
"libretranslate",
|
|
15
|
+
"yandexcloud"
|
|
16
|
+
];
|
|
17
|
+
/** What `check` looks for. `duplicates` is off by default: the same word under two keys is often fine. */
|
|
18
|
+
var CHECK_TYPES = [
|
|
19
|
+
"missing",
|
|
20
|
+
"empty",
|
|
21
|
+
"extra",
|
|
22
|
+
"placeholders",
|
|
23
|
+
"undefined",
|
|
24
|
+
"unused",
|
|
25
|
+
"duplicates"
|
|
26
|
+
];
|
|
27
|
+
var LINT_RULES = [
|
|
28
|
+
{
|
|
29
|
+
name: "no-trailing-spaces",
|
|
30
|
+
description: "Spaces at the end of a value or a line",
|
|
31
|
+
fixable: true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "no-multiple-spaces",
|
|
35
|
+
description: "Two or more spaces in a row",
|
|
36
|
+
fixable: true
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "no-empty-translation",
|
|
40
|
+
description: "An empty or blank value",
|
|
41
|
+
fixable: false
|
|
42
|
+
}
|
|
43
|
+
];
|
|
44
|
+
/** Commands that went away, and what to run instead. */
|
|
45
|
+
var REMOVED_COMMANDS = {
|
|
46
|
+
validate: "check",
|
|
47
|
+
diff: "check --only missing",
|
|
48
|
+
"check-duplicates": "check --only duplicates",
|
|
49
|
+
"export-csv": "export --format csv",
|
|
50
|
+
"import-csv": "import --format csv",
|
|
51
|
+
estimate: "translate --dryRun",
|
|
52
|
+
optimize: "check --only duplicates",
|
|
53
|
+
backup: "git, which already keeps your locale files",
|
|
54
|
+
restore: "git, which already keeps your locale files",
|
|
55
|
+
init: "nuxi init, then add nuxt-i18n-micro to modules",
|
|
56
|
+
split: "nothing: the module does not read split files",
|
|
57
|
+
"replace-values": "your editor or sed",
|
|
58
|
+
glossary: "editing .i18n-glossary.json by hand"
|
|
59
|
+
};
|
|
60
|
+
var shared = {
|
|
61
|
+
cwd: {
|
|
62
|
+
type: "string",
|
|
63
|
+
description: "Project directory",
|
|
64
|
+
defaultHint: "current directory"
|
|
65
|
+
},
|
|
66
|
+
logLevel: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "Output level",
|
|
69
|
+
values: ["silent", "info"],
|
|
70
|
+
default: "info"
|
|
71
|
+
},
|
|
72
|
+
debug: {
|
|
73
|
+
type: "boolean",
|
|
74
|
+
description: "Print a stack trace when a command fails",
|
|
75
|
+
default: false
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var translationDir = {
|
|
79
|
+
type: "string",
|
|
80
|
+
description: "Translation directory, relative to the project",
|
|
81
|
+
defaultHint: "`i18n.translationDir` of nuxt.config"
|
|
82
|
+
};
|
|
83
|
+
var projectArgs = {
|
|
84
|
+
...shared,
|
|
85
|
+
translationDir
|
|
86
|
+
};
|
|
87
|
+
var json = {
|
|
88
|
+
type: "boolean",
|
|
89
|
+
description: "Print JSON instead of text",
|
|
90
|
+
default: false
|
|
91
|
+
};
|
|
92
|
+
var ignore = {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "Comma-separated globs of source files to skip, relative to the project"
|
|
95
|
+
};
|
|
96
|
+
var commands = {
|
|
97
|
+
info: {
|
|
98
|
+
group: "Inspect",
|
|
99
|
+
description: "Print the resolved i18n configuration of the project",
|
|
100
|
+
args: {
|
|
101
|
+
...shared,
|
|
102
|
+
json
|
|
103
|
+
},
|
|
104
|
+
load: () => import("./info-rYas4Yw3.mjs")
|
|
105
|
+
},
|
|
106
|
+
check: {
|
|
107
|
+
group: "Inspect",
|
|
108
|
+
description: "The CI gate: fail on missing, empty, extra, undefined or unused keys and broken placeholders",
|
|
109
|
+
args: {
|
|
110
|
+
...projectArgs,
|
|
111
|
+
only: {
|
|
112
|
+
type: "string",
|
|
113
|
+
description: `Comma-separated checks to run: ${CHECK_TYPES.join(", ")}. \`unused\` named here fails when it cannot run`,
|
|
114
|
+
defaultHint: "all but duplicates"
|
|
115
|
+
},
|
|
116
|
+
duplicates: {
|
|
117
|
+
type: "boolean",
|
|
118
|
+
description: "Also report the same text under different keys",
|
|
119
|
+
default: false
|
|
120
|
+
},
|
|
121
|
+
ignore,
|
|
122
|
+
json
|
|
123
|
+
},
|
|
124
|
+
examples: ["i18n-micro check", "i18n-micro check --only missing,placeholders --json"],
|
|
125
|
+
load: () => import("./check-BaP_H-mS.mjs")
|
|
126
|
+
},
|
|
127
|
+
stats: {
|
|
128
|
+
group: "Inspect",
|
|
129
|
+
description: "Show translation completion per locale",
|
|
130
|
+
args: {
|
|
131
|
+
...projectArgs,
|
|
132
|
+
html: {
|
|
133
|
+
type: "string",
|
|
134
|
+
description: "Also write an HTML report to this file"
|
|
135
|
+
},
|
|
136
|
+
json,
|
|
137
|
+
baseRef: {
|
|
138
|
+
type: "string",
|
|
139
|
+
description: "Git ref to compare completion with, e.g. HEAD~1 or main"
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
examples: ["i18n-micro stats --baseRef main", "i18n-micro stats --html reports/i18n.html"],
|
|
143
|
+
load: () => import("./stats-BU-SmrVR.mjs")
|
|
144
|
+
},
|
|
145
|
+
lint: {
|
|
146
|
+
group: "Inspect",
|
|
147
|
+
description: "Check the text of translations; fails when issues remain",
|
|
148
|
+
args: {
|
|
149
|
+
...projectArgs,
|
|
150
|
+
rules: {
|
|
151
|
+
type: "string",
|
|
152
|
+
description: `Comma-separated rules to run: ${LINT_RULES.map((rule) => rule.name).join(", ")}, or a custom one`,
|
|
153
|
+
defaultHint: "all, or `enabledRules` of .i18n-lintrc.json"
|
|
154
|
+
},
|
|
155
|
+
fix: {
|
|
156
|
+
type: "boolean",
|
|
157
|
+
description: "Fix what can be fixed: spaces, and custom rules with a replacement",
|
|
158
|
+
default: false
|
|
159
|
+
},
|
|
160
|
+
json
|
|
161
|
+
},
|
|
162
|
+
examples: ["i18n-micro lint --rules no-trailing-spaces --fix"],
|
|
163
|
+
load: () => import("./lint-BaEIQGMl.mjs")
|
|
164
|
+
},
|
|
165
|
+
search: {
|
|
166
|
+
group: "Inspect",
|
|
167
|
+
description: "Search keys and values, and show where each key is used",
|
|
168
|
+
args: {
|
|
169
|
+
...projectArgs,
|
|
170
|
+
query: {
|
|
171
|
+
type: "positional",
|
|
172
|
+
description: "Text or key to search for",
|
|
173
|
+
required: false
|
|
174
|
+
},
|
|
175
|
+
caseSensitive: {
|
|
176
|
+
type: "boolean",
|
|
177
|
+
description: "Match case",
|
|
178
|
+
default: false
|
|
179
|
+
},
|
|
180
|
+
onlyUnused: {
|
|
181
|
+
type: "boolean",
|
|
182
|
+
description: "Only keys `clean` would remove; the query becomes optional",
|
|
183
|
+
default: false
|
|
184
|
+
},
|
|
185
|
+
scope: {
|
|
186
|
+
type: "string",
|
|
187
|
+
description: "Where to search",
|
|
188
|
+
values: [
|
|
189
|
+
"global",
|
|
190
|
+
"pages",
|
|
191
|
+
"all"
|
|
192
|
+
],
|
|
193
|
+
default: "all"
|
|
194
|
+
},
|
|
195
|
+
limit: {
|
|
196
|
+
type: "string",
|
|
197
|
+
description: "Return at most this many keys",
|
|
198
|
+
numeric: "int"
|
|
199
|
+
},
|
|
200
|
+
ignore,
|
|
201
|
+
json
|
|
202
|
+
},
|
|
203
|
+
examples: ["i18n-micro search \"Welcome\"", "i18n-micro search --onlyUnused"],
|
|
204
|
+
load: () => import("./search-Dj_Ynq_x.mjs")
|
|
205
|
+
},
|
|
206
|
+
extract: {
|
|
207
|
+
group: "Translation workflow",
|
|
208
|
+
description: "List the keys source files use that have no text yet, and add those with a default text to the default locale",
|
|
209
|
+
args: {
|
|
210
|
+
...projectArgs,
|
|
211
|
+
ignore
|
|
212
|
+
},
|
|
213
|
+
examples: ["i18n-micro extract"],
|
|
214
|
+
load: () => import("./extract-CJ8UfrVJ.mjs")
|
|
215
|
+
},
|
|
216
|
+
translate: {
|
|
217
|
+
group: "Translation workflow",
|
|
218
|
+
description: "Fill missing translations from the default locale through a translation service",
|
|
219
|
+
args: {
|
|
220
|
+
...projectArgs,
|
|
221
|
+
service: {
|
|
222
|
+
type: "string",
|
|
223
|
+
description: `Translation service: ${SERVICES.join(", ")}`
|
|
224
|
+
},
|
|
225
|
+
token: {
|
|
226
|
+
type: "string",
|
|
227
|
+
description: "API key. Prefer the I18N_TRANSLATE_TOKEN environment variable: flags end up in shell history"
|
|
228
|
+
},
|
|
229
|
+
options: {
|
|
230
|
+
type: "string",
|
|
231
|
+
description: "Service options as key:value pairs, separated by commas"
|
|
232
|
+
},
|
|
233
|
+
locale: {
|
|
234
|
+
type: "string",
|
|
235
|
+
description: "Comma-separated locales to fill",
|
|
236
|
+
defaultHint: "every locale but the default one"
|
|
237
|
+
},
|
|
238
|
+
replace: {
|
|
239
|
+
type: "boolean",
|
|
240
|
+
description: "Translate every key again, replacing existing values",
|
|
241
|
+
default: false
|
|
242
|
+
},
|
|
243
|
+
dryRun: {
|
|
244
|
+
type: "boolean",
|
|
245
|
+
description: "Count keys and characters per locale; send nothing",
|
|
246
|
+
default: false
|
|
247
|
+
},
|
|
248
|
+
chunkSize: {
|
|
249
|
+
type: "string",
|
|
250
|
+
description: "Strings per request; capped at what the service accepts",
|
|
251
|
+
numeric: "int"
|
|
252
|
+
},
|
|
253
|
+
pluralSeparator: {
|
|
254
|
+
type: "string",
|
|
255
|
+
description: "Separator of plural forms in messages",
|
|
256
|
+
default: "|"
|
|
257
|
+
},
|
|
258
|
+
glossaryFile: {
|
|
259
|
+
type: "string",
|
|
260
|
+
description: "Glossary file, used by the `ai` service",
|
|
261
|
+
defaultHint: "`.i18n-glossary.json` when it exists"
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
examples: [
|
|
265
|
+
"i18n-micro translate --dryRun",
|
|
266
|
+
"I18N_TRANSLATE_TOKEN=... i18n-micro translate --service deepl --locale de,fr",
|
|
267
|
+
"i18n-micro translate --service ai --options provider:openai,model:gpt-4o-mini",
|
|
268
|
+
"i18n-micro translate --service libretranslate --options baseUrl:https://translate.example.com"
|
|
269
|
+
],
|
|
270
|
+
load: () => import("./translate-CmnmgAHX.mjs")
|
|
271
|
+
},
|
|
272
|
+
sync: {
|
|
273
|
+
group: "Translation workflow",
|
|
274
|
+
description: "Remove keys the default locale does not have; stops when that cannot be proven safe. Missing keys stay missing",
|
|
275
|
+
args: {
|
|
276
|
+
...projectArgs,
|
|
277
|
+
ignore,
|
|
278
|
+
dryRun: {
|
|
279
|
+
type: "boolean",
|
|
280
|
+
description: "List what would be removed; write nothing",
|
|
281
|
+
default: false
|
|
282
|
+
},
|
|
283
|
+
force: {
|
|
284
|
+
type: "boolean",
|
|
285
|
+
description: "Remove keys even though it cannot be proven safe",
|
|
286
|
+
default: false
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
examples: ["i18n-micro sync --dryRun"],
|
|
290
|
+
load: () => import("./sync-BY2EFEda.mjs")
|
|
291
|
+
},
|
|
292
|
+
clean: {
|
|
293
|
+
group: "Translation workflow",
|
|
294
|
+
description: "Remove keys nothing in the project uses; stops when that cannot be proven",
|
|
295
|
+
args: {
|
|
296
|
+
...projectArgs,
|
|
297
|
+
include: {
|
|
298
|
+
type: "string",
|
|
299
|
+
description: "Regular expression: consider only matching keys for removal"
|
|
300
|
+
},
|
|
301
|
+
exclude: {
|
|
302
|
+
type: "string",
|
|
303
|
+
description: "Regular expression: never remove matching keys"
|
|
304
|
+
},
|
|
305
|
+
ignore,
|
|
306
|
+
dryRun: {
|
|
307
|
+
type: "boolean",
|
|
308
|
+
description: "List what would be removed; write nothing",
|
|
309
|
+
default: false
|
|
310
|
+
},
|
|
311
|
+
force: {
|
|
312
|
+
type: "boolean",
|
|
313
|
+
description: "Remove keys even though usage cannot be proven for all of them",
|
|
314
|
+
default: false
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
examples: ["i18n-micro clean --dryRun", `i18n-micro clean --exclude '^errors\\.'`],
|
|
318
|
+
load: () => import("./clean-bQVjG_2M.mjs")
|
|
319
|
+
},
|
|
320
|
+
"text-to-i18n": {
|
|
321
|
+
group: "Refactoring",
|
|
322
|
+
description: "Replace hardcoded text in Vue templates with translation calls, and report hardcoded text in code",
|
|
323
|
+
args: {
|
|
324
|
+
...shared,
|
|
325
|
+
translationFile: {
|
|
326
|
+
type: "string",
|
|
327
|
+
description: "File the new keys go to",
|
|
328
|
+
defaultHint: "`<translationDir>/<defaultLocale>.json`"
|
|
329
|
+
},
|
|
330
|
+
path: {
|
|
331
|
+
type: "string",
|
|
332
|
+
description: "File or directory to process",
|
|
333
|
+
defaultHint: "the whole project"
|
|
334
|
+
},
|
|
335
|
+
context: {
|
|
336
|
+
type: "string",
|
|
337
|
+
description: "Prefix for generated keys"
|
|
338
|
+
},
|
|
339
|
+
ignore,
|
|
340
|
+
dryRun: {
|
|
341
|
+
type: "boolean",
|
|
342
|
+
description: "Show what would change; write nothing",
|
|
343
|
+
default: false
|
|
344
|
+
},
|
|
345
|
+
interactive: {
|
|
346
|
+
type: "boolean",
|
|
347
|
+
description: "Confirm or edit every generated key",
|
|
348
|
+
default: false
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
examples: ["i18n-micro text-to-i18n --dryRun", "i18n-micro text-to-i18n --path components --context ui"],
|
|
352
|
+
load: () => import("./text-to-i18n-DweavFLm.mjs")
|
|
353
|
+
},
|
|
354
|
+
rename: {
|
|
355
|
+
group: "Refactoring",
|
|
356
|
+
description: "Rename a key or a namespace in every locale and in source files",
|
|
357
|
+
args: {
|
|
358
|
+
...projectArgs,
|
|
359
|
+
from: {
|
|
360
|
+
type: "string",
|
|
361
|
+
description: "Current key or namespace",
|
|
362
|
+
required: true
|
|
363
|
+
},
|
|
364
|
+
to: {
|
|
365
|
+
type: "string",
|
|
366
|
+
description: "New key or namespace",
|
|
367
|
+
required: true
|
|
368
|
+
},
|
|
369
|
+
ignore,
|
|
370
|
+
dryRun: {
|
|
371
|
+
type: "boolean",
|
|
372
|
+
description: "Report the changes; write nothing",
|
|
373
|
+
default: false
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
examples: ["i18n-micro rename --from common.save --to actions.save"],
|
|
377
|
+
load: () => import("./rename-uJnBX86A.mjs")
|
|
378
|
+
},
|
|
379
|
+
pseudo: {
|
|
380
|
+
group: "Refactoring",
|
|
381
|
+
description: "Fill a locale with pseudo-translations to find layout and hardcoded-text issues",
|
|
382
|
+
args: {
|
|
383
|
+
...projectArgs,
|
|
384
|
+
sourceLocale: {
|
|
385
|
+
type: "string",
|
|
386
|
+
description: "Locale to read",
|
|
387
|
+
defaultHint: "the default locale"
|
|
388
|
+
},
|
|
389
|
+
targetLocale: {
|
|
390
|
+
type: "string",
|
|
391
|
+
description: "Locale to fill",
|
|
392
|
+
required: true
|
|
393
|
+
},
|
|
394
|
+
replace: {
|
|
395
|
+
type: "boolean",
|
|
396
|
+
description: "Overwrite existing values",
|
|
397
|
+
default: false
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
load: () => import("./pseudo-oXBaONgF.mjs")
|
|
401
|
+
},
|
|
402
|
+
format: {
|
|
403
|
+
group: "Refactoring",
|
|
404
|
+
description: "Rewrite locale files with sorted keys and uniform indentation",
|
|
405
|
+
args: {
|
|
406
|
+
...projectArgs,
|
|
407
|
+
indent: {
|
|
408
|
+
type: "string",
|
|
409
|
+
description: "Spaces of indentation",
|
|
410
|
+
default: "2",
|
|
411
|
+
numeric: "int"
|
|
412
|
+
},
|
|
413
|
+
sortKeys: {
|
|
414
|
+
type: "boolean",
|
|
415
|
+
description: "Sort keys; use --no-sortKeys to keep their order",
|
|
416
|
+
default: true
|
|
417
|
+
},
|
|
418
|
+
check: {
|
|
419
|
+
type: "boolean",
|
|
420
|
+
description: "Fail if a file would change; write nothing",
|
|
421
|
+
default: false
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
examples: ["i18n-micro format --check"],
|
|
425
|
+
load: () => import("./format-6CtMgO-M.mjs")
|
|
426
|
+
},
|
|
427
|
+
export: {
|
|
428
|
+
group: "Exchange",
|
|
429
|
+
description: "Export locale files to PO or CSV, with the default-locale text as the source",
|
|
430
|
+
args: {
|
|
431
|
+
...projectArgs,
|
|
432
|
+
format: {
|
|
433
|
+
type: "string",
|
|
434
|
+
description: "File format",
|
|
435
|
+
values: ["po", "csv"],
|
|
436
|
+
default: "po"
|
|
437
|
+
},
|
|
438
|
+
dir: {
|
|
439
|
+
type: "string",
|
|
440
|
+
description: "Output directory, relative to the project",
|
|
441
|
+
defaultHint: "`pots` for po, `csv_exports` for csv"
|
|
442
|
+
},
|
|
443
|
+
force: {
|
|
444
|
+
type: "boolean",
|
|
445
|
+
description: "Overwrite files changed since the last export and never imported",
|
|
446
|
+
default: false
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
examples: ["i18n-micro export --format csv"],
|
|
450
|
+
load: () => import("./export-usBaNIpp.mjs")
|
|
451
|
+
},
|
|
452
|
+
import: {
|
|
453
|
+
group: "Exchange",
|
|
454
|
+
description: "Merge PO or CSV files into the locale files",
|
|
455
|
+
args: {
|
|
456
|
+
...projectArgs,
|
|
457
|
+
format: {
|
|
458
|
+
type: "string",
|
|
459
|
+
description: "File format",
|
|
460
|
+
values: ["po", "csv"],
|
|
461
|
+
default: "po"
|
|
462
|
+
},
|
|
463
|
+
dir: {
|
|
464
|
+
type: "string",
|
|
465
|
+
description: "Input directory, relative to the project",
|
|
466
|
+
defaultHint: "`pots` for po, `csv_exports` for csv"
|
|
467
|
+
},
|
|
468
|
+
dryRun: {
|
|
469
|
+
type: "boolean",
|
|
470
|
+
description: "Report what would be imported; write nothing",
|
|
471
|
+
default: false
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
examples: ["i18n-micro import --format csv --dryRun"],
|
|
475
|
+
load: () => import("./import-CnL8U6Za.mjs")
|
|
476
|
+
},
|
|
477
|
+
"sync-remote": {
|
|
478
|
+
group: "Exchange",
|
|
479
|
+
description: "Pull global translations from, or push them to, the HTTP endpoint in .i18n-remote.json",
|
|
480
|
+
args: {
|
|
481
|
+
...projectArgs,
|
|
482
|
+
pull: {
|
|
483
|
+
type: "boolean",
|
|
484
|
+
description: "Pull from the remote",
|
|
485
|
+
default: true
|
|
486
|
+
},
|
|
487
|
+
push: {
|
|
488
|
+
type: "boolean",
|
|
489
|
+
description: "Push to the remote",
|
|
490
|
+
default: false
|
|
491
|
+
},
|
|
492
|
+
force: {
|
|
493
|
+
type: "boolean",
|
|
494
|
+
description: "On a pull, take the remote value; on a push, overwrite text only the remote holds",
|
|
495
|
+
default: false
|
|
496
|
+
},
|
|
497
|
+
dryRun: {
|
|
498
|
+
type: "boolean",
|
|
499
|
+
description: "Report what a pull would change; write and push nothing",
|
|
500
|
+
default: false
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
examples: ["i18n-micro sync-remote --dryRun", "i18n-micro sync-remote --no-pull --push"],
|
|
504
|
+
load: () => import("./sync-remote-y3ZATbF2.mjs")
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
export { commands as a, REMOVED_COMMANDS as i, COMMAND_GROUPS as n, LINT_RULES as r, CHECK_TYPES as t };
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { A as log, P as style, S as validationError, a as flattenStrings, d as ownValue, l as isObject } from "./json-BV_apCMu.mjs";
|
|
2
|
+
import { h as writeFileAtomic, u as relativePath } from "./nuxt-dKxG087V.mjs";
|
|
3
|
+
import { t as Project } from "./project-DgWgr7pF.mjs";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
// src/commands/stats.ts
|
|
7
|
+
var HTML_ESCAPES = {
|
|
8
|
+
"&": "&",
|
|
9
|
+
"<": "<",
|
|
10
|
+
">": ">",
|
|
11
|
+
"\"": """,
|
|
12
|
+
"'": "'"
|
|
13
|
+
};
|
|
14
|
+
/** Locale codes come from the project, but a report is opened in a browser. */
|
|
15
|
+
var esc = (text) => text.replace(/[&<>"']/g, (char) => HTML_ESCAPES[char]);
|
|
16
|
+
var percent = (value) => `${value.toFixed(2)}%`;
|
|
17
|
+
var scopeKeys = (tree) => Object.keys(flattenStrings(tree));
|
|
18
|
+
function completionOf(code, trees, reference) {
|
|
19
|
+
const scopes = ["", ...Object.keys(reference.pages)];
|
|
20
|
+
const treeOf = (source, scope) => scope === "" ? source.global : ownValue(source.pages, scope) ?? {};
|
|
21
|
+
let totalKeys = 0;
|
|
22
|
+
let translatedKeys = 0;
|
|
23
|
+
for (const scope of scopes) {
|
|
24
|
+
const strings = flattenStrings(treeOf(trees, scope));
|
|
25
|
+
const keys = scopeKeys(treeOf(reference, scope));
|
|
26
|
+
totalKeys += keys.length;
|
|
27
|
+
translatedKeys += keys.filter((key) => Boolean(strings[key])).length;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
code,
|
|
31
|
+
totalKeys,
|
|
32
|
+
translatedKeys,
|
|
33
|
+
completion: totalKeys === 0 ? 100 : translatedKeys / totalKeys * 100
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
var reportOf = (byCode, defaultLocale) => [...byCode].map(([code, trees]) => completionOf(code, trees, byCode.get(defaultLocale)));
|
|
37
|
+
function git(rootDir, args, input) {
|
|
38
|
+
return execFileSync("git", args, {
|
|
39
|
+
cwd: rootDir,
|
|
40
|
+
encoding: "utf8",
|
|
41
|
+
input,
|
|
42
|
+
stdio: [
|
|
43
|
+
"pipe",
|
|
44
|
+
"pipe",
|
|
45
|
+
"ignore"
|
|
46
|
+
],
|
|
47
|
+
maxBuffer: Number.POSITIVE_INFINITY
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The object at a path of a ref, with links followed as the project loader follows them: `git show`
|
|
52
|
+
* would give a link's target as its text. `null`: no such path at that ref.
|
|
53
|
+
*/
|
|
54
|
+
function gitObject(rootDir, ref, path) {
|
|
55
|
+
const gitPath = relativePath(rootDir, path);
|
|
56
|
+
const answer = git(rootDir, [
|
|
57
|
+
"cat-file",
|
|
58
|
+
"--batch",
|
|
59
|
+
"--follow-symlinks"
|
|
60
|
+
], `${ref}:./${gitPath}\n`);
|
|
61
|
+
const header = answer.slice(0, answer.indexOf("\n"));
|
|
62
|
+
const found = /^(\S+) (blob|tree) \d+$/.exec(header);
|
|
63
|
+
if (found) return {
|
|
64
|
+
oid: found[1],
|
|
65
|
+
type: found[2],
|
|
66
|
+
body: answer.slice(header.length + 1)
|
|
67
|
+
};
|
|
68
|
+
if (header.endsWith(" missing") || header.startsWith("notdir ")) return null;
|
|
69
|
+
throw validationError(`Cannot read ${gitPath} at ${ref}: it is a link to something outside that commit.`);
|
|
70
|
+
}
|
|
71
|
+
function gitShow(rootDir, ref, path) {
|
|
72
|
+
const found = gitObject(rootDir, ref, path);
|
|
73
|
+
if (!found) return {};
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(found.body);
|
|
76
|
+
return isObject(parsed) ? parsed : {};
|
|
77
|
+
} catch {
|
|
78
|
+
throw validationError(`Invalid JSON at ${ref}: ${relativePath(rootDir, path)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function gitPageScopes(rootDir, ref, translationDir) {
|
|
82
|
+
const pages = gitObject(rootDir, ref, join(translationDir, "pages"));
|
|
83
|
+
if (pages?.type !== "tree") return [];
|
|
84
|
+
const scopes = git(rootDir, [
|
|
85
|
+
"ls-tree",
|
|
86
|
+
"-r",
|
|
87
|
+
"--name-only",
|
|
88
|
+
pages.oid
|
|
89
|
+
]).split("\n").filter((line) => line.endsWith(".json") && line.includes("/")).map((line) => line.slice(0, line.lastIndexOf("/")));
|
|
90
|
+
return [...new Set(scopes)];
|
|
91
|
+
}
|
|
92
|
+
function assertRef(rootDir, ref) {
|
|
93
|
+
try {
|
|
94
|
+
git(rootDir, [
|
|
95
|
+
"rev-parse",
|
|
96
|
+
"--verify",
|
|
97
|
+
`${ref}^{commit}`
|
|
98
|
+
]);
|
|
99
|
+
} catch {
|
|
100
|
+
throw validationError(`Unknown git ref: ${ref}`, ["Pass a branch, tag or commit of this repository."]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function treesAtRef(project, ref) {
|
|
104
|
+
const { rootDir, translationDir } = project.config;
|
|
105
|
+
assertRef(rootDir, ref);
|
|
106
|
+
const scopes = project.config.disablePageLocales ? [] : [.../* @__PURE__ */ new Set([...project.reference.pageScopes, ...gitPageScopes(rootDir, ref, translationDir)])];
|
|
107
|
+
return new Map(project.codes.map((code) => [code, {
|
|
108
|
+
global: gitShow(rootDir, ref, join(translationDir, `${code}.json`)),
|
|
109
|
+
pages: Object.fromEntries(scopes.map((scope) => [scope, gitShow(rootDir, ref, join(translationDir, "pages", scope, `${code}.json`))]))
|
|
110
|
+
}]));
|
|
111
|
+
}
|
|
112
|
+
/** Git knows the project's own files, so the comparison is between own files: no layer on either side. */
|
|
113
|
+
function buildDiff(project, ref) {
|
|
114
|
+
const current = new Map(project.codes.map((code) => [code, project.locale(code).own]));
|
|
115
|
+
const before = new Map(reportOf(treesAtRef(project, ref), project.config.defaultLocale).map((entry) => [entry.code, entry]));
|
|
116
|
+
return reportOf(current, project.config.defaultLocale).map((entry) => {
|
|
117
|
+
const base = before.get(entry.code);
|
|
118
|
+
return {
|
|
119
|
+
code: entry.code,
|
|
120
|
+
completionDelta: entry.completion - base.completion,
|
|
121
|
+
translatedKeysDelta: entry.translatedKeys - base.translatedKeys,
|
|
122
|
+
totalKeysDelta: entry.totalKeys - base.totalKeys
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
var htmlReport = (locales) => {
|
|
127
|
+
return `<!doctype html><meta charset="utf-8"><title>i18n completion</title><table><tr><th>Locale</th><th>Keys</th><th>Completion</th></tr>${locales.map(({ code, translatedKeys, totalKeys, completion }) => `<tr><td>${esc(code)}</td><td>${translatedKeys}/${totalKeys}</td><td>${percent(completion)}</td></tr>`).join("")}</table>\n`;
|
|
128
|
+
};
|
|
129
|
+
async function run(args) {
|
|
130
|
+
const project = await Project.load(args);
|
|
131
|
+
const { rootDir, defaultLocale } = project.config;
|
|
132
|
+
const locales = reportOf(new Map(project.codes.map((code) => [code, project.locale(code)])), defaultLocale);
|
|
133
|
+
const diff = args.baseRef ? {
|
|
134
|
+
baseRef: args.baseRef,
|
|
135
|
+
locales: buildDiff(project, args.baseRef)
|
|
136
|
+
} : void 0;
|
|
137
|
+
const htmlReportPath = args.html ? resolve(rootDir, args.html) : void 0;
|
|
138
|
+
if (htmlReportPath) writeFileAtomic(htmlReportPath, htmlReport(locales));
|
|
139
|
+
if (args.json) {
|
|
140
|
+
log.json({
|
|
141
|
+
locales,
|
|
142
|
+
diff,
|
|
143
|
+
htmlReportPath
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
log.section("Completion Summary");
|
|
148
|
+
const tone = (completion) => completion >= 100 ? "green" : completion >= 50 ? "yellow" : "red";
|
|
149
|
+
log.rows(locales.map(({ code, translatedKeys, totalKeys, completion }) => [code, `${translatedKeys}/${totalKeys} (${style(tone(completion), percent(completion))})`]));
|
|
150
|
+
if (diff) {
|
|
151
|
+
log.section(`Diff vs ${diff.baseRef}`);
|
|
152
|
+
log.rows(diff.locales.map(({ code, completionDelta }) => [code, `${completionDelta >= 0 ? "+" : ""}${percent(completionDelta)}`]));
|
|
153
|
+
}
|
|
154
|
+
if (htmlReportPath) log.success(`HTML report generated: ${htmlReportPath}`);
|
|
155
|
+
}
|
|
156
|
+
export { run as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { n as default, t as extraKeys } from "./sync-C0NVDabK.mjs";
|