@better-translate/cli 1.0.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -1
- package/dist/ai-sdk-generator-F2X3W7NC.js +89 -0
- package/dist/ai-sdk-generator.d.ts.map +1 -1
- package/dist/bin.js +75 -13
- package/dist/{chunk-JFSWNLL6.js → chunk-55TGASZJ.js} +643 -127
- package/dist/chunk-VYOBAIBH.js +8 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config.d.ts +1 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -5
- package/dist/extract.d.ts +3 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/generate.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/logger.d.ts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/types.d.ts +22 -12
- package/dist/types.d.ts.map +1 -1
- package/package.json +16 -5
- package/dist/ai-sdk-generator-WPQCTPGA.js +0 -37
- package/dist/chunk-WMIZO3GE.js +0 -19
- package/dist/provider-models.d.ts +0 -5
- package/dist/provider-models.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
# @better-translate/cli
|
|
2
2
|
|
|
3
|
-
`@better-translate/cli` generates translated message files and
|
|
3
|
+
`@better-translate/cli` extracts marked source strings into your source locale file, generates translated message files, and localizes markdown. Use it when you want Better Translate to create or update locale files for you.
|
|
4
|
+
|
|
5
|
+
Install the CLI and the provider package you want to use in your own project:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install -D @better-translate/cli @ai-sdk/openai
|
|
9
|
+
# or: npm install -D @better-translate/cli @ai-sdk/anthropic
|
|
10
|
+
# or: npm install -D @better-translate/cli @ai-sdk/moonshotai
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then configure the CLI with a real AI SDK language model. The flow is the same for any provider package:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { openai } from "@ai-sdk/openai";
|
|
17
|
+
import { defineConfig } from "@better-translate/cli/config";
|
|
18
|
+
|
|
19
|
+
export default defineConfig({
|
|
20
|
+
sourceLocale: "en",
|
|
21
|
+
locales: ["es", "fr"],
|
|
22
|
+
model: openai("gpt-5"),
|
|
23
|
+
messages: {
|
|
24
|
+
entry: "./src/messages/en.json",
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
31
|
+
import { defineConfig } from "@better-translate/cli/config";
|
|
32
|
+
|
|
33
|
+
export default defineConfig({
|
|
34
|
+
sourceLocale: "en",
|
|
35
|
+
locales: ["es", "fr"],
|
|
36
|
+
model: anthropic("claude-sonnet-4-5"),
|
|
37
|
+
messages: {
|
|
38
|
+
entry: "./src/messages/en.json",
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { moonshotai } from "@ai-sdk/moonshotai";
|
|
45
|
+
import { defineConfig } from "@better-translate/cli/config";
|
|
46
|
+
|
|
47
|
+
export default defineConfig({
|
|
48
|
+
sourceLocale: "en",
|
|
49
|
+
locales: ["es", "fr"],
|
|
50
|
+
model: moonshotai("kimi-k2-0905-preview"),
|
|
51
|
+
messages: {
|
|
52
|
+
entry: "./src/messages/en.json",
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If you need provider-specific settings, create the model in your app first and pass it through. Credentials and provider configuration stay entirely in the provider package setup:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
61
|
+
import { defineConfig } from "@better-translate/cli/config";
|
|
62
|
+
|
|
63
|
+
const model = createOpenAI({
|
|
64
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
65
|
+
baseURL: process.env.OPENAI_BASE_URL,
|
|
66
|
+
})("gpt-5");
|
|
67
|
+
|
|
68
|
+
export default defineConfig({
|
|
69
|
+
sourceLocale: "en",
|
|
70
|
+
locales: ["es", "fr"],
|
|
71
|
+
model,
|
|
72
|
+
messages: {
|
|
73
|
+
entry: "./src/messages/en.json",
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
```
|
|
4
77
|
|
|
5
78
|
Full docs: [better-translate-placeholder.com/en/docs/cli](https://better-translate-placeholder.com/en/docs/cli)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// src/ai-sdk-generator.ts
|
|
2
|
+
function validateGeneratedValue(value, request) {
|
|
3
|
+
if (!request.validate) {
|
|
4
|
+
return value;
|
|
5
|
+
}
|
|
6
|
+
return request.validate(value);
|
|
7
|
+
}
|
|
8
|
+
function createOutputValidator(request) {
|
|
9
|
+
return {
|
|
10
|
+
validate(value) {
|
|
11
|
+
try {
|
|
12
|
+
return {
|
|
13
|
+
success: true,
|
|
14
|
+
value: validateGeneratedValue(value, request)
|
|
15
|
+
};
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return {
|
|
18
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
19
|
+
success: false
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function isSchemaTooLargeError(error) {
|
|
26
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
27
|
+
const normalizedMessage = message.toLowerCase();
|
|
28
|
+
return normalizedMessage.includes("compiled grammar is too large") || normalizedMessage.includes("reduce the number of strict tools") || normalizedMessage.includes("simplify your tool schemas") || normalizedMessage.includes("tool schemas");
|
|
29
|
+
}
|
|
30
|
+
function extractJsonPayload(text) {
|
|
31
|
+
const trimmed = text.trim();
|
|
32
|
+
const fencedMatch = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
|
|
33
|
+
if (fencedMatch?.[1]) {
|
|
34
|
+
return fencedMatch[1].trim();
|
|
35
|
+
}
|
|
36
|
+
const firstBrace = trimmed.indexOf("{");
|
|
37
|
+
const lastBrace = trimmed.lastIndexOf("}");
|
|
38
|
+
if (firstBrace >= 0 && lastBrace > firstBrace) {
|
|
39
|
+
return trimmed.slice(firstBrace, lastBrace + 1);
|
|
40
|
+
}
|
|
41
|
+
return trimmed;
|
|
42
|
+
}
|
|
43
|
+
function parseJsonText(text, request) {
|
|
44
|
+
const payload = extractJsonPayload(text);
|
|
45
|
+
try {
|
|
46
|
+
return validateGeneratedValue(JSON.parse(payload), request);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Fallback JSON parsing failed for "${request.sourcePath}": ${reason}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function generateWithAiSdk(model, request) {
|
|
55
|
+
const { Output, generateText, jsonSchema } = await import("ai");
|
|
56
|
+
const baseInput = {
|
|
57
|
+
model,
|
|
58
|
+
prompt: request.prompt,
|
|
59
|
+
system: request.system,
|
|
60
|
+
temperature: 0
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
const result = await generateText({
|
|
64
|
+
...baseInput,
|
|
65
|
+
experimental_output: Output.object({
|
|
66
|
+
schema: jsonSchema(request.schema, createOutputValidator(request))
|
|
67
|
+
})
|
|
68
|
+
});
|
|
69
|
+
return result.experimental_output;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (!isSchemaTooLargeError(error)) {
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const fallbackResult = await generateText({
|
|
76
|
+
...baseInput,
|
|
77
|
+
prompt: [
|
|
78
|
+
request.prompt,
|
|
79
|
+
"",
|
|
80
|
+
"Return only a valid JSON object that matches the required shape exactly.",
|
|
81
|
+
"Do not wrap the JSON in markdown fences."
|
|
82
|
+
].join("\n"),
|
|
83
|
+
system: `${request.system} Return only valid JSON.`
|
|
84
|
+
});
|
|
85
|
+
return parseJsonText(fallbackResult.text, request);
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
generateWithAiSdk
|
|
89
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-sdk-generator.d.ts","sourceRoot":"","sources":["../src/ai-sdk-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"ai-sdk-generator.d.ts","sourceRoot":"","sources":["../src/ai-sdk-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAyF9D,wBAAsB,iBAAiB,CAAC,OAAO,EAC7C,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,2BAA2B,CAAC,OAAO,CAAC,GAC5C,OAAO,CAAC,OAAO,CAAC,CAoClB"}
|
package/dist/bin.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
extractProject,
|
|
3
4
|
generateProject
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-55TGASZJ.js";
|
|
5
6
|
|
|
6
7
|
// src/cli.ts
|
|
7
8
|
import pc2 from "picocolors";
|
|
@@ -69,7 +70,7 @@ ${pc.magenta("\u25C6")} locale: ${pc.bold(localeMatch[1])}`);
|
|
|
69
70
|
spinner.stopAndPersist({ symbol: "\u25CC", text: pc.dim(message) });
|
|
70
71
|
return;
|
|
71
72
|
}
|
|
72
|
-
if (message.startsWith("Using AI Gateway model:") || message.startsWith("Using
|
|
73
|
+
if (message.startsWith("Using AI Gateway model:") || message.startsWith("Using configured provider model:") || message.startsWith("Source locale:") || message.startsWith("Target locales:")) {
|
|
73
74
|
spinner.stop();
|
|
74
75
|
console.log(pc.dim(message));
|
|
75
76
|
return;
|
|
@@ -96,6 +97,33 @@ ${pc.magenta("\u25C6")} locale: ${pc.bold(localeMatch[1])}`);
|
|
|
96
97
|
console.log(pc.dim(message));
|
|
97
98
|
return;
|
|
98
99
|
}
|
|
100
|
+
const rewriteMatch = message.match(/^rewrote (.+)/);
|
|
101
|
+
if (rewriteMatch) {
|
|
102
|
+
const shortPath = (rewriteMatch[1] ?? "").split("/").slice(-3).join("/");
|
|
103
|
+
spinner.succeed(`${pc.green("\u2713")} ${shortPath}`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const updatedMsgMatch = message.match(/^updated messages (.+)/);
|
|
107
|
+
if (updatedMsgMatch) {
|
|
108
|
+
const shortPath = (updatedMsgMatch[1] ?? "").split("/").slice(-3).join("/");
|
|
109
|
+
spinner.stopAndPersist({ symbol: pc.blue("\u25C6"), text: shortPath });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (message.startsWith("warn ")) {
|
|
113
|
+
spinner.stopAndPersist({ symbol: pc.yellow("\u26A0"), text: pc.yellow(message.slice(5)) });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const extractSummaryMatch = message.match(
|
|
117
|
+
/^processed \d+ (?:file|files) and synced \d+ (?:key|keys)\./
|
|
118
|
+
);
|
|
119
|
+
if (extractSummaryMatch) {
|
|
120
|
+
spinner.stop();
|
|
121
|
+
console.log(
|
|
122
|
+
pc.bold(pc.green(`
|
|
123
|
+
\u2713 ${message.charAt(0).toUpperCase()}${message.slice(1)}`))
|
|
124
|
+
);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
99
127
|
console.log(message);
|
|
100
128
|
},
|
|
101
129
|
error(message) {
|
|
@@ -108,12 +136,14 @@ ${pc.magenta("\u25C6")} locale: ${pc.bold(localeMatch[1])}`);
|
|
|
108
136
|
function usage() {
|
|
109
137
|
return [
|
|
110
138
|
"Usage:",
|
|
139
|
+
" bt extract [--config ./better-translate.config.ts] [--dry-run] [--max-length 40]",
|
|
111
140
|
" bt generate [--config ./better-translate.config.ts] [--dry-run]"
|
|
112
141
|
].join("\n");
|
|
113
142
|
}
|
|
114
|
-
function
|
|
143
|
+
function parseCommonArgs(argv) {
|
|
115
144
|
let configPath;
|
|
116
145
|
let dryRun = false;
|
|
146
|
+
let maxLength;
|
|
117
147
|
for (let index = 0; index < argv.length; index += 1) {
|
|
118
148
|
const arg = argv[index];
|
|
119
149
|
if (arg === "--dry-run") {
|
|
@@ -129,11 +159,28 @@ function parseArgs(argv) {
|
|
|
129
159
|
index += 1;
|
|
130
160
|
continue;
|
|
131
161
|
}
|
|
162
|
+
if (arg === "--max-length") {
|
|
163
|
+
const value = argv[index + 1];
|
|
164
|
+
if (!value) {
|
|
165
|
+
throw new Error("--max-length requires a number.");
|
|
166
|
+
}
|
|
167
|
+
if (!/^\d+$/.test(value)) {
|
|
168
|
+
throw new Error("--max-length must be a positive integer.");
|
|
169
|
+
}
|
|
170
|
+
const parsed = Number.parseInt(value, 10);
|
|
171
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
172
|
+
throw new Error("--max-length must be a positive integer.");
|
|
173
|
+
}
|
|
174
|
+
maxLength = parsed;
|
|
175
|
+
index += 1;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
132
178
|
throw new Error(`Unknown argument "${arg}".`);
|
|
133
179
|
}
|
|
134
180
|
return {
|
|
135
181
|
configPath,
|
|
136
|
-
dryRun
|
|
182
|
+
dryRun,
|
|
183
|
+
maxLength
|
|
137
184
|
};
|
|
138
185
|
}
|
|
139
186
|
async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
@@ -144,24 +191,39 @@ async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
|
144
191
|
stdout(usage());
|
|
145
192
|
return command ? 0 : 1;
|
|
146
193
|
}
|
|
147
|
-
if (command !== "generate") {
|
|
194
|
+
if (command !== "extract" && command !== "generate") {
|
|
148
195
|
stderr(`Unknown command "${command}".
|
|
149
196
|
${usage()}`);
|
|
150
197
|
return 1;
|
|
151
198
|
}
|
|
152
199
|
try {
|
|
153
|
-
const parsed =
|
|
200
|
+
const parsed = parseCommonArgs(args);
|
|
201
|
+
if (command === "generate" && parsed.maxLength !== void 0) {
|
|
202
|
+
stderr(`--max-length is not valid for "generate".
|
|
203
|
+
${usage()}`);
|
|
204
|
+
return 1;
|
|
205
|
+
}
|
|
154
206
|
console.log(pc2.bold("\n better-translate\n"));
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
207
|
+
if (command === "extract") {
|
|
208
|
+
await extractProject({
|
|
209
|
+
configPath: parsed.configPath,
|
|
210
|
+
cwd: options.cwd,
|
|
211
|
+
dryRun: parsed.dryRun,
|
|
212
|
+
logger: createSpinnerLogger(),
|
|
213
|
+
maxLength: parsed.maxLength
|
|
214
|
+
});
|
|
215
|
+
} else {
|
|
216
|
+
await generateProject({
|
|
217
|
+
configPath: parsed.configPath,
|
|
218
|
+
cwd: options.cwd,
|
|
219
|
+
dryRun: parsed.dryRun,
|
|
220
|
+
logger: createSpinnerLogger()
|
|
221
|
+
});
|
|
222
|
+
}
|
|
161
223
|
return 0;
|
|
162
224
|
} catch (error) {
|
|
163
225
|
stderr(
|
|
164
|
-
`Better Translate
|
|
226
|
+
`Better Translate ${command} failed: ${error instanceof Error ? error.message : String(error)}`
|
|
165
227
|
);
|
|
166
228
|
return 1;
|
|
167
229
|
}
|