@ai-translate/cli 0.2.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -8
- package/dist/bin.mjs +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{src-Cnnq284f.mjs → src-SwBEpdIU.mjs} +571 -66
- package/dist/src-SwBEpdIU.mjs.map +1 -0
- package/package.json +9 -4
- package/dist/src-Cnnq284f.mjs.map +0 -1
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import { auditCatalogs, defineConfig, defineConfig as defineConfig$1, resolveStateScope, syncCatalogs, usesGeneratorSelfCheck, validateCatalogs, withTranslationIssueCache } from "@ai-translate/core";
|
|
2
3
|
import { adoptExistingTranslations } from "@ai-translate/fs-json";
|
|
3
4
|
import { existsSync, promises } from "node:fs";
|
|
4
5
|
import * as path from "node:path";
|
|
5
6
|
import { config, parse } from "dotenv";
|
|
6
7
|
import { createJiti } from "jiti";
|
|
7
|
-
import {
|
|
8
|
+
import { appleIntegration, expoIntegration } from "@ai-translate/apple";
|
|
9
|
+
import { detectProject, isLocaleTag, renderConfig, requiredConfigPackages } from "@ai-translate/integrations";
|
|
10
|
+
import { builtinIntegrations } from "@ai-translate/next";
|
|
11
|
+
import { execFile, spawn } from "node:child_process";
|
|
12
|
+
import { promisify } from "node:util";
|
|
8
13
|
import { randomUUID } from "node:crypto";
|
|
9
14
|
import * as os from "node:os";
|
|
10
15
|
import { supportsScopedSave } from "@ai-translate/core/types";
|
|
@@ -65,86 +70,537 @@ async function loadConfig(cwd, explicitPath) {
|
|
|
65
70
|
};
|
|
66
71
|
}
|
|
67
72
|
//#endregion
|
|
68
|
-
//#region src/init.ts
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
]
|
|
73
|
+
//#region src/init-project.ts
|
|
74
|
+
const MANAGERS = [
|
|
75
|
+
"npm",
|
|
76
|
+
"pnpm",
|
|
77
|
+
"yarn",
|
|
78
|
+
"bun"
|
|
79
|
+
];
|
|
80
|
+
const LOCKFILES = {
|
|
81
|
+
npm: ["package-lock.json", "npm-shrinkwrap.json"],
|
|
82
|
+
pnpm: ["pnpm-lock.yaml"],
|
|
83
|
+
yarn: ["yarn.lock"],
|
|
84
|
+
bun: ["bun.lock", "bun.lockb"]
|
|
85
|
+
};
|
|
86
|
+
const SCRIPTS = {
|
|
87
|
+
translate: "ai-translate sync",
|
|
88
|
+
"translate:check": "ai-translate check",
|
|
89
|
+
"translate:validate": "ai-translate validate"
|
|
90
|
+
};
|
|
91
|
+
const DEPENDENCY_FIELDS = [
|
|
92
|
+
"dependencies",
|
|
93
|
+
"devDependencies",
|
|
94
|
+
"optionalDependencies"
|
|
95
|
+
];
|
|
96
|
+
function isFirstPartyPackage(name) {
|
|
97
|
+
return name === "ai-translate" || name.startsWith("@ai-translate/");
|
|
79
98
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
/** Registry versions, ranges, and tags are refreshable. Paths, URLs, workspace
|
|
100
|
+
* protocols, and aliases are intentional user choices and remain untouched. */
|
|
101
|
+
function isRegistryVersion(version) {
|
|
102
|
+
return /^[a-z\d.*+~^<>=| _-]+$/iu.test(version.trim());
|
|
103
|
+
}
|
|
104
|
+
function installCommand(manager, packages, cwd, isWorkspaceRoot, field) {
|
|
105
|
+
const adding = packages.length > 0;
|
|
106
|
+
const saveFlag = field === void 0 ? void 0 : manager === "npm" || manager === "pnpm" ? {
|
|
107
|
+
dependencies: "--save-prod",
|
|
108
|
+
devDependencies: "--save-dev",
|
|
109
|
+
optionalDependencies: "--save-optional"
|
|
110
|
+
}[field] : {
|
|
111
|
+
dependencies: void 0,
|
|
112
|
+
devDependencies: "--dev",
|
|
113
|
+
optionalDependencies: "--optional"
|
|
114
|
+
}[field];
|
|
115
|
+
return {
|
|
116
|
+
command: manager,
|
|
117
|
+
args: [
|
|
118
|
+
manager === "npm" || !adding ? "install" : "add",
|
|
119
|
+
...saveFlag === void 0 ? [] : [saveFlag],
|
|
120
|
+
...manager === "pnpm" && adding && isWorkspaceRoot ? ["--workspace-root"] : [],
|
|
121
|
+
...manager === "yarn" ? [] : ["--ignore-scripts"],
|
|
122
|
+
...packages
|
|
123
|
+
],
|
|
124
|
+
cwd,
|
|
125
|
+
...manager === "yarn" ? { env: { YARN_ENABLE_SCRIPTS: "false" } } : {}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function isRecord(value) {
|
|
129
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
130
|
+
}
|
|
131
|
+
function parseManifest(contents, manifestPath) {
|
|
132
|
+
let value;
|
|
90
133
|
try {
|
|
91
|
-
|
|
92
|
-
const manifest = JSON.parse(raw);
|
|
93
|
-
const declared = /* @__PURE__ */ new Set([...Object.keys(manifest.dependencies ?? {}), ...Object.keys(manifest.devDependencies ?? {})]);
|
|
94
|
-
return expected.filter((name) => !declared.has(name));
|
|
134
|
+
value = JSON.parse(contents);
|
|
95
135
|
} catch {
|
|
96
|
-
|
|
136
|
+
throw new Error(`Invalid JSON in ${manifestPath}. Fix it before running init.`);
|
|
137
|
+
}
|
|
138
|
+
if (!isRecord(value)) throw new Error(`${manifestPath} must contain a JSON object.`);
|
|
139
|
+
for (const field of [
|
|
140
|
+
"dependencies",
|
|
141
|
+
"devDependencies",
|
|
142
|
+
"optionalDependencies",
|
|
143
|
+
"peerDependencies",
|
|
144
|
+
"scripts"
|
|
145
|
+
]) {
|
|
146
|
+
const entries = value[field];
|
|
147
|
+
if (entries !== void 0 && (!isRecord(entries) || Object.values(entries).some((entry) => typeof entry !== "string" || field !== "scripts" && entry.trim() === ""))) throw new Error(`${manifestPath}: ${field} must be an object of strings.`);
|
|
148
|
+
}
|
|
149
|
+
if (value.packageManager !== void 0 && typeof value.packageManager !== "string") throw new Error(`${manifestPath}: packageManager must be a string.`);
|
|
150
|
+
if (value.workspaces !== void 0) {
|
|
151
|
+
const patterns = isRecord(value.workspaces) ? value.workspaces.packages : value.workspaces;
|
|
152
|
+
if (!Array.isArray(patterns) || patterns.some((pattern) => typeof pattern !== "string" || pattern.trim() === "")) throw new Error(`${manifestPath}: workspaces must contain an array of package patterns.`);
|
|
153
|
+
}
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
async function readOptional(filePath) {
|
|
157
|
+
try {
|
|
158
|
+
return await promises.readFile(filePath, "utf8");
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (error.code === "ENOENT") return;
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function exists(filePath) {
|
|
165
|
+
try {
|
|
166
|
+
await promises.lstat(filePath);
|
|
167
|
+
return true;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (error.code === "ENOENT") return false;
|
|
170
|
+
throw error;
|
|
97
171
|
}
|
|
98
172
|
}
|
|
173
|
+
/** Only literal lists establish membership; YAML aliases and expressions do not. */
|
|
174
|
+
function pnpmWorkspacePatterns(contents) {
|
|
175
|
+
const lines = contents.split(/\r?\n/u);
|
|
176
|
+
const start = lines.findIndex((line) => /^packages\s*:/u.test(line));
|
|
177
|
+
if (start === -1) return;
|
|
178
|
+
const inline = lines[start]?.replace(/^packages\s*:\s*/u, "").trim() ?? "";
|
|
179
|
+
if (inline !== "" && !inline.startsWith("#")) try {
|
|
180
|
+
const parsed = JSON.parse(inline);
|
|
181
|
+
return Array.isArray(parsed) && parsed.every((value) => typeof value === "string") ? parsed : void 0;
|
|
182
|
+
} catch {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const patterns = [];
|
|
186
|
+
for (const line of lines.slice(start + 1)) {
|
|
187
|
+
if (line.trim() === "" || line.trimStart().startsWith("#")) continue;
|
|
188
|
+
if (!/^(?:\s|-)/u.test(line)) break;
|
|
189
|
+
const item = line.match(/^\s*-\s+(.+?)\s*$/u)?.[1];
|
|
190
|
+
if (item === void 0) return;
|
|
191
|
+
const doubleQuoted = item.match(/^("(?:[^"\\]|\\.)*")\s*(?:#.*)?$/u)?.[1];
|
|
192
|
+
const singleQuoted = item.match(/^'((?:[^']|'')*)'\s*(?:#.*)?$/u)?.[1];
|
|
193
|
+
if (doubleQuoted !== void 0) try {
|
|
194
|
+
patterns.push(JSON.parse(doubleQuoted));
|
|
195
|
+
} catch {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
else if (singleQuoted !== void 0) patterns.push(singleQuoted.replaceAll("''", "'"));
|
|
199
|
+
else {
|
|
200
|
+
const plain = item.replace(/\s+#.*$/u, "");
|
|
201
|
+
if (/^[!*&@[{?]|[\s"'#:>|]/u.test(plain)) return;
|
|
202
|
+
patterns.push(plain);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return patterns;
|
|
206
|
+
}
|
|
207
|
+
function matchesWorkspace(relative, patterns) {
|
|
208
|
+
const normalized = relative.split(path.sep).join("/");
|
|
209
|
+
const matches = (pattern) => path.matchesGlob(normalized, pattern.replace(/^\.\//u, "").replace(/\/$/u, ""));
|
|
210
|
+
return patterns.some((pattern) => !pattern.startsWith("!") && matches(pattern)) && !patterns.some((pattern) => pattern.startsWith("!") && matches(pattern.slice(1)));
|
|
211
|
+
}
|
|
212
|
+
async function workspaceRoot(cwd, manifest) {
|
|
213
|
+
for (let directory = cwd;; directory = path.dirname(directory)) {
|
|
214
|
+
const text = directory === cwd ? void 0 : await readOptional(path.join(directory, "package.json"));
|
|
215
|
+
const candidate = directory === cwd ? manifest : text === void 0 ? {} : parseManifest(text, path.join(directory, "package.json"));
|
|
216
|
+
const pnpmWorkspace = await readOptional(path.join(directory, "pnpm-workspace.yaml"));
|
|
217
|
+
if (candidate.workspaces !== void 0 || pnpmWorkspace !== void 0) {
|
|
218
|
+
const declared = isRecord(candidate.workspaces) ? candidate.workspaces.packages : candidate.workspaces;
|
|
219
|
+
const patterns = pnpmWorkspace === void 0 ? declared : pnpmWorkspacePatterns(pnpmWorkspace);
|
|
220
|
+
return directory === cwd || patterns !== void 0 && matchesWorkspace(path.relative(directory, cwd), patterns) ? {
|
|
221
|
+
root: directory,
|
|
222
|
+
manifest: candidate
|
|
223
|
+
} : void 0;
|
|
224
|
+
}
|
|
225
|
+
if (path.dirname(directory) === directory || await exists(path.join(directory, ".git"))) return;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function managerFromDeclaration(value) {
|
|
229
|
+
if (value === void 0) return;
|
|
230
|
+
if (typeof value !== "string") throw new Error("The package manager must be npm, pnpm, yarn, or bun.");
|
|
231
|
+
const name = value.split("@")[0];
|
|
232
|
+
if (!MANAGERS.includes(name)) throw new Error(`Unsupported package manager ${value}. Use --package-manager npm, pnpm, yarn, or bun.`);
|
|
233
|
+
return name;
|
|
234
|
+
}
|
|
235
|
+
/** Reads project metadata only. Preview never executes a package manager or writes files. */
|
|
236
|
+
async function planProjectSetup(cwd, packages, options = {}) {
|
|
237
|
+
for (const name of packages) if (name.length > 214 || !/^(?:@[a-z\d][a-z\d._-]*\/)?[a-z\d][a-z\d._-]*$/iu.test(name)) throw new Error(`Invalid dependency package name ${JSON.stringify(name)}.`);
|
|
238
|
+
const root = await promises.realpath(cwd);
|
|
239
|
+
const manifestPath = path.join(root, "package.json");
|
|
240
|
+
if (await exists(manifestPath)) {
|
|
241
|
+
if (!(await promises.lstat(manifestPath)).isFile()) throw new Error(`${manifestPath} must be a regular file, not a symlink or directory.`);
|
|
242
|
+
}
|
|
243
|
+
const originalManifest = await readOptional(manifestPath);
|
|
244
|
+
const manifest = originalManifest === void 0 ? { private: true } : parseManifest(originalManifest, manifestPath);
|
|
245
|
+
const workspace = await workspaceRoot(root, manifest);
|
|
246
|
+
let manager = managerFromDeclaration(options.packageManager ?? manifest.packageManager ?? workspace?.manifest.packageManager);
|
|
247
|
+
if (manager === void 0) {
|
|
248
|
+
const detected = /* @__PURE__ */ new Set();
|
|
249
|
+
for (let directory = root;; directory = path.dirname(directory)) {
|
|
250
|
+
for (const candidate of MANAGERS) for (const fileName of LOCKFILES[candidate]) if (await exists(path.join(directory, fileName))) detected.add(candidate);
|
|
251
|
+
if (directory === (workspace?.root ?? root)) break;
|
|
252
|
+
}
|
|
253
|
+
if (detected.size > 1) throw new Error(`Ambiguous package managers (${[...detected].join(", ")}). Pass --package-manager to choose.`);
|
|
254
|
+
manager = [...detected][0] ?? "npm";
|
|
255
|
+
}
|
|
256
|
+
const declared = new Set(DEPENDENCY_FIELDS.flatMap((field) => Object.keys(manifest[field] ?? {})));
|
|
257
|
+
const missing = [...new Set(packages)].filter((name) => !declared.has(name));
|
|
258
|
+
const grouped = {
|
|
259
|
+
dependencies: [],
|
|
260
|
+
devDependencies: [],
|
|
261
|
+
optionalDependencies: []
|
|
262
|
+
};
|
|
263
|
+
const notices = [];
|
|
264
|
+
for (const name of new Set(packages)) {
|
|
265
|
+
const fields = DEPENDENCY_FIELDS.filter((field) => Object.hasOwn(manifest[field] ?? {}, name));
|
|
266
|
+
if (!isFirstPartyPackage(name)) {
|
|
267
|
+
if (fields.length === 0) grouped.devDependencies.push(name);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (fields.length > 1) throw new Error(`${name} is declared in multiple dependency sections (${fields.join(", ")}). Keep it in one section before running init so its dependency category can be preserved.`);
|
|
271
|
+
const field = fields[0] ?? "devDependencies";
|
|
272
|
+
const version = manifest[field]?.[name];
|
|
273
|
+
if (version !== void 0 && !isRegistryVersion(version)) {
|
|
274
|
+
notices.push(`Kept ${name}'s custom source in ${field}. Init cannot refresh it automatically; ensure it supports the generated configuration.`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
grouped[field].push(`${name}@latest`);
|
|
278
|
+
if (version !== void 0) notices.push(`Refresh ${name} to the latest release, keeping it in ${field}.`);
|
|
279
|
+
}
|
|
280
|
+
const scripts = { ...manifest.scripts };
|
|
281
|
+
let changed = originalManifest === void 0;
|
|
282
|
+
for (const [name, command] of Object.entries(SCRIPTS)) if (!Object.hasOwn(scripts, name)) {
|
|
283
|
+
scripts[name] = command;
|
|
284
|
+
changed = true;
|
|
285
|
+
}
|
|
286
|
+
const indent = originalManifest?.match(/\r?\n([\t ]+)"/u)?.[1] ?? " ";
|
|
287
|
+
const newline = originalManifest?.includes("\r\n") === true ? "\r\n" : "\n";
|
|
288
|
+
const manifestContents = changed ? `${JSON.stringify({
|
|
289
|
+
...manifest,
|
|
290
|
+
scripts
|
|
291
|
+
}, null, indent).replaceAll("\n", newline)}${newline}` : void 0;
|
|
292
|
+
const installCommands = DEPENDENCY_FIELDS.filter((field) => grouped[field].length > 0).map((field) => installCommand(manager, grouped[field], root, workspace?.root === root, field));
|
|
293
|
+
if (installCommands.length === 0) installCommands.push(installCommand(manager, [], root, workspace?.root === root));
|
|
294
|
+
return {
|
|
295
|
+
packageManager: manager,
|
|
296
|
+
manifestPath,
|
|
297
|
+
manifestContents,
|
|
298
|
+
originalManifest,
|
|
299
|
+
packages: missing,
|
|
300
|
+
installCommands,
|
|
301
|
+
notices
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
const executeFile = promisify(execFile);
|
|
305
|
+
/** Windows package-manager shims need cmd.exe. Only fixed manager names and
|
|
306
|
+
* argument tokens generated by this module may enter its command string. */
|
|
307
|
+
function packageManagerProcess(command, args, platform = process.platform) {
|
|
308
|
+
if (platform !== "win32" || command === "bun") return {
|
|
309
|
+
command,
|
|
310
|
+
args: [...args]
|
|
311
|
+
};
|
|
312
|
+
if (!MANAGERS.includes(command) || args.some((arg) => !/^[-@a-z\d._/]+$/iu.test(arg))) throw new Error("Unsafe Windows package-manager command or argument.");
|
|
313
|
+
return {
|
|
314
|
+
command: process.env.ComSpec ?? process.env.COMSPEC ?? "cmd.exe",
|
|
315
|
+
args: [
|
|
316
|
+
"/d",
|
|
317
|
+
"/s",
|
|
318
|
+
"/c",
|
|
319
|
+
`"${command} ${args.join(" ")}"`
|
|
320
|
+
],
|
|
321
|
+
windowsVerbatimArguments: true
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
const installProject = async (command, args, cwd, env) => {
|
|
325
|
+
let actualArgs = [...args];
|
|
326
|
+
let actualEnv = {
|
|
327
|
+
...process.env,
|
|
328
|
+
...env
|
|
329
|
+
};
|
|
330
|
+
if (command === "yarn") {
|
|
331
|
+
const probe = packageManagerProcess(command, ["--version"]);
|
|
332
|
+
const { stdout } = await executeFile(probe.command, probe.args, {
|
|
333
|
+
cwd,
|
|
334
|
+
timeout: 15e3,
|
|
335
|
+
...probe.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: probe.windowsVerbatimArguments }
|
|
336
|
+
});
|
|
337
|
+
const major = Number.parseInt(stdout.trim().split(".")[0] ?? "", 10);
|
|
338
|
+
if (!Number.isFinite(major) || major < 1) throw new Error("Unable to determine the installed Yarn version.");
|
|
339
|
+
if (major === 1) {
|
|
340
|
+
actualArgs = [
|
|
341
|
+
...actualArgs,
|
|
342
|
+
"--ignore-scripts",
|
|
343
|
+
...actualArgs[0] === "add" ? ["--ignore-workspace-root-check"] : []
|
|
344
|
+
];
|
|
345
|
+
actualEnv = { ...process.env };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
await new Promise((resolve, reject) => {
|
|
349
|
+
const invocation = packageManagerProcess(command, actualArgs);
|
|
350
|
+
const child = spawn(invocation.command, invocation.args, {
|
|
351
|
+
cwd,
|
|
352
|
+
env: actualEnv,
|
|
353
|
+
shell: false,
|
|
354
|
+
stdio: "inherit",
|
|
355
|
+
...invocation.windowsVerbatimArguments === void 0 ? {} : { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
|
|
356
|
+
});
|
|
357
|
+
child.once("error", reject);
|
|
358
|
+
child.once("exit", (code, signal) => {
|
|
359
|
+
if (code === 0) resolve();
|
|
360
|
+
else reject(/* @__PURE__ */ new Error(`${command} exited ${signal === null ? `with code ${String(code)}` : `after signal ${signal}`}.`));
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
/** Leaves successful setup edits in place if installing fails, so init can be retried. */
|
|
365
|
+
async function applyProjectSetup(plan, options = {}) {
|
|
366
|
+
if (await readOptional(plan.manifestPath) !== plan.originalManifest) throw new Error(`${plan.manifestPath} changed during init. Run init again.`);
|
|
367
|
+
if (await exists(plan.manifestPath) && !(await promises.lstat(plan.manifestPath)).isFile()) throw new Error(`${plan.manifestPath} must be a regular file, not a symlink or directory.`);
|
|
368
|
+
const lines = [...plan.notices];
|
|
369
|
+
if (plan.manifestContents !== void 0) {
|
|
370
|
+
await promises.writeFile(plan.manifestPath, plan.manifestContents, {
|
|
371
|
+
encoding: "utf8",
|
|
372
|
+
flag: plan.originalManifest === void 0 ? "wx" : "w"
|
|
373
|
+
});
|
|
374
|
+
lines.push(`${plan.originalManifest === void 0 ? "Created" : "Updated"} package.json with translation scripts.`);
|
|
375
|
+
}
|
|
376
|
+
for (const { command, args, cwd, env } of plan.installCommands) {
|
|
377
|
+
if (options.install === false) {
|
|
378
|
+
if (command === "yarn") lines.push(`Install dependencies (Yarn 2+): YARN_ENABLE_SCRIPTS=false yarn ${args.join(" ")}`, `Install dependencies (Yarn 1): yarn ${args.join(" ")} --ignore-scripts${args[0] === "add" ? " --ignore-workspace-root-check" : ""}`);
|
|
379
|
+
else lines.push(`Install dependencies: ${command} ${args.join(" ")}`);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
await (options.installer ?? installProject)(command, args, cwd, env);
|
|
384
|
+
} catch (error) {
|
|
385
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
386
|
+
throw new Error(`Dependency installation failed: ${detail} Setup files were kept. Retry init to finish installing all required dependencies.`, { cause: error });
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (options.install !== false) lines.push(`Dependencies installed with ${plan.packageManager}; lifecycle scripts were disabled.`);
|
|
390
|
+
return lines;
|
|
391
|
+
}
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region src/init.ts
|
|
394
|
+
const CONFIG_FILENAME = "ai-translate.config.ts";
|
|
395
|
+
/** Detection and config plans stay independent of the installation workflow. */
|
|
396
|
+
const builtinInitIntegrations = [
|
|
397
|
+
...builtinIntegrations,
|
|
398
|
+
appleIntegration,
|
|
399
|
+
expoIntegration
|
|
400
|
+
];
|
|
401
|
+
function catalogs(setup) {
|
|
402
|
+
return [setup.plan.catalog, ...setup.plan.additionalCatalogs ?? []];
|
|
403
|
+
}
|
|
404
|
+
function hasResources(setup) {
|
|
405
|
+
return catalogs(setup).some((catalog) => catalog.kind !== "adapter" || !Array.isArray(catalog.options.include) || catalog.options.include.length > 0);
|
|
406
|
+
}
|
|
407
|
+
function overlaps(left, right) {
|
|
408
|
+
if (left.kind === "adapter" || right.kind === "adapter") return left.kind === "adapter" && right.kind === "adapter" && left.factory.from === right.factory.from && left.factory.name === right.factory.name && left.options.rootDir === right.options.rootDir;
|
|
409
|
+
const a = path.resolve(left.rootDir);
|
|
410
|
+
const b = path.resolve(right.rootDir);
|
|
411
|
+
const leftFiles = left.kind === "document-json" && left.localeFiles !== void 0 ? Object.values(left.localeFiles).map((file) => path.resolve(a, file)) : void 0;
|
|
412
|
+
const rightFiles = right.kind === "document-json" && right.localeFiles !== void 0 ? Object.values(right.localeFiles).map((file) => path.resolve(b, file)) : void 0;
|
|
413
|
+
if (leftFiles !== void 0 && rightFiles !== void 0) return leftFiles.some((file) => rightFiles.includes(file));
|
|
414
|
+
if (leftFiles !== void 0) return leftFiles.some((file) => right.kind === "document-json" ? path.dirname(file) === b : file.startsWith(`${b}${path.sep}`));
|
|
415
|
+
if (rightFiles !== void 0) return rightFiles.some((file) => left.kind === "document-json" ? path.dirname(file) === a : file.startsWith(`${a}${path.sep}`));
|
|
416
|
+
return a === b || a.startsWith(`${b}${path.sep}`) || b.startsWith(`${a}${path.sep}`);
|
|
417
|
+
}
|
|
99
418
|
function chooseSetup(setups, requested) {
|
|
100
419
|
if (requested !== void 0) {
|
|
101
420
|
const match = setups.find((setup) => setup.integrationId === requested);
|
|
102
421
|
if (match === void 0) throw new Error(`No ${requested} setup was detected. Detected: ${setups.map((setup) => setup.integrationId).join(", ") || "none"}.`);
|
|
103
422
|
return match;
|
|
104
423
|
}
|
|
105
|
-
const [best
|
|
106
|
-
if (best === void 0) throw new Error("No supported
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
424
|
+
const [best] = setups;
|
|
425
|
+
if (best === void 0) throw new Error("No supported localization setup was found. ai-translate init recognises next-intl and i18next, Expo locale mappings, Apple String Catalogs, localized .strings tables, and Xcode or Apple Swift package projects. For apps with hardcoded text, first externalize strings into localization resources. Run init from the project root; extraction and runtime wiring are described in docs/native-apps.md.");
|
|
426
|
+
const selected = [];
|
|
427
|
+
for (const candidate of setups.filter(hasResources)) {
|
|
428
|
+
const conflict = selected.find((other) => catalogs(other).some((left) => catalogs(candidate).some((right) => overlaps(left, right))));
|
|
429
|
+
if (conflict !== void 0) {
|
|
430
|
+
if (conflict.confidence === candidate.confidence) throw new Error(`Found overlapping localization setups (${conflict.integrationId}, ${candidate.integrationId}). Re-run with --integration <id> to choose.`);
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
selected.push(candidate);
|
|
434
|
+
}
|
|
435
|
+
if (selected.length < 2) return selected[0] ?? best;
|
|
436
|
+
if (selected.some((setup) => setup.plan.sourceLocale !== best.plan.sourceLocale)) throw new Error("Detected setups use different source locales. Run init with --integration <id> and use separate configs for each source locale.");
|
|
437
|
+
const targetLocales = resolveTargets(selected.flatMap((setup) => setup.plan.targetLocales), best.plan.sourceLocale);
|
|
438
|
+
const combined = selected.flatMap((setup) => catalogs(setup).map((catalog, index) => {
|
|
439
|
+
const id = `${setup.integrationId}-${String(index + 1)}`;
|
|
440
|
+
return catalog.kind === "adapter" ? {
|
|
441
|
+
...catalog,
|
|
442
|
+
options: {
|
|
443
|
+
...catalog.options,
|
|
444
|
+
id
|
|
445
|
+
}
|
|
446
|
+
} : {
|
|
447
|
+
...catalog,
|
|
448
|
+
id,
|
|
449
|
+
messageFormat: catalog.messageFormat ?? setup.plan.messageFormat
|
|
450
|
+
};
|
|
451
|
+
}));
|
|
452
|
+
return {
|
|
453
|
+
confidence: best.confidence,
|
|
454
|
+
displayName: selected.map((setup) => setup.displayName).join(" + "),
|
|
455
|
+
evidence: selected.flatMap((setup) => setup.evidence),
|
|
456
|
+
integrationId: selected.map((setup) => setup.integrationId).join("+"),
|
|
457
|
+
plan: {
|
|
458
|
+
...best.plan,
|
|
459
|
+
catalog: combined[0] ?? best.plan.catalog,
|
|
460
|
+
additionalCatalogs: combined.slice(1),
|
|
461
|
+
targetLocales,
|
|
462
|
+
warnings: selected.flatMap((setup) => setup.plan.warnings)
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function resolveTargets(locales, source) {
|
|
467
|
+
const names = /* @__PURE__ */ new Map();
|
|
468
|
+
const sourceTag = Intl.getCanonicalLocales(source)[0];
|
|
469
|
+
for (const locale of locales) {
|
|
470
|
+
if (!isLocaleTag(locale)) throw new Error(`Invalid target locale ${JSON.stringify(locale)}.`);
|
|
471
|
+
const canonical = Intl.getCanonicalLocales(locale)[0] ?? locale;
|
|
472
|
+
if (canonical === sourceTag) throw new Error(`Target locale ${locale} is also the source locale.`);
|
|
473
|
+
const previous = names.get(canonical);
|
|
474
|
+
if (previous !== void 0 && previous !== locale) throw new Error(`Locale aliases ${previous} and ${locale} refer to the same language. Normalize the resource names before combining setups.`);
|
|
475
|
+
names.set(canonical, locale);
|
|
476
|
+
}
|
|
477
|
+
return [...names.values()];
|
|
478
|
+
}
|
|
479
|
+
async function readSetupFile(cwd, name) {
|
|
480
|
+
try {
|
|
481
|
+
const file = path.join(cwd, name);
|
|
482
|
+
const stat = await promises.lstat(file);
|
|
483
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`${name} must be a regular file; init will not follow symbolic links.`);
|
|
484
|
+
return await promises.readFile(file, "utf8");
|
|
485
|
+
} catch (error) {
|
|
486
|
+
if (error.code === "ENOENT") return null;
|
|
487
|
+
throw error;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function appendLines(original, additions) {
|
|
491
|
+
const existing = original ?? "";
|
|
492
|
+
const missing = additions.filter((line) => !existing.split(/\r?\n/u).includes(line));
|
|
493
|
+
if (missing.length === 0) return existing;
|
|
494
|
+
const newline = existing.includes("\r\n") ? "\r\n" : "\n";
|
|
495
|
+
return `${existing}${existing.length > 0 && !existing.endsWith("\n") ? newline : ""}${missing.join(newline)}${newline}`;
|
|
496
|
+
}
|
|
497
|
+
function apiKeyVariable(options) {
|
|
498
|
+
if (options.provider !== "ai-sdk") return "OPENAI_API_KEY";
|
|
499
|
+
return {
|
|
500
|
+
"@ai-sdk/anthropic": "ANTHROPIC_API_KEY",
|
|
501
|
+
"@ai-sdk/google": "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
502
|
+
"@ai-sdk/groq": "GROQ_API_KEY",
|
|
503
|
+
"@ai-sdk/mistral": "MISTRAL_API_KEY",
|
|
504
|
+
"@ai-sdk/openai": "OPENAI_API_KEY",
|
|
505
|
+
"@ai-sdk/xai": "XAI_API_KEY"
|
|
506
|
+
}[options.providerPackage ?? "@ai-sdk/openai"];
|
|
507
|
+
}
|
|
508
|
+
/** Detect, plan all changes, then install the generated config's direct dependencies. */
|
|
117
509
|
async function runInit(cwd, options = {}) {
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
...
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
510
|
+
await readSetupFile(cwd, "package.json");
|
|
511
|
+
const setups = await detectProject(cwd, { integrations: options.integrations ?? builtinInitIntegrations });
|
|
512
|
+
const detected = chooseSetup(setups, options.integration);
|
|
513
|
+
const requestedLocales = options.locales === void 0 ? void 0 : resolveTargets(options.locales, detected.plan.sourceLocale).map((locale) => detected.plan.targetLocales.find((existing) => Intl.getCanonicalLocales(existing)[0] === Intl.getCanonicalLocales(locale)[0]) ?? locale);
|
|
514
|
+
const setup = requestedLocales === void 0 ? detected : {
|
|
515
|
+
...detected,
|
|
516
|
+
plan: {
|
|
517
|
+
...detected.plan,
|
|
518
|
+
targetLocales: requestedLocales
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
for (const catalog of catalogs(setup)) {
|
|
522
|
+
if (catalog.kind !== "document-json" || catalog.localeFiles === void 0) continue;
|
|
523
|
+
const missing = setup.plan.targetLocales.filter((locale) => !Object.hasOwn(catalog.localeFiles ?? {}, locale));
|
|
524
|
+
if (missing.length > 0) throw new Error(`Missing locale file mappings for ${missing.join(", ")}. Add them to the project's locale configuration (expo.locales for Expo), or use --locale to select only mapped languages.`);
|
|
525
|
+
}
|
|
526
|
+
const contents = renderConfig(setup.plan, options);
|
|
527
|
+
const lines = [
|
|
528
|
+
`Detected ${setup.displayName}:`,
|
|
529
|
+
...setup.evidence.map((item) => ` - ${item.detail} (${item.source})`),
|
|
530
|
+
` - Source locale ${setup.plan.sourceLocale}, ${String(setup.plan.targetLocales.length)} target locale(s): ${setup.plan.targetLocales.join(", ")}`,
|
|
531
|
+
...setup.plan.warnings.map((warning) => ` ! ${warning}`)
|
|
532
|
+
];
|
|
533
|
+
const selectedIds = setup.integrationId.split("+");
|
|
534
|
+
const others = setups.filter((candidate) => !selectedIds.includes(candidate.integrationId));
|
|
129
535
|
if (others.length > 0) lines.push(`Also detected, not used: ${others.map((candidate) => candidate.displayName).join(", ")}.`);
|
|
536
|
+
const existing = [];
|
|
537
|
+
for (const name of CONFIG_CANDIDATES) {
|
|
538
|
+
const original = await readSetupFile(cwd, name);
|
|
539
|
+
if (original !== null) existing.push({
|
|
540
|
+
contents,
|
|
541
|
+
name,
|
|
542
|
+
original
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
if (existing.length > 1) throw new Error(`Multiple ai-translate configs exist: ${existing.map((file) => file.name).join(", ")}. Keep one config before running init.`);
|
|
546
|
+
const config = existing[0] ?? {
|
|
547
|
+
contents,
|
|
548
|
+
name: CONFIG_FILENAME,
|
|
549
|
+
original: null
|
|
550
|
+
};
|
|
551
|
+
if (config.original !== null && config.original !== contents && options.force !== true && options.preview !== true) throw new Error(`${config.name} already exists with different contents. Use --preview to review changes or --force to overwrite it.`);
|
|
552
|
+
const project = await planProjectSetup(cwd, requiredConfigPackages(setup.plan, options), options.packageManager === void 0 ? {} : { packageManager: options.packageManager });
|
|
553
|
+
const files = [config];
|
|
554
|
+
const ignore = await readSetupFile(cwd, ".gitignore");
|
|
555
|
+
files.push({
|
|
556
|
+
name: ".gitignore",
|
|
557
|
+
original: ignore,
|
|
558
|
+
contents: appendLines(ignore, [
|
|
559
|
+
"node_modules/",
|
|
560
|
+
".env.local",
|
|
561
|
+
".env.*.local"
|
|
562
|
+
])
|
|
563
|
+
});
|
|
564
|
+
const key = apiKeyVariable(options);
|
|
565
|
+
if (key !== void 0) {
|
|
566
|
+
const example = await readSetupFile(cwd, ".env.example");
|
|
567
|
+
const hasKey = example?.split(/\r?\n/u).some((line) => line.trimStart().startsWith(`${key}=`)) === true;
|
|
568
|
+
files.push({
|
|
569
|
+
name: ".env.example",
|
|
570
|
+
original: example,
|
|
571
|
+
contents: hasKey ? example : appendLines(example, [`${key}=`])
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
lines.push("", `Package manager: ${project.packageManager}.`);
|
|
130
575
|
if (options.preview === true) {
|
|
131
|
-
lines.push(
|
|
576
|
+
lines.push(...project.notices);
|
|
577
|
+
for (const file of files) if (file.contents !== file.original || file === config) lines.push("", `Would write ${file.name}:`, "", file.name === ".env.example" ? `${key ?? "PROVIDER_API_KEY"}= (existing entries preserved)` : file.contents);
|
|
578
|
+
if (project.manifestContents !== void 0) lines.push("", "Would write package.json:", "", project.manifestContents);
|
|
579
|
+
for (const command of project.installCommands) lines.push("", `Would ${options.install === false ? "skip installation; run" : "install with"}: ${command.command} ${command.args.join(" ")}`);
|
|
132
580
|
return {
|
|
133
581
|
configPath: null,
|
|
134
582
|
lines,
|
|
135
583
|
setup
|
|
136
584
|
};
|
|
137
585
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
586
|
+
for (const file of files) {
|
|
587
|
+
if (file.contents === file.original) continue;
|
|
588
|
+
if (await readSetupFile(cwd, file.name) !== file.original) throw new Error(`${file.name} changed during init; rerun to review the current project.`);
|
|
589
|
+
await promises.writeFile(path.join(cwd, file.name), file.contents, {
|
|
590
|
+
encoding: "utf8",
|
|
591
|
+
flag: file.original === null ? "wx" : "w"
|
|
592
|
+
});
|
|
593
|
+
lines.push(`Wrote ${file.name}.`);
|
|
594
|
+
}
|
|
595
|
+
lines.push(...await applyProjectSetup(project, { install: options.install !== false }));
|
|
596
|
+
lines.push("", "Setup complete. Before translating:");
|
|
597
|
+
if (key !== void 0) lines.push(` Set ${key} in your shell or .env.local; .env.example documents the variable.`);
|
|
598
|
+
else lines.push(` Configure the credentials your ${options.providerPackage ?? "AI SDK"} provider reads.`);
|
|
599
|
+
if (setup.plan.targetLocales.length === 0) lines.push(` Choose target languages in ${config.name}, or rerun init --locale fr --locale de --force.`);
|
|
600
|
+
if (!hasResources(setup)) lines.push(" Extract localization resources using the instructions above, then rerun init --force.");
|
|
601
|
+
lines.push(" Run npx ai-translate sync --dry-run to review the work, then npx ai-translate sync to translate.");
|
|
146
602
|
return {
|
|
147
|
-
configPath,
|
|
603
|
+
configPath: path.join(cwd, config.name),
|
|
148
604
|
lines,
|
|
149
605
|
setup
|
|
150
606
|
};
|
|
@@ -159,6 +615,7 @@ function cloneEntry(entry) {
|
|
|
159
615
|
return {
|
|
160
616
|
...entry,
|
|
161
617
|
address: entry.address.map((segment) => ({ ...segment })),
|
|
618
|
+
...entry.context === void 0 ? {} : { context: structuredClone(entry.context) },
|
|
162
619
|
...entry.meta === void 0 ? {} : { meta: { ...entry.meta } },
|
|
163
620
|
...entry.tokens === void 0 ? {} : { tokens: entry.tokens.map((token) => ({ ...token })) }
|
|
164
621
|
};
|
|
@@ -265,6 +722,12 @@ var StagedCatalogs = class {
|
|
|
265
722
|
async promote() {
|
|
266
723
|
for (const staged of this.files.values()) await writeFileAtomic(staged.realPath, await promises.readFile(staged.tempPath), staged.mode);
|
|
267
724
|
}
|
|
725
|
+
async verifyOriginals() {
|
|
726
|
+
await Promise.all([...this.files.values()].map(async (staged) => {
|
|
727
|
+
const current = await readOriginal(staged.realPath);
|
|
728
|
+
if (!(current.original === null ? staged.original === null : staged.original !== null && current.original.equals(staged.original)) || current.mode !== staged.mode) throw new Error(`Localization file changed during translation: ${staged.realPath}. No staged changes were committed; rerun with the updated file.`);
|
|
729
|
+
}));
|
|
730
|
+
}
|
|
268
731
|
async durableChanges() {
|
|
269
732
|
return Promise.all([...this.files.values()].map(async (staged) => ({
|
|
270
733
|
...staged.mode === void 0 ? {} : { mode: staged.mode },
|
|
@@ -344,7 +807,10 @@ var StagedCatalogs = class {
|
|
|
344
807
|
const mergeStagedState = catalog.mergeStagedState?.bind(catalog);
|
|
345
808
|
return {
|
|
346
809
|
createDocumentRef: (sourceRef, locale) => catalog.createDocumentRef(sourceRef, locale),
|
|
810
|
+
...catalog.createScaffoldDocument === void 0 ? {} : { createScaffoldDocument: catalog.createScaffoldDocument.bind(catalog) },
|
|
347
811
|
id: catalog.id,
|
|
812
|
+
...catalog.messageFormats === void 0 ? {} : { messageFormats: catalog.messageFormats },
|
|
813
|
+
...catalog.localizeSourceDocument === void 0 ? {} : { localizeSourceDocument: catalog.localizeSourceDocument.bind(catalog) },
|
|
348
814
|
listDocumentRefs: (sourceLocale) => catalog.listDocumentRefs(sourceLocale),
|
|
349
815
|
loadDocument: (ref) => this.loadStaged(catalog, ref),
|
|
350
816
|
...mergeStagedState === void 0 ? {} : { mergeStagedState },
|
|
@@ -385,11 +851,24 @@ var StagedCatalogs = class {
|
|
|
385
851
|
skippedDocuments += 1;
|
|
386
852
|
continue;
|
|
387
853
|
}
|
|
388
|
-
|
|
854
|
+
const localizedSource = adapter.createScaffoldDocument !== void 0 || adapter.localizeSourceDocument === void 0 ? source : await adapter.localizeSourceDocument({
|
|
855
|
+
locale: options.locale,
|
|
856
|
+
source
|
|
857
|
+
});
|
|
858
|
+
const scaffold = adapter.createScaffoldDocument === void 0 ? await adapter.reconcileDocument({
|
|
389
859
|
ref: targetRef,
|
|
390
|
-
source,
|
|
860
|
+
source: localizedSource,
|
|
391
861
|
target: null
|
|
392
|
-
})
|
|
862
|
+
}) : await adapter.createScaffoldDocument({
|
|
863
|
+
ref: targetRef,
|
|
864
|
+
source: localizedSource,
|
|
865
|
+
strategy
|
|
866
|
+
});
|
|
867
|
+
if (scaffold === null) {
|
|
868
|
+
skippedDocuments += 1;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
await adapter.writeDocument(scaffold);
|
|
393
872
|
createdDocuments += 1;
|
|
394
873
|
}
|
|
395
874
|
return {
|
|
@@ -425,6 +904,7 @@ async function runStagedCatalogTransaction(config, operation, shouldCommit = ()
|
|
|
425
904
|
try {
|
|
426
905
|
const result = await operation(stagedConfig);
|
|
427
906
|
if (!shouldCommit(result)) return result;
|
|
907
|
+
await stagedCatalogs.verifyOriginals();
|
|
428
908
|
const durableStore = durableStateStore(config.state);
|
|
429
909
|
if (durableStore !== null) {
|
|
430
910
|
const documents = await stagedCatalogs.durableChanges();
|
|
@@ -473,6 +953,10 @@ function requireProviderChoice(value) {
|
|
|
473
953
|
if (value !== "ai-sdk" && value !== "openai") throw new Error(`Option "--provider" accepts "openai" or "ai-sdk", not "${value}".`);
|
|
474
954
|
return value;
|
|
475
955
|
}
|
|
956
|
+
function requirePackageManager(value) {
|
|
957
|
+
if (value !== "npm" && value !== "pnpm" && value !== "yarn" && value !== "bun") throw new Error("Option \"--package-manager\" accepts npm, pnpm, yarn, or bun.");
|
|
958
|
+
return value;
|
|
959
|
+
}
|
|
476
960
|
function requireIdenticalToSourcePolicy(value) {
|
|
477
961
|
if (value !== "adopt" && value !== "skip") throw new Error(`Option "--identical-to-source" accepts "adopt" or "skip", not "${value}".`);
|
|
478
962
|
return value;
|
|
@@ -626,7 +1110,7 @@ function printHelp() {
|
|
|
626
1110
|
console.log(`ai-translate
|
|
627
1111
|
|
|
628
1112
|
Usage:
|
|
629
|
-
ai-translate init [--integration <
|
|
1113
|
+
ai-translate init [--locale <locale>] [--integration <id>] [--provider <openai|ai-sdk>] [--provider-package <@ai-sdk/...>] [--model <id>] [--package-manager <npm|pnpm|yarn|bun>] [--no-install] [--preview] [--force]
|
|
630
1114
|
ai-translate validate [--config <path>]
|
|
631
1115
|
ai-translate check [--config <path>] [--locale <locale>] [--catalog <id>] [--unit <id>] [--include-path <json-pointer>] [--max-pending-translations <count>]
|
|
632
1116
|
ai-translate audit [--check] [--refresh] [--config <path>] [--locale <locale>] [--catalog <id>] [--unit <id>] [--include-path <json-pointer>]
|
|
@@ -659,6 +1143,13 @@ function parseCommand(argv) {
|
|
|
659
1143
|
if (flag.length === 0) throw new Error("Encountered an empty option flag.");
|
|
660
1144
|
const nextValue = inlineValue ?? argv[index + 1];
|
|
661
1145
|
switch (flag) {
|
|
1146
|
+
case "no-install":
|
|
1147
|
+
options.install = false;
|
|
1148
|
+
break;
|
|
1149
|
+
case "package-manager":
|
|
1150
|
+
options.packageManager = requireOptionValue(flag, nextValue);
|
|
1151
|
+
if (inlineValue === void 0) index += 1;
|
|
1152
|
+
break;
|
|
662
1153
|
case "check":
|
|
663
1154
|
options.auditCheck = true;
|
|
664
1155
|
break;
|
|
@@ -758,20 +1249,34 @@ async function runCli(argv = process.argv.slice(2), cwd = process.cwd()) {
|
|
|
758
1249
|
return 0;
|
|
759
1250
|
}
|
|
760
1251
|
if (parsed.command === "version") {
|
|
761
|
-
|
|
1252
|
+
const manifest = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
1253
|
+
console.log(manifest.version);
|
|
762
1254
|
return 0;
|
|
763
1255
|
}
|
|
764
1256
|
switch (parsed.command) {
|
|
765
1257
|
case "init": {
|
|
766
1258
|
const result = await runInit(cwd, {
|
|
767
1259
|
force: parsed.options.force === true,
|
|
1260
|
+
install: parsed.options.install !== false,
|
|
768
1261
|
...parsed.options.integration === void 0 ? {} : { integration: parsed.options.integration },
|
|
769
1262
|
...parsed.options.model === void 0 ? {} : { model: parsed.options.model },
|
|
1263
|
+
...parsed.options.locales === void 0 ? {} : { locales: parsed.options.locales },
|
|
1264
|
+
...parsed.options.packageManager === void 0 ? {} : { packageManager: requirePackageManager(parsed.options.packageManager) },
|
|
770
1265
|
preview: parsed.options.preview === true || parsed.options.dryRun === true,
|
|
771
1266
|
...parsed.options.provider === void 0 ? {} : { provider: requireProviderChoice(parsed.options.provider) },
|
|
772
1267
|
...parsed.options.providerPackage === void 0 ? {} : { providerPackage: parsed.options.providerPackage }
|
|
773
1268
|
});
|
|
774
1269
|
console.log(result.lines.join("\n"));
|
|
1270
|
+
if (result.configPath !== null && parsed.options.install !== false) {
|
|
1271
|
+
await loadEnvFiles(cwd);
|
|
1272
|
+
const { config, configPath } = await loadConfig(cwd, result.configPath);
|
|
1273
|
+
const validation = await validateConfig(config, configPath, { locales: [] });
|
|
1274
|
+
if (validation.issues.filter((issue) => issue.severity === "error").length > 0) {
|
|
1275
|
+
console.error(JSON.stringify(validation, null, 2));
|
|
1276
|
+
throw new Error("Setup files were created, but localization validation failed. Fix the reported resources and run npx ai-translate validate.");
|
|
1277
|
+
}
|
|
1278
|
+
console.log("Generated configuration and source resources validated.");
|
|
1279
|
+
}
|
|
775
1280
|
return 0;
|
|
776
1281
|
}
|
|
777
1282
|
case "validate": {
|
|
@@ -944,4 +1449,4 @@ async function runCli(argv = process.argv.slice(2), cwd = process.cwd()) {
|
|
|
944
1449
|
//#endregion
|
|
945
1450
|
export { loadEnvFiles as a, loadConfig as i, runCli as n, findConfigPath as r, defineConfig$1 as t };
|
|
946
1451
|
|
|
947
|
-
//# sourceMappingURL=src-
|
|
1452
|
+
//# sourceMappingURL=src-SwBEpdIU.mjs.map
|