@ccgp/i18n-ai 0.1.2 → 0.1.4
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 +43 -30
- package/dist/cli.js +0 -17
- package/dist/cli.mjs +0 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,8 +5,10 @@ AI-powered internationalization (i18n) translation and synchronization library.
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- 🤖 **AI-Powered**: Uses Google Gemini (via OpenRouter) to generate context-aware translations.
|
|
8
|
-
-
|
|
9
|
-
-
|
|
8
|
+
- � **Parallel Processing**: Translations run concurrently for maximum speed (5x faster).
|
|
9
|
+
- �🔄 **Smart Synchronization**: Detects new, modified, and obsolete keys.
|
|
10
|
+
- �️ **Variable Protection**: Automatically preserves variables like `{name}` or `{count}`.
|
|
11
|
+
- �🔒 **Lock File System**: Prevents unnecessary re-translations of already translated content.
|
|
10
12
|
- 🧩 **Framework Agnostic**: Works with any i18n library that uses JSON files (next-intl, react-i18next, etc.).
|
|
11
13
|
|
|
12
14
|
## Installation
|
|
@@ -23,32 +25,7 @@ npm install @ccgp/i18n-ai
|
|
|
23
25
|
|
|
24
26
|
The easiest way to use `i18n-ai` is via the CLI.
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
```json
|
|
29
|
-
{
|
|
30
|
-
"scripts": {
|
|
31
|
-
"i18n:sync": "i18n-ai sync"
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Or run it directly:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
bun x i18n-ai sync
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
#### Configuration
|
|
43
|
-
|
|
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
|
-
|
|
46
|
-
i18n-ai sync --locales en,es,fr --default en --dir messages
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Configuration File (Recommended)
|
|
50
|
-
|
|
51
|
-
You can run `i18n-ai init` to generate a configuration file interactively:
|
|
28
|
+
1. **Initialize configuration:**
|
|
52
29
|
|
|
53
30
|
```bash
|
|
54
31
|
bun x i18n-ai init
|
|
@@ -64,7 +41,7 @@ This will create an `i18n-ai.config.json` file:
|
|
|
64
41
|
}
|
|
65
42
|
```
|
|
66
43
|
|
|
67
|
-
|
|
44
|
+
2. **Run synchronization:**
|
|
68
45
|
|
|
69
46
|
```bash
|
|
70
47
|
bun x i18n-ai sync
|
|
@@ -80,12 +57,48 @@ Create a `.env` file:
|
|
|
80
57
|
OPENROUTER_API_KEY=your_api_key
|
|
81
58
|
```
|
|
82
59
|
|
|
60
|
+
### Automate with GitHub Actions
|
|
61
|
+
|
|
62
|
+
You can automate translations whenever you push changes to your default locale.
|
|
63
|
+
|
|
64
|
+
Create `.github/workflows/i18n-translate.yml`:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
name: AI Translation Sync
|
|
68
|
+
|
|
69
|
+
on:
|
|
70
|
+
push:
|
|
71
|
+
paths:
|
|
72
|
+
- 'public/messages/en.json' # Monitor ONLY source locale
|
|
73
|
+
|
|
74
|
+
jobs:
|
|
75
|
+
translate:
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
permissions:
|
|
78
|
+
contents: write
|
|
79
|
+
steps:
|
|
80
|
+
- uses: actions/checkout@v4
|
|
81
|
+
- uses: oven-sh/setup-bun@v1
|
|
82
|
+
- run: bun install
|
|
83
|
+
- run: bun x i18n-ai sync
|
|
84
|
+
env:
|
|
85
|
+
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
86
|
+
- run: |
|
|
87
|
+
git config --global user.name "github-actions[bot]"
|
|
88
|
+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
|
89
|
+
if [[ -n $(git status -s) ]]; then
|
|
90
|
+
git add public/messages/*.json translation-lock.json
|
|
91
|
+
git commit -m "chore(i18n): update translations [skip ci]"
|
|
92
|
+
git push
|
|
93
|
+
fi
|
|
94
|
+
```
|
|
95
|
+
|
|
83
96
|
## Programmatic Usage
|
|
84
97
|
|
|
85
98
|
You can also use the library programmatically:
|
|
86
99
|
|
|
87
100
|
```typescript
|
|
88
|
-
import { TranslationService } from 'i18n-ai';
|
|
101
|
+
import { TranslationService } from '@ccgp/i18n-ai';
|
|
89
102
|
import { resolve } from 'path';
|
|
90
103
|
|
|
91
104
|
const service = new TranslationService({
|
package/dist/cli.js
CHANGED
|
@@ -415,23 +415,6 @@ program.command("sync").description("Synchronize translations using AI").option(
|
|
|
415
415
|
let locales = options.locales ? options.locales.split(",") : config ? config.locales : [];
|
|
416
416
|
let defaultLocale = options.default || (config ? config.defaultLocale : null);
|
|
417
417
|
if (!messagesDir || locales.length === 0 || !defaultLocale) {
|
|
418
|
-
if (!config) {
|
|
419
|
-
try {
|
|
420
|
-
const routingPath = (0, import_path2.join)(cwd, "i18n", "routing.ts");
|
|
421
|
-
const routingContent = await (0, import_promises3.readFile)(routingPath, "utf-8").catch(() => "");
|
|
422
|
-
if (routingContent) {
|
|
423
|
-
const localesMatch = routingContent.match(/locales:\s*\[([\s\S]*?)\]/);
|
|
424
|
-
const defaultMatch = routingContent.match(/defaultLocale:\s*["'](\w+)["']/);
|
|
425
|
-
if (localesMatch && locales.length === 0) {
|
|
426
|
-
locales = localesMatch[1].split(",").map((l) => l.trim().replace(/['"]/g, "")).filter(Boolean);
|
|
427
|
-
}
|
|
428
|
-
if (defaultMatch && !defaultLocale) {
|
|
429
|
-
defaultLocale = defaultMatch[1];
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
} catch {
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
418
|
if (!messagesDir) messagesDir = (0, import_path2.resolve)(cwd, "messages");
|
|
436
419
|
if (!defaultLocale) defaultLocale = "en";
|
|
437
420
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -79,23 +79,6 @@ program.command("sync").description("Synchronize translations using AI").option(
|
|
|
79
79
|
let locales = options.locales ? options.locales.split(",") : config ? config.locales : [];
|
|
80
80
|
let defaultLocale = options.default || (config ? config.defaultLocale : null);
|
|
81
81
|
if (!messagesDir || locales.length === 0 || !defaultLocale) {
|
|
82
|
-
if (!config) {
|
|
83
|
-
try {
|
|
84
|
-
const routingPath = join(cwd, "i18n", "routing.ts");
|
|
85
|
-
const routingContent = await readFile(routingPath, "utf-8").catch(() => "");
|
|
86
|
-
if (routingContent) {
|
|
87
|
-
const localesMatch = routingContent.match(/locales:\s*\[([\s\S]*?)\]/);
|
|
88
|
-
const defaultMatch = routingContent.match(/defaultLocale:\s*["'](\w+)["']/);
|
|
89
|
-
if (localesMatch && locales.length === 0) {
|
|
90
|
-
locales = localesMatch[1].split(",").map((l) => l.trim().replace(/['"]/g, "")).filter(Boolean);
|
|
91
|
-
}
|
|
92
|
-
if (defaultMatch && !defaultLocale) {
|
|
93
|
-
defaultLocale = defaultMatch[1];
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
} catch {
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
82
|
if (!messagesDir) messagesDir = resolve(cwd, "messages");
|
|
100
83
|
if (!defaultLocale) defaultLocale = "en";
|
|
101
84
|
}
|