glotto 2.9.0 → 3.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/README.md +150 -54
- package/esm/cli.js +155 -43
- package/esm/deno.d.ts +5 -1
- package/esm/deno.js +18 -7
- package/esm/src/config.d.ts +4 -0
- package/esm/src/config.d.ts.map +1 -0
- package/esm/src/config.js +95 -0
- package/esm/src/contants.d.ts +6 -2
- package/esm/src/contants.d.ts.map +1 -1
- package/esm/src/contants.js +34 -15
- package/esm/src/diff.d.ts +4 -0
- package/esm/src/diff.d.ts.map +1 -0
- package/esm/src/diff.js +53 -0
- package/esm/src/file.d.ts +5 -9
- package/esm/src/file.d.ts.map +1 -1
- package/esm/src/file.js +14 -103
- package/esm/src/providers/anthropic.d.ts +6 -11
- package/esm/src/providers/anthropic.d.ts.map +1 -1
- package/esm/src/providers/anthropic.js +21 -107
- package/esm/src/providers/gemini.d.ts +6 -11
- package/esm/src/providers/gemini.d.ts.map +1 -1
- package/esm/src/providers/gemini.js +20 -113
- package/esm/src/providers/openai.d.ts +6 -11
- package/esm/src/providers/openai.d.ts.map +1 -1
- package/esm/src/providers/openai.js +17 -108
- package/esm/src/translator.d.ts +15 -0
- package/esm/src/translator.d.ts.map +1 -0
- package/esm/src/translator.js +284 -0
- package/esm/src/types.d.ts +58 -13
- package/esm/src/types.d.ts.map +1 -1
- package/esm/src/utilites.d.ts +3 -10
- package/esm/src/utilites.d.ts.map +1 -1
- package/esm/src/utilites.js +41 -131
- package/package.json +20 -6
- package/schema/glotto.schema.json +87 -0
- package/script/cli.js +153 -41
- package/script/deno.d.ts +5 -1
- package/script/deno.js +18 -7
- package/script/src/config.d.ts +4 -0
- package/script/src/config.d.ts.map +1 -0
- package/script/src/config.js +132 -0
- package/script/src/contants.d.ts +6 -2
- package/script/src/contants.d.ts.map +1 -1
- package/script/src/contants.js +35 -16
- package/script/src/diff.d.ts +4 -0
- package/script/src/diff.d.ts.map +1 -0
- package/script/src/diff.js +57 -0
- package/script/src/file.d.ts +5 -9
- package/script/src/file.d.ts.map +1 -1
- package/script/src/file.js +19 -113
- package/script/src/providers/anthropic.d.ts +6 -11
- package/script/src/providers/anthropic.d.ts.map +1 -1
- package/script/src/providers/anthropic.js +20 -106
- package/script/src/providers/gemini.d.ts +6 -11
- package/script/src/providers/gemini.d.ts.map +1 -1
- package/script/src/providers/gemini.js +19 -112
- package/script/src/providers/openai.d.ts +6 -11
- package/script/src/providers/openai.d.ts.map +1 -1
- package/script/src/providers/openai.js +16 -107
- package/script/src/translator.d.ts +15 -0
- package/script/src/translator.d.ts.map +1 -0
- package/script/src/translator.js +294 -0
- package/script/src/types.d.ts +58 -13
- package/script/src/types.d.ts.map +1 -1
- package/script/src/utilites.d.ts +3 -10
- package/script/src/utilites.d.ts.map +1 -1
- package/script/src/utilites.js +44 -138
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts +0 -23
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_common16.js +0 -51
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_common32.d.ts +0 -35
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_common32.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_common32.js +0 -192
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_common64.d.ts +0 -35
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_common64.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_common64.js +0 -113
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts +0 -4
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_common_detach.js +0 -13
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts +0 -9
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_types.js +0 -2
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.d.ts +0 -2
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.js +0 -26
- package/esm/deps/jsr.io/@std/encoding/1.0.10/ascii85.d.ts +0 -61
- package/esm/deps/jsr.io/@std/encoding/1.0.10/ascii85.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.10/ascii85.js +0 -152
- package/esm/deps/jsr.io/@std/encoding/1.0.10/base32.d.ts +0 -40
- package/esm/deps/jsr.io/@std/encoding/1.0.10/base32.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.10/base32.js +0 -87
- package/esm/deps/jsr.io/@std/encoding/1.0.10/base58.d.ts +0 -40
- package/esm/deps/jsr.io/@std/encoding/1.0.10/base58.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.10/base58.js +0 -131
- package/esm/deps/jsr.io/@std/encoding/1.0.10/base64.d.ts +0 -40
- package/esm/deps/jsr.io/@std/encoding/1.0.10/base64.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.10/base64.js +0 -82
- package/esm/deps/jsr.io/@std/encoding/1.0.10/base64url.d.ts +0 -40
- package/esm/deps/jsr.io/@std/encoding/1.0.10/base64url.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.10/base64url.js +0 -72
- package/esm/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts +0 -39
- package/esm/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.10/hex.js +0 -87
- package/esm/deps/jsr.io/@std/encoding/1.0.10/mod.d.ts +0 -98
- package/esm/deps/jsr.io/@std/encoding/1.0.10/mod.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.10/mod.js +0 -99
- package/esm/deps/jsr.io/@std/encoding/1.0.10/varint.d.ts +0 -120
- package/esm/deps/jsr.io/@std/encoding/1.0.10/varint.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.10/varint.js +0 -205
- package/script/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts +0 -23
- package/script/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/encoding/1.0.10/_common16.js +0 -57
- package/script/deps/jsr.io/@std/encoding/1.0.10/_common32.d.ts +0 -35
- package/script/deps/jsr.io/@std/encoding/1.0.10/_common32.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/encoding/1.0.10/_common32.js +0 -198
- package/script/deps/jsr.io/@std/encoding/1.0.10/_common64.d.ts +0 -35
- package/script/deps/jsr.io/@std/encoding/1.0.10/_common64.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/encoding/1.0.10/_common64.js +0 -119
- package/script/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts +0 -4
- package/script/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/encoding/1.0.10/_common_detach.js +0 -16
- package/script/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts +0 -9
- package/script/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/encoding/1.0.10/_types.js +0 -3
- package/script/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.d.ts +0 -2
- package/script/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.js +0 -29
- package/script/deps/jsr.io/@std/encoding/1.0.10/ascii85.d.ts +0 -61
- package/script/deps/jsr.io/@std/encoding/1.0.10/ascii85.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/encoding/1.0.10/ascii85.js +0 -156
- package/script/deps/jsr.io/@std/encoding/1.0.10/base32.d.ts +0 -40
- package/script/deps/jsr.io/@std/encoding/1.0.10/base32.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/encoding/1.0.10/base32.js +0 -91
- package/script/deps/jsr.io/@std/encoding/1.0.10/base58.d.ts +0 -40
- package/script/deps/jsr.io/@std/encoding/1.0.10/base58.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/encoding/1.0.10/base58.js +0 -135
- package/script/deps/jsr.io/@std/encoding/1.0.10/base64.d.ts +0 -40
- package/script/deps/jsr.io/@std/encoding/1.0.10/base64.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/encoding/1.0.10/base64.js +0 -86
- package/script/deps/jsr.io/@std/encoding/1.0.10/base64url.d.ts +0 -40
- package/script/deps/jsr.io/@std/encoding/1.0.10/base64url.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/encoding/1.0.10/base64url.js +0 -76
- package/script/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts +0 -39
- package/script/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/encoding/1.0.10/hex.js +0 -91
- package/script/deps/jsr.io/@std/encoding/1.0.10/mod.d.ts +0 -98
- package/script/deps/jsr.io/@std/encoding/1.0.10/mod.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/encoding/1.0.10/mod.js +0 -115
- package/script/deps/jsr.io/@std/encoding/1.0.10/varint.d.ts +0 -120
- package/script/deps/jsr.io/@std/encoding/1.0.10/varint.d.ts.map +0 -1
- package/script/deps/jsr.io/@std/encoding/1.0.10/varint.js +0 -211
package/README.md
CHANGED
|
@@ -1,32 +1,18 @@
|
|
|
1
1
|
# Glotto AI Translator
|
|
2
2
|
|
|
3
|
-
Glotto
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
## Features
|
|
7
|
-
|
|
8
|
-
- Translate JSON files while preserving the original structure
|
|
9
|
-
- Support for multiple language pairs
|
|
10
|
-
- Configurable batch processing with customizable key limits
|
|
11
|
-
- Command-line interface for easy integration into your workflow
|
|
3
|
+
Glotto translates i18n JSON files (i18next, react-intl, vue-i18n, etc.) using AI providers without forcing the model to produce JSON. It walks the input JSON,
|
|
4
|
+
extracts every string leaf with its path, sends them to the model as plain-text tagged batches, and reconstructs the JSON from the responses. The original
|
|
5
|
+
structure, keys, arrays, variables and HTML tags are preserved by Glotto itself — the model only sees and produces text.
|
|
12
6
|
|
|
13
7
|
## Installation
|
|
14
8
|
|
|
15
|
-
Glotto can be installed and used through either JSR (Deno) or npm. Choose the method that best suits your development environment.
|
|
16
|
-
|
|
17
9
|
### [JSR (Deno)](https://jsr.io/@ibodev/glotto)
|
|
18
10
|
|
|
19
|
-
#### Global Installation
|
|
20
|
-
|
|
21
|
-
Install Glotto globally to use it as a CLI tool from anywhere:
|
|
22
|
-
|
|
23
11
|
```bash
|
|
24
12
|
deno install --global --name glotto -A jsr:@ibodev/glotto
|
|
25
13
|
```
|
|
26
14
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Alternatively, run Glotto directly without installation:
|
|
15
|
+
Or run without installing:
|
|
30
16
|
|
|
31
17
|
```bash
|
|
32
18
|
deno run -A jsr:@ibodev/glotto
|
|
@@ -34,93 +20,203 @@ deno run -A jsr:@ibodev/glotto
|
|
|
34
20
|
|
|
35
21
|
### [npm (Node)](https://www.npmjs.com/package/glotto)
|
|
36
22
|
|
|
37
|
-
|
|
23
|
+
```bash
|
|
24
|
+
npm install --global glotto
|
|
25
|
+
```
|
|
38
26
|
|
|
39
|
-
|
|
27
|
+
Or via npx:
|
|
40
28
|
|
|
41
29
|
```bash
|
|
42
|
-
|
|
30
|
+
npx glotto
|
|
43
31
|
```
|
|
44
32
|
|
|
45
|
-
|
|
33
|
+
### [pnpm](https://pnpm.io/)
|
|
46
34
|
|
|
47
|
-
|
|
35
|
+
```bash
|
|
36
|
+
pnpm add --global glotto
|
|
37
|
+
```
|
|
48
38
|
|
|
49
|
-
|
|
39
|
+
Or via pnpx:
|
|
50
40
|
|
|
51
41
|
```bash
|
|
52
|
-
|
|
42
|
+
pnpx glotto
|
|
53
43
|
```
|
|
54
44
|
|
|
55
45
|
## Usage
|
|
56
46
|
|
|
57
|
-
### Basic
|
|
47
|
+
### Basic (default OpenAI)
|
|
58
48
|
|
|
59
49
|
```bash
|
|
60
|
-
glotto --key <
|
|
50
|
+
glotto --key <openai-api-key> -i en.json -o tr.json -f English -t Turkish
|
|
61
51
|
```
|
|
62
52
|
|
|
63
|
-
###
|
|
53
|
+
### Gemini
|
|
64
54
|
|
|
65
55
|
```bash
|
|
66
|
-
glotto --key <
|
|
56
|
+
glotto --key <gemini-api-key> -i en.json -o ar.json -f English -t Arabic -p gemini
|
|
67
57
|
```
|
|
68
58
|
|
|
69
|
-
|
|
59
|
+
### OpenAI / OpenAI-compatible endpoints
|
|
70
60
|
|
|
71
61
|
```bash
|
|
72
|
-
glotto --key <openai-api-key>
|
|
62
|
+
glotto --key <openai-api-key> -i en.json -o tr.json -f English -t Turkish -p openai
|
|
73
63
|
```
|
|
74
64
|
|
|
75
|
-
|
|
65
|
+
Custom base URL (e.g. self-hosted OpenAI-compatible server):
|
|
76
66
|
|
|
77
67
|
```bash
|
|
78
|
-
glotto --key <
|
|
68
|
+
glotto --key <key> -i en.json -o tr.json -f English -t Turkish \
|
|
69
|
+
-p openai -m <model-name> --url <base-url>
|
|
79
70
|
```
|
|
80
71
|
|
|
81
|
-
|
|
72
|
+
### Anthropic (Claude)
|
|
82
73
|
|
|
83
74
|
```bash
|
|
84
|
-
glotto --key <anthropic-api-key>
|
|
75
|
+
glotto --key <anthropic-api-key> -i en.json -o tr.json -f English -t Turkish -p anthropic
|
|
85
76
|
```
|
|
86
77
|
|
|
87
|
-
Override
|
|
78
|
+
### Override the model
|
|
88
79
|
|
|
89
80
|
```bash
|
|
90
|
-
glotto --key <
|
|
81
|
+
glotto --key <key> -i en.json -o tr.json -f English -t Turkish -p openai -m <model-name>
|
|
91
82
|
```
|
|
92
83
|
|
|
93
|
-
###
|
|
84
|
+
### Multi-target — translate to several languages in one run
|
|
94
85
|
|
|
95
|
-
-
|
|
96
|
-
- `--input` or `-i`: Source JSON file
|
|
97
|
-
- `--output` or `-o`: Target JSON file
|
|
98
|
-
- `--from` or `-f`: Source language
|
|
99
|
-
- `--to` or `-t`: Target language
|
|
100
|
-
- `--provider` or `-p`: AI provider (`gemini` | `openai` | `anthropic`, default: `gemini`)
|
|
101
|
-
- `--model` or `-m`: Model name for the selected provider (defaults: `gemini-2.5-flash`, `gpt-4.1-mini`, `claude-3-5-haiku-latest`)
|
|
102
|
-
- `--url`: Custom base URL for OpenAI/Anthropic (optional)
|
|
86
|
+
Pass comma-separated values to `-t/--to` and `-o/--output`. The two lists must have the same length; entries pair up by index.
|
|
103
87
|
|
|
104
|
-
|
|
88
|
+
```bash
|
|
89
|
+
glotto --key <key> -i en.json -f English \
|
|
90
|
+
-t "Turkish,French,German" \
|
|
91
|
+
-o "tr.json,fr.json,de.json"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Incremental — translate only missing keys
|
|
95
|
+
|
|
96
|
+
When `--incremental` is set and the target file already exists, Glotto compares it against the source and only sends keys whose target value is missing or
|
|
97
|
+
empty. The existing target structure is preserved; only the missing values are filled in.
|
|
105
98
|
|
|
106
|
-
|
|
99
|
+
```bash
|
|
100
|
+
glotto --key <key> -i en.json -o tr.json -f English -t Turkish --incremental
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
If the target file does not exist, Glotto falls back to a full translation.
|
|
104
|
+
|
|
105
|
+
### Stats — see token usage
|
|
106
|
+
|
|
107
|
+
`--stats` prints input/output token totals, call counts, batch sizes and per-target breakdown after the run finishes. Off by default.
|
|
107
108
|
|
|
108
109
|
```bash
|
|
109
|
-
|
|
110
|
+
glotto --key <key> -i en.json -o tr.json -f English -t Turkish --stats
|
|
110
111
|
```
|
|
111
112
|
|
|
112
|
-
###
|
|
113
|
+
### Config file (`glotto.config.json`)
|
|
114
|
+
|
|
115
|
+
You can keep flags in a config file so you don't have to retype them. Glotto looks for `glotto.config.json` in the current working directory by default; pass
|
|
116
|
+
`--config <path>` to use a custom location. CLI flags always override config values.
|
|
117
|
+
|
|
118
|
+
Reference the JSON Schema for editor autocompletion and validation:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"$schema": "https://raw.githubusercontent.com/ibodev1/glotto/main/schema/glotto.schema.json",
|
|
123
|
+
"provider": "openai",
|
|
124
|
+
"model": "gpt-4.1-mini",
|
|
125
|
+
"input": "locales/en.json",
|
|
126
|
+
"from": "English",
|
|
127
|
+
"to": ["Turkish", "French", "German"],
|
|
128
|
+
"output": ["locales/tr.json", "locales/fr.json", "locales/de.json"],
|
|
129
|
+
"incremental": true,
|
|
130
|
+
"stats": true
|
|
131
|
+
}
|
|
132
|
+
```
|
|
113
133
|
|
|
114
|
-
|
|
134
|
+
With this config you only need to provide the API key on the CLI:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
glotto --key <key>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Disable rate-limit delay and request timeout
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
glotto --key <key> -i en.json -o tr.json -f English -t Turkish --no-limit --no-timeout
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Tune batch size
|
|
147
|
+
|
|
148
|
+
`--max-batch-size` is the maximum source bytes per batch, in KB. Smaller batches mean more requests but lower per-request token cost; larger batches mean fewer
|
|
149
|
+
requests.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
glotto --key <key> -i en.json -o tr.json -f English -t Turkish --max-batch-size 8
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Parameters
|
|
156
|
+
|
|
157
|
+
| Flag | Description |
|
|
158
|
+
| ------------------ | ------------------------------------------------------------------------------------ |
|
|
159
|
+
| `--key` | API key for the chosen provider (required) |
|
|
160
|
+
| `-p`, `--provider` | `gemini` \| `openai` \| `anthropic` (default `openai`) |
|
|
161
|
+
| `-m`, `--model` | Model name (defaults: `gpt-4.1-mini`, `gemini-2.5-flash`, `claude-3-5-haiku-latest`) |
|
|
162
|
+
| `-i`, `--input` | Source JSON file (required) |
|
|
163
|
+
| `-o`, `--output` | Target JSON file path. Comma-separated for multi-target (required) |
|
|
164
|
+
| `-f`, `--from` | Source language |
|
|
165
|
+
| `-t`, `--to` | Target language. Comma-separated for multi-target |
|
|
166
|
+
| `--url` | Custom base URL for OpenAI/Anthropic |
|
|
167
|
+
| `--config` | Path to a config file (default: `./glotto.config.json` if present) |
|
|
168
|
+
| `--stats` | Print AI usage stats (tokens, calls, bytes) at the end |
|
|
169
|
+
| `--incremental` | Translate only missing/empty keys when the target already exists |
|
|
170
|
+
| `--no-limit` | Skip the inter-batch rate-limit delay |
|
|
171
|
+
| `--no-timeout` | Disable request timeout |
|
|
172
|
+
| `--max-batch-size` | Max source bytes per batch in KB (default 12) |
|
|
173
|
+
| `-h`, `--help` | Help |
|
|
174
|
+
| `-v`, `--version` | Version |
|
|
175
|
+
|
|
176
|
+
## Config file fields
|
|
177
|
+
|
|
178
|
+
`glotto.config.json` accepts the keys below. CLI flags override config values.
|
|
179
|
+
|
|
180
|
+
| Key | Type | Notes |
|
|
181
|
+
| -------------- | ----------------------------------- | --------------------------------------- |
|
|
182
|
+
| `key` | string | API key |
|
|
183
|
+
| `provider` | `openai` \| `gemini` \| `anthropic` | |
|
|
184
|
+
| `model` | string | |
|
|
185
|
+
| `input` | string | Source JSON path |
|
|
186
|
+
| `from` | string | Source language |
|
|
187
|
+
| `to` | string \| string[] | Single target or array for multi-target |
|
|
188
|
+
| `output` | string \| string[] | Must match `to` length when array |
|
|
189
|
+
| `url` | string | Custom base URL |
|
|
190
|
+
| `noLimit` | boolean | |
|
|
191
|
+
| `noTimeout` | boolean | |
|
|
192
|
+
| `maxBatchSize` | integer (KB) | |
|
|
193
|
+
| `stats` | boolean | |
|
|
194
|
+
| `incremental` | boolean | |
|
|
195
|
+
|
|
196
|
+
## Development
|
|
197
|
+
|
|
198
|
+
Run from source:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
deno task cli --key <key> -i en.json -o tr.json -f English -t Turkish
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Build a single-file binary:
|
|
115
205
|
|
|
116
206
|
```bash
|
|
117
207
|
deno task build
|
|
118
208
|
```
|
|
119
209
|
|
|
120
|
-
|
|
210
|
+
Build npm package:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
deno task build:npm
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Star us
|
|
121
217
|
|
|
122
|
-
|
|
218
|
+
If Glotto saves you time, consider giving it a ⭐ on [GitHub](https://github.com/ibodev1/glotto) — it really helps the project reach more people.
|
|
123
219
|
|
|
124
220
|
## License
|
|
125
221
|
|
|
126
|
-
MIT
|
|
222
|
+
MIT © 2026
|
package/esm/cli.js
CHANGED
|
@@ -3,8 +3,11 @@ import "./_dnt.polyfills.js";
|
|
|
3
3
|
import * as dntShim from "./_dnt.shims.js";
|
|
4
4
|
import { Spinner } from './deps/jsr.io/@std/cli/1.0.29/unstable_spinner.js';
|
|
5
5
|
import { parseArgs } from './deps/jsr.io/@std/cli/1.0.29/mod.js';
|
|
6
|
-
import { getImportJson,
|
|
7
|
-
import {
|
|
6
|
+
import { getImportJson, readJsonIfExists, resolvePath, writeOutput } from './src/file.js';
|
|
7
|
+
import { extractLeaves, groupIntoBatches, reconstruct, runBatches } from './src/translator.js';
|
|
8
|
+
import { findMissingLeaves, mergeTranslations } from './src/diff.js';
|
|
9
|
+
import { applyConfig, loadConfig } from './src/config.js';
|
|
10
|
+
import { DEFAULT_MAX_BATCH_BYTES, DEFAULT_PROVIDER, GITHUB_REPO_URL, HELP_TEXT } from './src/contants.js';
|
|
8
11
|
import { formatBytes, validateArgs } from './src/utilites.js';
|
|
9
12
|
import { logger } from './src/logger.js';
|
|
10
13
|
import denoJson from './deno.js';
|
|
@@ -12,11 +15,110 @@ import Gemini from './src/providers/gemini.js';
|
|
|
12
15
|
import OpenAIModel from './src/providers/openai.js';
|
|
13
16
|
import AnthropicModel from './src/providers/anthropic.js';
|
|
14
17
|
const spinner = new Spinner({ message: 'AI Thinks...', color: 'cyan' });
|
|
18
|
+
function createTranslator(args, options) {
|
|
19
|
+
switch (args.provider) {
|
|
20
|
+
case 'gemini':
|
|
21
|
+
return new Gemini(args.key, args.model, options);
|
|
22
|
+
case 'openai':
|
|
23
|
+
return new OpenAIModel(args.key, args.url, args.model, options);
|
|
24
|
+
case 'anthropic':
|
|
25
|
+
return new AnthropicModel(args.key, args.url, args.model, options);
|
|
26
|
+
default:
|
|
27
|
+
throw new Error(`Unknown provider: ${args.provider}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function printBanner() {
|
|
31
|
+
logger.box(`Glotto AI Translator v${denoJson.version}\n${GITHUB_REPO_URL}`);
|
|
32
|
+
}
|
|
33
|
+
function printStats(args, perTarget) {
|
|
34
|
+
const totalInput = perTarget.reduce((sum, t) => sum + t.usage.inputTokens, 0);
|
|
35
|
+
const totalOutput = perTarget.reduce((sum, t) => sum + t.usage.outputTokens, 0);
|
|
36
|
+
const totalCalls = perTarget.reduce((sum, t) => sum + t.calls, 0);
|
|
37
|
+
const totalBytes = perTarget.reduce((sum, t) => sum + t.bytes, 0);
|
|
38
|
+
const totalTranslated = perTarget.reduce((sum, t) => sum + t.translatedCount, 0);
|
|
39
|
+
const lines = [];
|
|
40
|
+
lines.push(`Provider: ${args.provider}${args.model ? ` (${args.model})` : ''}`);
|
|
41
|
+
lines.push(`Targets: ${perTarget.length}`);
|
|
42
|
+
lines.push('');
|
|
43
|
+
for (const t of perTarget) {
|
|
44
|
+
const mode = t.incrementalApplied ? ' [incremental]' : '';
|
|
45
|
+
lines.push(`→ ${t.to} (${t.output})${mode}: ${t.translatedCount}/${t.fullCount} entries, ${t.batchCount} batch(es), ${formatBytes(t.bytes)}, ${t.calls} call(s)`);
|
|
46
|
+
lines.push(` tokens — in: ${t.usage.inputTokens}, out: ${t.usage.outputTokens}`);
|
|
47
|
+
}
|
|
48
|
+
lines.push('');
|
|
49
|
+
lines.push(`Total: ${totalTranslated} entries translated, ${totalCalls} call(s), ${formatBytes(totalBytes)}`);
|
|
50
|
+
lines.push(`Total tokens — in: ${totalInput}, out: ${totalOutput}, sum: ${totalInput + totalOutput}`);
|
|
51
|
+
logger.box(lines.join('\n'));
|
|
52
|
+
}
|
|
53
|
+
async function translateForTarget(validatedArgs, fileContent, allLeaves, to, outputRel, translator, translateOptions, targetIndex, totalTargets) {
|
|
54
|
+
const outputPath = resolvePath(outputRel);
|
|
55
|
+
const targetLabel = `[Target ${targetIndex + 1}/${totalTargets}: ${to}]`;
|
|
56
|
+
logger.info(`${targetLabel} → ${outputRel}`);
|
|
57
|
+
const translatableLeaves = allLeaves.filter((leaf) => leaf.translatable);
|
|
58
|
+
let leavesToTranslate = translatableLeaves;
|
|
59
|
+
let existingTarget = null;
|
|
60
|
+
let incrementalApplied = false;
|
|
61
|
+
if (validatedArgs.incremental) {
|
|
62
|
+
existingTarget = await readJsonIfExists(outputPath);
|
|
63
|
+
if (existingTarget !== null) {
|
|
64
|
+
leavesToTranslate = findMissingLeaves(allLeaves, existingTarget);
|
|
65
|
+
incrementalApplied = true;
|
|
66
|
+
logger.info(`${targetLabel} Incremental: ${leavesToTranslate.length}/${translatableLeaves.length} entries missing in existing target`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
logger.info(`${targetLabel} Incremental: target file not found, doing full translation`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (leavesToTranslate.length === 0) {
|
|
73
|
+
logger.info(`${targetLabel} Nothing to translate, writing existing/source content`);
|
|
74
|
+
const content = existingTarget !== null ? existingTarget : fileContent;
|
|
75
|
+
await writeOutput(outputPath, JSON.stringify(content, null, 2));
|
|
76
|
+
return {
|
|
77
|
+
to,
|
|
78
|
+
output: outputRel,
|
|
79
|
+
fullCount: translatableLeaves.length,
|
|
80
|
+
translatedCount: 0,
|
|
81
|
+
batchCount: 0,
|
|
82
|
+
bytes: 0,
|
|
83
|
+
calls: 0,
|
|
84
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
85
|
+
incrementalApplied,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const batches = groupIntoBatches(leavesToTranslate, validatedArgs.maxBatchBytes);
|
|
89
|
+
const totalBytes = batches.reduce((sum, b) => sum + b.byteSize, 0);
|
|
90
|
+
logger.info(`${targetLabel} ${leavesToTranslate.length} translatable entries, ${formatBytes(totalBytes)}, split into ${batches.length} batch(es)`);
|
|
91
|
+
for (const batch of batches) {
|
|
92
|
+
logger.info(` Batch ${batch.index + 1}: ${batch.leaves.length} entries, ${formatBytes(batch.byteSize)}`);
|
|
93
|
+
}
|
|
94
|
+
spinner.start();
|
|
95
|
+
const result = await runBatches(batches, translator, validatedArgs.from, to, translateOptions);
|
|
96
|
+
spinner.stop();
|
|
97
|
+
let finalContent;
|
|
98
|
+
if (incrementalApplied && existingTarget !== null) {
|
|
99
|
+
finalContent = mergeTranslations(existingTarget, allLeaves, result.translations);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
finalContent = reconstruct(allLeaves, result.translations);
|
|
103
|
+
}
|
|
104
|
+
await writeOutput(outputPath, JSON.stringify(finalContent, null, 2));
|
|
105
|
+
return {
|
|
106
|
+
to,
|
|
107
|
+
output: outputRel,
|
|
108
|
+
fullCount: translatableLeaves.length,
|
|
109
|
+
translatedCount: result.translations.size,
|
|
110
|
+
batchCount: batches.length,
|
|
111
|
+
bytes: totalBytes,
|
|
112
|
+
calls: result.calls,
|
|
113
|
+
usage: result.usage,
|
|
114
|
+
incrementalApplied,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
15
117
|
async function main() {
|
|
16
118
|
try {
|
|
17
119
|
const args = parseArgs(dntShim.Deno.args, {
|
|
18
|
-
string: ['key', 'provider', 'model', 'input', 'output', 'from', 'to', 'url'],
|
|
19
|
-
boolean: ['help', 'version'],
|
|
120
|
+
string: ['key', 'provider', 'model', 'input', 'output', 'from', 'to', 'url', 'max-batch-size', 'config'],
|
|
121
|
+
boolean: ['help', 'version', 'no-limit', 'no-timeout', 'stats', 'incremental'],
|
|
20
122
|
alias: {
|
|
21
123
|
provider: 'p',
|
|
22
124
|
model: 'm',
|
|
@@ -28,75 +130,85 @@ async function main() {
|
|
|
28
130
|
help: 'h',
|
|
29
131
|
version: 'v',
|
|
30
132
|
},
|
|
31
|
-
default: { provider: DEFAULT_PROVIDER },
|
|
32
133
|
});
|
|
33
134
|
const help = args.help || dntShim.Deno.args.length === 0;
|
|
34
135
|
const version = args.version;
|
|
35
136
|
if (version) {
|
|
36
|
-
|
|
37
|
-
logger.info('Glotto version: ' + VERSION);
|
|
137
|
+
logger.info('Glotto version: ' + denoJson.version);
|
|
38
138
|
dntShim.Deno.exit(0);
|
|
39
139
|
}
|
|
40
140
|
if (help) {
|
|
141
|
+
printBanner();
|
|
41
142
|
logger.box(HELP_TEXT);
|
|
42
143
|
dntShim.Deno.exit(0);
|
|
43
144
|
}
|
|
44
|
-
|
|
145
|
+
printBanner();
|
|
146
|
+
const config = await loadConfig(args.config);
|
|
147
|
+
const merged = applyConfig(args, config, dntShim.Deno.args);
|
|
148
|
+
if (!merged.provider) {
|
|
149
|
+
merged.provider = DEFAULT_PROVIDER;
|
|
150
|
+
}
|
|
151
|
+
const validatedArgs = validateArgs(merged);
|
|
45
152
|
const fileContent = await getImportJson(validatedArgs.input);
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const totalKeys = chunks.reduce((sum, c) => sum + c.keyCount, 0);
|
|
49
|
-
const outputPath = resolvePath(validatedArgs.output);
|
|
153
|
+
const allLeaves = extractLeaves(fileContent);
|
|
154
|
+
const translatableLeaves = allLeaves.filter((leaf) => leaf.translatable);
|
|
50
155
|
logger.info('Provider: ', validatedArgs.provider);
|
|
51
156
|
logger.info('Input: ', validatedArgs.input);
|
|
52
|
-
logger.info('
|
|
157
|
+
logger.info('Targets: ', validatedArgs.to.map((to, i) => `${to} → ${validatedArgs.output[i]}`).join(', '));
|
|
53
158
|
logger.info('From: ', validatedArgs.from);
|
|
54
|
-
logger.info('To: ', validatedArgs.to);
|
|
55
159
|
if (validatedArgs.model) {
|
|
56
160
|
logger.info('Model: ', validatedArgs.model);
|
|
57
161
|
}
|
|
58
162
|
if (validatedArgs.url) {
|
|
59
163
|
logger.info('URL: ', validatedArgs.url);
|
|
60
164
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
logger.info(` Chunk ${chunk.index + 1}: ${chunk.keyCount} keys, ${formatBytes(chunk.byteSize)}`);
|
|
165
|
+
if (validatedArgs.noLimit) {
|
|
166
|
+
logger.info('Rate limit protection: disabled (--no-limit)');
|
|
64
167
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
default: {
|
|
84
|
-
logger.warn('Provider not found');
|
|
85
|
-
break;
|
|
168
|
+
if (validatedArgs.noTimeout) {
|
|
169
|
+
logger.info('Request timeout: disabled (--no-timeout)');
|
|
170
|
+
}
|
|
171
|
+
if (validatedArgs.incremental) {
|
|
172
|
+
logger.info('Incremental mode: enabled (--incremental)');
|
|
173
|
+
}
|
|
174
|
+
if (validatedArgs.stats) {
|
|
175
|
+
logger.info('Stats: enabled (--stats)');
|
|
176
|
+
}
|
|
177
|
+
if (validatedArgs.maxBatchBytes !== DEFAULT_MAX_BATCH_BYTES) {
|
|
178
|
+
logger.info(`Max batch size: ${formatBytes(validatedArgs.maxBatchBytes)}`);
|
|
179
|
+
}
|
|
180
|
+
logger.info(`Source: ${translatableLeaves.length} translatable entries (of ${allLeaves.length} leaves)`);
|
|
181
|
+
if (translatableLeaves.length === 0 && !validatedArgs.incremental) {
|
|
182
|
+
logger.warn('No translatable entries found, copying input to output(s)');
|
|
183
|
+
for (const outputRel of validatedArgs.output) {
|
|
184
|
+
await writeOutput(resolvePath(outputRel), JSON.stringify(fileContent, null, 2));
|
|
86
185
|
}
|
|
186
|
+
logger.success('Translation completed');
|
|
187
|
+
logger.info(`★ Glotto faydalı olduysa GitHub'da yıldızlamayı unutma: ${GITHUB_REPO_URL}`);
|
|
188
|
+
dntShim.Deno.exit(0);
|
|
189
|
+
}
|
|
190
|
+
const translateOptions = {
|
|
191
|
+
noLimit: validatedArgs.noLimit,
|
|
192
|
+
noTimeout: validatedArgs.noTimeout,
|
|
193
|
+
};
|
|
194
|
+
const translator = createTranslator(validatedArgs, translateOptions);
|
|
195
|
+
const perTarget = [];
|
|
196
|
+
for (let i = 0; i < validatedArgs.to.length; i++) {
|
|
197
|
+
const stat = await translateForTarget(validatedArgs, fileContent, allLeaves, validatedArgs.to[i], validatedArgs.output[i], translator, translateOptions, i, validatedArgs.to.length);
|
|
198
|
+
perTarget.push(stat);
|
|
87
199
|
}
|
|
88
|
-
await writeOutput(outputPath, result);
|
|
89
200
|
spinner.stop();
|
|
90
201
|
logger.success('Translation completed');
|
|
202
|
+
if (validatedArgs.stats) {
|
|
203
|
+
printStats(validatedArgs, perTarget);
|
|
204
|
+
}
|
|
205
|
+
logger.info(`★ Glotto faydalı olduysa GitHub'da yıldızlamayı unutma: ${GITHUB_REPO_URL}`);
|
|
206
|
+
dntShim.Deno.exit(0);
|
|
91
207
|
}
|
|
92
208
|
catch (error) {
|
|
93
209
|
spinner.stop();
|
|
94
210
|
logger.error(error);
|
|
95
211
|
dntShim.Deno.exit(1);
|
|
96
212
|
}
|
|
97
|
-
finally {
|
|
98
|
-
spinner.stop();
|
|
99
|
-
dntShim.Deno.exit(0);
|
|
100
|
-
}
|
|
101
213
|
}
|
|
102
214
|
main();
|
package/esm/deno.d.ts
CHANGED
|
@@ -11,19 +11,23 @@ declare namespace _default {
|
|
|
11
11
|
publish: string;
|
|
12
12
|
"publish:npm": string;
|
|
13
13
|
check: string;
|
|
14
|
+
test: string;
|
|
14
15
|
};
|
|
15
16
|
namespace fmt {
|
|
16
17
|
let semiColons: boolean;
|
|
17
18
|
let singleQuote: boolean;
|
|
18
19
|
let lineWidth: number;
|
|
19
20
|
}
|
|
21
|
+
namespace publish {
|
|
22
|
+
let exclude: string[];
|
|
23
|
+
}
|
|
20
24
|
let imports: {
|
|
21
25
|
"@anthropic-ai/sdk": string;
|
|
22
26
|
"@deno/dnt": string;
|
|
23
27
|
"@google/genai": string;
|
|
24
28
|
"@openai/openai": string;
|
|
29
|
+
"@std/assert": string;
|
|
25
30
|
"@std/cli": string;
|
|
26
|
-
"@std/encoding": string;
|
|
27
31
|
"@std/path": string;
|
|
28
32
|
consola: string;
|
|
29
33
|
};
|
package/esm/deno.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
"name": "@ibodev/glotto",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"exports": "./cli.ts",
|
|
5
5
|
"lock": false,
|
|
6
6
|
"nodeModulesDir": "auto",
|
|
@@ -10,22 +10,33 @@ export default {
|
|
|
10
10
|
"build:npm": "deno run -A scripts/build_npm.ts",
|
|
11
11
|
"publish": "deno publish",
|
|
12
12
|
"publish:npm": "cd ./npm && npm publish",
|
|
13
|
-
"check": "deno check **/*.ts"
|
|
13
|
+
"check": "deno check **/*.ts",
|
|
14
|
+
"test": "deno test -A --no-check tests/"
|
|
14
15
|
},
|
|
15
16
|
"fmt": {
|
|
16
17
|
"semiColons": true,
|
|
17
18
|
"singleQuote": true,
|
|
18
19
|
"lineWidth": 160
|
|
19
20
|
},
|
|
21
|
+
"publish": {
|
|
22
|
+
"exclude": [
|
|
23
|
+
"tests/",
|
|
24
|
+
"scripts/",
|
|
25
|
+
"npm/",
|
|
26
|
+
"assets/",
|
|
27
|
+
"glotto.exe",
|
|
28
|
+
"CLAUDE.md"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
20
31
|
"imports": {
|
|
21
|
-
"@anthropic-ai/sdk": "npm:@anthropic-ai/sdk@^0.95.
|
|
32
|
+
"@anthropic-ai/sdk": "npm:@anthropic-ai/sdk@^0.95.1",
|
|
22
33
|
"@deno/dnt": "jsr:@deno/dnt@^0.42.3",
|
|
23
|
-
"@google/genai": "npm:@google/genai@^
|
|
24
|
-
"@openai/openai": "npm:openai@^6.
|
|
34
|
+
"@google/genai": "npm:@google/genai@^2.0.0",
|
|
35
|
+
"@openai/openai": "npm:openai@^6.37.0",
|
|
36
|
+
"@std/assert": "jsr:@std/assert@^1.0.19",
|
|
25
37
|
"@std/cli": "jsr:@std/cli@^1.0.29",
|
|
26
|
-
"@std/encoding": "jsr:@std/encoding@^1.0.10",
|
|
27
38
|
"@std/path": "jsr:@std/path@^1.1.4",
|
|
28
39
|
"consola": "npm:consola@^3.4.2"
|
|
29
40
|
},
|
|
30
|
-
"allowScripts": ["npm:@google/genai@1.52.0", "npm:protobufjs@7.5.6"]
|
|
41
|
+
"allowScripts": ["npm:@google/genai@1.52.0", "npm:@google/genai@2.0.0", "npm:protobufjs@7.5.6"]
|
|
31
42
|
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { GlottoConfig, TranslateArgs } from './types.js';
|
|
2
|
+
export declare function loadConfig(explicitPath?: string): Promise<GlottoConfig>;
|
|
3
|
+
export declare function applyConfig(cli: TranslateArgs, config: GlottoConfig, rawArgs: string[]): TranslateArgs;
|
|
4
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAuB9D,wBAAsB,UAAU,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAc7E;AAkBD,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,aAAa,CA0CtG"}
|