glotto 3.0.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 (67) hide show
  1. package/README.md +112 -47
  2. package/esm/cli.js +136 -32
  3. package/esm/deno.d.ts +5 -0
  4. package/esm/deno.js +18 -6
  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 +4 -0
  9. package/esm/src/contants.d.ts.map +1 -1
  10. package/esm/src/contants.js +14 -5
  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 +4 -3
  15. package/esm/src/file.d.ts.map +1 -1
  16. package/esm/src/file.js +23 -5
  17. package/esm/src/providers/anthropic.d.ts +2 -2
  18. package/esm/src/providers/anthropic.d.ts.map +1 -1
  19. package/esm/src/providers/anthropic.js +9 -1
  20. package/esm/src/providers/gemini.d.ts +2 -2
  21. package/esm/src/providers/gemini.d.ts.map +1 -1
  22. package/esm/src/providers/gemini.js +10 -2
  23. package/esm/src/providers/openai.d.ts +2 -2
  24. package/esm/src/providers/openai.d.ts.map +1 -1
  25. package/esm/src/providers/openai.js +9 -1
  26. package/esm/src/translator.d.ts +14 -7
  27. package/esm/src/translator.d.ts.map +1 -1
  28. package/esm/src/translator.js +161 -77
  29. package/esm/src/types.d.ts +31 -3
  30. package/esm/src/types.d.ts.map +1 -1
  31. package/esm/src/utilites.d.ts +3 -4
  32. package/esm/src/utilites.d.ts.map +1 -1
  33. package/esm/src/utilites.js +40 -16
  34. package/package.json +20 -6
  35. package/schema/glotto.schema.json +87 -0
  36. package/script/cli.js +134 -30
  37. package/script/deno.d.ts +5 -0
  38. package/script/deno.js +18 -6
  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 +4 -0
  43. package/script/src/contants.d.ts.map +1 -1
  44. package/script/src/contants.js +15 -6
  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 +4 -3
  49. package/script/src/file.d.ts.map +1 -1
  50. package/script/src/file.js +28 -10
  51. package/script/src/providers/anthropic.d.ts +2 -2
  52. package/script/src/providers/anthropic.d.ts.map +1 -1
  53. package/script/src/providers/anthropic.js +9 -1
  54. package/script/src/providers/gemini.d.ts +2 -2
  55. package/script/src/providers/gemini.d.ts.map +1 -1
  56. package/script/src/providers/gemini.js +10 -2
  57. package/script/src/providers/openai.d.ts +2 -2
  58. package/script/src/providers/openai.d.ts.map +1 -1
  59. package/script/src/providers/openai.js +9 -1
  60. package/script/src/translator.d.ts +14 -7
  61. package/script/src/translator.d.ts.map +1 -1
  62. package/script/src/translator.js +168 -83
  63. package/script/src/types.d.ts +31 -3
  64. package/script/src/types.d.ts.map +1 -1
  65. package/script/src/utilites.d.ts +3 -4
  66. package/script/src/utilites.d.ts.map +1 -1
  67. package/script/src/utilites.js +43 -20
@@ -12,6 +12,9 @@ export type TranslateArgs = {
12
12
  from?: string;
13
13
  to?: string;
14
14
  url?: string;
15
+ config?: string;
16
+ stats?: boolean;
17
+ incremental?: boolean;
15
18
  'no-limit'?: boolean;
16
19
  'no-timeout'?: boolean;
17
20
  'max-batch-size'?: string;
@@ -21,13 +24,30 @@ export type ValidatedTranslateArgs = {
21
24
  provider: Provider;
22
25
  model?: string;
23
26
  input: string;
24
- output: string;
27
+ output: string[];
25
28
  from: string;
26
- to: string;
29
+ to: string[];
27
30
  url?: string;
28
31
  noLimit: boolean;
29
32
  noTimeout: boolean;
30
33
  maxBatchBytes: number;
34
+ stats: boolean;
35
+ incremental: boolean;
36
+ };
37
+ export type GlottoConfig = {
38
+ key?: string;
39
+ provider?: Provider;
40
+ model?: string;
41
+ input?: string;
42
+ output?: string | string[];
43
+ from?: string;
44
+ to?: string | string[];
45
+ url?: string;
46
+ noLimit?: boolean;
47
+ noTimeout?: boolean;
48
+ maxBatchSize?: number;
49
+ stats?: boolean;
50
+ incremental?: boolean;
31
51
  };
32
52
  export type JsonValue = string | number | boolean | null | JsonObject | JsonArray;
33
53
  export interface JsonObject {
@@ -47,7 +67,15 @@ export type Batch = {
47
67
  leaves: Leaf[];
48
68
  byteSize: number;
49
69
  };
70
+ export type TranslateUsage = {
71
+ inputTokens: number;
72
+ outputTokens: number;
73
+ };
74
+ export type TranslateResult = {
75
+ text: string;
76
+ usage?: TranslateUsage;
77
+ };
50
78
  export interface TextTranslator {
51
- translate(prompt: string): Promise<string>;
79
+ translate(prompt: string): Promise<TranslateResult>;
52
80
  }
53
81
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEzD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,UAAU,GAAG,SAAS,CAAC;AAClF,MAAM,WAAW,UAAU;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AACD,MAAM,MAAM,SAAS,GAAG,SAAS,EAAE,CAAC;AAEpC,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,CAAC;AAC1C,MAAM,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;AAEjC,MAAM,MAAM,IAAI,GAAG;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,IAAI,CAAC;IACX,KAAK,EAAE,SAAS,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,IAAI,EAAE,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5C"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/src/types.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,5 @@
1
1
  import type { TranslateArgs, ValidatedTranslateArgs } from './types.js';
2
- import type { Args } from '../deps/jsr.io/@std/cli/1.0.29/mod.js';
3
- export declare const validateArgs: (args: Args<TranslateArgs>) => ValidatedTranslateArgs;
4
- export declare const formatBytes: (bytes: number) => string;
5
- export declare const delay: (ms: number) => Promise<void>;
2
+ export declare function validateArgs(args: TranslateArgs): ValidatedTranslateArgs;
3
+ export declare function formatBytes(bytes: number): string;
4
+ export declare function delay(ms: number): Promise<void>;
6
5
  //# sourceMappingURL=utilites.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utilites.d.ts","sourceRoot":"","sources":["../../src/src/utilites.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,aAAa,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uCAAuC,CAAC;AAGlE,eAAO,MAAM,YAAY,GAAI,MAAM,IAAI,CAAC,aAAa,CAAC,KAAG,sBA+BxD,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,MAI3C,CAAC;AAEF,eAAO,MAAM,KAAK,GAAI,IAAI,MAAM,KAAG,OAAO,CAAC,IAAI,CAE9C,CAAC"}
1
+ {"version":3,"file":"utilites.d.ts","sourceRoot":"","sources":["../../src/src/utilites.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,aAAa,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAOlF,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,sBAAsB,CA0DxE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C"}
@@ -1,17 +1,37 @@
1
1
  import { DEFAULT_MAX_BATCH_BYTES } from './contants.js';
2
- export const validateArgs = (args) => {
3
- if (!args.key)
2
+ function splitCsv(value) {
3
+ return value.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
4
+ }
5
+ export function validateArgs(args) {
6
+ if (!args.key) {
4
7
  throw new Error('AI Key is required');
5
- if (!args.provider)
8
+ }
9
+ if (!args.provider) {
6
10
  throw new Error('Provider parameter is required');
7
- if (!args.input)
11
+ }
12
+ if (!args.input) {
8
13
  throw new Error('Input parameter is required');
9
- if (!args.output)
14
+ }
15
+ if (!args.output) {
10
16
  throw new Error('Output parameter is required');
11
- if (!args.from)
17
+ }
18
+ if (!args.from) {
12
19
  throw new Error('Source language (from) parameter is required');
13
- if (!args.to)
20
+ }
21
+ if (!args.to) {
14
22
  throw new Error('Target language (to) parameter is required');
23
+ }
24
+ const toList = splitCsv(args.to);
25
+ const outputList = splitCsv(args.output);
26
+ if (toList.length === 0) {
27
+ throw new Error('Target language (to) must contain at least one value');
28
+ }
29
+ if (outputList.length === 0) {
30
+ throw new Error('Output must contain at least one value');
31
+ }
32
+ if (toList.length !== outputList.length) {
33
+ throw new Error(`--to (${toList.length}) and --output (${outputList.length}) must have the same number of values`);
34
+ }
15
35
  let maxBatchBytes = DEFAULT_MAX_BATCH_BYTES;
16
36
  const rawMaxBatchSize = args['max-batch-size'];
17
37
  if (rawMaxBatchSize !== undefined) {
@@ -26,22 +46,26 @@ export const validateArgs = (args) => {
26
46
  provider: args.provider,
27
47
  model: args.model,
28
48
  input: args.input,
29
- output: args.output,
49
+ output: outputList,
30
50
  from: args.from,
31
- to: args.to,
51
+ to: toList,
32
52
  url: args.url,
33
53
  noLimit: args['no-limit'] ?? false,
34
54
  noTimeout: args['no-timeout'] ?? false,
35
55
  maxBatchBytes,
56
+ stats: args.stats ?? false,
57
+ incremental: args.incremental ?? false,
36
58
  };
37
- };
38
- export const formatBytes = (bytes) => {
39
- if (bytes < 1024)
59
+ }
60
+ export function formatBytes(bytes) {
61
+ if (bytes < 1024) {
40
62
  return `${bytes} B`;
41
- if (bytes < 1024 * 1024)
63
+ }
64
+ if (bytes < 1024 * 1024) {
42
65
  return `${(bytes / 1024).toFixed(1)} KB`;
66
+ }
43
67
  return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
44
- };
45
- export const delay = (ms) => {
68
+ }
69
+ export function delay(ms) {
46
70
  return new Promise((resolve) => setTimeout(resolve, ms));
47
- };
71
+ }
package/package.json CHANGED
@@ -1,8 +1,22 @@
1
1
  {
2
2
  "name": "glotto",
3
- "version": "3.0.0",
4
- "description": "A tool for translating i18n JSON files using AI services.",
3
+ "version": "3.1.0",
4
+ "description": "AI-powered i18n JSON translator. Multi-target, incremental, provider-agnostic (OpenAI, Anthropic, Gemini).",
5
+ "keywords": [
6
+ "i18n",
7
+ "translation",
8
+ "translator",
9
+ "ai",
10
+ "openai",
11
+ "anthropic",
12
+ "gemini",
13
+ "localization",
14
+ "l10n",
15
+ "json",
16
+ "cli"
17
+ ],
5
18
  "author": "Ibrahim Odev <me@ibrahimo.dev>",
19
+ "homepage": "https://github.com/ibodev1/glotto#readme",
6
20
  "repository": {
7
21
  "type": "git",
8
22
  "url": "git+https://github.com/ibodev1/glotto.git"
@@ -19,13 +33,13 @@
19
33
  "access": "public"
20
34
  },
21
35
  "engines": {
22
- "node": ">=20.0.0"
36
+ "node": ">=20.10.0"
23
37
  },
24
38
  "dependencies": {
25
- "@anthropic-ai/sdk": "^0.95.0",
26
- "@google/genai": "^1.52.0",
39
+ "@anthropic-ai/sdk": "^0.95.1",
40
+ "@google/genai": "^2.0.0",
27
41
  "consola": "^3.4.2",
28
- "openai": "^6.36.0",
42
+ "openai": "^6.37.0",
29
43
  "@deno/shim-deno": "~0.18.0"
30
44
  },
31
45
  "devDependencies": {
@@ -0,0 +1,87 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/ibodev1/glotto/main/schema/glotto.schema.json",
4
+ "title": "Glotto config",
5
+ "description": "Configuration for the Glotto AI translator. CLI flags override values defined here.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "$schema": {
10
+ "type": "string",
11
+ "description": "JSON Schema reference for editor autocomplete."
12
+ },
13
+ "key": {
14
+ "type": "string",
15
+ "description": "API key for the AI provider."
16
+ },
17
+ "provider": {
18
+ "type": "string",
19
+ "enum": ["openai", "gemini", "anthropic"],
20
+ "description": "AI translation provider."
21
+ },
22
+ "model": {
23
+ "type": "string",
24
+ "description": "Model name for the selected provider."
25
+ },
26
+ "input": {
27
+ "type": "string",
28
+ "description": "Path to source JSON file."
29
+ },
30
+ "output": {
31
+ "description": "Single target file path or array of paths (one per target language).",
32
+ "oneOf": [
33
+ { "type": "string" },
34
+ {
35
+ "type": "array",
36
+ "items": { "type": "string" },
37
+ "minItems": 1
38
+ }
39
+ ]
40
+ },
41
+ "from": {
42
+ "type": "string",
43
+ "description": "Source language name."
44
+ },
45
+ "to": {
46
+ "description": "Target language name or array of names. Length must match output array.",
47
+ "oneOf": [
48
+ { "type": "string" },
49
+ {
50
+ "type": "array",
51
+ "items": { "type": "string" },
52
+ "minItems": 1
53
+ }
54
+ ]
55
+ },
56
+ "url": {
57
+ "type": "string",
58
+ "description": "Custom base URL for OpenAI/Anthropic providers."
59
+ },
60
+ "noLimit": {
61
+ "type": "boolean",
62
+ "description": "Disable inter-batch rate-limit delay.",
63
+ "default": false
64
+ },
65
+ "noTimeout": {
66
+ "type": "boolean",
67
+ "description": "Disable request timeout.",
68
+ "default": false
69
+ },
70
+ "maxBatchSize": {
71
+ "type": "integer",
72
+ "minimum": 1,
73
+ "description": "Maximum source size per batch in KB.",
74
+ "default": 12
75
+ },
76
+ "stats": {
77
+ "type": "boolean",
78
+ "description": "Print AI usage statistics at the end of the run.",
79
+ "default": false
80
+ },
81
+ "incremental": {
82
+ "type": "boolean",
83
+ "description": "Translate only missing or empty keys when target file already exists.",
84
+ "default": false
85
+ }
86
+ }
87
+ }
package/script/cli.js CHANGED
@@ -43,6 +43,8 @@ const unstable_spinner_js_1 = require("./deps/jsr.io/@std/cli/1.0.29/unstable_sp
43
43
  const mod_js_1 = require("./deps/jsr.io/@std/cli/1.0.29/mod.js");
44
44
  const file_js_1 = require("./src/file.js");
45
45
  const translator_js_1 = require("./src/translator.js");
46
+ const diff_js_1 = require("./src/diff.js");
47
+ const config_js_1 = require("./src/config.js");
46
48
  const contants_js_1 = require("./src/contants.js");
47
49
  const utilites_js_1 = require("./src/utilites.js");
48
50
  const logger_js_1 = require("./src/logger.js");
@@ -51,7 +53,7 @@ const gemini_js_1 = __importDefault(require("./src/providers/gemini.js"));
51
53
  const openai_js_1 = __importDefault(require("./src/providers/openai.js"));
52
54
  const anthropic_js_1 = __importDefault(require("./src/providers/anthropic.js"));
53
55
  const spinner = new unstable_spinner_js_1.Spinner({ message: 'AI Thinks...', color: 'cyan' });
54
- const createTranslator = (args, options) => {
56
+ function createTranslator(args, options) {
55
57
  switch (args.provider) {
56
58
  case 'gemini':
57
59
  return new gemini_js_1.default(args.key, args.model, options);
@@ -62,12 +64,99 @@ const createTranslator = (args, options) => {
62
64
  default:
63
65
  throw new Error(`Unknown provider: ${args.provider}`);
64
66
  }
65
- };
67
+ }
68
+ function printBanner() {
69
+ logger_js_1.logger.box(`Glotto AI Translator v${deno_js_1.default.version}\n${contants_js_1.GITHUB_REPO_URL}`);
70
+ }
71
+ function printStats(args, perTarget) {
72
+ const totalInput = perTarget.reduce((sum, t) => sum + t.usage.inputTokens, 0);
73
+ const totalOutput = perTarget.reduce((sum, t) => sum + t.usage.outputTokens, 0);
74
+ const totalCalls = perTarget.reduce((sum, t) => sum + t.calls, 0);
75
+ const totalBytes = perTarget.reduce((sum, t) => sum + t.bytes, 0);
76
+ const totalTranslated = perTarget.reduce((sum, t) => sum + t.translatedCount, 0);
77
+ const lines = [];
78
+ lines.push(`Provider: ${args.provider}${args.model ? ` (${args.model})` : ''}`);
79
+ lines.push(`Targets: ${perTarget.length}`);
80
+ lines.push('');
81
+ for (const t of perTarget) {
82
+ const mode = t.incrementalApplied ? ' [incremental]' : '';
83
+ lines.push(`→ ${t.to} (${t.output})${mode}: ${t.translatedCount}/${t.fullCount} entries, ${t.batchCount} batch(es), ${(0, utilites_js_1.formatBytes)(t.bytes)}, ${t.calls} call(s)`);
84
+ lines.push(` tokens — in: ${t.usage.inputTokens}, out: ${t.usage.outputTokens}`);
85
+ }
86
+ lines.push('');
87
+ lines.push(`Total: ${totalTranslated} entries translated, ${totalCalls} call(s), ${(0, utilites_js_1.formatBytes)(totalBytes)}`);
88
+ lines.push(`Total tokens — in: ${totalInput}, out: ${totalOutput}, sum: ${totalInput + totalOutput}`);
89
+ logger_js_1.logger.box(lines.join('\n'));
90
+ }
91
+ async function translateForTarget(validatedArgs, fileContent, allLeaves, to, outputRel, translator, translateOptions, targetIndex, totalTargets) {
92
+ const outputPath = (0, file_js_1.resolvePath)(outputRel);
93
+ const targetLabel = `[Target ${targetIndex + 1}/${totalTargets}: ${to}]`;
94
+ logger_js_1.logger.info(`${targetLabel} → ${outputRel}`);
95
+ const translatableLeaves = allLeaves.filter((leaf) => leaf.translatable);
96
+ let leavesToTranslate = translatableLeaves;
97
+ let existingTarget = null;
98
+ let incrementalApplied = false;
99
+ if (validatedArgs.incremental) {
100
+ existingTarget = await (0, file_js_1.readJsonIfExists)(outputPath);
101
+ if (existingTarget !== null) {
102
+ leavesToTranslate = (0, diff_js_1.findMissingLeaves)(allLeaves, existingTarget);
103
+ incrementalApplied = true;
104
+ logger_js_1.logger.info(`${targetLabel} Incremental: ${leavesToTranslate.length}/${translatableLeaves.length} entries missing in existing target`);
105
+ }
106
+ else {
107
+ logger_js_1.logger.info(`${targetLabel} Incremental: target file not found, doing full translation`);
108
+ }
109
+ }
110
+ if (leavesToTranslate.length === 0) {
111
+ logger_js_1.logger.info(`${targetLabel} Nothing to translate, writing existing/source content`);
112
+ const content = existingTarget !== null ? existingTarget : fileContent;
113
+ await (0, file_js_1.writeOutput)(outputPath, JSON.stringify(content, null, 2));
114
+ return {
115
+ to,
116
+ output: outputRel,
117
+ fullCount: translatableLeaves.length,
118
+ translatedCount: 0,
119
+ batchCount: 0,
120
+ bytes: 0,
121
+ calls: 0,
122
+ usage: { inputTokens: 0, outputTokens: 0 },
123
+ incrementalApplied,
124
+ };
125
+ }
126
+ const batches = (0, translator_js_1.groupIntoBatches)(leavesToTranslate, validatedArgs.maxBatchBytes);
127
+ const totalBytes = batches.reduce((sum, b) => sum + b.byteSize, 0);
128
+ logger_js_1.logger.info(`${targetLabel} ${leavesToTranslate.length} translatable entries, ${(0, utilites_js_1.formatBytes)(totalBytes)}, split into ${batches.length} batch(es)`);
129
+ for (const batch of batches) {
130
+ logger_js_1.logger.info(` Batch ${batch.index + 1}: ${batch.leaves.length} entries, ${(0, utilites_js_1.formatBytes)(batch.byteSize)}`);
131
+ }
132
+ spinner.start();
133
+ const result = await (0, translator_js_1.runBatches)(batches, translator, validatedArgs.from, to, translateOptions);
134
+ spinner.stop();
135
+ let finalContent;
136
+ if (incrementalApplied && existingTarget !== null) {
137
+ finalContent = (0, diff_js_1.mergeTranslations)(existingTarget, allLeaves, result.translations);
138
+ }
139
+ else {
140
+ finalContent = (0, translator_js_1.reconstruct)(allLeaves, result.translations);
141
+ }
142
+ await (0, file_js_1.writeOutput)(outputPath, JSON.stringify(finalContent, null, 2));
143
+ return {
144
+ to,
145
+ output: outputRel,
146
+ fullCount: translatableLeaves.length,
147
+ translatedCount: result.translations.size,
148
+ batchCount: batches.length,
149
+ bytes: totalBytes,
150
+ calls: result.calls,
151
+ usage: result.usage,
152
+ incrementalApplied,
153
+ };
154
+ }
66
155
  async function main() {
67
156
  try {
68
157
  const args = (0, mod_js_1.parseArgs)(dntShim.Deno.args, {
69
- string: ['key', 'provider', 'model', 'input', 'output', 'from', 'to', 'url', 'max-batch-size'],
70
- boolean: ['help', 'version', 'no-limit', 'no-timeout'],
158
+ string: ['key', 'provider', 'model', 'input', 'output', 'from', 'to', 'url', 'max-batch-size', 'config'],
159
+ boolean: ['help', 'version', 'no-limit', 'no-timeout', 'stats', 'incremental'],
71
160
  alias: {
72
161
  provider: 'p',
73
162
  model: 'm',
@@ -79,7 +168,6 @@ async function main() {
79
168
  help: 'h',
80
169
  version: 'v',
81
170
  },
82
- default: { provider: contants_js_1.DEFAULT_PROVIDER },
83
171
  });
84
172
  const help = args.help || dntShim.Deno.args.length === 0;
85
173
  const version = args.version;
@@ -88,38 +176,53 @@ async function main() {
88
176
  dntShim.Deno.exit(0);
89
177
  }
90
178
  if (help) {
179
+ printBanner();
91
180
  logger_js_1.logger.box(contants_js_1.HELP_TEXT);
92
181
  dntShim.Deno.exit(0);
93
182
  }
94
- const validatedArgs = (0, utilites_js_1.validateArgs)(args);
183
+ printBanner();
184
+ const config = await (0, config_js_1.loadConfig)(args.config);
185
+ const merged = (0, config_js_1.applyConfig)(args, config, dntShim.Deno.args);
186
+ if (!merged.provider) {
187
+ merged.provider = contants_js_1.DEFAULT_PROVIDER;
188
+ }
189
+ const validatedArgs = (0, utilites_js_1.validateArgs)(merged);
95
190
  const fileContent = await (0, file_js_1.getImportJson)(validatedArgs.input);
96
191
  const allLeaves = (0, translator_js_1.extractLeaves)(fileContent);
97
192
  const translatableLeaves = allLeaves.filter((leaf) => leaf.translatable);
98
- const batches = (0, translator_js_1.groupIntoBatches)(allLeaves, validatedArgs.maxBatchBytes);
99
- const totalBytes = batches.reduce((sum, b) => sum + b.byteSize, 0);
100
193
  logger_js_1.logger.info('Provider: ', validatedArgs.provider);
101
194
  logger_js_1.logger.info('Input: ', validatedArgs.input);
102
- logger_js_1.logger.info('Output: ', validatedArgs.output);
195
+ logger_js_1.logger.info('Targets: ', validatedArgs.to.map((to, i) => `${to} → ${validatedArgs.output[i]}`).join(', '));
103
196
  logger_js_1.logger.info('From: ', validatedArgs.from);
104
- logger_js_1.logger.info('To: ', validatedArgs.to);
105
- if (validatedArgs.model)
197
+ if (validatedArgs.model) {
106
198
  logger_js_1.logger.info('Model: ', validatedArgs.model);
107
- if (validatedArgs.url)
199
+ }
200
+ if (validatedArgs.url) {
108
201
  logger_js_1.logger.info('URL: ', validatedArgs.url);
109
- if (validatedArgs.noLimit)
202
+ }
203
+ if (validatedArgs.noLimit) {
110
204
  logger_js_1.logger.info('Rate limit protection: disabled (--no-limit)');
111
- if (validatedArgs.noTimeout)
205
+ }
206
+ if (validatedArgs.noTimeout) {
112
207
  logger_js_1.logger.info('Request timeout: disabled (--no-timeout)');
208
+ }
209
+ if (validatedArgs.incremental) {
210
+ logger_js_1.logger.info('Incremental mode: enabled (--incremental)');
211
+ }
212
+ if (validatedArgs.stats) {
213
+ logger_js_1.logger.info('Stats: enabled (--stats)');
214
+ }
113
215
  if (validatedArgs.maxBatchBytes !== contants_js_1.DEFAULT_MAX_BATCH_BYTES) {
114
216
  logger_js_1.logger.info(`Max batch size: ${(0, utilites_js_1.formatBytes)(validatedArgs.maxBatchBytes)}`);
115
217
  }
116
- logger_js_1.logger.info(`Total: ${translatableLeaves.length} translatable entries (of ${allLeaves.length} leaves), ${(0, utilites_js_1.formatBytes)(totalBytes)}, split into ${batches.length} batch(es)`);
117
- for (const batch of batches) {
118
- logger_js_1.logger.info(` Batch ${batch.index + 1}: ${batch.leaves.length} entries, ${(0, utilites_js_1.formatBytes)(batch.byteSize)}`);
119
- }
120
- if (batches.length === 0) {
121
- logger_js_1.logger.warn('No translatable entries found, copying input to output');
122
- await (0, file_js_1.writeOutput)((0, file_js_1.resolvePath)(validatedArgs.output), JSON.stringify(fileContent, null, 2));
218
+ logger_js_1.logger.info(`Source: ${translatableLeaves.length} translatable entries (of ${allLeaves.length} leaves)`);
219
+ if (translatableLeaves.length === 0 && !validatedArgs.incremental) {
220
+ logger_js_1.logger.warn('No translatable entries found, copying input to output(s)');
221
+ for (const outputRel of validatedArgs.output) {
222
+ await (0, file_js_1.writeOutput)((0, file_js_1.resolvePath)(outputRel), JSON.stringify(fileContent, null, 2));
223
+ }
224
+ logger_js_1.logger.success('Translation completed');
225
+ logger_js_1.logger.info(`★ Glotto faydalı olduysa GitHub'da yıldızlamayı unutma: ${contants_js_1.GITHUB_REPO_URL}`);
123
226
  dntShim.Deno.exit(0);
124
227
  }
125
228
  const translateOptions = {
@@ -127,22 +230,23 @@ async function main() {
127
230
  noTimeout: validatedArgs.noTimeout,
128
231
  };
129
232
  const translator = createTranslator(validatedArgs, translateOptions);
130
- spinner.start();
131
- const translations = await (0, translator_js_1.runBatches)(batches, translator, validatedArgs.from, validatedArgs.to, translateOptions);
132
- const result = (0, translator_js_1.reconstruct)(allLeaves, translations);
133
- const outputPath = (0, file_js_1.resolvePath)(validatedArgs.output);
134
- await (0, file_js_1.writeOutput)(outputPath, JSON.stringify(result, null, 2));
233
+ const perTarget = [];
234
+ for (let i = 0; i < validatedArgs.to.length; i++) {
235
+ const stat = await translateForTarget(validatedArgs, fileContent, allLeaves, validatedArgs.to[i], validatedArgs.output[i], translator, translateOptions, i, validatedArgs.to.length);
236
+ perTarget.push(stat);
237
+ }
135
238
  spinner.stop();
136
239
  logger_js_1.logger.success('Translation completed');
240
+ if (validatedArgs.stats) {
241
+ printStats(validatedArgs, perTarget);
242
+ }
243
+ logger_js_1.logger.info(`★ Glotto faydalı olduysa GitHub'da yıldızlamayı unutma: ${contants_js_1.GITHUB_REPO_URL}`);
244
+ dntShim.Deno.exit(0);
137
245
  }
138
246
  catch (error) {
139
247
  spinner.stop();
140
248
  logger_js_1.logger.error(error);
141
249
  dntShim.Deno.exit(1);
142
250
  }
143
- finally {
144
- spinner.stop();
145
- dntShim.Deno.exit(0);
146
- }
147
251
  }
148
252
  main();
package/script/deno.d.ts CHANGED
@@ -11,17 +11,22 @@ 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
31
  "@std/path": string;
27
32
  consola: string;
package/script/deno.js CHANGED
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = {
4
4
  "name": "@ibodev/glotto",
5
- "version": "3.0.0",
5
+ "version": "3.1.0",
6
6
  "exports": "./cli.ts",
7
7
  "lock": false,
8
8
  "nodeModulesDir": "auto",
@@ -12,21 +12,33 @@ exports.default = {
12
12
  "build:npm": "deno run -A scripts/build_npm.ts",
13
13
  "publish": "deno publish",
14
14
  "publish:npm": "cd ./npm && npm publish",
15
- "check": "deno check **/*.ts"
15
+ "check": "deno check **/*.ts",
16
+ "test": "deno test -A --no-check tests/"
16
17
  },
17
18
  "fmt": {
18
19
  "semiColons": true,
19
20
  "singleQuote": true,
20
21
  "lineWidth": 160
21
22
  },
23
+ "publish": {
24
+ "exclude": [
25
+ "tests/",
26
+ "scripts/",
27
+ "npm/",
28
+ "assets/",
29
+ "glotto.exe",
30
+ "CLAUDE.md"
31
+ ]
32
+ },
22
33
  "imports": {
23
- "@anthropic-ai/sdk": "npm:@anthropic-ai/sdk@^0.95.0",
34
+ "@anthropic-ai/sdk": "npm:@anthropic-ai/sdk@^0.95.1",
24
35
  "@deno/dnt": "jsr:@deno/dnt@^0.42.3",
25
- "@google/genai": "npm:@google/genai@^1.52.0",
26
- "@openai/openai": "npm:openai@^6.36.0",
36
+ "@google/genai": "npm:@google/genai@^2.0.0",
37
+ "@openai/openai": "npm:openai@^6.37.0",
38
+ "@std/assert": "jsr:@std/assert@^1.0.19",
27
39
  "@std/cli": "jsr:@std/cli@^1.0.29",
28
40
  "@std/path": "jsr:@std/path@^1.1.4",
29
41
  "consola": "npm:consola@^3.4.2"
30
42
  },
31
- "allowScripts": ["npm:@google/genai@1.52.0", "npm:protobufjs@7.5.6"]
43
+ "allowScripts": ["npm:@google/genai@1.52.0", "npm:@google/genai@2.0.0", "npm:protobufjs@7.5.6"]
32
44
  };
@@ -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"}