@inline-i18n-multi/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +327 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +329 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
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/dist/bin.d.ts
ADDED
package/dist/bin.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/bin.ts
|
|
27
|
+
var import_commander = require("commander");
|
|
28
|
+
|
|
29
|
+
// src/commands/find.ts
|
|
30
|
+
var import_chalk = __toESM(require("chalk"));
|
|
31
|
+
|
|
32
|
+
// src/parser.ts
|
|
33
|
+
var import_parser = require("@babel/parser");
|
|
34
|
+
var import_traverse = __toESM(require("@babel/traverse"));
|
|
35
|
+
var fs = __toESM(require("fs"));
|
|
36
|
+
var import_fast_glob = __toESM(require("fast-glob"));
|
|
37
|
+
var IT_FUNCTION_NAMES = [
|
|
38
|
+
"it",
|
|
39
|
+
"it_ja",
|
|
40
|
+
"it_zh",
|
|
41
|
+
"it_es",
|
|
42
|
+
"it_fr",
|
|
43
|
+
"it_de",
|
|
44
|
+
"en_ja",
|
|
45
|
+
"en_zh",
|
|
46
|
+
"en_es",
|
|
47
|
+
"en_fr",
|
|
48
|
+
"en_de",
|
|
49
|
+
"ja_zh",
|
|
50
|
+
"ja_es",
|
|
51
|
+
"zh_es"
|
|
52
|
+
];
|
|
53
|
+
var PAIR_MAPPING = {
|
|
54
|
+
it: ["ko", "en"],
|
|
55
|
+
it_ja: ["ko", "ja"],
|
|
56
|
+
it_zh: ["ko", "zh"],
|
|
57
|
+
it_es: ["ko", "es"],
|
|
58
|
+
it_fr: ["ko", "fr"],
|
|
59
|
+
it_de: ["ko", "de"],
|
|
60
|
+
en_ja: ["en", "ja"],
|
|
61
|
+
en_zh: ["en", "zh"],
|
|
62
|
+
en_es: ["en", "es"],
|
|
63
|
+
en_fr: ["en", "fr"],
|
|
64
|
+
en_de: ["en", "de"],
|
|
65
|
+
ja_zh: ["ja", "zh"],
|
|
66
|
+
ja_es: ["ja", "es"],
|
|
67
|
+
zh_es: ["zh", "es"]
|
|
68
|
+
};
|
|
69
|
+
function extractVariables(text) {
|
|
70
|
+
const matches = text.match(/\{(\w+)\}/g);
|
|
71
|
+
if (!matches) return [];
|
|
72
|
+
return matches.map((m) => m.slice(1, -1));
|
|
73
|
+
}
|
|
74
|
+
function parseFile(filePath) {
|
|
75
|
+
const entries = [];
|
|
76
|
+
const code = fs.readFileSync(filePath, "utf-8");
|
|
77
|
+
let ast;
|
|
78
|
+
try {
|
|
79
|
+
ast = (0, import_parser.parse)(code, {
|
|
80
|
+
sourceType: "module",
|
|
81
|
+
plugins: ["typescript", "jsx"]
|
|
82
|
+
});
|
|
83
|
+
} catch {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
(0, import_traverse.default)(ast, {
|
|
87
|
+
CallExpression(nodePath) {
|
|
88
|
+
const { node } = nodePath;
|
|
89
|
+
const { callee } = node;
|
|
90
|
+
if (callee.type !== "Identifier") return;
|
|
91
|
+
if (!IT_FUNCTION_NAMES.includes(callee.name)) return;
|
|
92
|
+
const funcName = callee.name;
|
|
93
|
+
const args = node.arguments;
|
|
94
|
+
const loc = node.loc;
|
|
95
|
+
if (!loc) return;
|
|
96
|
+
const entry = {
|
|
97
|
+
file: filePath,
|
|
98
|
+
line: loc.start.line,
|
|
99
|
+
column: loc.start.column,
|
|
100
|
+
translations: {},
|
|
101
|
+
variables: []
|
|
102
|
+
};
|
|
103
|
+
if (args[0]?.type === "ObjectExpression") {
|
|
104
|
+
const obj = args[0];
|
|
105
|
+
for (const prop of obj.properties) {
|
|
106
|
+
if (prop.type === "ObjectProperty" && prop.key.type === "Identifier" && prop.value.type === "StringLiteral") {
|
|
107
|
+
entry.translations[prop.key.name] = prop.value.value;
|
|
108
|
+
entry.variables.push(...extractVariables(prop.value.value));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} else if (args[0]?.type === "StringLiteral" && args[1]?.type === "StringLiteral") {
|
|
112
|
+
const [lang1, lang2] = PAIR_MAPPING[funcName] || ["ko", "en"];
|
|
113
|
+
entry.translations[lang1] = args[0].value;
|
|
114
|
+
entry.translations[lang2] = args[1].value;
|
|
115
|
+
entry.variables.push(...extractVariables(args[0].value));
|
|
116
|
+
entry.variables.push(...extractVariables(args[1].value));
|
|
117
|
+
}
|
|
118
|
+
entry.variables = [...new Set(entry.variables)];
|
|
119
|
+
if (Object.keys(entry.translations).length > 0) {
|
|
120
|
+
entries.push(entry);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
return entries;
|
|
125
|
+
}
|
|
126
|
+
async function parseProject(options = {}) {
|
|
127
|
+
const {
|
|
128
|
+
cwd = process.cwd(),
|
|
129
|
+
include = ["**/*.{ts,tsx,js,jsx}"],
|
|
130
|
+
exclude = ["**/node_modules/**", "**/dist/**", "**/.next/**"]
|
|
131
|
+
} = options;
|
|
132
|
+
const files = await (0, import_fast_glob.default)(include, {
|
|
133
|
+
cwd,
|
|
134
|
+
ignore: exclude,
|
|
135
|
+
absolute: true
|
|
136
|
+
});
|
|
137
|
+
const allEntries = [];
|
|
138
|
+
for (const file of files) {
|
|
139
|
+
const entries = parseFile(file);
|
|
140
|
+
allEntries.push(...entries);
|
|
141
|
+
}
|
|
142
|
+
return allEntries;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/commands/find.ts
|
|
146
|
+
async function find(options) {
|
|
147
|
+
const { query, cwd } = options;
|
|
148
|
+
console.log(import_chalk.default.blue(`
|
|
149
|
+
Searching for: "${query}"
|
|
150
|
+
`));
|
|
151
|
+
const entries = await parseProject({ cwd });
|
|
152
|
+
const results = [];
|
|
153
|
+
const lowerQuery = query.toLowerCase();
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
const values = Object.values(entry.translations);
|
|
156
|
+
const matches = values.some((v) => v.toLowerCase().includes(lowerQuery));
|
|
157
|
+
if (matches) {
|
|
158
|
+
results.push(entry);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (results.length === 0) {
|
|
162
|
+
console.log(import_chalk.default.yellow("No results found."));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
console.log(import_chalk.default.green(`Found ${results.length} occurrence(s):
|
|
166
|
+
`));
|
|
167
|
+
for (const result of results) {
|
|
168
|
+
const relativePath = result.file.replace(process.cwd() + "/", "");
|
|
169
|
+
console.log(import_chalk.default.gray(`${relativePath}:${result.line}:${result.column}`));
|
|
170
|
+
for (const [locale, text] of Object.entries(result.translations)) {
|
|
171
|
+
const highlighted = text.replace(
|
|
172
|
+
new RegExp(`(${query})`, "gi"),
|
|
173
|
+
import_chalk.default.yellow("$1")
|
|
174
|
+
);
|
|
175
|
+
console.log(` ${import_chalk.default.cyan(locale)}: ${highlighted}`);
|
|
176
|
+
}
|
|
177
|
+
console.log();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/commands/validate.ts
|
|
182
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
183
|
+
async function validate(options = {}) {
|
|
184
|
+
const { cwd, locales } = options;
|
|
185
|
+
console.log(import_chalk2.default.blue("\nValidating translations...\n"));
|
|
186
|
+
const entries = await parseProject({ cwd });
|
|
187
|
+
const issues = [];
|
|
188
|
+
const groups = /* @__PURE__ */ new Map();
|
|
189
|
+
for (const entry of entries) {
|
|
190
|
+
const key = Object.values(entry.translations)[0] || "";
|
|
191
|
+
if (!groups.has(key)) {
|
|
192
|
+
groups.set(key, []);
|
|
193
|
+
}
|
|
194
|
+
groups.get(key).push(entry);
|
|
195
|
+
}
|
|
196
|
+
for (const [key, group] of groups) {
|
|
197
|
+
if (group.length < 2) continue;
|
|
198
|
+
const translationSets = group.map((e) => JSON.stringify(e.translations));
|
|
199
|
+
const uniqueSets = [...new Set(translationSets)];
|
|
200
|
+
if (uniqueSets.length > 1) {
|
|
201
|
+
issues.push({
|
|
202
|
+
type: "inconsistent",
|
|
203
|
+
message: `Inconsistent translations for "${key}"`,
|
|
204
|
+
entries: group
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (locales && locales.length > 0) {
|
|
209
|
+
for (const entry of entries) {
|
|
210
|
+
const missing = locales.filter((l) => !entry.translations[l]);
|
|
211
|
+
if (missing.length > 0) {
|
|
212
|
+
issues.push({
|
|
213
|
+
type: "missing",
|
|
214
|
+
message: `Missing locales: ${missing.join(", ")}`,
|
|
215
|
+
entries: [entry]
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (const entry of entries) {
|
|
221
|
+
const variableSets = Object.entries(entry.translations).map(([locale, text]) => {
|
|
222
|
+
const vars = text.match(/\{(\w+)\}/g) || [];
|
|
223
|
+
return { locale, vars: vars.sort().join(",") };
|
|
224
|
+
});
|
|
225
|
+
const uniqueVarSets = [...new Set(variableSets.map((v) => v.vars))];
|
|
226
|
+
if (uniqueVarSets.length > 1) {
|
|
227
|
+
issues.push({
|
|
228
|
+
type: "variable_mismatch",
|
|
229
|
+
message: "Variable mismatch between translations",
|
|
230
|
+
entries: [entry]
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (issues.length === 0) {
|
|
235
|
+
console.log(import_chalk2.default.green("\u2705 All translations are valid!\n"));
|
|
236
|
+
console.log(import_chalk2.default.gray(`Checked ${entries.length} translation(s)`));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
console.log(import_chalk2.default.red(`\u274C Found ${issues.length} issue(s):
|
|
240
|
+
`));
|
|
241
|
+
for (const issue of issues) {
|
|
242
|
+
const icon = issue.type === "inconsistent" ? "\u26A0\uFE0F" : issue.type === "missing" ? "\u{1F4ED}" : "\u{1F500}";
|
|
243
|
+
console.log(`${icon} ${import_chalk2.default.yellow(issue.message)}`);
|
|
244
|
+
for (const entry of issue.entries) {
|
|
245
|
+
const relativePath = entry.file.replace(process.cwd() + "/", "");
|
|
246
|
+
console.log(import_chalk2.default.gray(` ${relativePath}:${entry.line}`));
|
|
247
|
+
for (const [locale, text] of Object.entries(entry.translations)) {
|
|
248
|
+
console.log(` ${import_chalk2.default.cyan(locale)}: ${text}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
console.log();
|
|
252
|
+
}
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/commands/coverage.ts
|
|
257
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
258
|
+
async function coverage(options) {
|
|
259
|
+
const { cwd, locales } = options;
|
|
260
|
+
console.log(import_chalk3.default.blue("\nAnalyzing translation coverage...\n"));
|
|
261
|
+
const entries = await parseProject({ cwd });
|
|
262
|
+
if (entries.length === 0) {
|
|
263
|
+
console.log(import_chalk3.default.yellow("No translations found."));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const coverageData = [];
|
|
267
|
+
for (const locale of locales) {
|
|
268
|
+
let translated = 0;
|
|
269
|
+
for (const entry of entries) {
|
|
270
|
+
if (entry.translations[locale]) {
|
|
271
|
+
translated++;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const percentage = Math.round(translated / entries.length * 100);
|
|
275
|
+
coverageData.push({
|
|
276
|
+
locale,
|
|
277
|
+
total: entries.length,
|
|
278
|
+
translated,
|
|
279
|
+
percentage
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
console.log(import_chalk3.default.bold("Translation Coverage:\n"));
|
|
283
|
+
const maxLocaleLen = Math.max(...locales.map((l) => l.length), 6);
|
|
284
|
+
console.log(
|
|
285
|
+
import_chalk3.default.gray(
|
|
286
|
+
`${"Locale".padEnd(maxLocaleLen)} ${"Coverage".padStart(10)} ${"Translated".padStart(12)}`
|
|
287
|
+
)
|
|
288
|
+
);
|
|
289
|
+
console.log(import_chalk3.default.gray("\u2500".repeat(maxLocaleLen + 26)));
|
|
290
|
+
for (const data of coverageData) {
|
|
291
|
+
const color = data.percentage === 100 ? import_chalk3.default.green : data.percentage >= 80 ? import_chalk3.default.yellow : import_chalk3.default.red;
|
|
292
|
+
const bar = createProgressBar(data.percentage, 10);
|
|
293
|
+
const percentStr = `${data.percentage}%`.padStart(4);
|
|
294
|
+
console.log(
|
|
295
|
+
`${data.locale.padEnd(maxLocaleLen)} ${color(bar)} ${color(percentStr)} ${import_chalk3.default.gray(`${data.translated}/${data.total}`)}`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
console.log();
|
|
299
|
+
const fullyCovered = coverageData.filter((d) => d.percentage === 100).length;
|
|
300
|
+
const partiallyCovered = coverageData.filter((d) => d.percentage > 0 && d.percentage < 100).length;
|
|
301
|
+
const notCovered = coverageData.filter((d) => d.percentage === 0).length;
|
|
302
|
+
if (fullyCovered === locales.length) {
|
|
303
|
+
console.log(import_chalk3.default.green("\u2705 All locales are fully translated!"));
|
|
304
|
+
} else {
|
|
305
|
+
console.log(import_chalk3.default.gray(`Full: ${fullyCovered}, Partial: ${partiallyCovered}, Empty: ${notCovered}`));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function createProgressBar(percentage, width) {
|
|
309
|
+
const filled = Math.round(percentage / 100 * width);
|
|
310
|
+
const empty = width - filled;
|
|
311
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/bin.ts
|
|
315
|
+
var program = new import_commander.Command();
|
|
316
|
+
program.name("inline-i18n").description("CLI tools for inline-i18n-multi").version("0.1.0");
|
|
317
|
+
program.command("find <query>").description("Find translations containing the query text").option("-c, --cwd <path>", "Working directory").action(async (query, options) => {
|
|
318
|
+
await find({ query, cwd: options.cwd });
|
|
319
|
+
});
|
|
320
|
+
program.command("validate").description("Validate translation consistency").option("-c, --cwd <path>", "Working directory").option("-l, --locales <locales...>", "Required locales to check").action(async (options) => {
|
|
321
|
+
await validate(options);
|
|
322
|
+
});
|
|
323
|
+
program.command("coverage").description("Show translation coverage by locale").option("-c, --cwd <path>", "Working directory").option("-l, --locales <locales...>", "Locales to check", ["ko", "en"]).action(async (options) => {
|
|
324
|
+
await coverage(options);
|
|
325
|
+
});
|
|
326
|
+
program.parse();
|
|
327
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bin.ts","../src/commands/find.ts","../src/parser.ts","../src/commands/validate.ts","../src/commands/coverage.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { find } from './commands/find'\nimport { validate } from './commands/validate'\nimport { coverage } from './commands/coverage'\n\nconst program = new Command()\n\nprogram\n .name('inline-i18n')\n .description('CLI tools for inline-i18n-multi')\n .version('0.1.0')\n\nprogram\n .command('find <query>')\n .description('Find translations containing the query text')\n .option('-c, --cwd <path>', 'Working directory')\n .action(async (query: string, options: { cwd?: string }) => {\n await find({ query, cwd: options.cwd })\n })\n\nprogram\n .command('validate')\n .description('Validate translation consistency')\n .option('-c, --cwd <path>', 'Working directory')\n .option('-l, --locales <locales...>', 'Required locales to check')\n .action(async (options: { cwd?: string; locales?: string[] }) => {\n await validate(options)\n })\n\nprogram\n .command('coverage')\n .description('Show translation coverage by locale')\n .option('-c, --cwd <path>', 'Working directory')\n .option('-l, --locales <locales...>', 'Locales to check', ['ko', 'en'])\n .action(async (options: { cwd?: string; locales: string[] }) => {\n await coverage(options)\n })\n\nprogram.parse()\n","import chalk from 'chalk'\nimport { parseProject, type TranslationEntry } from '../parser'\n\ninterface FindOptions {\n query: string\n cwd?: string\n}\n\nexport async function find(options: FindOptions): Promise<void> {\n const { query, cwd } = options\n\n console.log(chalk.blue(`\\nSearching for: \"${query}\"\\n`))\n\n const entries = await parseProject({ cwd })\n const results: TranslationEntry[] = []\n\n const lowerQuery = query.toLowerCase()\n\n for (const entry of entries) {\n const values = Object.values(entry.translations)\n const matches = values.some((v) => v.toLowerCase().includes(lowerQuery))\n\n if (matches) {\n results.push(entry)\n }\n }\n\n if (results.length === 0) {\n console.log(chalk.yellow('No results found.'))\n return\n }\n\n console.log(chalk.green(`Found ${results.length} occurrence(s):\\n`))\n\n for (const result of results) {\n const relativePath = result.file.replace(process.cwd() + '/', '')\n console.log(chalk.gray(`${relativePath}:${result.line}:${result.column}`))\n\n for (const [locale, text] of Object.entries(result.translations)) {\n const highlighted = text.replace(\n new RegExp(`(${query})`, 'gi'),\n chalk.yellow('$1')\n )\n console.log(` ${chalk.cyan(locale)}: ${highlighted}`)\n }\n console.log()\n }\n}\n","import { parse } from '@babel/parser'\nimport traverse from '@babel/traverse'\nimport * as fs from 'fs'\nimport fg from 'fast-glob'\n\nexport interface TranslationEntry {\n file: string\n line: number\n column: number\n translations: Record<string, string>\n variables: string[]\n}\n\ninterface ParseOptions {\n cwd?: string\n include?: string[]\n exclude?: string[]\n}\n\nconst IT_FUNCTION_NAMES = [\n 'it',\n 'it_ja', 'it_zh', 'it_es', 'it_fr', 'it_de',\n 'en_ja', 'en_zh', 'en_es', 'en_fr', 'en_de',\n 'ja_zh', 'ja_es', 'zh_es',\n]\n\nconst PAIR_MAPPING: Record<string, [string, string]> = {\n it: ['ko', 'en'],\n it_ja: ['ko', 'ja'],\n it_zh: ['ko', 'zh'],\n it_es: ['ko', 'es'],\n it_fr: ['ko', 'fr'],\n it_de: ['ko', 'de'],\n en_ja: ['en', 'ja'],\n en_zh: ['en', 'zh'],\n en_es: ['en', 'es'],\n en_fr: ['en', 'fr'],\n en_de: ['en', 'de'],\n ja_zh: ['ja', 'zh'],\n ja_es: ['ja', 'es'],\n zh_es: ['zh', 'es'],\n}\n\nfunction extractVariables(text: string): string[] {\n const matches = text.match(/\\{(\\w+)\\}/g)\n if (!matches) return []\n return matches.map((m) => m.slice(1, -1))\n}\n\nfunction parseFile(filePath: string): TranslationEntry[] {\n const entries: TranslationEntry[] = []\n const code = fs.readFileSync(filePath, 'utf-8')\n\n let ast\n try {\n ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n })\n } catch {\n return []\n }\n\n traverse(ast, {\n CallExpression(nodePath) {\n const { node } = nodePath\n const { callee } = node\n\n if (callee.type !== 'Identifier') return\n if (!IT_FUNCTION_NAMES.includes(callee.name)) return\n\n const funcName = callee.name\n const args = node.arguments\n const loc = node.loc\n\n if (!loc) return\n\n const entry: TranslationEntry = {\n file: filePath,\n line: loc.start.line,\n column: loc.start.column,\n translations: {},\n variables: [],\n }\n\n if (args[0]?.type === 'ObjectExpression') {\n const obj = args[0]\n for (const prop of obj.properties) {\n if (\n prop.type === 'ObjectProperty' &&\n prop.key.type === 'Identifier' &&\n prop.value.type === 'StringLiteral'\n ) {\n entry.translations[prop.key.name] = prop.value.value\n entry.variables.push(...extractVariables(prop.value.value))\n }\n }\n } else if (\n args[0]?.type === 'StringLiteral' &&\n args[1]?.type === 'StringLiteral'\n ) {\n const [lang1, lang2] = PAIR_MAPPING[funcName] || ['ko', 'en']\n entry.translations[lang1] = args[0].value\n entry.translations[lang2] = args[1].value\n entry.variables.push(...extractVariables(args[0].value))\n entry.variables.push(...extractVariables(args[1].value))\n }\n\n entry.variables = [...new Set(entry.variables)]\n\n if (Object.keys(entry.translations).length > 0) {\n entries.push(entry)\n }\n },\n })\n\n return entries\n}\n\nexport async function parseProject(options: ParseOptions = {}): Promise<TranslationEntry[]> {\n const {\n cwd = process.cwd(),\n include = ['**/*.{ts,tsx,js,jsx}'],\n exclude = ['**/node_modules/**', '**/dist/**', '**/.next/**'],\n } = options\n\n const files = await fg(include, {\n cwd,\n ignore: exclude,\n absolute: true,\n })\n\n const allEntries: TranslationEntry[] = []\n\n for (const file of files) {\n const entries = parseFile(file)\n allEntries.push(...entries)\n }\n\n return allEntries\n}\n","import chalk from 'chalk'\nimport { parseProject, type TranslationEntry } from '../parser'\n\ninterface ValidateOptions {\n cwd?: string\n locales?: string[]\n}\n\ninterface Issue {\n type: 'inconsistent' | 'missing' | 'variable_mismatch'\n message: string\n entries: TranslationEntry[]\n}\n\nexport async function validate(options: ValidateOptions = {}): Promise<void> {\n const { cwd, locales } = options\n\n console.log(chalk.blue('\\nValidating translations...\\n'))\n\n const entries = await parseProject({ cwd })\n const issues: Issue[] = []\n\n // group by first language text (usually ko)\n const groups = new Map<string, TranslationEntry[]>()\n\n for (const entry of entries) {\n const key = Object.values(entry.translations)[0] || ''\n if (!groups.has(key)) {\n groups.set(key, [])\n }\n groups.get(key)!.push(entry)\n }\n\n // check for inconsistent translations\n for (const [key, group] of groups) {\n if (group.length < 2) continue\n\n const translationSets = group.map((e) => JSON.stringify(e.translations))\n const uniqueSets = [...new Set(translationSets)]\n\n if (uniqueSets.length > 1) {\n issues.push({\n type: 'inconsistent',\n message: `Inconsistent translations for \"${key}\"`,\n entries: group,\n })\n }\n }\n\n // check for missing locales\n if (locales && locales.length > 0) {\n for (const entry of entries) {\n const missing = locales.filter((l) => !entry.translations[l])\n\n if (missing.length > 0) {\n issues.push({\n type: 'missing',\n message: `Missing locales: ${missing.join(', ')}`,\n entries: [entry],\n })\n }\n }\n }\n\n // check for variable mismatches\n for (const entry of entries) {\n const variableSets = Object.entries(entry.translations).map(([locale, text]) => {\n const vars = text.match(/\\{(\\w+)\\}/g) || []\n return { locale, vars: vars.sort().join(',') }\n })\n\n const uniqueVarSets = [...new Set(variableSets.map((v) => v.vars))]\n\n if (uniqueVarSets.length > 1) {\n issues.push({\n type: 'variable_mismatch',\n message: 'Variable mismatch between translations',\n entries: [entry],\n })\n }\n }\n\n // print results\n if (issues.length === 0) {\n console.log(chalk.green('✅ All translations are valid!\\n'))\n console.log(chalk.gray(`Checked ${entries.length} translation(s)`))\n return\n }\n\n console.log(chalk.red(`❌ Found ${issues.length} issue(s):\\n`))\n\n for (const issue of issues) {\n const icon =\n issue.type === 'inconsistent' ? '⚠️' :\n issue.type === 'missing' ? '📭' : '🔀'\n\n console.log(`${icon} ${chalk.yellow(issue.message)}`)\n\n for (const entry of issue.entries) {\n const relativePath = entry.file.replace(process.cwd() + '/', '')\n console.log(chalk.gray(` ${relativePath}:${entry.line}`))\n\n for (const [locale, text] of Object.entries(entry.translations)) {\n console.log(` ${chalk.cyan(locale)}: ${text}`)\n }\n }\n console.log()\n }\n\n process.exit(1)\n}\n","import chalk from 'chalk'\nimport { parseProject } from '../parser'\n\ninterface CoverageOptions {\n cwd?: string\n locales: string[]\n}\n\ninterface LocaleCoverage {\n locale: string\n total: number\n translated: number\n percentage: number\n}\n\nexport async function coverage(options: CoverageOptions): Promise<void> {\n const { cwd, locales } = options\n\n console.log(chalk.blue('\\nAnalyzing translation coverage...\\n'))\n\n const entries = await parseProject({ cwd })\n\n if (entries.length === 0) {\n console.log(chalk.yellow('No translations found.'))\n return\n }\n\n const coverageData: LocaleCoverage[] = []\n\n for (const locale of locales) {\n let translated = 0\n\n for (const entry of entries) {\n if (entry.translations[locale]) {\n translated++\n }\n }\n\n const percentage = Math.round((translated / entries.length) * 100)\n\n coverageData.push({\n locale,\n total: entries.length,\n translated,\n percentage,\n })\n }\n\n // print coverage table\n console.log(chalk.bold('Translation Coverage:\\n'))\n\n const maxLocaleLen = Math.max(...locales.map((l) => l.length), 6)\n\n console.log(\n chalk.gray(\n `${'Locale'.padEnd(maxLocaleLen)} ${'Coverage'.padStart(10)} ${'Translated'.padStart(12)}`\n )\n )\n console.log(chalk.gray('─'.repeat(maxLocaleLen + 26)))\n\n for (const data of coverageData) {\n const color =\n data.percentage === 100 ? chalk.green :\n data.percentage >= 80 ? chalk.yellow : chalk.red\n\n const bar = createProgressBar(data.percentage, 10)\n const percentStr = `${data.percentage}%`.padStart(4)\n\n console.log(\n `${data.locale.padEnd(maxLocaleLen)} ${color(bar)} ${color(percentStr)} ${chalk.gray(`${data.translated}/${data.total}`)}`\n )\n }\n\n console.log()\n\n // summary\n const fullyCovered = coverageData.filter((d) => d.percentage === 100).length\n const partiallyCovered = coverageData.filter((d) => d.percentage > 0 && d.percentage < 100).length\n const notCovered = coverageData.filter((d) => d.percentage === 0).length\n\n if (fullyCovered === locales.length) {\n console.log(chalk.green('✅ All locales are fully translated!'))\n } else {\n console.log(chalk.gray(`Full: ${fullyCovered}, Partial: ${partiallyCovered}, Empty: ${notCovered}`))\n }\n}\n\nfunction createProgressBar(percentage: number, width: number): string {\n const filled = Math.round((percentage / 100) * width)\n const empty = width - filled\n\n return '█'.repeat(filled) + '░'.repeat(empty)\n}\n"],"mappings":"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","names":["traverse","fg","chalk","import_chalk","chalk","import_chalk","chalk"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
interface TranslationEntry {
|
|
2
|
+
file: string;
|
|
3
|
+
line: number;
|
|
4
|
+
column: number;
|
|
5
|
+
translations: Record<string, string>;
|
|
6
|
+
variables: string[];
|
|
7
|
+
}
|
|
8
|
+
interface ParseOptions {
|
|
9
|
+
cwd?: string;
|
|
10
|
+
include?: string[];
|
|
11
|
+
exclude?: string[];
|
|
12
|
+
}
|
|
13
|
+
declare function parseProject(options?: ParseOptions): Promise<TranslationEntry[]>;
|
|
14
|
+
|
|
15
|
+
interface FindOptions {
|
|
16
|
+
query: string;
|
|
17
|
+
cwd?: string;
|
|
18
|
+
}
|
|
19
|
+
declare function find(options: FindOptions): Promise<void>;
|
|
20
|
+
|
|
21
|
+
interface ValidateOptions {
|
|
22
|
+
cwd?: string;
|
|
23
|
+
locales?: string[];
|
|
24
|
+
}
|
|
25
|
+
declare function validate(options?: ValidateOptions): Promise<void>;
|
|
26
|
+
|
|
27
|
+
interface CoverageOptions {
|
|
28
|
+
cwd?: string;
|
|
29
|
+
locales: string[];
|
|
30
|
+
}
|
|
31
|
+
declare function coverage(options: CoverageOptions): Promise<void>;
|
|
32
|
+
|
|
33
|
+
export { type TranslationEntry, coverage, find, parseProject, validate };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
coverage: () => coverage,
|
|
34
|
+
find: () => find,
|
|
35
|
+
parseProject: () => parseProject,
|
|
36
|
+
validate: () => validate
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(index_exports);
|
|
39
|
+
|
|
40
|
+
// src/parser.ts
|
|
41
|
+
var import_parser = require("@babel/parser");
|
|
42
|
+
var import_traverse = __toESM(require("@babel/traverse"));
|
|
43
|
+
var fs = __toESM(require("fs"));
|
|
44
|
+
var import_fast_glob = __toESM(require("fast-glob"));
|
|
45
|
+
var IT_FUNCTION_NAMES = [
|
|
46
|
+
"it",
|
|
47
|
+
"it_ja",
|
|
48
|
+
"it_zh",
|
|
49
|
+
"it_es",
|
|
50
|
+
"it_fr",
|
|
51
|
+
"it_de",
|
|
52
|
+
"en_ja",
|
|
53
|
+
"en_zh",
|
|
54
|
+
"en_es",
|
|
55
|
+
"en_fr",
|
|
56
|
+
"en_de",
|
|
57
|
+
"ja_zh",
|
|
58
|
+
"ja_es",
|
|
59
|
+
"zh_es"
|
|
60
|
+
];
|
|
61
|
+
var PAIR_MAPPING = {
|
|
62
|
+
it: ["ko", "en"],
|
|
63
|
+
it_ja: ["ko", "ja"],
|
|
64
|
+
it_zh: ["ko", "zh"],
|
|
65
|
+
it_es: ["ko", "es"],
|
|
66
|
+
it_fr: ["ko", "fr"],
|
|
67
|
+
it_de: ["ko", "de"],
|
|
68
|
+
en_ja: ["en", "ja"],
|
|
69
|
+
en_zh: ["en", "zh"],
|
|
70
|
+
en_es: ["en", "es"],
|
|
71
|
+
en_fr: ["en", "fr"],
|
|
72
|
+
en_de: ["en", "de"],
|
|
73
|
+
ja_zh: ["ja", "zh"],
|
|
74
|
+
ja_es: ["ja", "es"],
|
|
75
|
+
zh_es: ["zh", "es"]
|
|
76
|
+
};
|
|
77
|
+
function extractVariables(text) {
|
|
78
|
+
const matches = text.match(/\{(\w+)\}/g);
|
|
79
|
+
if (!matches) return [];
|
|
80
|
+
return matches.map((m) => m.slice(1, -1));
|
|
81
|
+
}
|
|
82
|
+
function parseFile(filePath) {
|
|
83
|
+
const entries = [];
|
|
84
|
+
const code = fs.readFileSync(filePath, "utf-8");
|
|
85
|
+
let ast;
|
|
86
|
+
try {
|
|
87
|
+
ast = (0, import_parser.parse)(code, {
|
|
88
|
+
sourceType: "module",
|
|
89
|
+
plugins: ["typescript", "jsx"]
|
|
90
|
+
});
|
|
91
|
+
} catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
(0, import_traverse.default)(ast, {
|
|
95
|
+
CallExpression(nodePath) {
|
|
96
|
+
const { node } = nodePath;
|
|
97
|
+
const { callee } = node;
|
|
98
|
+
if (callee.type !== "Identifier") return;
|
|
99
|
+
if (!IT_FUNCTION_NAMES.includes(callee.name)) return;
|
|
100
|
+
const funcName = callee.name;
|
|
101
|
+
const args = node.arguments;
|
|
102
|
+
const loc = node.loc;
|
|
103
|
+
if (!loc) return;
|
|
104
|
+
const entry = {
|
|
105
|
+
file: filePath,
|
|
106
|
+
line: loc.start.line,
|
|
107
|
+
column: loc.start.column,
|
|
108
|
+
translations: {},
|
|
109
|
+
variables: []
|
|
110
|
+
};
|
|
111
|
+
if (args[0]?.type === "ObjectExpression") {
|
|
112
|
+
const obj = args[0];
|
|
113
|
+
for (const prop of obj.properties) {
|
|
114
|
+
if (prop.type === "ObjectProperty" && prop.key.type === "Identifier" && prop.value.type === "StringLiteral") {
|
|
115
|
+
entry.translations[prop.key.name] = prop.value.value;
|
|
116
|
+
entry.variables.push(...extractVariables(prop.value.value));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} else if (args[0]?.type === "StringLiteral" && args[1]?.type === "StringLiteral") {
|
|
120
|
+
const [lang1, lang2] = PAIR_MAPPING[funcName] || ["ko", "en"];
|
|
121
|
+
entry.translations[lang1] = args[0].value;
|
|
122
|
+
entry.translations[lang2] = args[1].value;
|
|
123
|
+
entry.variables.push(...extractVariables(args[0].value));
|
|
124
|
+
entry.variables.push(...extractVariables(args[1].value));
|
|
125
|
+
}
|
|
126
|
+
entry.variables = [...new Set(entry.variables)];
|
|
127
|
+
if (Object.keys(entry.translations).length > 0) {
|
|
128
|
+
entries.push(entry);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
return entries;
|
|
133
|
+
}
|
|
134
|
+
async function parseProject(options = {}) {
|
|
135
|
+
const {
|
|
136
|
+
cwd = process.cwd(),
|
|
137
|
+
include = ["**/*.{ts,tsx,js,jsx}"],
|
|
138
|
+
exclude = ["**/node_modules/**", "**/dist/**", "**/.next/**"]
|
|
139
|
+
} = options;
|
|
140
|
+
const files = await (0, import_fast_glob.default)(include, {
|
|
141
|
+
cwd,
|
|
142
|
+
ignore: exclude,
|
|
143
|
+
absolute: true
|
|
144
|
+
});
|
|
145
|
+
const allEntries = [];
|
|
146
|
+
for (const file of files) {
|
|
147
|
+
const entries = parseFile(file);
|
|
148
|
+
allEntries.push(...entries);
|
|
149
|
+
}
|
|
150
|
+
return allEntries;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/commands/find.ts
|
|
154
|
+
var import_chalk = __toESM(require("chalk"));
|
|
155
|
+
async function find(options) {
|
|
156
|
+
const { query, cwd } = options;
|
|
157
|
+
console.log(import_chalk.default.blue(`
|
|
158
|
+
Searching for: "${query}"
|
|
159
|
+
`));
|
|
160
|
+
const entries = await parseProject({ cwd });
|
|
161
|
+
const results = [];
|
|
162
|
+
const lowerQuery = query.toLowerCase();
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
const values = Object.values(entry.translations);
|
|
165
|
+
const matches = values.some((v) => v.toLowerCase().includes(lowerQuery));
|
|
166
|
+
if (matches) {
|
|
167
|
+
results.push(entry);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (results.length === 0) {
|
|
171
|
+
console.log(import_chalk.default.yellow("No results found."));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
console.log(import_chalk.default.green(`Found ${results.length} occurrence(s):
|
|
175
|
+
`));
|
|
176
|
+
for (const result of results) {
|
|
177
|
+
const relativePath = result.file.replace(process.cwd() + "/", "");
|
|
178
|
+
console.log(import_chalk.default.gray(`${relativePath}:${result.line}:${result.column}`));
|
|
179
|
+
for (const [locale, text] of Object.entries(result.translations)) {
|
|
180
|
+
const highlighted = text.replace(
|
|
181
|
+
new RegExp(`(${query})`, "gi"),
|
|
182
|
+
import_chalk.default.yellow("$1")
|
|
183
|
+
);
|
|
184
|
+
console.log(` ${import_chalk.default.cyan(locale)}: ${highlighted}`);
|
|
185
|
+
}
|
|
186
|
+
console.log();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/commands/validate.ts
|
|
191
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
192
|
+
async function validate(options = {}) {
|
|
193
|
+
const { cwd, locales } = options;
|
|
194
|
+
console.log(import_chalk2.default.blue("\nValidating translations...\n"));
|
|
195
|
+
const entries = await parseProject({ cwd });
|
|
196
|
+
const issues = [];
|
|
197
|
+
const groups = /* @__PURE__ */ new Map();
|
|
198
|
+
for (const entry of entries) {
|
|
199
|
+
const key = Object.values(entry.translations)[0] || "";
|
|
200
|
+
if (!groups.has(key)) {
|
|
201
|
+
groups.set(key, []);
|
|
202
|
+
}
|
|
203
|
+
groups.get(key).push(entry);
|
|
204
|
+
}
|
|
205
|
+
for (const [key, group] of groups) {
|
|
206
|
+
if (group.length < 2) continue;
|
|
207
|
+
const translationSets = group.map((e) => JSON.stringify(e.translations));
|
|
208
|
+
const uniqueSets = [...new Set(translationSets)];
|
|
209
|
+
if (uniqueSets.length > 1) {
|
|
210
|
+
issues.push({
|
|
211
|
+
type: "inconsistent",
|
|
212
|
+
message: `Inconsistent translations for "${key}"`,
|
|
213
|
+
entries: group
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (locales && locales.length > 0) {
|
|
218
|
+
for (const entry of entries) {
|
|
219
|
+
const missing = locales.filter((l) => !entry.translations[l]);
|
|
220
|
+
if (missing.length > 0) {
|
|
221
|
+
issues.push({
|
|
222
|
+
type: "missing",
|
|
223
|
+
message: `Missing locales: ${missing.join(", ")}`,
|
|
224
|
+
entries: [entry]
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const entry of entries) {
|
|
230
|
+
const variableSets = Object.entries(entry.translations).map(([locale, text]) => {
|
|
231
|
+
const vars = text.match(/\{(\w+)\}/g) || [];
|
|
232
|
+
return { locale, vars: vars.sort().join(",") };
|
|
233
|
+
});
|
|
234
|
+
const uniqueVarSets = [...new Set(variableSets.map((v) => v.vars))];
|
|
235
|
+
if (uniqueVarSets.length > 1) {
|
|
236
|
+
issues.push({
|
|
237
|
+
type: "variable_mismatch",
|
|
238
|
+
message: "Variable mismatch between translations",
|
|
239
|
+
entries: [entry]
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (issues.length === 0) {
|
|
244
|
+
console.log(import_chalk2.default.green("\u2705 All translations are valid!\n"));
|
|
245
|
+
console.log(import_chalk2.default.gray(`Checked ${entries.length} translation(s)`));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
console.log(import_chalk2.default.red(`\u274C Found ${issues.length} issue(s):
|
|
249
|
+
`));
|
|
250
|
+
for (const issue of issues) {
|
|
251
|
+
const icon = issue.type === "inconsistent" ? "\u26A0\uFE0F" : issue.type === "missing" ? "\u{1F4ED}" : "\u{1F500}";
|
|
252
|
+
console.log(`${icon} ${import_chalk2.default.yellow(issue.message)}`);
|
|
253
|
+
for (const entry of issue.entries) {
|
|
254
|
+
const relativePath = entry.file.replace(process.cwd() + "/", "");
|
|
255
|
+
console.log(import_chalk2.default.gray(` ${relativePath}:${entry.line}`));
|
|
256
|
+
for (const [locale, text] of Object.entries(entry.translations)) {
|
|
257
|
+
console.log(` ${import_chalk2.default.cyan(locale)}: ${text}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
console.log();
|
|
261
|
+
}
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/commands/coverage.ts
|
|
266
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
267
|
+
async function coverage(options) {
|
|
268
|
+
const { cwd, locales } = options;
|
|
269
|
+
console.log(import_chalk3.default.blue("\nAnalyzing translation coverage...\n"));
|
|
270
|
+
const entries = await parseProject({ cwd });
|
|
271
|
+
if (entries.length === 0) {
|
|
272
|
+
console.log(import_chalk3.default.yellow("No translations found."));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const coverageData = [];
|
|
276
|
+
for (const locale of locales) {
|
|
277
|
+
let translated = 0;
|
|
278
|
+
for (const entry of entries) {
|
|
279
|
+
if (entry.translations[locale]) {
|
|
280
|
+
translated++;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const percentage = Math.round(translated / entries.length * 100);
|
|
284
|
+
coverageData.push({
|
|
285
|
+
locale,
|
|
286
|
+
total: entries.length,
|
|
287
|
+
translated,
|
|
288
|
+
percentage
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
console.log(import_chalk3.default.bold("Translation Coverage:\n"));
|
|
292
|
+
const maxLocaleLen = Math.max(...locales.map((l) => l.length), 6);
|
|
293
|
+
console.log(
|
|
294
|
+
import_chalk3.default.gray(
|
|
295
|
+
`${"Locale".padEnd(maxLocaleLen)} ${"Coverage".padStart(10)} ${"Translated".padStart(12)}`
|
|
296
|
+
)
|
|
297
|
+
);
|
|
298
|
+
console.log(import_chalk3.default.gray("\u2500".repeat(maxLocaleLen + 26)));
|
|
299
|
+
for (const data of coverageData) {
|
|
300
|
+
const color = data.percentage === 100 ? import_chalk3.default.green : data.percentage >= 80 ? import_chalk3.default.yellow : import_chalk3.default.red;
|
|
301
|
+
const bar = createProgressBar(data.percentage, 10);
|
|
302
|
+
const percentStr = `${data.percentage}%`.padStart(4);
|
|
303
|
+
console.log(
|
|
304
|
+
`${data.locale.padEnd(maxLocaleLen)} ${color(bar)} ${color(percentStr)} ${import_chalk3.default.gray(`${data.translated}/${data.total}`)}`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
console.log();
|
|
308
|
+
const fullyCovered = coverageData.filter((d) => d.percentage === 100).length;
|
|
309
|
+
const partiallyCovered = coverageData.filter((d) => d.percentage > 0 && d.percentage < 100).length;
|
|
310
|
+
const notCovered = coverageData.filter((d) => d.percentage === 0).length;
|
|
311
|
+
if (fullyCovered === locales.length) {
|
|
312
|
+
console.log(import_chalk3.default.green("\u2705 All locales are fully translated!"));
|
|
313
|
+
} else {
|
|
314
|
+
console.log(import_chalk3.default.gray(`Full: ${fullyCovered}, Partial: ${partiallyCovered}, Empty: ${notCovered}`));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function createProgressBar(percentage, width) {
|
|
318
|
+
const filled = Math.round(percentage / 100 * width);
|
|
319
|
+
const empty = width - filled;
|
|
320
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
321
|
+
}
|
|
322
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
323
|
+
0 && (module.exports = {
|
|
324
|
+
coverage,
|
|
325
|
+
find,
|
|
326
|
+
parseProject,
|
|
327
|
+
validate
|
|
328
|
+
});
|
|
329
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/parser.ts","../src/commands/find.ts","../src/commands/validate.ts","../src/commands/coverage.ts"],"sourcesContent":["export { parseProject, type TranslationEntry } from './parser'\nexport { find } from './commands/find'\nexport { validate } from './commands/validate'\nexport { coverage } from './commands/coverage'\n","import { parse } from '@babel/parser'\nimport traverse from '@babel/traverse'\nimport * as fs from 'fs'\nimport fg from 'fast-glob'\n\nexport interface TranslationEntry {\n file: string\n line: number\n column: number\n translations: Record<string, string>\n variables: string[]\n}\n\ninterface ParseOptions {\n cwd?: string\n include?: string[]\n exclude?: string[]\n}\n\nconst IT_FUNCTION_NAMES = [\n 'it',\n 'it_ja', 'it_zh', 'it_es', 'it_fr', 'it_de',\n 'en_ja', 'en_zh', 'en_es', 'en_fr', 'en_de',\n 'ja_zh', 'ja_es', 'zh_es',\n]\n\nconst PAIR_MAPPING: Record<string, [string, string]> = {\n it: ['ko', 'en'],\n it_ja: ['ko', 'ja'],\n it_zh: ['ko', 'zh'],\n it_es: ['ko', 'es'],\n it_fr: ['ko', 'fr'],\n it_de: ['ko', 'de'],\n en_ja: ['en', 'ja'],\n en_zh: ['en', 'zh'],\n en_es: ['en', 'es'],\n en_fr: ['en', 'fr'],\n en_de: ['en', 'de'],\n ja_zh: ['ja', 'zh'],\n ja_es: ['ja', 'es'],\n zh_es: ['zh', 'es'],\n}\n\nfunction extractVariables(text: string): string[] {\n const matches = text.match(/\\{(\\w+)\\}/g)\n if (!matches) return []\n return matches.map((m) => m.slice(1, -1))\n}\n\nfunction parseFile(filePath: string): TranslationEntry[] {\n const entries: TranslationEntry[] = []\n const code = fs.readFileSync(filePath, 'utf-8')\n\n let ast\n try {\n ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n })\n } catch {\n return []\n }\n\n traverse(ast, {\n CallExpression(nodePath) {\n const { node } = nodePath\n const { callee } = node\n\n if (callee.type !== 'Identifier') return\n if (!IT_FUNCTION_NAMES.includes(callee.name)) return\n\n const funcName = callee.name\n const args = node.arguments\n const loc = node.loc\n\n if (!loc) return\n\n const entry: TranslationEntry = {\n file: filePath,\n line: loc.start.line,\n column: loc.start.column,\n translations: {},\n variables: [],\n }\n\n if (args[0]?.type === 'ObjectExpression') {\n const obj = args[0]\n for (const prop of obj.properties) {\n if (\n prop.type === 'ObjectProperty' &&\n prop.key.type === 'Identifier' &&\n prop.value.type === 'StringLiteral'\n ) {\n entry.translations[prop.key.name] = prop.value.value\n entry.variables.push(...extractVariables(prop.value.value))\n }\n }\n } else if (\n args[0]?.type === 'StringLiteral' &&\n args[1]?.type === 'StringLiteral'\n ) {\n const [lang1, lang2] = PAIR_MAPPING[funcName] || ['ko', 'en']\n entry.translations[lang1] = args[0].value\n entry.translations[lang2] = args[1].value\n entry.variables.push(...extractVariables(args[0].value))\n entry.variables.push(...extractVariables(args[1].value))\n }\n\n entry.variables = [...new Set(entry.variables)]\n\n if (Object.keys(entry.translations).length > 0) {\n entries.push(entry)\n }\n },\n })\n\n return entries\n}\n\nexport async function parseProject(options: ParseOptions = {}): Promise<TranslationEntry[]> {\n const {\n cwd = process.cwd(),\n include = ['**/*.{ts,tsx,js,jsx}'],\n exclude = ['**/node_modules/**', '**/dist/**', '**/.next/**'],\n } = options\n\n const files = await fg(include, {\n cwd,\n ignore: exclude,\n absolute: true,\n })\n\n const allEntries: TranslationEntry[] = []\n\n for (const file of files) {\n const entries = parseFile(file)\n allEntries.push(...entries)\n }\n\n return allEntries\n}\n","import chalk from 'chalk'\nimport { parseProject, type TranslationEntry } from '../parser'\n\ninterface FindOptions {\n query: string\n cwd?: string\n}\n\nexport async function find(options: FindOptions): Promise<void> {\n const { query, cwd } = options\n\n console.log(chalk.blue(`\\nSearching for: \"${query}\"\\n`))\n\n const entries = await parseProject({ cwd })\n const results: TranslationEntry[] = []\n\n const lowerQuery = query.toLowerCase()\n\n for (const entry of entries) {\n const values = Object.values(entry.translations)\n const matches = values.some((v) => v.toLowerCase().includes(lowerQuery))\n\n if (matches) {\n results.push(entry)\n }\n }\n\n if (results.length === 0) {\n console.log(chalk.yellow('No results found.'))\n return\n }\n\n console.log(chalk.green(`Found ${results.length} occurrence(s):\\n`))\n\n for (const result of results) {\n const relativePath = result.file.replace(process.cwd() + '/', '')\n console.log(chalk.gray(`${relativePath}:${result.line}:${result.column}`))\n\n for (const [locale, text] of Object.entries(result.translations)) {\n const highlighted = text.replace(\n new RegExp(`(${query})`, 'gi'),\n chalk.yellow('$1')\n )\n console.log(` ${chalk.cyan(locale)}: ${highlighted}`)\n }\n console.log()\n }\n}\n","import chalk from 'chalk'\nimport { parseProject, type TranslationEntry } from '../parser'\n\ninterface ValidateOptions {\n cwd?: string\n locales?: string[]\n}\n\ninterface Issue {\n type: 'inconsistent' | 'missing' | 'variable_mismatch'\n message: string\n entries: TranslationEntry[]\n}\n\nexport async function validate(options: ValidateOptions = {}): Promise<void> {\n const { cwd, locales } = options\n\n console.log(chalk.blue('\\nValidating translations...\\n'))\n\n const entries = await parseProject({ cwd })\n const issues: Issue[] = []\n\n // group by first language text (usually ko)\n const groups = new Map<string, TranslationEntry[]>()\n\n for (const entry of entries) {\n const key = Object.values(entry.translations)[0] || ''\n if (!groups.has(key)) {\n groups.set(key, [])\n }\n groups.get(key)!.push(entry)\n }\n\n // check for inconsistent translations\n for (const [key, group] of groups) {\n if (group.length < 2) continue\n\n const translationSets = group.map((e) => JSON.stringify(e.translations))\n const uniqueSets = [...new Set(translationSets)]\n\n if (uniqueSets.length > 1) {\n issues.push({\n type: 'inconsistent',\n message: `Inconsistent translations for \"${key}\"`,\n entries: group,\n })\n }\n }\n\n // check for missing locales\n if (locales && locales.length > 0) {\n for (const entry of entries) {\n const missing = locales.filter((l) => !entry.translations[l])\n\n if (missing.length > 0) {\n issues.push({\n type: 'missing',\n message: `Missing locales: ${missing.join(', ')}`,\n entries: [entry],\n })\n }\n }\n }\n\n // check for variable mismatches\n for (const entry of entries) {\n const variableSets = Object.entries(entry.translations).map(([locale, text]) => {\n const vars = text.match(/\\{(\\w+)\\}/g) || []\n return { locale, vars: vars.sort().join(',') }\n })\n\n const uniqueVarSets = [...new Set(variableSets.map((v) => v.vars))]\n\n if (uniqueVarSets.length > 1) {\n issues.push({\n type: 'variable_mismatch',\n message: 'Variable mismatch between translations',\n entries: [entry],\n })\n }\n }\n\n // print results\n if (issues.length === 0) {\n console.log(chalk.green('✅ All translations are valid!\\n'))\n console.log(chalk.gray(`Checked ${entries.length} translation(s)`))\n return\n }\n\n console.log(chalk.red(`❌ Found ${issues.length} issue(s):\\n`))\n\n for (const issue of issues) {\n const icon =\n issue.type === 'inconsistent' ? '⚠️' :\n issue.type === 'missing' ? '📭' : '🔀'\n\n console.log(`${icon} ${chalk.yellow(issue.message)}`)\n\n for (const entry of issue.entries) {\n const relativePath = entry.file.replace(process.cwd() + '/', '')\n console.log(chalk.gray(` ${relativePath}:${entry.line}`))\n\n for (const [locale, text] of Object.entries(entry.translations)) {\n console.log(` ${chalk.cyan(locale)}: ${text}`)\n }\n }\n console.log()\n }\n\n process.exit(1)\n}\n","import chalk from 'chalk'\nimport { parseProject } from '../parser'\n\ninterface CoverageOptions {\n cwd?: string\n locales: string[]\n}\n\ninterface LocaleCoverage {\n locale: string\n total: number\n translated: number\n percentage: number\n}\n\nexport async function coverage(options: CoverageOptions): Promise<void> {\n const { cwd, locales } = options\n\n console.log(chalk.blue('\\nAnalyzing translation coverage...\\n'))\n\n const entries = await parseProject({ cwd })\n\n if (entries.length === 0) {\n console.log(chalk.yellow('No translations found.'))\n return\n }\n\n const coverageData: LocaleCoverage[] = []\n\n for (const locale of locales) {\n let translated = 0\n\n for (const entry of entries) {\n if (entry.translations[locale]) {\n translated++\n }\n }\n\n const percentage = Math.round((translated / entries.length) * 100)\n\n coverageData.push({\n locale,\n total: entries.length,\n translated,\n percentage,\n })\n }\n\n // print coverage table\n console.log(chalk.bold('Translation Coverage:\\n'))\n\n const maxLocaleLen = Math.max(...locales.map((l) => l.length), 6)\n\n console.log(\n chalk.gray(\n `${'Locale'.padEnd(maxLocaleLen)} ${'Coverage'.padStart(10)} ${'Translated'.padStart(12)}`\n )\n )\n console.log(chalk.gray('─'.repeat(maxLocaleLen + 26)))\n\n for (const data of coverageData) {\n const color =\n data.percentage === 100 ? chalk.green :\n data.percentage >= 80 ? chalk.yellow : chalk.red\n\n const bar = createProgressBar(data.percentage, 10)\n const percentStr = `${data.percentage}%`.padStart(4)\n\n console.log(\n `${data.locale.padEnd(maxLocaleLen)} ${color(bar)} ${color(percentStr)} ${chalk.gray(`${data.translated}/${data.total}`)}`\n )\n }\n\n console.log()\n\n // summary\n const fullyCovered = coverageData.filter((d) => d.percentage === 100).length\n const partiallyCovered = coverageData.filter((d) => d.percentage > 0 && d.percentage < 100).length\n const notCovered = coverageData.filter((d) => d.percentage === 0).length\n\n if (fullyCovered === locales.length) {\n console.log(chalk.green('✅ All locales are fully translated!'))\n } else {\n console.log(chalk.gray(`Full: ${fullyCovered}, Partial: ${partiallyCovered}, Empty: ${notCovered}`))\n }\n}\n\nfunction createProgressBar(percentage: number, width: number): string {\n const filled = Math.round((percentage / 100) * width)\n const empty = width - filled\n\n return '█'.repeat(filled) + '░'.repeat(empty)\n}\n"],"mappings":"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","names":["traverse","fg","chalk","import_chalk","chalk","import_chalk","chalk"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@inline-i18n-multi/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tools for inline-i18n-multi",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"inline-i18n": "./dist/bin.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"i18n",
|
|
15
|
+
"cli",
|
|
16
|
+
"internationalization",
|
|
17
|
+
"translation"
|
|
18
|
+
],
|
|
19
|
+
"author": "exiivy98",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/exiivy98/inline-i18n-multi.git",
|
|
24
|
+
"directory": "packages/cli"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@babel/parser": "^7.26.3",
|
|
28
|
+
"@babel/traverse": "^7.26.4",
|
|
29
|
+
"chalk": "^5.4.1",
|
|
30
|
+
"commander": "^13.0.0",
|
|
31
|
+
"fast-glob": "^3.3.3"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@babel/types": "^7.26.3",
|
|
35
|
+
"@types/babel__traverse": "^7.20.6",
|
|
36
|
+
"@types/node": "^22.10.2",
|
|
37
|
+
"tsup": "^8.3.5",
|
|
38
|
+
"typescript": "^5.7.2",
|
|
39
|
+
"vitest": "^2.1.8"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsup",
|
|
46
|
+
"dev": "tsup --watch",
|
|
47
|
+
"test": "vitest --passWithNoTests",
|
|
48
|
+
"lint": "eslint src",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"clean": "rm -rf dist"
|
|
51
|
+
}
|
|
52
|
+
}
|