glotto 2.9.0 → 3.0.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 (138) hide show
  1. package/README.md +86 -55
  2. package/esm/cli.js +47 -39
  3. package/esm/deno.d.ts +0 -1
  4. package/esm/deno.js +1 -2
  5. package/esm/src/contants.d.ts +2 -2
  6. package/esm/src/contants.d.ts.map +1 -1
  7. package/esm/src/contants.js +22 -12
  8. package/esm/src/file.d.ts +2 -7
  9. package/esm/src/file.d.ts.map +1 -1
  10. package/esm/src/file.js +1 -108
  11. package/esm/src/providers/anthropic.d.ts +6 -11
  12. package/esm/src/providers/anthropic.d.ts.map +1 -1
  13. package/esm/src/providers/anthropic.js +14 -108
  14. package/esm/src/providers/gemini.d.ts +6 -11
  15. package/esm/src/providers/gemini.d.ts.map +1 -1
  16. package/esm/src/providers/gemini.js +13 -114
  17. package/esm/src/providers/openai.d.ts +6 -11
  18. package/esm/src/providers/openai.d.ts.map +1 -1
  19. package/esm/src/providers/openai.js +10 -109
  20. package/esm/src/translator.d.ts +8 -0
  21. package/esm/src/translator.d.ts.map +1 -0
  22. package/esm/src/translator.js +200 -0
  23. package/esm/src/types.d.ts +28 -11
  24. package/esm/src/types.d.ts.map +1 -1
  25. package/esm/src/utilites.d.ts +0 -6
  26. package/esm/src/utilites.d.ts.map +1 -1
  27. package/esm/src/utilites.js +18 -132
  28. package/package.json +1 -1
  29. package/script/cli.js +45 -37
  30. package/script/deno.d.ts +0 -1
  31. package/script/deno.js +1 -2
  32. package/script/src/contants.d.ts +2 -2
  33. package/script/src/contants.d.ts.map +1 -1
  34. package/script/src/contants.js +23 -13
  35. package/script/src/file.d.ts +2 -7
  36. package/script/src/file.d.ts.map +1 -1
  37. package/script/src/file.js +2 -114
  38. package/script/src/providers/anthropic.d.ts +6 -11
  39. package/script/src/providers/anthropic.d.ts.map +1 -1
  40. package/script/src/providers/anthropic.js +13 -107
  41. package/script/src/providers/gemini.d.ts +6 -11
  42. package/script/src/providers/gemini.d.ts.map +1 -1
  43. package/script/src/providers/gemini.js +12 -113
  44. package/script/src/providers/openai.d.ts +6 -11
  45. package/script/src/providers/openai.d.ts.map +1 -1
  46. package/script/src/providers/openai.js +9 -108
  47. package/script/src/translator.d.ts +8 -0
  48. package/script/src/translator.d.ts.map +1 -0
  49. package/script/src/translator.js +209 -0
  50. package/script/src/types.d.ts +28 -11
  51. package/script/src/types.d.ts.map +1 -1
  52. package/script/src/utilites.d.ts +0 -6
  53. package/script/src/utilites.d.ts.map +1 -1
  54. package/script/src/utilites.js +19 -136
  55. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts +0 -23
  56. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts.map +0 -1
  57. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common16.js +0 -51
  58. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common32.d.ts +0 -35
  59. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common32.d.ts.map +0 -1
  60. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common32.js +0 -192
  61. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common64.d.ts +0 -35
  62. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common64.d.ts.map +0 -1
  63. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common64.js +0 -113
  64. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts +0 -4
  65. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts.map +0 -1
  66. package/esm/deps/jsr.io/@std/encoding/1.0.10/_common_detach.js +0 -13
  67. package/esm/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts +0 -9
  68. package/esm/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts.map +0 -1
  69. package/esm/deps/jsr.io/@std/encoding/1.0.10/_types.js +0 -2
  70. package/esm/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.d.ts +0 -2
  71. package/esm/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.d.ts.map +0 -1
  72. package/esm/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.js +0 -26
  73. package/esm/deps/jsr.io/@std/encoding/1.0.10/ascii85.d.ts +0 -61
  74. package/esm/deps/jsr.io/@std/encoding/1.0.10/ascii85.d.ts.map +0 -1
  75. package/esm/deps/jsr.io/@std/encoding/1.0.10/ascii85.js +0 -152
  76. package/esm/deps/jsr.io/@std/encoding/1.0.10/base32.d.ts +0 -40
  77. package/esm/deps/jsr.io/@std/encoding/1.0.10/base32.d.ts.map +0 -1
  78. package/esm/deps/jsr.io/@std/encoding/1.0.10/base32.js +0 -87
  79. package/esm/deps/jsr.io/@std/encoding/1.0.10/base58.d.ts +0 -40
  80. package/esm/deps/jsr.io/@std/encoding/1.0.10/base58.d.ts.map +0 -1
  81. package/esm/deps/jsr.io/@std/encoding/1.0.10/base58.js +0 -131
  82. package/esm/deps/jsr.io/@std/encoding/1.0.10/base64.d.ts +0 -40
  83. package/esm/deps/jsr.io/@std/encoding/1.0.10/base64.d.ts.map +0 -1
  84. package/esm/deps/jsr.io/@std/encoding/1.0.10/base64.js +0 -82
  85. package/esm/deps/jsr.io/@std/encoding/1.0.10/base64url.d.ts +0 -40
  86. package/esm/deps/jsr.io/@std/encoding/1.0.10/base64url.d.ts.map +0 -1
  87. package/esm/deps/jsr.io/@std/encoding/1.0.10/base64url.js +0 -72
  88. package/esm/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts +0 -39
  89. package/esm/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts.map +0 -1
  90. package/esm/deps/jsr.io/@std/encoding/1.0.10/hex.js +0 -87
  91. package/esm/deps/jsr.io/@std/encoding/1.0.10/mod.d.ts +0 -98
  92. package/esm/deps/jsr.io/@std/encoding/1.0.10/mod.d.ts.map +0 -1
  93. package/esm/deps/jsr.io/@std/encoding/1.0.10/mod.js +0 -99
  94. package/esm/deps/jsr.io/@std/encoding/1.0.10/varint.d.ts +0 -120
  95. package/esm/deps/jsr.io/@std/encoding/1.0.10/varint.d.ts.map +0 -1
  96. package/esm/deps/jsr.io/@std/encoding/1.0.10/varint.js +0 -205
  97. package/script/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts +0 -23
  98. package/script/deps/jsr.io/@std/encoding/1.0.10/_common16.d.ts.map +0 -1
  99. package/script/deps/jsr.io/@std/encoding/1.0.10/_common16.js +0 -57
  100. package/script/deps/jsr.io/@std/encoding/1.0.10/_common32.d.ts +0 -35
  101. package/script/deps/jsr.io/@std/encoding/1.0.10/_common32.d.ts.map +0 -1
  102. package/script/deps/jsr.io/@std/encoding/1.0.10/_common32.js +0 -198
  103. package/script/deps/jsr.io/@std/encoding/1.0.10/_common64.d.ts +0 -35
  104. package/script/deps/jsr.io/@std/encoding/1.0.10/_common64.d.ts.map +0 -1
  105. package/script/deps/jsr.io/@std/encoding/1.0.10/_common64.js +0 -119
  106. package/script/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts +0 -4
  107. package/script/deps/jsr.io/@std/encoding/1.0.10/_common_detach.d.ts.map +0 -1
  108. package/script/deps/jsr.io/@std/encoding/1.0.10/_common_detach.js +0 -16
  109. package/script/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts +0 -9
  110. package/script/deps/jsr.io/@std/encoding/1.0.10/_types.d.ts.map +0 -1
  111. package/script/deps/jsr.io/@std/encoding/1.0.10/_types.js +0 -3
  112. package/script/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.d.ts +0 -2
  113. package/script/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.d.ts.map +0 -1
  114. package/script/deps/jsr.io/@std/encoding/1.0.10/_validate_binary_like.js +0 -29
  115. package/script/deps/jsr.io/@std/encoding/1.0.10/ascii85.d.ts +0 -61
  116. package/script/deps/jsr.io/@std/encoding/1.0.10/ascii85.d.ts.map +0 -1
  117. package/script/deps/jsr.io/@std/encoding/1.0.10/ascii85.js +0 -156
  118. package/script/deps/jsr.io/@std/encoding/1.0.10/base32.d.ts +0 -40
  119. package/script/deps/jsr.io/@std/encoding/1.0.10/base32.d.ts.map +0 -1
  120. package/script/deps/jsr.io/@std/encoding/1.0.10/base32.js +0 -91
  121. package/script/deps/jsr.io/@std/encoding/1.0.10/base58.d.ts +0 -40
  122. package/script/deps/jsr.io/@std/encoding/1.0.10/base58.d.ts.map +0 -1
  123. package/script/deps/jsr.io/@std/encoding/1.0.10/base58.js +0 -135
  124. package/script/deps/jsr.io/@std/encoding/1.0.10/base64.d.ts +0 -40
  125. package/script/deps/jsr.io/@std/encoding/1.0.10/base64.d.ts.map +0 -1
  126. package/script/deps/jsr.io/@std/encoding/1.0.10/base64.js +0 -86
  127. package/script/deps/jsr.io/@std/encoding/1.0.10/base64url.d.ts +0 -40
  128. package/script/deps/jsr.io/@std/encoding/1.0.10/base64url.d.ts.map +0 -1
  129. package/script/deps/jsr.io/@std/encoding/1.0.10/base64url.js +0 -76
  130. package/script/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts +0 -39
  131. package/script/deps/jsr.io/@std/encoding/1.0.10/hex.d.ts.map +0 -1
  132. package/script/deps/jsr.io/@std/encoding/1.0.10/hex.js +0 -91
  133. package/script/deps/jsr.io/@std/encoding/1.0.10/mod.d.ts +0 -98
  134. package/script/deps/jsr.io/@std/encoding/1.0.10/mod.d.ts.map +0 -1
  135. package/script/deps/jsr.io/@std/encoding/1.0.10/mod.js +0 -115
  136. package/script/deps/jsr.io/@std/encoding/1.0.10/varint.d.ts +0 -120
  137. package/script/deps/jsr.io/@std/encoding/1.0.10/varint.d.ts.map +0 -1
  138. package/script/deps/jsr.io/@std/encoding/1.0.10/varint.js +0 -211
package/README.md CHANGED
@@ -1,32 +1,42 @@
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.
6
+
7
+ ## Why this approach
8
+
9
+ - **Provider-agnostic**: works with Gemini, OpenAI/OpenAI-compatible (Ollama, vLLM, TogetherAI, etc.) and Anthropic. Every provider implements the same
10
+ `TextTranslator` contract: text in, text out.
11
+ - **No JSON deadlock**: the model is never asked to emit valid JSON, so grammar-constrained sampling, broken responses and infinite loops on local models are
12
+ eliminated.
13
+ - **Structure-safe**: keys, nesting, arrays and non-string leaves (numbers, booleans, null, empty objects/arrays) are preserved by reconstruction, not by the
14
+ model.
15
+ - **Token efficient**: source size per request is bounded; failed entries are retried individually instead of re-sending the whole batch.
16
+ - **Variable preservation**: the prompt instructs the model to keep `{{name}}`, `__VAR__`, `$t(...)`, `%s`, `%d`, HTML tags and markdown intact.
17
+
18
+ ## How it works
19
+
20
+ 1. **Extract** — recursively walk the JSON and collect every leaf as `{ id, path, value, translatable }`. Empty objects/arrays are kept as non-translatable
21
+ leaves so the structure is rebuildable.
22
+ 2. **Batch** — group translatable leaves into batches by source byte size (`--max-batch-size`, default 12 KB).
23
+ 3. **Encode** — wrap each entry as `≪id≫value≪/id≫` and send a single prompt per batch.
24
+ 4. **Translate** — call `provider.translate(prompt) → string`. No JSON mode is requested.
25
+ 5. **Decode** — parse the response with the same tag regex, mapping `id → translation`.
26
+ 6. **Reconstruct** — walk all leaves, place translated values (or original values for non-translatable leaves) back into a fresh object/array tree following the
27
+ recorded path, then write the resulting JSON.
28
+
29
+ If a batch response is missing some IDs, only the missing entries are retried (with exponential backoff) — the successful ones are kept.
12
30
 
13
31
  ## Installation
14
32
 
15
- Glotto can be installed and used through either JSR (Deno) or npm. Choose the method that best suits your development environment.
16
-
17
33
  ### [JSR (Deno)](https://jsr.io/@ibodev/glotto)
18
34
 
19
- #### Global Installation
20
-
21
- Install Glotto globally to use it as a CLI tool from anywhere:
22
-
23
35
  ```bash
24
36
  deno install --global --name glotto -A jsr:@ibodev/glotto
25
37
  ```
26
38
 
27
- #### Direct Execution
28
-
29
- Alternatively, run Glotto directly without installation:
39
+ Or run without installing:
30
40
 
31
41
  ```bash
32
42
  deno run -A jsr:@ibodev/glotto
@@ -34,19 +44,11 @@ deno run -A jsr:@ibodev/glotto
34
44
 
35
45
  ### [npm (Node)](https://www.npmjs.com/package/glotto)
36
46
 
37
- #### Global Installation
38
-
39
- Install Glotto globally via npm to use it as a CLI tool:
40
-
41
47
  ```bash
42
48
  npm install --global glotto
43
49
  ```
44
50
 
45
- After installation, the `glotto` command will be available in your terminal.
46
-
47
- #### Direct Execution
48
-
49
- Run Glotto directly using npx without installation:
51
+ Or via npx:
50
52
 
51
53
  ```bash
52
54
  npx glotto
@@ -54,73 +56,102 @@ npx glotto
54
56
 
55
57
  ## Usage
56
58
 
57
- ### Basic usage (default Gemini)
59
+ ### Basic (default Gemini)
58
60
 
59
61
  ```bash
60
62
  glotto --key <gemini-api-key> -i en.json -o ar.json -f English -t Arabic
61
63
  ```
62
64
 
63
- ### Use OpenAI instead of Gemini
65
+ ### OpenAI / OpenAI-compatible (Ollama, vLLM, etc.)
66
+
67
+ ```bash
68
+ glotto --key <openai-api-key> -i en.json -o tr.json -f English -t Turkish -p openai
69
+ ```
70
+
71
+ Custom base URL (e.g. local Ollama):
64
72
 
65
73
  ```bash
66
- glotto --key <openai-api-key> --input=en.json --output=tr.json --from=English --to=Turkish -p openai
74
+ glotto --key any-string -i en.json -o tr.json -f English -t Turkish \
75
+ -p openai -m qwen2.5:32b --url http://localhost:11434/v1
67
76
  ```
68
77
 
69
- Optional custom OpenAI base URL:
78
+ ### Anthropic (Claude)
70
79
 
71
80
  ```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
81
+ glotto --key <anthropic-api-key> -i en.json -o tr.json -f English -t Turkish -p anthropic
73
82
  ```
74
83
 
75
- ### Use Anthropic (Claude)
84
+ ### Override the model
76
85
 
77
86
  ```bash
78
- glotto --key <anthropic-api-key> --input=en.json --output=tr.json --from=English --to=Turkish -p anthropic
87
+ glotto --key <openai-api-key> -i en.json -o tr.json -f English -t Turkish -p openai -m gpt-4.1
79
88
  ```
80
89
 
81
- Optional custom Anthropic base URL:
90
+ ### Disable rate-limit delay and request timeout
82
91
 
83
92
  ```bash
84
- glotto --key <anthropic-api-key> --input=en.json --output=tr.json --from=English --to=Turkish -p anthropic --url=https://api.anthropic.com
93
+ glotto --key <key> -i en.json -o tr.json -f English -t Turkish --no-limit --no-timeout
85
94
  ```
86
95
 
87
- Override default model for a provider:
96
+ ### Tune batch size
97
+
98
+ `--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
99
+ requests.
88
100
 
89
101
  ```bash
90
- glotto --key <openai-api-key> --input=en.json --output=tr.json --from=English --to=Turkish -p openai -m gpt-4.1
102
+ glotto --key <key> -i en.json -o tr.json -f English -t Turkish --max-batch-size 8
91
103
  ```
92
104
 
93
- ### Parameters
105
+ ## Parameters
106
+
107
+ | Flag | Description |
108
+ | ------------------ | ------------------------------------------------------------------------------------ |
109
+ | `--key` | API key for the chosen provider (required) |
110
+ | `-p`, `--provider` | `gemini` \| `openai` \| `anthropic` (default `gemini`) |
111
+ | `-m`, `--model` | Model name (defaults: `gemini-2.5-flash`, `gpt-4.1-mini`, `claude-3-5-haiku-latest`) |
112
+ | `-i`, `--input` | Source JSON file (required) |
113
+ | `-o`, `--output` | Target JSON file (required) |
114
+ | `-f`, `--from` | Source language |
115
+ | `-t`, `--to` | Target language |
116
+ | `--url` | Custom base URL for OpenAI/Anthropic |
117
+ | `--no-limit` | Skip the inter-batch rate-limit delay |
118
+ | `--no-timeout` | Disable request timeout |
119
+ | `--max-batch-size` | Max source bytes per batch in KB (default 12) |
120
+ | `-h`, `--help` | Help |
121
+ | `-v`, `--version` | Version |
122
+
123
+ ## Recommended models
94
124
 
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)
125
+ For high-quality multilingual output via OpenAI-compatible endpoints (Ollama, vLLM):
126
+
127
+ - `aya-expanse:32b` Cohere multilingual specialist, strong Turkish/Arabic/EU languages
128
+ - `qwen2.5:32b` / `qwen2.5:14b` strong general multilingual + instruction-following
129
+ - `mistral-small3:24b` lighter, ~13 GB at q4
130
+ - `gemma3:27b-it` Google instruction-tuned
131
+
132
+ Pure translation-only models (e.g. translategemma) are not ideal here: the prompt asks the model to preserve tags and follow per-entry instructions, which
133
+ requires instruction-following rather than a sentence-translation fine-tune.
103
134
 
104
135
  ## Development
105
136
 
106
- To run the project in development mode:
137
+ Run from source:
107
138
 
108
139
  ```bash
109
- deno task cli --key <gemini-or-openai-api-key> --input=en.json --output=tr.json --from=English --to=Turkish -p gemini
140
+ deno task cli --key <key> -i en.json -o tr.json -f English -t Turkish -p gemini
110
141
  ```
111
142
 
112
- ### Building the Project
113
-
114
- To create a production build:
143
+ Build a single-file binary:
115
144
 
116
145
  ```bash
117
146
  deno task build
118
147
  ```
119
148
 
120
- ## Contributing
149
+ Build npm package:
121
150
 
122
- Contributions are welcome! Feel free to submit issues and pull requests.
151
+ ```bash
152
+ deno task build:npm
153
+ ```
123
154
 
124
155
  ## License
125
156
 
126
- MIT Licence @ 2026
157
+ MIT © 2026
package/esm/cli.js CHANGED
@@ -3,8 +3,9 @@ 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, resolvePath, writeOutput } from './src/file.js';
7
+ import { extractLeaves, groupIntoBatches, reconstruct, runBatches } from './src/translator.js';
8
+ import { DEFAULT_MAX_BATCH_BYTES, DEFAULT_PROVIDER, HELP_TEXT } from './src/contants.js';
8
9
  import { formatBytes, validateArgs } from './src/utilites.js';
9
10
  import { logger } from './src/logger.js';
10
11
  import denoJson from './deno.js';
@@ -12,11 +13,23 @@ import Gemini from './src/providers/gemini.js';
12
13
  import OpenAIModel from './src/providers/openai.js';
13
14
  import AnthropicModel from './src/providers/anthropic.js';
14
15
  const spinner = new Spinner({ message: 'AI Thinks...', color: 'cyan' });
16
+ const createTranslator = (args, options) => {
17
+ switch (args.provider) {
18
+ case 'gemini':
19
+ return new Gemini(args.key, args.model, options);
20
+ case 'openai':
21
+ return new OpenAIModel(args.key, args.url, args.model, options);
22
+ case 'anthropic':
23
+ return new AnthropicModel(args.key, args.url, args.model, options);
24
+ default:
25
+ throw new Error(`Unknown provider: ${args.provider}`);
26
+ }
27
+ };
15
28
  async function main() {
16
29
  try {
17
30
  const args = parseArgs(dntShim.Deno.args, {
18
- string: ['key', 'provider', 'model', 'input', 'output', 'from', 'to', 'url'],
19
- boolean: ['help', 'version'],
31
+ string: ['key', 'provider', 'model', 'input', 'output', 'from', 'to', 'url', 'max-batch-size'],
32
+ boolean: ['help', 'version', 'no-limit', 'no-timeout'],
20
33
  alias: {
21
34
  provider: 'p',
22
35
  model: 'm',
@@ -33,8 +46,7 @@ async function main() {
33
46
  const help = args.help || dntShim.Deno.args.length === 0;
34
47
  const version = args.version;
35
48
  if (version) {
36
- const VERSION = denoJson.version;
37
- logger.info('Glotto version: ' + VERSION);
49
+ logger.info('Glotto version: ' + denoJson.version);
38
50
  dntShim.Deno.exit(0);
39
51
  }
40
52
  if (help) {
@@ -43,49 +55,45 @@ async function main() {
43
55
  }
44
56
  const validatedArgs = validateArgs(args);
45
57
  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);
58
+ const allLeaves = extractLeaves(fileContent);
59
+ const translatableLeaves = allLeaves.filter((leaf) => leaf.translatable);
60
+ const batches = groupIntoBatches(allLeaves, validatedArgs.maxBatchBytes);
61
+ const totalBytes = batches.reduce((sum, b) => sum + b.byteSize, 0);
50
62
  logger.info('Provider: ', validatedArgs.provider);
51
63
  logger.info('Input: ', validatedArgs.input);
52
64
  logger.info('Output: ', validatedArgs.output);
53
65
  logger.info('From: ', validatedArgs.from);
54
66
  logger.info('To: ', validatedArgs.to);
55
- if (validatedArgs.model) {
67
+ if (validatedArgs.model)
56
68
  logger.info('Model: ', validatedArgs.model);
57
- }
58
- if (validatedArgs.url) {
69
+ if (validatedArgs.url)
59
70
  logger.info('URL: ', validatedArgs.url);
71
+ if (validatedArgs.noLimit)
72
+ logger.info('Rate limit protection: disabled (--no-limit)');
73
+ if (validatedArgs.noTimeout)
74
+ logger.info('Request timeout: disabled (--no-timeout)');
75
+ if (validatedArgs.maxBatchBytes !== DEFAULT_MAX_BATCH_BYTES) {
76
+ logger.info(`Max batch size: ${formatBytes(validatedArgs.maxBatchBytes)}`);
60
77
  }
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)}`);
78
+ logger.info(`Total: ${translatableLeaves.length} translatable entries (of ${allLeaves.length} leaves), ${formatBytes(totalBytes)}, split into ${batches.length} batch(es)`);
79
+ for (const batch of batches) {
80
+ logger.info(` Batch ${batch.index + 1}: ${batch.leaves.length} entries, ${formatBytes(batch.byteSize)}`);
64
81
  }
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;
86
- }
82
+ if (batches.length === 0) {
83
+ logger.warn('No translatable entries found, copying input to output');
84
+ await writeOutput(resolvePath(validatedArgs.output), JSON.stringify(fileContent, null, 2));
85
+ dntShim.Deno.exit(0);
87
86
  }
88
- await writeOutput(outputPath, result);
87
+ const translateOptions = {
88
+ noLimit: validatedArgs.noLimit,
89
+ noTimeout: validatedArgs.noTimeout,
90
+ };
91
+ const translator = createTranslator(validatedArgs, translateOptions);
92
+ spinner.start();
93
+ const translations = await runBatches(batches, translator, validatedArgs.from, validatedArgs.to, translateOptions);
94
+ const result = reconstruct(allLeaves, translations);
95
+ const outputPath = resolvePath(validatedArgs.output);
96
+ await writeOutput(outputPath, JSON.stringify(result, null, 2));
89
97
  spinner.stop();
90
98
  logger.success('Translation completed');
91
99
  }
package/esm/deno.d.ts CHANGED
@@ -23,7 +23,6 @@ declare namespace _default {
23
23
  "@google/genai": string;
24
24
  "@openai/openai": string;
25
25
  "@std/cli": string;
26
- "@std/encoding": string;
27
26
  "@std/path": string;
28
27
  consola: string;
29
28
  };
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.0.0",
4
4
  "exports": "./cli.ts",
5
5
  "lock": false,
6
6
  "nodeModulesDir": "auto",
@@ -23,7 +23,6 @@ export default {
23
23
  "@google/genai": "npm:@google/genai@^1.52.0",
24
24
  "@openai/openai": "npm:openai@^6.36.0",
25
25
  "@std/cli": "jsr:@std/cli@^1.0.29",
26
- "@std/encoding": "jsr:@std/encoding@^1.0.10",
27
26
  "@std/path": "jsr:@std/path@^1.1.4",
28
27
  "consola": "npm:consola@^3.4.2"
29
28
  },
@@ -1,9 +1,9 @@
1
1
  import type { Provider } from './types.js';
2
2
  export declare const DEFAULT_PROVIDER: Provider;
3
3
  export declare const DEFAULT_MODELS: Record<Provider, string>;
4
- export declare const DEFAULT_MAX_CHUNK_BYTES: 30000;
4
+ export declare const DEFAULT_MAX_BATCH_BYTES: 12000;
5
5
  export declare const MAX_RETRIES: 3;
6
6
  export declare const BASE_RETRY_DELAY_MS: 2000;
7
- export declare const INTER_CHUNK_DELAY_MS: 1500;
7
+ export declare const INTER_BATCH_DELAY_MS: 1500;
8
8
  export declare const HELP_TEXT: string;
9
9
  //# sourceMappingURL=contants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contants.d.ts","sourceRoot":"","sources":["../../src/src/contants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,eAAO,MAAM,gBAAgB,EAAE,QAAmB,CAAC;AAEnD,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAInD,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAG,KAAe,CAAC;AAEvD,eAAO,MAAM,WAAW,EAAG,CAAU,CAAC;AAEtC,eAAO,MAAM,mBAAmB,EAAG,IAAc,CAAC;AAElD,eAAO,MAAM,oBAAoB,EAAG,IAAc,CAAC;AAEnD,eAAO,MAAM,SAAS,QAsBrB,CAAC"}
1
+ {"version":3,"file":"contants.d.ts","sourceRoot":"","sources":["../../src/src/contants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,eAAO,MAAM,gBAAgB,EAAE,QAAmB,CAAC;AAEnD,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAInD,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAG,KAAe,CAAC;AAEvD,eAAO,MAAM,WAAW,EAAG,CAAU,CAAC;AAEtC,eAAO,MAAM,mBAAmB,EAAG,IAAc,CAAC;AAElD,eAAO,MAAM,oBAAoB,EAAG,IAAc,CAAC;AAEnD,eAAO,MAAM,SAAS,QAgCrB,CAAC"}
@@ -4,30 +4,40 @@ export const DEFAULT_MODELS = {
4
4
  openai: 'gpt-4.1-mini',
5
5
  anthropic: 'claude-3-5-haiku-latest',
6
6
  };
7
- export const DEFAULT_MAX_CHUNK_BYTES = 30_000;
7
+ export const DEFAULT_MAX_BATCH_BYTES = 12_000;
8
8
  export const MAX_RETRIES = 3;
9
9
  export const BASE_RETRY_DELAY_MS = 2_000;
10
- export const INTER_CHUNK_DELAY_MS = 1_500;
10
+ export const INTER_BATCH_DELAY_MS = 1_500;
11
11
  export const HELP_TEXT = `
12
12
  Glotto AI Translator
13
13
  -------------------
14
14
  A tool for translating i18n JSON files using AI services.
15
15
 
16
+ Glotto walks the input JSON, extracts every string leaf with its path, sends them to the
17
+ chosen provider as plain-text batches (using ≪id≫value≪/id≫ tagged entries), and
18
+ reconstructs the JSON from the responses. JSON structure, keys, variables and HTML tags
19
+ are preserved by the tool itself — the model only sees and produces text.
20
+
16
21
  Options:
17
- --key API key for the AI service (required)
18
- -p, --provider AI translation provider to use (default: ${DEFAULT_PROVIDER})
19
- -m, --model Model name for the selected provider (optional)
20
- -i, --input Path to source JSON file (required)
21
- -o, --output Path to target JSON file (required)
22
- -f, --from Source language (required)
23
- -t, --to Target language (required)
24
- --url Custom base URL for OpenAI/Anthropic (optional)
25
- -h, --help Display this help message
26
- -v, --version Display version
22
+ --key API key for the AI service (required)
23
+ -p, --provider AI translation provider to use (default: ${DEFAULT_PROVIDER})
24
+ -m, --model Model name for the selected provider (optional)
25
+ -i, --input Path to source JSON file (required)
26
+ -o, --output Path to target JSON file (required)
27
+ -f, --from Source language (required)
28
+ -t, --to Target language (required)
29
+ --url Custom base URL for OpenAI/Anthropic (optional)
30
+ --no-limit Disable rate limit delay between batches
31
+ --no-timeout Disable request timeout (wait indefinitely for AI response)
32
+ --max-batch-size Maximum source size per batch, in KB (default: ${DEFAULT_MAX_BATCH_BYTES / 1024} KB)
33
+ -h, --help Display this help message
34
+ -v, --version Display version
27
35
 
28
36
  Examples:
29
37
  glotto --key {{key}} --input=en.json --output=tr.json --from=english --to=turkish
30
38
  glotto --key {{key}} -i en.json -o tr.json -f english -t turkish -p gemini
31
39
  glotto --key {{key}} -i en.json -o tr.json -f english -t turkish -p openai
32
40
  glotto --key {{key}} -i en.json -o tr.json -f english -t turkish -p anthropic
41
+ glotto --key {{key}} -i en.json -o tr.json -f english -t turkish --no-limit --no-timeout
42
+ glotto --key {{key}} -i en.json -o tr.json -f english -t turkish --max-batch-size 8
33
43
  `;
package/esm/src/file.d.ts CHANGED
@@ -1,10 +1,5 @@
1
- import type { ChunkInfo, JsonObject } from './types.js';
1
+ import type { JsonValue } from './types.js';
2
2
  export declare const resolvePath: (...paths: string[]) => string;
3
- export declare const getImportJson: <T = JsonObject>(input: string) => Promise<T>;
3
+ export declare const getImportJson: <T = JsonValue>(input: string) => Promise<T>;
4
4
  export declare const writeOutput: (outputPath: string, content: string) => Promise<void>;
5
- export declare const existsFile: (path: string) => Promise<boolean>;
6
- export declare const ensureDirectoryExists: (directory: string) => Promise<void>;
7
- export declare const splitJson: (data: JsonObject, maxChunkBytes?: number) => ChunkInfo[];
8
- export declare const mergeInputs: (inputs: JsonObject[]) => JsonObject;
9
- export declare const writeTemp: (targetLanguage: string, tempJsonFileName: string, content: string) => Promise<void>;
10
5
  //# sourceMappingURL=file.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/src/file.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGxD,eAAO,MAAM,WAAW,GAAI,GAAG,OAAO,MAAM,EAAE,WAE7C,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,CAAC,GAAG,UAAU,EAAE,OAAO,MAAM,KAAG,OAAO,CAAC,CAAC,CAS5E,CAAC;AAEF,eAAO,MAAM,WAAW,GAAU,YAAY,MAAM,EAAE,SAAS,MAAM,kBAEpE,CAAC;AAEF,eAAO,MAAM,UAAU,GAAU,MAAM,MAAM,qBAW5C,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAU,WAAW,MAAM,KAAG,OAAO,CAAC,IAAI,CAK3E,CAAC;AAeF,eAAO,MAAM,SAAS,GAAI,MAAM,UAAU,EAAE,gBAAe,MAAe,KAAG,SAAS,EA4DrF,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,QAAQ,UAAU,EAAE,KAAG,UAElD,CAAC;AAkBF,eAAO,MAAM,SAAS,GAAU,gBAAgB,MAAM,EAAE,kBAAkB,MAAM,EAAE,SAAS,MAAM,kBAKhG,CAAC"}
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/src/file.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,eAAO,MAAM,WAAW,GAAI,GAAG,OAAO,MAAM,EAAE,KAAG,MAA4C,CAAC;AAE9F,eAAO,MAAM,aAAa,GAAU,CAAC,GAAG,SAAS,EAAE,OAAO,MAAM,KAAG,OAAO,CAAC,CAAC,CAO3E,CAAC;AAEF,eAAO,MAAM,WAAW,GAAU,YAAY,MAAM,EAAE,SAAS,MAAM,KAAG,OAAO,CAAC,IAAI,CAEnF,CAAC"}
package/esm/src/file.js CHANGED
@@ -1,9 +1,6 @@
1
1
  import * as dntShim from "../_dnt.shims.js";
2
2
  import { join } from '../deps/jsr.io/@std/path/1.1.4/mod.js';
3
- import { logger } from './logger.js';
4
- export const resolvePath = (...paths) => {
5
- return join(dntShim.Deno.cwd(), ...paths);
6
- };
3
+ export const resolvePath = (...paths) => join(dntShim.Deno.cwd(), ...paths);
7
4
  export const getImportJson = async (input) => {
8
5
  const filePath = resolvePath(input);
9
6
  const fileContent = await dntShim.Deno.readTextFile(filePath);
@@ -15,107 +12,3 @@ export const getImportJson = async (input) => {
15
12
  export const writeOutput = async (outputPath, content) => {
16
13
  await dntShim.Deno.writeTextFile(outputPath, content, { create: true });
17
14
  };
18
- export const existsFile = async (path) => {
19
- try {
20
- await dntShim.Deno.stat(path);
21
- return true;
22
- }
23
- catch (error) {
24
- if (error instanceof dntShim.Deno.errors.NotFound) {
25
- return false;
26
- }
27
- throw error;
28
- }
29
- };
30
- export const ensureDirectoryExists = async (directory) => {
31
- const directoryExists = await existsFile(directory);
32
- if (!directoryExists) {
33
- await dntShim.Deno.mkdir(directory, { recursive: true });
34
- }
35
- };
36
- const getByteSize = (str) => {
37
- return new TextEncoder().encode(str).byteLength;
38
- };
39
- const buildChunkWithSize = (data, keys) => {
40
- const chunk = Object.create(null);
41
- for (const key of keys) {
42
- chunk[key] = data[key];
43
- }
44
- const json = JSON.stringify(chunk);
45
- return { json, size: getByteSize(json) };
46
- };
47
- export const splitJson = (data, maxChunkBytes = 30_000) => {
48
- const encoder = new TextEncoder();
49
- const dataKeys = Object.keys(data);
50
- const totalKeys = dataKeys.length;
51
- const fullJson = JSON.stringify(data);
52
- const totalBytes = getByteSize(fullJson);
53
- if (totalBytes <= maxChunkBytes) {
54
- return [
55
- {
56
- data: encoder.encode(fullJson),
57
- keyCount: totalKeys,
58
- byteSize: totalBytes,
59
- index: 0,
60
- },
61
- ];
62
- }
63
- const chunks = [];
64
- let currentKeys = [];
65
- let currentSize = 2;
66
- for (let i = 0; i < totalKeys; i++) {
67
- const key = dataKeys[i];
68
- const keyValueJson = JSON.stringify({ [key]: data[key] });
69
- const entrySize = getByteSize(keyValueJson) - 2;
70
- const separatorSize = currentKeys.length > 0 ? 1 : 0;
71
- const projectedSize = currentSize + entrySize + separatorSize;
72
- if (projectedSize > maxChunkBytes && currentKeys.length > 0) {
73
- const { json, size } = buildChunkWithSize(data, currentKeys);
74
- chunks.push({
75
- data: encoder.encode(json),
76
- keyCount: currentKeys.length,
77
- byteSize: size,
78
- index: chunks.length,
79
- });
80
- currentKeys = [key];
81
- currentSize = 2 + entrySize;
82
- }
83
- else {
84
- currentKeys.push(key);
85
- currentSize = projectedSize;
86
- }
87
- }
88
- if (currentKeys.length > 0) {
89
- const { json, size } = buildChunkWithSize(data, currentKeys);
90
- chunks.push({
91
- data: encoder.encode(json),
92
- keyCount: currentKeys.length,
93
- byteSize: size,
94
- index: chunks.length,
95
- });
96
- }
97
- return chunks;
98
- };
99
- export const mergeInputs = (inputs) => {
100
- return Object.assign({}, ...inputs);
101
- };
102
- const getOsTempDir = () => {
103
- if (dntShim.Deno.build.os === 'windows') {
104
- return dntShim.Deno.env.get('TEMP') ?? dntShim.Deno.env.get('TMP') ?? 'C:\\Windows\\Temp';
105
- }
106
- return dntShim.Deno.env.get('TMPDIR') ?? '/tmp';
107
- };
108
- const resolveTempDir = async (targetLanguage) => {
109
- const tempDir = getOsTempDir();
110
- const glottoTempDir = join(tempDir, 'glotto');
111
- await ensureDirectoryExists(glottoTempDir);
112
- const languageTempDir = join(glottoTempDir, targetLanguage);
113
- await ensureDirectoryExists(languageTempDir);
114
- return languageTempDir;
115
- };
116
- export const writeTemp = async (targetLanguage, tempJsonFileName, content) => {
117
- const tempDir = await resolveTempDir(targetLanguage);
118
- const tempJsonFilePath = join(tempDir, tempJsonFileName);
119
- await dntShim.Deno.writeTextFile(tempJsonFilePath, content, { create: true });
120
- logger.info(`Temp saved: ${tempJsonFilePath}`);
121
- };
@@ -1,14 +1,9 @@
1
- import Anthropic from '@anthropic-ai/sdk';
2
- import type { ChunkInfo, Translatable } from '../types.js';
3
- declare class AnthropicModel implements Translatable {
4
- chunks: ChunkInfo[];
5
- from: string;
6
- to: string;
7
- client: Anthropic;
8
- model: string;
9
- constructor(key: string, chunks: ChunkInfo[], from: string, to: string, baseUrl?: string, modelName?: string);
10
- private translateChunk;
11
- translate(): Promise<string>;
1
+ import type { TextTranslator, TranslateOptions } from '../types.js';
2
+ declare class AnthropicModel implements TextTranslator {
3
+ private client;
4
+ private model;
5
+ constructor(key: string, baseUrl?: string, modelName?: string, options?: TranslateOptions);
6
+ translate(prompt: string): Promise<string>;
12
7
  }
13
8
  export default AnthropicModel;
14
9
  //# sourceMappingURL=anthropic.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/src/providers/anthropic.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAc,YAAY,EAAE,MAAM,aAAa,CAAC;AAQvE,cAAM,cAAe,YAAW,YAAY;IAC1C,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;gBAEF,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;YAW9F,cAAc;IAkEtB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;CAqCnC;AAED,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/src/providers/anthropic.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpE,cAAM,cAAe,YAAW,cAAc;IAC5C,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,KAAK,CAAS;gBAGpB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE,gBAAuD;IAU5D,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAYjD;AAED,eAAe,cAAc,CAAC"}