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.
Files changed (151) hide show
  1. package/README.md +150 -54
  2. package/esm/cli.js +155 -43
  3. package/esm/deno.d.ts +5 -1
  4. package/esm/deno.js +18 -7
  5. package/esm/src/config.d.ts +4 -0
  6. package/esm/src/config.d.ts.map +1 -0
  7. package/esm/src/config.js +95 -0
  8. package/esm/src/contants.d.ts +6 -2
  9. package/esm/src/contants.d.ts.map +1 -1
  10. package/esm/src/contants.js +34 -15
  11. package/esm/src/diff.d.ts +4 -0
  12. package/esm/src/diff.d.ts.map +1 -0
  13. package/esm/src/diff.js +53 -0
  14. package/esm/src/file.d.ts +5 -9
  15. package/esm/src/file.d.ts.map +1 -1
  16. package/esm/src/file.js +14 -103
  17. package/esm/src/providers/anthropic.d.ts +6 -11
  18. package/esm/src/providers/anthropic.d.ts.map +1 -1
  19. package/esm/src/providers/anthropic.js +21 -107
  20. package/esm/src/providers/gemini.d.ts +6 -11
  21. package/esm/src/providers/gemini.d.ts.map +1 -1
  22. package/esm/src/providers/gemini.js +20 -113
  23. package/esm/src/providers/openai.d.ts +6 -11
  24. package/esm/src/providers/openai.d.ts.map +1 -1
  25. package/esm/src/providers/openai.js +17 -108
  26. package/esm/src/translator.d.ts +15 -0
  27. package/esm/src/translator.d.ts.map +1 -0
  28. package/esm/src/translator.js +284 -0
  29. package/esm/src/types.d.ts +58 -13
  30. package/esm/src/types.d.ts.map +1 -1
  31. package/esm/src/utilites.d.ts +3 -10
  32. package/esm/src/utilites.d.ts.map +1 -1
  33. package/esm/src/utilites.js +41 -131
  34. package/package.json +20 -6
  35. package/schema/glotto.schema.json +87 -0
  36. package/script/cli.js +153 -41
  37. package/script/deno.d.ts +5 -1
  38. package/script/deno.js +18 -7
  39. package/script/src/config.d.ts +4 -0
  40. package/script/src/config.d.ts.map +1 -0
  41. package/script/src/config.js +132 -0
  42. package/script/src/contants.d.ts +6 -2
  43. package/script/src/contants.d.ts.map +1 -1
  44. package/script/src/contants.js +35 -16
  45. package/script/src/diff.d.ts +4 -0
  46. package/script/src/diff.d.ts.map +1 -0
  47. package/script/src/diff.js +57 -0
  48. package/script/src/file.d.ts +5 -9
  49. package/script/src/file.d.ts.map +1 -1
  50. package/script/src/file.js +19 -113
  51. package/script/src/providers/anthropic.d.ts +6 -11
  52. package/script/src/providers/anthropic.d.ts.map +1 -1
  53. package/script/src/providers/anthropic.js +20 -106
  54. package/script/src/providers/gemini.d.ts +6 -11
  55. package/script/src/providers/gemini.d.ts.map +1 -1
  56. package/script/src/providers/gemini.js +19 -112
  57. package/script/src/providers/openai.d.ts +6 -11
  58. package/script/src/providers/openai.d.ts.map +1 -1
  59. package/script/src/providers/openai.js +16 -107
  60. package/script/src/translator.d.ts +15 -0
  61. package/script/src/translator.d.ts.map +1 -0
  62. package/script/src/translator.js +294 -0
  63. package/script/src/types.d.ts +58 -13
  64. package/script/src/types.d.ts.map +1 -1
  65. package/script/src/utilites.d.ts +3 -10
  66. package/script/src/utilites.d.ts.map +1 -1
  67. package/script/src/utilites.js +44 -138
  68. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts +0 -23
  69. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts.map +0 -1
  70. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common16.js +0 -51
  71. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common32.d.ts +0 -35
  72. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common32.d.ts.map +0 -1
  73. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common32.js +0 -192
  74. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common64.d.ts +0 -35
  75. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common64.d.ts.map +0 -1
  76. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common64.js +0 -113
  77. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts +0 -4
  78. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts.map +0 -1
  79. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common_detach.js +0 -13
  80. package/esm/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts +0 -9
  81. package/esm/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts.map +0 -1
  82. package/esm/deps/jsr.io/@std/encoding/1.0.10/_types.js +0 -2
  83. package/esm/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.d.ts +0 -2
  84. package/esm/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.d.ts.map +0 -1
  85. package/esm/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.js +0 -26
  86. package/esm/deps/jsr.io/@std/encoding/1.0.10/ascii85.d.ts +0 -61
  87. package/esm/deps/jsr.io/@std/encoding/1.0.10/ascii85.d.ts.map +0 -1
  88. package/esm/deps/jsr.io/@std/encoding/1.0.10/ascii85.js +0 -152
  89. package/esm/deps/jsr.io/@std/encoding/1.0.10/base32.d.ts +0 -40
  90. package/esm/deps/jsr.io/@std/encoding/1.0.10/base32.d.ts.map +0 -1
  91. package/esm/deps/jsr.io/@std/encoding/1.0.10/base32.js +0 -87
  92. package/esm/deps/jsr.io/@std/encoding/1.0.10/base58.d.ts +0 -40
  93. package/esm/deps/jsr.io/@std/encoding/1.0.10/base58.d.ts.map +0 -1
  94. package/esm/deps/jsr.io/@std/encoding/1.0.10/base58.js +0 -131
  95. package/esm/deps/jsr.io/@std/encoding/1.0.10/base64.d.ts +0 -40
  96. package/esm/deps/jsr.io/@std/encoding/1.0.10/base64.d.ts.map +0 -1
  97. package/esm/deps/jsr.io/@std/encoding/1.0.10/base64.js +0 -82
  98. package/esm/deps/jsr.io/@std/encoding/1.0.10/base64url.d.ts +0 -40
  99. package/esm/deps/jsr.io/@std/encoding/1.0.10/base64url.d.ts.map +0 -1
  100. package/esm/deps/jsr.io/@std/encoding/1.0.10/base64url.js +0 -72
  101. package/esm/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts +0 -39
  102. package/esm/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts.map +0 -1
  103. package/esm/deps/jsr.io/@std/encoding/1.0.10/hex.js +0 -87
  104. package/esm/deps/jsr.io/@std/encoding/1.0.10/mod.d.ts +0 -98
  105. package/esm/deps/jsr.io/@std/encoding/1.0.10/mod.d.ts.map +0 -1
  106. package/esm/deps/jsr.io/@std/encoding/1.0.10/mod.js +0 -99
  107. package/esm/deps/jsr.io/@std/encoding/1.0.10/varint.d.ts +0 -120
  108. package/esm/deps/jsr.io/@std/encoding/1.0.10/varint.d.ts.map +0 -1
  109. package/esm/deps/jsr.io/@std/encoding/1.0.10/varint.js +0 -205
  110. package/script/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts +0 -23
  111. package/script/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts.map +0 -1
  112. package/script/deps/jsr.io/@std/encoding/1.0.10/_common16.js +0 -57
  113. package/script/deps/jsr.io/@std/encoding/1.0.10/_common32.d.ts +0 -35
  114. package/script/deps/jsr.io/@std/encoding/1.0.10/_common32.d.ts.map +0 -1
  115. package/script/deps/jsr.io/@std/encoding/1.0.10/_common32.js +0 -198
  116. package/script/deps/jsr.io/@std/encoding/1.0.10/_common64.d.ts +0 -35
  117. package/script/deps/jsr.io/@std/encoding/1.0.10/_common64.d.ts.map +0 -1
  118. package/script/deps/jsr.io/@std/encoding/1.0.10/_common64.js +0 -119
  119. package/script/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts +0 -4
  120. package/script/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts.map +0 -1
  121. package/script/deps/jsr.io/@std/encoding/1.0.10/_common_detach.js +0 -16
  122. package/script/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts +0 -9
  123. package/script/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts.map +0 -1
  124. package/script/deps/jsr.io/@std/encoding/1.0.10/_types.js +0 -3
  125. package/script/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.d.ts +0 -2
  126. package/script/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.d.ts.map +0 -1
  127. package/script/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.js +0 -29
  128. package/script/deps/jsr.io/@std/encoding/1.0.10/ascii85.d.ts +0 -61
  129. package/script/deps/jsr.io/@std/encoding/1.0.10/ascii85.d.ts.map +0 -1
  130. package/script/deps/jsr.io/@std/encoding/1.0.10/ascii85.js +0 -156
  131. package/script/deps/jsr.io/@std/encoding/1.0.10/base32.d.ts +0 -40
  132. package/script/deps/jsr.io/@std/encoding/1.0.10/base32.d.ts.map +0 -1
  133. package/script/deps/jsr.io/@std/encoding/1.0.10/base32.js +0 -91
  134. package/script/deps/jsr.io/@std/encoding/1.0.10/base58.d.ts +0 -40
  135. package/script/deps/jsr.io/@std/encoding/1.0.10/base58.d.ts.map +0 -1
  136. package/script/deps/jsr.io/@std/encoding/1.0.10/base58.js +0 -135
  137. package/script/deps/jsr.io/@std/encoding/1.0.10/base64.d.ts +0 -40
  138. package/script/deps/jsr.io/@std/encoding/1.0.10/base64.d.ts.map +0 -1
  139. package/script/deps/jsr.io/@std/encoding/1.0.10/base64.js +0 -86
  140. package/script/deps/jsr.io/@std/encoding/1.0.10/base64url.d.ts +0 -40
  141. package/script/deps/jsr.io/@std/encoding/1.0.10/base64url.d.ts.map +0 -1
  142. package/script/deps/jsr.io/@std/encoding/1.0.10/base64url.js +0 -76
  143. package/script/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts +0 -39
  144. package/script/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts.map +0 -1
  145. package/script/deps/jsr.io/@std/encoding/1.0.10/hex.js +0 -91
  146. package/script/deps/jsr.io/@std/encoding/1.0.10/mod.d.ts +0 -98
  147. package/script/deps/jsr.io/@std/encoding/1.0.10/mod.d.ts.map +0 -1
  148. package/script/deps/jsr.io/@std/encoding/1.0.10/mod.js +0 -115
  149. package/script/deps/jsr.io/@std/encoding/1.0.10/varint.d.ts +0 -120
  150. package/script/deps/jsr.io/@std/encoding/1.0.10/varint.d.ts.map +0 -1
  151. 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 is a powerful tool designed to translate i18n JSON files using advanced AI technology. It streamlines the localization process by automatically
4
- translating your language files to any target language while maintaining the JSON structure.
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
- #### Direct Execution
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
- #### Global Installation
23
+ ```bash
24
+ npm install --global glotto
25
+ ```
38
26
 
39
- Install Glotto globally via npm to use it as a CLI tool:
27
+ Or via npx:
40
28
 
41
29
  ```bash
42
- npm install --global glotto
30
+ npx glotto
43
31
  ```
44
32
 
45
- After installation, the `glotto` command will be available in your terminal.
33
+ ### [pnpm](https://pnpm.io/)
46
34
 
47
- #### Direct Execution
35
+ ```bash
36
+ pnpm add --global glotto
37
+ ```
48
38
 
49
- Run Glotto directly using npx without installation:
39
+ Or via pnpx:
50
40
 
51
41
  ```bash
52
- npx glotto
42
+ pnpx glotto
53
43
  ```
54
44
 
55
45
  ## Usage
56
46
 
57
- ### Basic usage (default Gemini)
47
+ ### Basic (default OpenAI)
58
48
 
59
49
  ```bash
60
- glotto --key <gemini-api-key> -i en.json -o ar.json -f English -t Arabic
50
+ glotto --key <openai-api-key> -i en.json -o tr.json -f English -t Turkish
61
51
  ```
62
52
 
63
- ### Use OpenAI instead of Gemini
53
+ ### Gemini
64
54
 
65
55
  ```bash
66
- glotto --key <openai-api-key> --input=en.json --output=tr.json --from=English --to=Turkish -p openai
56
+ glotto --key <gemini-api-key> -i en.json -o ar.json -f English -t Arabic -p gemini
67
57
  ```
68
58
 
69
- Optional custom OpenAI base URL:
59
+ ### OpenAI / OpenAI-compatible endpoints
70
60
 
71
61
  ```bash
72
- glotto --key <openai-api-key> --input=en.json --output=tr.json --from=English --to=Turkish -p openai --url=https://api.openai.com/v1
62
+ glotto --key <openai-api-key> -i en.json -o tr.json -f English -t Turkish -p openai
73
63
  ```
74
64
 
75
- ### Use Anthropic (Claude)
65
+ Custom base URL (e.g. self-hosted OpenAI-compatible server):
76
66
 
77
67
  ```bash
78
- glotto --key <anthropic-api-key> --input=en.json --output=tr.json --from=English --to=Turkish -p anthropic
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
- Optional custom Anthropic base URL:
72
+ ### Anthropic (Claude)
82
73
 
83
74
  ```bash
84
- glotto --key <anthropic-api-key> --input=en.json --output=tr.json --from=English --to=Turkish -p anthropic --url=https://api.anthropic.com
75
+ glotto --key <anthropic-api-key> -i en.json -o tr.json -f English -t Turkish -p anthropic
85
76
  ```
86
77
 
87
- Override default model for a provider:
78
+ ### Override the model
88
79
 
89
80
  ```bash
90
- glotto --key <openai-api-key> --input=en.json --output=tr.json --from=English --to=Turkish -p openai -m gpt-4.1
81
+ glotto --key <key> -i en.json -o tr.json -f English -t Turkish -p openai -m <model-name>
91
82
  ```
92
83
 
93
- ### Parameters
84
+ ### Multi-target — translate to several languages in one run
94
85
 
95
- - `--key`: API key for the selected AI provider
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
- ## Development
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
- To run the project in development mode:
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
- deno task cli --key <gemini-or-openai-api-key> --input=en.json --output=tr.json --from=English --to=Turkish -p gemini
110
+ glotto --key <key> -i en.json -o tr.json -f English -t Turkish --stats
110
111
  ```
111
112
 
112
- ### Building the Project
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
- To create a production build:
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
- ## Contributing
210
+ Build npm package:
211
+
212
+ ```bash
213
+ deno task build:npm
214
+ ```
215
+
216
+ ## Star us
121
217
 
122
- Contributions are welcome! Feel free to submit issues and pull requests.
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 Licence @ 2026
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, resolvePath, splitJson, writeOutput } from './src/file.js';
7
- import { DEFAULT_PROVIDER, HELP_TEXT } from './src/contants.js';
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
- const VERSION = denoJson.version;
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
- const validatedArgs = validateArgs(args);
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 chunks = splitJson(fileContent);
47
- const totalBytes = chunks.reduce((sum, c) => sum + c.byteSize, 0);
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('Output: ', validatedArgs.output);
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
- logger.info(`Total: ${totalKeys} keys, ${formatBytes(totalBytes)}, split into ${chunks.length} chunk(s)`);
62
- for (const chunk of chunks) {
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
- spinner.start();
66
- let result = '';
67
- switch (validatedArgs.provider) {
68
- case 'gemini': {
69
- const gemini = new Gemini(validatedArgs.key, chunks, validatedArgs.from, validatedArgs.to, validatedArgs.model);
70
- result = await gemini.translate();
71
- break;
72
- }
73
- case 'openai': {
74
- const openai = new OpenAIModel(validatedArgs.key, chunks, validatedArgs.from, validatedArgs.to, validatedArgs.url, validatedArgs.model);
75
- result = await openai.translate();
76
- break;
77
- }
78
- case 'anthropic': {
79
- const anthropic = new AnthropicModel(validatedArgs.key, chunks, validatedArgs.from, validatedArgs.to, validatedArgs.url, validatedArgs.model);
80
- result = await anthropic.translate();
81
- break;
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": "2.9.0",
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.0",
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@^1.52.0",
24
- "@openai/openai": "npm:openai@^6.36.0",
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"}