@ccgp/i18n-ai 0.0.1
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 +83 -0
- package/dist/chunk-QBRFUS6N.mjs +292 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +356 -0
- package/dist/cli.mjs +53 -0
- package/dist/index.d.mts +94 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +338 -0
- package/dist/index.mjs +26 -0
- package/package.json +51 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
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/cli.ts
|
|
27
|
+
var import_commander = require("commander");
|
|
28
|
+
var import_path2 = require("path");
|
|
29
|
+
var import_promises3 = require("fs/promises");
|
|
30
|
+
|
|
31
|
+
// src/core/sync.ts
|
|
32
|
+
var import_promises2 = require("fs/promises");
|
|
33
|
+
var import_path = require("path");
|
|
34
|
+
|
|
35
|
+
// src/core/translator.ts
|
|
36
|
+
var import_ai_sdk_provider = require("@openrouter/ai-sdk-provider");
|
|
37
|
+
var import_ai = require("ai");
|
|
38
|
+
var import_zod = __toESM(require("zod"));
|
|
39
|
+
var translate = async ({ text, lang, targetLang, apiKey, model }) => {
|
|
40
|
+
const token = apiKey || process.env.OPENROUTER_API_KEY || process.env.AI_API_KEY;
|
|
41
|
+
if (!token) {
|
|
42
|
+
throw new Error("Missing API Key. Please provide apiKey or set OPENROUTER_API_KEY / AI_API_KEY environment variable.");
|
|
43
|
+
}
|
|
44
|
+
const openrouter = (0, import_ai_sdk_provider.createOpenRouter)({
|
|
45
|
+
apiKey: token
|
|
46
|
+
});
|
|
47
|
+
const { output } = await (0, import_ai.generateText)({
|
|
48
|
+
model: openrouter.languageModel(model || "google/gemini-2.5-flash"),
|
|
49
|
+
prompt: `Translate the following text from ${lang} to ${targetLang}. Return ONLY the translated text, no explanations or additional text.
|
|
50
|
+
|
|
51
|
+
Text to translate: ${text}`,
|
|
52
|
+
output: import_ai.Output.object({
|
|
53
|
+
schema: import_zod.default.object({
|
|
54
|
+
translatedText: import_zod.default.string()
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
});
|
|
58
|
+
return output.translatedText;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// src/core/lock-manager.ts
|
|
62
|
+
var import_crypto = require("crypto");
|
|
63
|
+
var import_promises = require("fs/promises");
|
|
64
|
+
function generateHash(text) {
|
|
65
|
+
return (0, import_crypto.createHash)("sha256").update(text).digest("hex").substring(0, 16);
|
|
66
|
+
}
|
|
67
|
+
async function loadLock(lockPath) {
|
|
68
|
+
try {
|
|
69
|
+
const content = await (0, import_promises.readFile)(lockPath, "utf-8");
|
|
70
|
+
return JSON.parse(content);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return {
|
|
73
|
+
version: "1.0.0",
|
|
74
|
+
lastSync: (/* @__PURE__ */ new Date()).toISOString(),
|
|
75
|
+
locales: {}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function saveLock(lockPath, lock) {
|
|
80
|
+
lock.lastSync = (/* @__PURE__ */ new Date()).toISOString();
|
|
81
|
+
const content = JSON.stringify(lock, null, 2);
|
|
82
|
+
await (0, import_promises.writeFile)(lockPath, content + "\n", "utf-8");
|
|
83
|
+
}
|
|
84
|
+
function detectChange(key, locale, sourceText, currentTranslation, lock) {
|
|
85
|
+
const localeData = lock.locales[locale];
|
|
86
|
+
const lockEntry = localeData?.[key];
|
|
87
|
+
if (!lockEntry) {
|
|
88
|
+
return {
|
|
89
|
+
key,
|
|
90
|
+
status: "new",
|
|
91
|
+
sourceText,
|
|
92
|
+
currentTranslation
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const currentHash = generateHash(sourceText);
|
|
96
|
+
if (currentHash !== lockEntry.sourceHash) {
|
|
97
|
+
return {
|
|
98
|
+
key,
|
|
99
|
+
status: "modified",
|
|
100
|
+
sourceText,
|
|
101
|
+
currentTranslation,
|
|
102
|
+
previousSourceText: lockEntry.sourceText
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
key,
|
|
107
|
+
status: "unchanged",
|
|
108
|
+
sourceText,
|
|
109
|
+
currentTranslation: lockEntry.translation
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function updateLockEntry(lock, locale, key, sourceText, translation) {
|
|
113
|
+
if (!lock.locales[locale]) {
|
|
114
|
+
lock.locales[locale] = {};
|
|
115
|
+
}
|
|
116
|
+
lock.locales[locale][key] = {
|
|
117
|
+
sourceHash: generateHash(sourceText),
|
|
118
|
+
sourceText,
|
|
119
|
+
translation,
|
|
120
|
+
translatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function analyzeChanges(baseFlat, targetFlat, locale, lock) {
|
|
124
|
+
const result = {
|
|
125
|
+
new: [],
|
|
126
|
+
modified: [],
|
|
127
|
+
unchanged: []
|
|
128
|
+
};
|
|
129
|
+
for (const [key, sourceText] of baseFlat.entries()) {
|
|
130
|
+
const currentTranslation = targetFlat.get(key);
|
|
131
|
+
const change = detectChange(key, locale, sourceText, currentTranslation, lock);
|
|
132
|
+
result[change.status].push(change);
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
function cleanupLock(lock, locale, validKeys) {
|
|
137
|
+
if (!lock.locales[locale]) {
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
const currentKeys = Object.keys(lock.locales[locale]);
|
|
141
|
+
let removedCount = 0;
|
|
142
|
+
for (const key of currentKeys) {
|
|
143
|
+
if (!validKeys.has(key)) {
|
|
144
|
+
delete lock.locales[locale][key];
|
|
145
|
+
removedCount++;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return removedCount;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/core/sync.ts
|
|
152
|
+
var import_chalk = __toESM(require("chalk"));
|
|
153
|
+
function flattenObject(obj, prefix = "") {
|
|
154
|
+
const result = /* @__PURE__ */ new Map();
|
|
155
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
156
|
+
const fullPath = prefix ? `${prefix}.${key}` : key;
|
|
157
|
+
if (typeof value === "string") {
|
|
158
|
+
result.set(fullPath, value);
|
|
159
|
+
} else if (typeof value === "object" && value !== null) {
|
|
160
|
+
const nested = flattenObject(value, fullPath);
|
|
161
|
+
nested.forEach((val, path) => result.set(path, val));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
function unflattenObject(flatMap) {
|
|
167
|
+
const result = {};
|
|
168
|
+
for (const [path, value] of flatMap.entries()) {
|
|
169
|
+
const keys = path.split(".");
|
|
170
|
+
let current = result;
|
|
171
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
172
|
+
const key = keys[i];
|
|
173
|
+
if (!current[key]) {
|
|
174
|
+
current[key] = {};
|
|
175
|
+
}
|
|
176
|
+
current = current[key];
|
|
177
|
+
}
|
|
178
|
+
current[keys[keys.length - 1]] = value;
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
var TranslationService = class {
|
|
183
|
+
constructor(config) {
|
|
184
|
+
this.config = config;
|
|
185
|
+
}
|
|
186
|
+
async sync() {
|
|
187
|
+
console.log(import_chalk.default.bold("\u{1F30D} AI Translation Synchronization System"));
|
|
188
|
+
console.log(import_chalk.default.dim("\u2550".repeat(50)));
|
|
189
|
+
console.log("");
|
|
190
|
+
try {
|
|
191
|
+
console.log(`\u2713 Locales found: ${import_chalk.default.green(this.config.locales.join(", "))}`);
|
|
192
|
+
console.log(`\u2713 Base locale: ${import_chalk.default.cyan(this.config.defaultLocale)}`);
|
|
193
|
+
const basePath = (0, import_path.join)(this.config.messagesDir, `${this.config.defaultLocale}.json`);
|
|
194
|
+
let baseContent;
|
|
195
|
+
try {
|
|
196
|
+
baseContent = await (0, import_promises2.readFile)(basePath, "utf-8");
|
|
197
|
+
} catch (e) {
|
|
198
|
+
throw new Error(`Could not read base locale file at ${basePath}`);
|
|
199
|
+
}
|
|
200
|
+
const baseObj = JSON.parse(baseContent);
|
|
201
|
+
const baseFlat = flattenObject(baseObj);
|
|
202
|
+
console.log(`\u{1F4D6} Loading ${this.config.defaultLocale}.json (${baseFlat.size} keys total)`);
|
|
203
|
+
console.log("\u{1F510} Loading lock file...");
|
|
204
|
+
const lock = await loadLock(this.config.lockFilePath);
|
|
205
|
+
const targetLocales = this.config.locales.filter((l) => l !== this.config.defaultLocale);
|
|
206
|
+
for (const targetLocale of targetLocales) {
|
|
207
|
+
await this.syncLocale(targetLocale, baseFlat, lock);
|
|
208
|
+
}
|
|
209
|
+
await saveLock(this.config.lockFilePath, lock);
|
|
210
|
+
console.log(`
|
|
211
|
+
\u{1F510} Lock file updated at ${this.config.lockFilePath}`);
|
|
212
|
+
console.log(import_chalk.default.dim("\n\u2550".repeat(50)));
|
|
213
|
+
console.log(import_chalk.default.green("\u2705 Synchronization complete!\n"));
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error(import_chalk.default.red("\n\u274C Error during synchronization:"), error);
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async syncLocale(targetLocale, baseFlat, lock) {
|
|
220
|
+
console.log(`
|
|
221
|
+
\u{1F50E} Synchronizing: ${import_chalk.default.yellow(targetLocale)}`);
|
|
222
|
+
console.log(import_chalk.default.dim("\u2500".repeat(50)));
|
|
223
|
+
const targetPath = (0, import_path.join)(this.config.messagesDir, `${targetLocale}.json`);
|
|
224
|
+
let targetObj = {};
|
|
225
|
+
try {
|
|
226
|
+
const targetContent = await (0, import_promises2.readFile)(targetPath, "utf-8");
|
|
227
|
+
targetObj = JSON.parse(targetContent);
|
|
228
|
+
} catch {
|
|
229
|
+
console.log(import_chalk.default.yellow(`\u26A0\uFE0F File ${targetLocale}.json does not exist, creating new one
|
|
230
|
+
`));
|
|
231
|
+
}
|
|
232
|
+
const targetFlat = flattenObject(targetObj);
|
|
233
|
+
const changes = analyzeChanges(baseFlat, targetFlat, targetLocale, lock);
|
|
234
|
+
const validKeys = new Set(baseFlat.keys());
|
|
235
|
+
const obsoleteKeys = [];
|
|
236
|
+
for (const key of targetFlat.keys()) {
|
|
237
|
+
if (!validKeys.has(key)) {
|
|
238
|
+
obsoleteKeys.push(key);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
console.log("\n\u{1F4CA} Change analysis:");
|
|
242
|
+
console.log(` \u{1F4DD} ${changes.new.length} new key(s)`);
|
|
243
|
+
console.log(` \u{1F504} ${changes.modified.length} modified key(s)`);
|
|
244
|
+
console.log(` \u2705 ${changes.unchanged.length} unchanged key(s)`);
|
|
245
|
+
console.log(` \u{1F5D1}\uFE0F ${obsoleteKeys.length} obsolete key(s)`);
|
|
246
|
+
const toTranslate = [...changes.new, ...changes.modified];
|
|
247
|
+
if (toTranslate.length === 0 && obsoleteKeys.length === 0) {
|
|
248
|
+
console.log(import_chalk.default.green(`
|
|
249
|
+
\u2705 ${targetLocale}.json is already fully synchronized`));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (changes.new.length > 0) {
|
|
253
|
+
console.log("\n\u{1F4DD} New keys:");
|
|
254
|
+
changes.new.forEach(({ key }) => console.log(import_chalk.default.green(` + ${key}`)));
|
|
255
|
+
}
|
|
256
|
+
if (changes.modified.length > 0) {
|
|
257
|
+
console.log("\n\u{1F504} Modified keys:");
|
|
258
|
+
changes.modified.forEach(({ key, previousSourceText, sourceText }) => {
|
|
259
|
+
console.log(import_chalk.default.yellow(` ~ ${key}`));
|
|
260
|
+
console.log(import_chalk.default.dim(` Old: "${previousSourceText}"`));
|
|
261
|
+
console.log(import_chalk.default.dim(` New: "${sourceText}"`));
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
if (toTranslate.length > 0) {
|
|
265
|
+
console.log(import_chalk.default.blue(`
|
|
266
|
+
\u{1F916} Translating ${toTranslate.length} keys with AI...`));
|
|
267
|
+
console.log(import_chalk.default.dim("\u2500".repeat(50)));
|
|
268
|
+
}
|
|
269
|
+
let translatedCount = 0;
|
|
270
|
+
if (toTranslate.length > 0) {
|
|
271
|
+
for (const change of toTranslate) {
|
|
272
|
+
try {
|
|
273
|
+
const translatedValue = await translate({
|
|
274
|
+
text: change.sourceText,
|
|
275
|
+
lang: this.config.defaultLocale,
|
|
276
|
+
targetLang: targetLocale,
|
|
277
|
+
apiKey: this.config.apiKey,
|
|
278
|
+
model: this.config.model
|
|
279
|
+
});
|
|
280
|
+
targetFlat.set(change.key, translatedValue);
|
|
281
|
+
updateLockEntry(lock, targetLocale, change.key, change.sourceText, translatedValue);
|
|
282
|
+
translatedCount++;
|
|
283
|
+
const icon = change.status === "new" ? "\u2795" : "\u{1F504}";
|
|
284
|
+
console.log(` ${icon} ${change.key}`);
|
|
285
|
+
console.log(import_chalk.default.dim(` "${change.sourceText}"`));
|
|
286
|
+
console.log(import_chalk.default.cyan(` \u2192 "${translatedValue}"`));
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error(import_chalk.default.red(` \u274C Error translating ${change.key}:`), error);
|
|
289
|
+
if (change.currentTranslation) {
|
|
290
|
+
targetFlat.set(change.key, change.currentTranslation);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
for (const change of changes.unchanged) {
|
|
296
|
+
if (change.currentTranslation) {
|
|
297
|
+
targetFlat.set(change.key, change.currentTranslation);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
for (const key of obsoleteKeys) {
|
|
301
|
+
targetFlat.delete(key);
|
|
302
|
+
}
|
|
303
|
+
cleanupLock(lock, targetLocale, validKeys);
|
|
304
|
+
const updatedObj = unflattenObject(targetFlat);
|
|
305
|
+
const updatedContent = JSON.stringify(updatedObj, null, 2);
|
|
306
|
+
await (0, import_promises2.writeFile)(targetPath, updatedContent + "\n", "utf-8");
|
|
307
|
+
console.log(import_chalk.default.green(`
|
|
308
|
+
\u{1F4BE} ${targetPath} updated`));
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// src/cli.ts
|
|
313
|
+
var import_dotenv = __toESM(require("dotenv"));
|
|
314
|
+
import_dotenv.default.config();
|
|
315
|
+
var program = new import_commander.Command();
|
|
316
|
+
program.name("i18n-ai").description("AI-powered translation CLI").version("0.0.1");
|
|
317
|
+
program.command("sync").description("Synchronize translations using AI").option("-d, --dir <path>", "Messages directory", "messages").option("-l, --locales <items>", "Comma separated list of locales").option("--default <locale>", "Default locale", "en").option("--lock <path>", "Lock file path", "translation-lock.json").action(async (options) => {
|
|
318
|
+
try {
|
|
319
|
+
const cwd = process.cwd();
|
|
320
|
+
const messagesDir = (0, import_path2.resolve)(cwd, options.dir);
|
|
321
|
+
let locales = options.locales ? options.locales.split(",") : [];
|
|
322
|
+
let defaultLocale = options.default;
|
|
323
|
+
if (locales.length === 0) {
|
|
324
|
+
try {
|
|
325
|
+
const routingPath = (0, import_path2.join)(cwd, "i18n", "routing.ts");
|
|
326
|
+
const routingContent = await (0, import_promises3.readFile)(routingPath, "utf-8").catch(() => "");
|
|
327
|
+
if (routingContent) {
|
|
328
|
+
const localesMatch = routingContent.match(/locales:\s*\[([\s\S]*?)\]/);
|
|
329
|
+
const defaultMatch = routingContent.match(/defaultLocale:\s*["'](\w+)["']/);
|
|
330
|
+
if (localesMatch) {
|
|
331
|
+
locales = localesMatch[1].split(",").map((l) => l.trim().replace(/['"]/g, "")).filter(Boolean);
|
|
332
|
+
}
|
|
333
|
+
if (defaultMatch && !defaultLocale) {
|
|
334
|
+
defaultLocale = defaultMatch[1];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
} catch (e) {
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (locales.length === 0) {
|
|
341
|
+
console.error("\u274C No locales found. Please specify --locales or ensure i18n/routing.ts exists.");
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
const service = new TranslationService({
|
|
345
|
+
locales,
|
|
346
|
+
defaultLocale,
|
|
347
|
+
messagesDir,
|
|
348
|
+
lockFilePath: (0, import_path2.resolve)(cwd, options.lock)
|
|
349
|
+
});
|
|
350
|
+
await service.sync();
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.error("Fatal error:", error);
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
program.parse();
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
TranslationService
|
|
4
|
+
} from "./chunk-QBRFUS6N.mjs";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { resolve, join } from "path";
|
|
9
|
+
import { readFile } from "fs/promises";
|
|
10
|
+
import dotenv from "dotenv";
|
|
11
|
+
dotenv.config();
|
|
12
|
+
var program = new Command();
|
|
13
|
+
program.name("i18n-ai").description("AI-powered translation CLI").version("0.0.1");
|
|
14
|
+
program.command("sync").description("Synchronize translations using AI").option("-d, --dir <path>", "Messages directory", "messages").option("-l, --locales <items>", "Comma separated list of locales").option("--default <locale>", "Default locale", "en").option("--lock <path>", "Lock file path", "translation-lock.json").action(async (options) => {
|
|
15
|
+
try {
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
const messagesDir = resolve(cwd, options.dir);
|
|
18
|
+
let locales = options.locales ? options.locales.split(",") : [];
|
|
19
|
+
let defaultLocale = options.default;
|
|
20
|
+
if (locales.length === 0) {
|
|
21
|
+
try {
|
|
22
|
+
const routingPath = join(cwd, "i18n", "routing.ts");
|
|
23
|
+
const routingContent = await readFile(routingPath, "utf-8").catch(() => "");
|
|
24
|
+
if (routingContent) {
|
|
25
|
+
const localesMatch = routingContent.match(/locales:\s*\[([\s\S]*?)\]/);
|
|
26
|
+
const defaultMatch = routingContent.match(/defaultLocale:\s*["'](\w+)["']/);
|
|
27
|
+
if (localesMatch) {
|
|
28
|
+
locales = localesMatch[1].split(",").map((l) => l.trim().replace(/['"]/g, "")).filter(Boolean);
|
|
29
|
+
}
|
|
30
|
+
if (defaultMatch && !defaultLocale) {
|
|
31
|
+
defaultLocale = defaultMatch[1];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch (e) {
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (locales.length === 0) {
|
|
38
|
+
console.error("\u274C No locales found. Please specify --locales or ensure i18n/routing.ts exists.");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
const service = new TranslationService({
|
|
42
|
+
locales,
|
|
43
|
+
defaultLocale,
|
|
44
|
+
messagesDir,
|
|
45
|
+
lockFilePath: resolve(cwd, options.lock)
|
|
46
|
+
});
|
|
47
|
+
await service.sync();
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error("Fatal error:", error);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
program.parse();
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structure of the lock file
|
|
3
|
+
*/
|
|
4
|
+
interface TranslationLock {
|
|
5
|
+
version: string;
|
|
6
|
+
lastSync: string;
|
|
7
|
+
locales: {
|
|
8
|
+
[locale: string]: {
|
|
9
|
+
[key: string]: LockEntry;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
interface LockEntry {
|
|
14
|
+
sourceHash: string;
|
|
15
|
+
sourceText: string;
|
|
16
|
+
translation: string;
|
|
17
|
+
translatedAt: string;
|
|
18
|
+
}
|
|
19
|
+
interface ChangeDetection {
|
|
20
|
+
key: string;
|
|
21
|
+
status: 'new' | 'modified' | 'unchanged';
|
|
22
|
+
sourceText: string;
|
|
23
|
+
currentTranslation?: string;
|
|
24
|
+
previousSourceText?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Generates a SHA-256 hash of the text
|
|
28
|
+
*/
|
|
29
|
+
declare function generateHash(text: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Loads the lock file or creates a new one
|
|
32
|
+
*/
|
|
33
|
+
declare function loadLock(lockPath: string): Promise<TranslationLock>;
|
|
34
|
+
/**
|
|
35
|
+
* Saves the lock file
|
|
36
|
+
*/
|
|
37
|
+
declare function saveLock(lockPath: string, lock: TranslationLock): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Checks if a key has changed by comparing with the lock
|
|
40
|
+
*/
|
|
41
|
+
declare function detectChange(key: string, locale: string, sourceText: string, currentTranslation: string | undefined, lock: TranslationLock): ChangeDetection;
|
|
42
|
+
/**
|
|
43
|
+
* Updates an entry in the lock
|
|
44
|
+
*/
|
|
45
|
+
declare function updateLockEntry(lock: TranslationLock, locale: string, key: string, sourceText: string, translation: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* Analyzes all changes between base file and lock
|
|
48
|
+
*/
|
|
49
|
+
declare function analyzeChanges(baseFlat: Map<string, string>, targetFlat: Map<string, string>, locale: string, lock: TranslationLock): {
|
|
50
|
+
new: ChangeDetection[];
|
|
51
|
+
modified: ChangeDetection[];
|
|
52
|
+
unchanged: ChangeDetection[];
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Cleans up lock entries that no longer exist in the base file
|
|
56
|
+
*/
|
|
57
|
+
declare function cleanupLock(lock: TranslationLock, locale: string, validKeys: Set<string>): number;
|
|
58
|
+
|
|
59
|
+
interface TranslateProps {
|
|
60
|
+
text: string;
|
|
61
|
+
lang: string;
|
|
62
|
+
targetLang: string;
|
|
63
|
+
apiKey?: string;
|
|
64
|
+
model?: string;
|
|
65
|
+
}
|
|
66
|
+
declare const translate: ({ text, lang, targetLang, apiKey, model }: TranslateProps) => Promise<string>;
|
|
67
|
+
|
|
68
|
+
interface TranslationObject {
|
|
69
|
+
[key: string]: string | TranslationObject;
|
|
70
|
+
}
|
|
71
|
+
interface SyncConfig {
|
|
72
|
+
locales: string[];
|
|
73
|
+
defaultLocale: string;
|
|
74
|
+
messagesDir: string;
|
|
75
|
+
lockFilePath: string;
|
|
76
|
+
apiKey?: string;
|
|
77
|
+
model?: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Flattens a nested object to a map of flat paths
|
|
81
|
+
*/
|
|
82
|
+
declare function flattenObject(obj: TranslationObject, prefix?: string): Map<string, string>;
|
|
83
|
+
/**
|
|
84
|
+
* Reconstructs a nested object from a flat map
|
|
85
|
+
*/
|
|
86
|
+
declare function unflattenObject(flatMap: Map<string, string>): TranslationObject;
|
|
87
|
+
declare class TranslationService {
|
|
88
|
+
private config;
|
|
89
|
+
constructor(config: SyncConfig);
|
|
90
|
+
sync(): Promise<void>;
|
|
91
|
+
private syncLocale;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { type ChangeDetection, type LockEntry, type SyncConfig, type TranslationLock, TranslationService, analyzeChanges, cleanupLock, detectChange, flattenObject, generateHash, loadLock, saveLock, translate, unflattenObject, updateLockEntry };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structure of the lock file
|
|
3
|
+
*/
|
|
4
|
+
interface TranslationLock {
|
|
5
|
+
version: string;
|
|
6
|
+
lastSync: string;
|
|
7
|
+
locales: {
|
|
8
|
+
[locale: string]: {
|
|
9
|
+
[key: string]: LockEntry;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
interface LockEntry {
|
|
14
|
+
sourceHash: string;
|
|
15
|
+
sourceText: string;
|
|
16
|
+
translation: string;
|
|
17
|
+
translatedAt: string;
|
|
18
|
+
}
|
|
19
|
+
interface ChangeDetection {
|
|
20
|
+
key: string;
|
|
21
|
+
status: 'new' | 'modified' | 'unchanged';
|
|
22
|
+
sourceText: string;
|
|
23
|
+
currentTranslation?: string;
|
|
24
|
+
previousSourceText?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Generates a SHA-256 hash of the text
|
|
28
|
+
*/
|
|
29
|
+
declare function generateHash(text: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Loads the lock file or creates a new one
|
|
32
|
+
*/
|
|
33
|
+
declare function loadLock(lockPath: string): Promise<TranslationLock>;
|
|
34
|
+
/**
|
|
35
|
+
* Saves the lock file
|
|
36
|
+
*/
|
|
37
|
+
declare function saveLock(lockPath: string, lock: TranslationLock): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Checks if a key has changed by comparing with the lock
|
|
40
|
+
*/
|
|
41
|
+
declare function detectChange(key: string, locale: string, sourceText: string, currentTranslation: string | undefined, lock: TranslationLock): ChangeDetection;
|
|
42
|
+
/**
|
|
43
|
+
* Updates an entry in the lock
|
|
44
|
+
*/
|
|
45
|
+
declare function updateLockEntry(lock: TranslationLock, locale: string, key: string, sourceText: string, translation: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* Analyzes all changes between base file and lock
|
|
48
|
+
*/
|
|
49
|
+
declare function analyzeChanges(baseFlat: Map<string, string>, targetFlat: Map<string, string>, locale: string, lock: TranslationLock): {
|
|
50
|
+
new: ChangeDetection[];
|
|
51
|
+
modified: ChangeDetection[];
|
|
52
|
+
unchanged: ChangeDetection[];
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Cleans up lock entries that no longer exist in the base file
|
|
56
|
+
*/
|
|
57
|
+
declare function cleanupLock(lock: TranslationLock, locale: string, validKeys: Set<string>): number;
|
|
58
|
+
|
|
59
|
+
interface TranslateProps {
|
|
60
|
+
text: string;
|
|
61
|
+
lang: string;
|
|
62
|
+
targetLang: string;
|
|
63
|
+
apiKey?: string;
|
|
64
|
+
model?: string;
|
|
65
|
+
}
|
|
66
|
+
declare const translate: ({ text, lang, targetLang, apiKey, model }: TranslateProps) => Promise<string>;
|
|
67
|
+
|
|
68
|
+
interface TranslationObject {
|
|
69
|
+
[key: string]: string | TranslationObject;
|
|
70
|
+
}
|
|
71
|
+
interface SyncConfig {
|
|
72
|
+
locales: string[];
|
|
73
|
+
defaultLocale: string;
|
|
74
|
+
messagesDir: string;
|
|
75
|
+
lockFilePath: string;
|
|
76
|
+
apiKey?: string;
|
|
77
|
+
model?: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Flattens a nested object to a map of flat paths
|
|
81
|
+
*/
|
|
82
|
+
declare function flattenObject(obj: TranslationObject, prefix?: string): Map<string, string>;
|
|
83
|
+
/**
|
|
84
|
+
* Reconstructs a nested object from a flat map
|
|
85
|
+
*/
|
|
86
|
+
declare function unflattenObject(flatMap: Map<string, string>): TranslationObject;
|
|
87
|
+
declare class TranslationService {
|
|
88
|
+
private config;
|
|
89
|
+
constructor(config: SyncConfig);
|
|
90
|
+
sync(): Promise<void>;
|
|
91
|
+
private syncLocale;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { type ChangeDetection, type LockEntry, type SyncConfig, type TranslationLock, TranslationService, analyzeChanges, cleanupLock, detectChange, flattenObject, generateHash, loadLock, saveLock, translate, unflattenObject, updateLockEntry };
|