@inline-i18n-multi/cli 0.6.0 → 0.8.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/README.md +64 -0
- package/dist/bin.js +383 -34
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +42 -1
- package/dist/index.js +390 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,8 +31,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
coverage: () => coverage,
|
|
34
|
+
extractProjectDictionaryKeys: () => extractProjectDictionaryKeys,
|
|
35
|
+
extractProjectTCalls: () => extractProjectTCalls,
|
|
34
36
|
find: () => find,
|
|
37
|
+
parseDictionaryKeys: () => parseDictionaryKeys,
|
|
35
38
|
parseProject: () => parseProject,
|
|
39
|
+
parseTCalls: () => parseTCalls,
|
|
40
|
+
typegen: () => typegen,
|
|
41
|
+
unused: () => unused,
|
|
36
42
|
validate: () => validate
|
|
37
43
|
});
|
|
38
44
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -79,6 +85,16 @@ function extractVariables(text) {
|
|
|
79
85
|
if (!matches) return [];
|
|
80
86
|
return matches.map((m) => m.slice(1, -1));
|
|
81
87
|
}
|
|
88
|
+
var ICU_TYPE_PATTERN = /\{(\w+),\s*(\w+)/g;
|
|
89
|
+
function extractICUTypes(text) {
|
|
90
|
+
const types = [];
|
|
91
|
+
let match;
|
|
92
|
+
ICU_TYPE_PATTERN.lastIndex = 0;
|
|
93
|
+
while ((match = ICU_TYPE_PATTERN.exec(text)) !== null) {
|
|
94
|
+
types.push({ variable: match[1], type: match[2] });
|
|
95
|
+
}
|
|
96
|
+
return types;
|
|
97
|
+
}
|
|
82
98
|
function parseFile(filePath) {
|
|
83
99
|
const entries = [];
|
|
84
100
|
const code = fs.readFileSync(filePath, "utf-8");
|
|
@@ -114,6 +130,11 @@ function parseFile(filePath) {
|
|
|
114
130
|
if (prop.type === "ObjectProperty" && prop.key.type === "Identifier" && prop.value.type === "StringLiteral") {
|
|
115
131
|
entry.translations[prop.key.name] = prop.value.value;
|
|
116
132
|
entry.variables.push(...extractVariables(prop.value.value));
|
|
133
|
+
const types = extractICUTypes(prop.value.value);
|
|
134
|
+
if (types.length > 0) {
|
|
135
|
+
if (!entry.icuTypes) entry.icuTypes = {};
|
|
136
|
+
entry.icuTypes[prop.key.name] = types;
|
|
137
|
+
}
|
|
117
138
|
}
|
|
118
139
|
}
|
|
119
140
|
} else if (args[0]?.type === "StringLiteral" && args[1]?.type === "StringLiteral") {
|
|
@@ -122,6 +143,16 @@ function parseFile(filePath) {
|
|
|
122
143
|
entry.translations[lang2] = args[1].value;
|
|
123
144
|
entry.variables.push(...extractVariables(args[0].value));
|
|
124
145
|
entry.variables.push(...extractVariables(args[1].value));
|
|
146
|
+
const types1 = extractICUTypes(args[0].value);
|
|
147
|
+
if (types1.length > 0) {
|
|
148
|
+
if (!entry.icuTypes) entry.icuTypes = {};
|
|
149
|
+
entry.icuTypes[lang1] = types1;
|
|
150
|
+
}
|
|
151
|
+
const types2 = extractICUTypes(args[1].value);
|
|
152
|
+
if (types2.length > 0) {
|
|
153
|
+
if (!entry.icuTypes) entry.icuTypes = {};
|
|
154
|
+
entry.icuTypes[lang2] = types2;
|
|
155
|
+
}
|
|
125
156
|
}
|
|
126
157
|
entry.variables = [...new Set(entry.variables)];
|
|
127
158
|
if (Object.keys(entry.translations).length > 0) {
|
|
@@ -131,6 +162,125 @@ function parseFile(filePath) {
|
|
|
131
162
|
});
|
|
132
163
|
return entries;
|
|
133
164
|
}
|
|
165
|
+
function flattenObjectKeys(node, prefix = "") {
|
|
166
|
+
const keys = [];
|
|
167
|
+
if (node.type !== "ObjectExpression" || !node.properties) return keys;
|
|
168
|
+
for (const prop of node.properties) {
|
|
169
|
+
if (prop.type !== "ObjectProperty") continue;
|
|
170
|
+
let propName;
|
|
171
|
+
if (prop.key.type === "Identifier") propName = prop.key.name;
|
|
172
|
+
else if (prop.key.type === "StringLiteral") propName = prop.key.value;
|
|
173
|
+
if (!propName) continue;
|
|
174
|
+
const fullKey = prefix ? `${prefix}.${propName}` : propName;
|
|
175
|
+
if (prop.value.type === "StringLiteral") {
|
|
176
|
+
keys.push(fullKey);
|
|
177
|
+
} else if (prop.value.type === "ObjectExpression") {
|
|
178
|
+
keys.push(...flattenObjectKeys(prop.value, fullKey));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return keys;
|
|
182
|
+
}
|
|
183
|
+
function parseDictionaryKeys(filePath) {
|
|
184
|
+
const entries = [];
|
|
185
|
+
const code = fs.readFileSync(filePath, "utf-8");
|
|
186
|
+
let ast;
|
|
187
|
+
try {
|
|
188
|
+
ast = (0, import_parser.parse)(code, {
|
|
189
|
+
sourceType: "module",
|
|
190
|
+
plugins: ["typescript", "jsx"]
|
|
191
|
+
});
|
|
192
|
+
} catch {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
(0, import_traverse.default)(ast, {
|
|
196
|
+
CallExpression(nodePath) {
|
|
197
|
+
const { node } = nodePath;
|
|
198
|
+
const { callee } = node;
|
|
199
|
+
if (callee.type !== "Identifier" || callee.name !== "loadDictionaries") return;
|
|
200
|
+
const args = node.arguments;
|
|
201
|
+
const loc = node.loc;
|
|
202
|
+
if (!loc || !args[0] || args[0].type !== "ObjectExpression") return;
|
|
203
|
+
let namespace = "default";
|
|
204
|
+
if (args[1]?.type === "StringLiteral") {
|
|
205
|
+
namespace = args[1].value;
|
|
206
|
+
}
|
|
207
|
+
const dictObj = args[0];
|
|
208
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
209
|
+
for (const localeProp of dictObj.properties) {
|
|
210
|
+
if (localeProp.type !== "ObjectProperty") continue;
|
|
211
|
+
if (localeProp.value.type !== "ObjectExpression") continue;
|
|
212
|
+
const localeKeys = flattenObjectKeys(localeProp.value);
|
|
213
|
+
for (const key of localeKeys) {
|
|
214
|
+
allKeys.add(key);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (allKeys.size > 0) {
|
|
218
|
+
entries.push({
|
|
219
|
+
file: filePath,
|
|
220
|
+
line: loc.start.line,
|
|
221
|
+
namespace,
|
|
222
|
+
keys: [...allKeys]
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
return entries;
|
|
228
|
+
}
|
|
229
|
+
function parseTCalls(filePath) {
|
|
230
|
+
const entries = [];
|
|
231
|
+
const code = fs.readFileSync(filePath, "utf-8");
|
|
232
|
+
let ast;
|
|
233
|
+
try {
|
|
234
|
+
ast = (0, import_parser.parse)(code, {
|
|
235
|
+
sourceType: "module",
|
|
236
|
+
plugins: ["typescript", "jsx"]
|
|
237
|
+
});
|
|
238
|
+
} catch {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
(0, import_traverse.default)(ast, {
|
|
242
|
+
CallExpression(nodePath) {
|
|
243
|
+
const { node } = nodePath;
|
|
244
|
+
const { callee } = node;
|
|
245
|
+
if (callee.type !== "Identifier" || callee.name !== "t") return;
|
|
246
|
+
const args = node.arguments;
|
|
247
|
+
const loc = node.loc;
|
|
248
|
+
if (!loc || !args[0] || args[0].type !== "StringLiteral") return;
|
|
249
|
+
entries.push({
|
|
250
|
+
file: filePath,
|
|
251
|
+
line: loc.start.line,
|
|
252
|
+
key: args[0].value
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
return entries;
|
|
257
|
+
}
|
|
258
|
+
async function extractProjectDictionaryKeys(options = {}) {
|
|
259
|
+
const {
|
|
260
|
+
cwd = process.cwd(),
|
|
261
|
+
include = ["**/*.{ts,tsx,js,jsx}"],
|
|
262
|
+
exclude = ["**/node_modules/**", "**/dist/**", "**/.next/**"]
|
|
263
|
+
} = options;
|
|
264
|
+
const files = await (0, import_fast_glob.default)(include, { cwd, ignore: exclude, absolute: true });
|
|
265
|
+
const allEntries = [];
|
|
266
|
+
for (const file of files) {
|
|
267
|
+
allEntries.push(...parseDictionaryKeys(file));
|
|
268
|
+
}
|
|
269
|
+
return allEntries;
|
|
270
|
+
}
|
|
271
|
+
async function extractProjectTCalls(options = {}) {
|
|
272
|
+
const {
|
|
273
|
+
cwd = process.cwd(),
|
|
274
|
+
include = ["**/*.{ts,tsx,js,jsx}"],
|
|
275
|
+
exclude = ["**/node_modules/**", "**/dist/**", "**/.next/**"]
|
|
276
|
+
} = options;
|
|
277
|
+
const files = await (0, import_fast_glob.default)(include, { cwd, ignore: exclude, absolute: true });
|
|
278
|
+
const allEntries = [];
|
|
279
|
+
for (const file of files) {
|
|
280
|
+
allEntries.push(...parseTCalls(file));
|
|
281
|
+
}
|
|
282
|
+
return allEntries;
|
|
283
|
+
}
|
|
134
284
|
async function parseProject(options = {}) {
|
|
135
285
|
const {
|
|
136
286
|
cwd = process.cwd(),
|
|
@@ -188,10 +338,131 @@ Searching for: "${query}"
|
|
|
188
338
|
}
|
|
189
339
|
|
|
190
340
|
// src/commands/validate.ts
|
|
341
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
342
|
+
|
|
343
|
+
// src/commands/unused.ts
|
|
191
344
|
var import_chalk2 = __toESM(require("chalk"));
|
|
345
|
+
var PLURAL_SUFFIXES = ["_zero", "_one", "_two", "_few", "_many", "_other"];
|
|
346
|
+
async function unused(options = {}) {
|
|
347
|
+
const { cwd } = options;
|
|
348
|
+
console.log(import_chalk2.default.blue("\nDetecting unused translations...\n"));
|
|
349
|
+
const dictEntries = await extractProjectDictionaryKeys({ cwd });
|
|
350
|
+
const tCalls = await extractProjectTCalls({ cwd });
|
|
351
|
+
const usedKeys = /* @__PURE__ */ new Set();
|
|
352
|
+
for (const call of tCalls) {
|
|
353
|
+
usedKeys.add(call.key);
|
|
354
|
+
if (!call.key.includes(":")) {
|
|
355
|
+
usedKeys.add(call.key);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const unusedKeys = [];
|
|
359
|
+
for (const entry of dictEntries) {
|
|
360
|
+
for (const key of entry.keys) {
|
|
361
|
+
const fullKey = entry.namespace === "default" ? key : `${entry.namespace}:${key}`;
|
|
362
|
+
if (usedKeys.has(fullKey)) continue;
|
|
363
|
+
let isPluralVariant = false;
|
|
364
|
+
for (const suffix of PLURAL_SUFFIXES) {
|
|
365
|
+
if (key.endsWith(suffix)) {
|
|
366
|
+
const baseKey = key.slice(0, -suffix.length);
|
|
367
|
+
const fullBaseKey = entry.namespace === "default" ? baseKey : `${entry.namespace}:${baseKey}`;
|
|
368
|
+
if (usedKeys.has(fullBaseKey)) {
|
|
369
|
+
isPluralVariant = true;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (isPluralVariant) continue;
|
|
375
|
+
unusedKeys.push({
|
|
376
|
+
namespace: entry.namespace,
|
|
377
|
+
key: fullKey,
|
|
378
|
+
definedIn: entry.file,
|
|
379
|
+
line: entry.line
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (unusedKeys.length === 0) {
|
|
384
|
+
const totalKeys = dictEntries.reduce((sum, e) => sum + e.keys.length, 0);
|
|
385
|
+
console.log(import_chalk2.default.green("No unused translations found!\n"));
|
|
386
|
+
console.log(import_chalk2.default.gray(`Checked ${totalKeys} dictionary key(s) against ${tCalls.length} t() call(s)`));
|
|
387
|
+
return { unusedKeys };
|
|
388
|
+
}
|
|
389
|
+
console.log(import_chalk2.default.yellow(`Found ${unusedKeys.length} unused translation key(s):
|
|
390
|
+
`));
|
|
391
|
+
for (const item of unusedKeys) {
|
|
392
|
+
const relativePath = item.definedIn.replace(process.cwd() + "/", "");
|
|
393
|
+
console.log(` ${import_chalk2.default.red("-")} ${import_chalk2.default.cyan(item.key)}`);
|
|
394
|
+
console.log(import_chalk2.default.gray(` defined in ${relativePath}:${item.line}`));
|
|
395
|
+
}
|
|
396
|
+
console.log();
|
|
397
|
+
return { unusedKeys };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/commands/validate.ts
|
|
401
|
+
function checkVariableConsistency(entry) {
|
|
402
|
+
const varsByLocale = [];
|
|
403
|
+
for (const [locale, text] of Object.entries(entry.translations)) {
|
|
404
|
+
const matches = text.match(/\{(\w+)\}/g) || [];
|
|
405
|
+
const varNames = [...new Set(matches.map((v) => v.slice(1, -1)))].sort();
|
|
406
|
+
varsByLocale.push({ locale, vars: varNames });
|
|
407
|
+
}
|
|
408
|
+
if (varsByLocale.length < 2) return null;
|
|
409
|
+
const reference = varsByLocale[0];
|
|
410
|
+
const details = [];
|
|
411
|
+
for (let i = 1; i < varsByLocale.length; i++) {
|
|
412
|
+
const current = varsByLocale[i];
|
|
413
|
+
const refSet = new Set(reference.vars);
|
|
414
|
+
const curSet = new Set(current.vars);
|
|
415
|
+
const onlyInRef = reference.vars.filter((v) => !curSet.has(v));
|
|
416
|
+
const onlyInCur = current.vars.filter((v) => !refSet.has(v));
|
|
417
|
+
if (onlyInRef.length > 0) {
|
|
418
|
+
details.push(
|
|
419
|
+
`${reference.locale} has {${onlyInRef.join("}, {")}} missing in ${current.locale}`
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
if (onlyInCur.length > 0) {
|
|
423
|
+
details.push(
|
|
424
|
+
`${current.locale} has {${onlyInCur.join("}, {")}} missing in ${reference.locale}`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (details.length === 0) return null;
|
|
429
|
+
return {
|
|
430
|
+
type: "variable_mismatch",
|
|
431
|
+
message: "Variable mismatch between translations",
|
|
432
|
+
entries: [entry],
|
|
433
|
+
details
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function checkICUTypeConsistency(entry) {
|
|
437
|
+
if (!entry.icuTypes) return null;
|
|
438
|
+
const locales = Object.keys(entry.icuTypes);
|
|
439
|
+
if (locales.length < 2) return null;
|
|
440
|
+
const details = [];
|
|
441
|
+
const reference = locales[0];
|
|
442
|
+
const refTypes = entry.icuTypes[reference];
|
|
443
|
+
for (let i = 1; i < locales.length; i++) {
|
|
444
|
+
const locale = locales[i];
|
|
445
|
+
const curTypes = entry.icuTypes[locale];
|
|
446
|
+
for (const refType of refTypes) {
|
|
447
|
+
const match = curTypes.find((t) => t.variable === refType.variable);
|
|
448
|
+
if (match && match.type !== refType.type) {
|
|
449
|
+
details.push(
|
|
450
|
+
`{${refType.variable}} is "${refType.type}" in ${reference} but "${match.type}" in ${locale}`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (details.length === 0) return null;
|
|
456
|
+
return {
|
|
457
|
+
type: "icu_type_mismatch",
|
|
458
|
+
message: "ICU type mismatch between translations",
|
|
459
|
+
entries: [entry],
|
|
460
|
+
details
|
|
461
|
+
};
|
|
462
|
+
}
|
|
192
463
|
async function validate(options = {}) {
|
|
193
|
-
const { cwd, locales } = options;
|
|
194
|
-
console.log(
|
|
464
|
+
const { cwd, locales, strict, unused: checkUnused } = options;
|
|
465
|
+
console.log(import_chalk3.default.blue("\nValidating translations...\n"));
|
|
195
466
|
const entries = await parseProject({ cwd });
|
|
196
467
|
const issues = [];
|
|
197
468
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -227,49 +498,62 @@ async function validate(options = {}) {
|
|
|
227
498
|
}
|
|
228
499
|
}
|
|
229
500
|
for (const entry of entries) {
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
issues.push(
|
|
237
|
-
type: "variable_mismatch",
|
|
238
|
-
message: "Variable mismatch between translations",
|
|
239
|
-
entries: [entry]
|
|
240
|
-
});
|
|
501
|
+
const issue = checkVariableConsistency(entry);
|
|
502
|
+
if (issue) issues.push(issue);
|
|
503
|
+
}
|
|
504
|
+
if (strict) {
|
|
505
|
+
for (const entry of entries) {
|
|
506
|
+
const issue = checkICUTypeConsistency(entry);
|
|
507
|
+
if (issue) issues.push(issue);
|
|
241
508
|
}
|
|
242
509
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
510
|
+
let unusedCount = 0;
|
|
511
|
+
if (checkUnused) {
|
|
512
|
+
const result = await unused({ cwd });
|
|
513
|
+
unusedCount = result.unusedKeys.length;
|
|
514
|
+
}
|
|
515
|
+
if (issues.length === 0 && unusedCount === 0) {
|
|
516
|
+
console.log(import_chalk3.default.green("All translations are valid!\n"));
|
|
517
|
+
console.log(import_chalk3.default.gray(`Checked ${entries.length} translation(s)`));
|
|
518
|
+
if (strict) {
|
|
519
|
+
console.log(import_chalk3.default.gray("(strict mode enabled)"));
|
|
520
|
+
}
|
|
521
|
+
if (checkUnused) {
|
|
522
|
+
console.log(import_chalk3.default.gray("(unused key detection enabled)"));
|
|
523
|
+
}
|
|
246
524
|
return;
|
|
247
525
|
}
|
|
248
|
-
console.log(
|
|
526
|
+
console.log(import_chalk3.default.red(`Found ${issues.length} issue(s):
|
|
249
527
|
`));
|
|
250
528
|
for (const issue of issues) {
|
|
251
|
-
|
|
252
|
-
console.log(`${icon} ${import_chalk2.default.yellow(issue.message)}`);
|
|
529
|
+
console.log(` ${import_chalk3.default.yellow(issue.message)}`);
|
|
253
530
|
for (const entry of issue.entries) {
|
|
254
531
|
const relativePath = entry.file.replace(process.cwd() + "/", "");
|
|
255
|
-
console.log(
|
|
532
|
+
console.log(import_chalk3.default.gray(` ${relativePath}:${entry.line}`));
|
|
256
533
|
for (const [locale, text] of Object.entries(entry.translations)) {
|
|
257
|
-
console.log(` ${
|
|
534
|
+
console.log(` ${import_chalk3.default.cyan(locale)}: ${text}`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (issue.details && issue.details.length > 0) {
|
|
538
|
+
for (const detail of issue.details) {
|
|
539
|
+
console.log(import_chalk3.default.gray(` \u2192 ${detail}`));
|
|
258
540
|
}
|
|
259
541
|
}
|
|
260
542
|
console.log();
|
|
261
543
|
}
|
|
262
|
-
|
|
544
|
+
if (issues.length > 0 || unusedCount > 0) {
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
263
547
|
}
|
|
264
548
|
|
|
265
549
|
// src/commands/coverage.ts
|
|
266
|
-
var
|
|
550
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
267
551
|
async function coverage(options) {
|
|
268
552
|
const { cwd, locales } = options;
|
|
269
|
-
console.log(
|
|
553
|
+
console.log(import_chalk4.default.blue("\nAnalyzing translation coverage...\n"));
|
|
270
554
|
const entries = await parseProject({ cwd });
|
|
271
555
|
if (entries.length === 0) {
|
|
272
|
-
console.log(
|
|
556
|
+
console.log(import_chalk4.default.yellow("No translations found."));
|
|
273
557
|
return;
|
|
274
558
|
}
|
|
275
559
|
const coverageData = [];
|
|
@@ -288,20 +572,20 @@ async function coverage(options) {
|
|
|
288
572
|
percentage
|
|
289
573
|
});
|
|
290
574
|
}
|
|
291
|
-
console.log(
|
|
575
|
+
console.log(import_chalk4.default.bold("Translation Coverage:\n"));
|
|
292
576
|
const maxLocaleLen = Math.max(...locales.map((l) => l.length), 6);
|
|
293
577
|
console.log(
|
|
294
|
-
|
|
578
|
+
import_chalk4.default.gray(
|
|
295
579
|
`${"Locale".padEnd(maxLocaleLen)} ${"Coverage".padStart(10)} ${"Translated".padStart(12)}`
|
|
296
580
|
)
|
|
297
581
|
);
|
|
298
|
-
console.log(
|
|
582
|
+
console.log(import_chalk4.default.gray("\u2500".repeat(maxLocaleLen + 26)));
|
|
299
583
|
for (const data of coverageData) {
|
|
300
|
-
const color = data.percentage === 100 ?
|
|
584
|
+
const color = data.percentage === 100 ? import_chalk4.default.green : data.percentage >= 80 ? import_chalk4.default.yellow : import_chalk4.default.red;
|
|
301
585
|
const bar = createProgressBar(data.percentage, 10);
|
|
302
586
|
const percentStr = `${data.percentage}%`.padStart(4);
|
|
303
587
|
console.log(
|
|
304
|
-
`${data.locale.padEnd(maxLocaleLen)} ${color(bar)} ${color(percentStr)} ${
|
|
588
|
+
`${data.locale.padEnd(maxLocaleLen)} ${color(bar)} ${color(percentStr)} ${import_chalk4.default.gray(`${data.translated}/${data.total}`)}`
|
|
305
589
|
);
|
|
306
590
|
}
|
|
307
591
|
console.log();
|
|
@@ -309,9 +593,9 @@ async function coverage(options) {
|
|
|
309
593
|
const partiallyCovered = coverageData.filter((d) => d.percentage > 0 && d.percentage < 100).length;
|
|
310
594
|
const notCovered = coverageData.filter((d) => d.percentage === 0).length;
|
|
311
595
|
if (fullyCovered === locales.length) {
|
|
312
|
-
console.log(
|
|
596
|
+
console.log(import_chalk4.default.green("All locales are fully translated!"));
|
|
313
597
|
} else {
|
|
314
|
-
console.log(
|
|
598
|
+
console.log(import_chalk4.default.gray(`Full: ${fullyCovered}, Partial: ${partiallyCovered}, Empty: ${notCovered}`));
|
|
315
599
|
}
|
|
316
600
|
}
|
|
317
601
|
function createProgressBar(percentage, width) {
|
|
@@ -319,11 +603,85 @@ function createProgressBar(percentage, width) {
|
|
|
319
603
|
const empty = width - filled;
|
|
320
604
|
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
321
605
|
}
|
|
606
|
+
|
|
607
|
+
// src/commands/typegen.ts
|
|
608
|
+
var fs2 = __toESM(require("fs"));
|
|
609
|
+
var path = __toESM(require("path"));
|
|
610
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
611
|
+
var PLURAL_SUFFIXES2 = ["_zero", "_one", "_two", "_few", "_many", "_other"];
|
|
612
|
+
async function typegen(options = {}) {
|
|
613
|
+
const { cwd, output = "src/i18n.d.ts" } = options;
|
|
614
|
+
console.log(import_chalk5.default.blue("\nGenerating TypeScript types...\n"));
|
|
615
|
+
const dictEntries = await extractProjectDictionaryKeys({ cwd });
|
|
616
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
617
|
+
for (const entry of dictEntries) {
|
|
618
|
+
for (const key of entry.keys) {
|
|
619
|
+
const fullKey = entry.namespace === "default" ? key : `${entry.namespace}:${key}`;
|
|
620
|
+
allKeys.add(fullKey);
|
|
621
|
+
for (const suffix of PLURAL_SUFFIXES2) {
|
|
622
|
+
if (key.endsWith(suffix)) {
|
|
623
|
+
const baseKey = key.slice(0, -suffix.length);
|
|
624
|
+
const fullBaseKey = entry.namespace === "default" ? baseKey : `${entry.namespace}:${baseKey}`;
|
|
625
|
+
allKeys.add(fullBaseKey);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (allKeys.size === 0) {
|
|
631
|
+
console.log(import_chalk5.default.yellow("No dictionary keys found. Make sure loadDictionaries() calls are present."));
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
const sortedKeys = [...allKeys].sort();
|
|
635
|
+
const keyUnion = sortedKeys.map((k) => ` | '${k}'`).join("\n");
|
|
636
|
+
const content = `// Auto-generated by inline-i18n typegen
|
|
637
|
+
// Do not edit manually. Re-run: npx inline-i18n typegen
|
|
638
|
+
|
|
639
|
+
import 'inline-i18n-multi'
|
|
640
|
+
|
|
641
|
+
declare module 'inline-i18n-multi' {
|
|
642
|
+
export type TranslationKey =
|
|
643
|
+
${keyUnion}
|
|
644
|
+
|
|
645
|
+
export function t(
|
|
646
|
+
key: TranslationKey,
|
|
647
|
+
vars?: TranslationVars,
|
|
648
|
+
locale?: string
|
|
649
|
+
): string
|
|
650
|
+
|
|
651
|
+
export function hasTranslation(
|
|
652
|
+
key: TranslationKey,
|
|
653
|
+
locale?: string
|
|
654
|
+
): boolean
|
|
655
|
+
}
|
|
656
|
+
`;
|
|
657
|
+
const outputPath = path.resolve(cwd || process.cwd(), output);
|
|
658
|
+
const outputDir = path.dirname(outputPath);
|
|
659
|
+
if (!fs2.existsSync(outputDir)) {
|
|
660
|
+
fs2.mkdirSync(outputDir, { recursive: true });
|
|
661
|
+
}
|
|
662
|
+
fs2.writeFileSync(outputPath, content, "utf-8");
|
|
663
|
+
console.log(import_chalk5.default.green(`Generated ${sortedKeys.length} translation key types`));
|
|
664
|
+
console.log(import_chalk5.default.gray(`Output: ${outputPath}
|
|
665
|
+
`));
|
|
666
|
+
const sample = sortedKeys.slice(0, 5);
|
|
667
|
+
for (const key of sample) {
|
|
668
|
+
console.log(` ${import_chalk5.default.cyan(key)}`);
|
|
669
|
+
}
|
|
670
|
+
if (sortedKeys.length > 5) {
|
|
671
|
+
console.log(import_chalk5.default.gray(` ... and ${sortedKeys.length - 5} more`));
|
|
672
|
+
}
|
|
673
|
+
}
|
|
322
674
|
// Annotate the CommonJS export names for ESM import in node:
|
|
323
675
|
0 && (module.exports = {
|
|
324
676
|
coverage,
|
|
677
|
+
extractProjectDictionaryKeys,
|
|
678
|
+
extractProjectTCalls,
|
|
325
679
|
find,
|
|
680
|
+
parseDictionaryKeys,
|
|
326
681
|
parseProject,
|
|
682
|
+
parseTCalls,
|
|
683
|
+
typegen,
|
|
684
|
+
unused,
|
|
327
685
|
validate
|
|
328
686
|
});
|
|
329
687
|
//# sourceMappingURL=index.js.map
|