@ccgp/i18n-ai 0.1.0 → 0.1.2
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 +24 -1
- package/dist/{chunk-NY2C35K2.mjs → chunk-KVP2GFHU.mjs} +39 -6
- package/dist/cli.js +39 -6
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +39 -6
- package/dist/index.mjs +1 -1
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -43,10 +43,33 @@ bun x i18n-ai sync
|
|
|
43
43
|
|
|
44
44
|
The CLI attempts to auto-detect your configuration from `i18n/routing.ts` if you are using `next-intl`. Otherwise, you can specify options:
|
|
45
45
|
|
|
46
|
-
```bash
|
|
47
46
|
i18n-ai sync --locales en,es,fr --default en --dir messages
|
|
48
47
|
```
|
|
49
48
|
|
|
49
|
+
### Configuration File (Recommended)
|
|
50
|
+
|
|
51
|
+
You can run `i18n-ai init` to generate a configuration file interactively:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
bun x i18n-ai init
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This will create an `i18n-ai.config.json` file:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"defaultLocale": "en",
|
|
62
|
+
"locales": ["en", "es", "fr"],
|
|
63
|
+
"messagesDir": "messages"
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Then you can simply run:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
bun x i18n-ai sync
|
|
71
|
+
```
|
|
72
|
+
|
|
50
73
|
### Environment Variables
|
|
51
74
|
|
|
52
75
|
You need to provide an API key for the AI provider.
|
|
@@ -10,11 +10,23 @@ var translate = async ({ text, lang, targetLang, apiKey, model }) => {
|
|
|
10
10
|
const openrouter = createOpenRouter({
|
|
11
11
|
apiKey: token
|
|
12
12
|
});
|
|
13
|
+
const system = `
|
|
14
|
+
You are a professional translator. Your main task is to translate text from "${lang}" to "${targetLang}".
|
|
15
|
+
|
|
16
|
+
IMPORTANT RULES:
|
|
17
|
+
1. Return ONLY the translated text. No explanations, no quotes around the output.
|
|
18
|
+
2. Do NOT translate text inside curly braces, e.g., "{name}", "{count}". These are variables and must remain exactly as they are.
|
|
19
|
+
3. Maintain the original tone and context.
|
|
20
|
+
4. If the text is a single word or short phrase, translate it directly.
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
- Input: "Hello {name}, welcome back!" -> Output (ES): "Hola {name}, \xA1bienvenido de nuevo!"
|
|
24
|
+
- Input: "Contact us calling to {number}" -> Output (FR): "Contactez-nous en appelant le {number}"
|
|
25
|
+
`;
|
|
13
26
|
const { output } = await generateText({
|
|
14
27
|
model: openrouter.languageModel(model || "google/gemini-2.5-flash"),
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Text to translate: ${text}`,
|
|
28
|
+
system,
|
|
29
|
+
prompt: `Text to translate: "${text}"`,
|
|
18
30
|
output: Output.object({
|
|
19
31
|
schema: z.object({
|
|
20
32
|
translatedText: z.string()
|
|
@@ -236,9 +248,30 @@ var TranslationService = class {
|
|
|
236
248
|
}
|
|
237
249
|
let translatedCount = 0;
|
|
238
250
|
if (toTranslate.length > 0) {
|
|
239
|
-
|
|
251
|
+
const limit = (concurrency) => {
|
|
252
|
+
let active = 0;
|
|
253
|
+
const queue = [];
|
|
254
|
+
const run = async (fn) => {
|
|
255
|
+
if (active >= concurrency) {
|
|
256
|
+
await new Promise((resolve) => queue.push(resolve));
|
|
257
|
+
}
|
|
258
|
+
active++;
|
|
259
|
+
try {
|
|
260
|
+
return await fn();
|
|
261
|
+
} finally {
|
|
262
|
+
active--;
|
|
263
|
+
if (queue.length > 0) {
|
|
264
|
+
queue.shift()();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
return run;
|
|
269
|
+
};
|
|
270
|
+
const runTask = limit(5);
|
|
271
|
+
await Promise.all(toTranslate.map((change) => runTask(async () => {
|
|
240
272
|
try {
|
|
241
|
-
const
|
|
273
|
+
const _translate = this.config.translator || translate;
|
|
274
|
+
const translatedValue = await _translate({
|
|
242
275
|
text: change.sourceText,
|
|
243
276
|
lang: this.config.defaultLocale,
|
|
244
277
|
targetLang: targetLocale,
|
|
@@ -258,7 +291,7 @@ var TranslationService = class {
|
|
|
258
291
|
targetFlat.set(change.key, change.currentTranslation);
|
|
259
292
|
}
|
|
260
293
|
}
|
|
261
|
-
}
|
|
294
|
+
})));
|
|
262
295
|
}
|
|
263
296
|
for (const change of changes.unchanged) {
|
|
264
297
|
if (change.currentTranslation) {
|
package/dist/cli.js
CHANGED
|
@@ -44,11 +44,23 @@ var translate = async ({ text, lang, targetLang, apiKey, model }) => {
|
|
|
44
44
|
const openrouter = (0, import_ai_sdk_provider.createOpenRouter)({
|
|
45
45
|
apiKey: token
|
|
46
46
|
});
|
|
47
|
+
const system = `
|
|
48
|
+
You are a professional translator. Your main task is to translate text from "${lang}" to "${targetLang}".
|
|
49
|
+
|
|
50
|
+
IMPORTANT RULES:
|
|
51
|
+
1. Return ONLY the translated text. No explanations, no quotes around the output.
|
|
52
|
+
2. Do NOT translate text inside curly braces, e.g., "{name}", "{count}". These are variables and must remain exactly as they are.
|
|
53
|
+
3. Maintain the original tone and context.
|
|
54
|
+
4. If the text is a single word or short phrase, translate it directly.
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
57
|
+
- Input: "Hello {name}, welcome back!" -> Output (ES): "Hola {name}, \xA1bienvenido de nuevo!"
|
|
58
|
+
- Input: "Contact us calling to {number}" -> Output (FR): "Contactez-nous en appelant le {number}"
|
|
59
|
+
`;
|
|
47
60
|
const { output } = await (0, import_ai.generateText)({
|
|
48
61
|
model: openrouter.languageModel(model || "google/gemini-2.5-flash"),
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Text to translate: ${text}`,
|
|
62
|
+
system,
|
|
63
|
+
prompt: `Text to translate: "${text}"`,
|
|
52
64
|
output: import_ai.Output.object({
|
|
53
65
|
schema: import_zod.default.object({
|
|
54
66
|
translatedText: import_zod.default.string()
|
|
@@ -268,9 +280,30 @@ var TranslationService = class {
|
|
|
268
280
|
}
|
|
269
281
|
let translatedCount = 0;
|
|
270
282
|
if (toTranslate.length > 0) {
|
|
271
|
-
|
|
283
|
+
const limit = (concurrency) => {
|
|
284
|
+
let active = 0;
|
|
285
|
+
const queue = [];
|
|
286
|
+
const run = async (fn) => {
|
|
287
|
+
if (active >= concurrency) {
|
|
288
|
+
await new Promise((resolve2) => queue.push(resolve2));
|
|
289
|
+
}
|
|
290
|
+
active++;
|
|
291
|
+
try {
|
|
292
|
+
return await fn();
|
|
293
|
+
} finally {
|
|
294
|
+
active--;
|
|
295
|
+
if (queue.length > 0) {
|
|
296
|
+
queue.shift()();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
return run;
|
|
301
|
+
};
|
|
302
|
+
const runTask = limit(5);
|
|
303
|
+
await Promise.all(toTranslate.map((change) => runTask(async () => {
|
|
272
304
|
try {
|
|
273
|
-
const
|
|
305
|
+
const _translate = this.config.translator || translate;
|
|
306
|
+
const translatedValue = await _translate({
|
|
274
307
|
text: change.sourceText,
|
|
275
308
|
lang: this.config.defaultLocale,
|
|
276
309
|
targetLang: targetLocale,
|
|
@@ -290,7 +323,7 @@ var TranslationService = class {
|
|
|
290
323
|
targetFlat.set(change.key, change.currentTranslation);
|
|
291
324
|
}
|
|
292
325
|
}
|
|
293
|
-
}
|
|
326
|
+
})));
|
|
294
327
|
}
|
|
295
328
|
for (const change of changes.unchanged) {
|
|
296
329
|
if (change.currentTranslation) {
|
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -75,6 +75,13 @@ interface SyncConfig {
|
|
|
75
75
|
lockFilePath: string;
|
|
76
76
|
apiKey?: string;
|
|
77
77
|
model?: string;
|
|
78
|
+
translator?: (params: {
|
|
79
|
+
text: string;
|
|
80
|
+
lang: string;
|
|
81
|
+
targetLang: string;
|
|
82
|
+
apiKey?: string;
|
|
83
|
+
model?: string;
|
|
84
|
+
}) => Promise<string>;
|
|
78
85
|
}
|
|
79
86
|
/**
|
|
80
87
|
* Flattens a nested object to a map of flat paths
|
package/dist/index.d.ts
CHANGED
|
@@ -75,6 +75,13 @@ interface SyncConfig {
|
|
|
75
75
|
lockFilePath: string;
|
|
76
76
|
apiKey?: string;
|
|
77
77
|
model?: string;
|
|
78
|
+
translator?: (params: {
|
|
79
|
+
text: string;
|
|
80
|
+
lang: string;
|
|
81
|
+
targetLang: string;
|
|
82
|
+
apiKey?: string;
|
|
83
|
+
model?: string;
|
|
84
|
+
}) => Promise<string>;
|
|
78
85
|
}
|
|
79
86
|
/**
|
|
80
87
|
* Flattens a nested object to a map of flat paths
|
package/dist/index.js
CHANGED
|
@@ -146,11 +146,23 @@ var translate = async ({ text, lang, targetLang, apiKey, model }) => {
|
|
|
146
146
|
const openrouter = (0, import_ai_sdk_provider.createOpenRouter)({
|
|
147
147
|
apiKey: token
|
|
148
148
|
});
|
|
149
|
+
const system = `
|
|
150
|
+
You are a professional translator. Your main task is to translate text from "${lang}" to "${targetLang}".
|
|
151
|
+
|
|
152
|
+
IMPORTANT RULES:
|
|
153
|
+
1. Return ONLY the translated text. No explanations, no quotes around the output.
|
|
154
|
+
2. Do NOT translate text inside curly braces, e.g., "{name}", "{count}". These are variables and must remain exactly as they are.
|
|
155
|
+
3. Maintain the original tone and context.
|
|
156
|
+
4. If the text is a single word or short phrase, translate it directly.
|
|
157
|
+
|
|
158
|
+
Examples:
|
|
159
|
+
- Input: "Hello {name}, welcome back!" -> Output (ES): "Hola {name}, \xA1bienvenido de nuevo!"
|
|
160
|
+
- Input: "Contact us calling to {number}" -> Output (FR): "Contactez-nous en appelant le {number}"
|
|
161
|
+
`;
|
|
149
162
|
const { output } = await (0, import_ai.generateText)({
|
|
150
163
|
model: openrouter.languageModel(model || "google/gemini-2.5-flash"),
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
Text to translate: ${text}`,
|
|
164
|
+
system,
|
|
165
|
+
prompt: `Text to translate: "${text}"`,
|
|
154
166
|
output: import_ai.Output.object({
|
|
155
167
|
schema: import_zod.default.object({
|
|
156
168
|
translatedText: import_zod.default.string()
|
|
@@ -282,9 +294,30 @@ var TranslationService = class {
|
|
|
282
294
|
}
|
|
283
295
|
let translatedCount = 0;
|
|
284
296
|
if (toTranslate.length > 0) {
|
|
285
|
-
|
|
297
|
+
const limit = (concurrency) => {
|
|
298
|
+
let active = 0;
|
|
299
|
+
const queue = [];
|
|
300
|
+
const run = async (fn) => {
|
|
301
|
+
if (active >= concurrency) {
|
|
302
|
+
await new Promise((resolve) => queue.push(resolve));
|
|
303
|
+
}
|
|
304
|
+
active++;
|
|
305
|
+
try {
|
|
306
|
+
return await fn();
|
|
307
|
+
} finally {
|
|
308
|
+
active--;
|
|
309
|
+
if (queue.length > 0) {
|
|
310
|
+
queue.shift()();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
return run;
|
|
315
|
+
};
|
|
316
|
+
const runTask = limit(5);
|
|
317
|
+
await Promise.all(toTranslate.map((change) => runTask(async () => {
|
|
286
318
|
try {
|
|
287
|
-
const
|
|
319
|
+
const _translate = this.config.translator || translate;
|
|
320
|
+
const translatedValue = await _translate({
|
|
288
321
|
text: change.sourceText,
|
|
289
322
|
lang: this.config.defaultLocale,
|
|
290
323
|
targetLang: targetLocale,
|
|
@@ -304,7 +337,7 @@ var TranslationService = class {
|
|
|
304
337
|
targetFlat.set(change.key, change.currentTranslation);
|
|
305
338
|
}
|
|
306
339
|
}
|
|
307
|
-
}
|
|
340
|
+
})));
|
|
308
341
|
}
|
|
309
342
|
for (const change of changes.unchanged) {
|
|
310
343
|
if (change.currentTranslation) {
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ccgp/i18n-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "AI-powered i18n translation and synchronization library",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -19,8 +19,7 @@
|
|
|
19
19
|
"build": "tsup src/index.ts src/cli.ts --format esm,cjs --dts --clean",
|
|
20
20
|
"dev": "tsup --watch",
|
|
21
21
|
"start": "node dist/cli.js",
|
|
22
|
-
"lint": "tsc --noEmit"
|
|
23
|
-
"prepublishOnly": "bun run build"
|
|
22
|
+
"lint": "tsc --noEmit"
|
|
24
23
|
},
|
|
25
24
|
"repository": {
|
|
26
25
|
"type": "git",
|