@mariokreitz/langsync 0.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.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mario Kreitz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # @mariokreitz/langsync
2
+
3
+ > Modern localization workflow tooling for TypeScript applications.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@mariokreitz/langsync.svg?style=flat-square&color=2563eb)](https://www.npmjs.com/package/@mariokreitz/langsync)
6
+ [![Node](https://img.shields.io/node/v/@mariokreitz/langsync?style=flat-square&color=2563eb)](https://nodejs.org)
7
+ [![License](https://img.shields.io/npm/l/@mariokreitz/langsync.svg?style=flat-square)](https://github.com/mariokreitz/langsync/blob/main/LICENSE)
8
+
9
+ LangSync is a fast, typed, framework-agnostic CLI that keeps your translation
10
+ files consistent across every locale and every collaborator — without the
11
+ chaos of hand-edited JSON or fragile Excel hand-offs.
12
+
13
+ - **Typed configuration** with `defineConfig()` and IntelliSense.
14
+ - **Bidirectional Excel I/O** for non-technical translators.
15
+ - **Strict validation** with CI-friendly exit codes and JSON output.
16
+ - **Auto-detected integrations** for `i18next`, `ngx-translate`, `react-intl`.
17
+ - **Interactive setup** that scaffolds your config and locale files.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pnpm add -D @mariokreitz/langsync
23
+ # or: npm install -D @mariokreitz/langsync
24
+ # or: yarn add -D @mariokreitz/langsync
25
+ ```
26
+
27
+ > Requires **Node.js 22+** and an ESM-compatible project.
28
+
29
+ ## Quick start
30
+
31
+ ```bash
32
+ # 1. Scaffold typed config + locale stubs
33
+ npx langsync init
34
+
35
+ # 2. Check every locale against the reference
36
+ npx langsync validate
37
+
38
+ # 3. Add missing keys (empty placeholders) to non-reference locales
39
+ npx langsync sync
40
+
41
+ # 4. Hand off to translators via Excel
42
+ npx langsync export excel
43
+
44
+ # 5. Import their work back
45
+ npx langsync import excel
46
+ ```
47
+
48
+ ## Commands
49
+
50
+ | Command | Description |
51
+ | ----------------------- | ------------------------------------------------------------------- |
52
+ | `langsync init` | Initialize a typed `langsync.config.ts` and scaffold locale files. |
53
+ | `langsync validate` | Report missing, extra, and empty keys; exits non-zero on errors. |
54
+ | `langsync find-missing` | Report missing keys per locale; exits non-zero on errors. |
55
+ | `langsync sync` | Synchronize keys from the reference locale into every other locale. |
56
+ | `langsync export excel` | Export all locales into a single `.xlsx` workbook. |
57
+ | `langsync import excel` | Import translations from a workbook back into JSON files. |
58
+
59
+ All read commands support `--reporter json`. All write commands support
60
+ `--dry-run`.
61
+
62
+ ## Configuration
63
+
64
+ ```ts
65
+ // langsync.config.ts
66
+ import { defineConfig } from '@mariokreitz/langsync';
67
+
68
+ export default defineConfig({
69
+ input: './src/i18n',
70
+ output: './translations',
71
+ locales: ['en', 'de', 'fr'],
72
+ defaultLocale: 'en',
73
+ framework: 'i18next',
74
+ });
75
+ ```
76
+
77
+ JSON, JS, and MJS configs are also supported via cosmiconfig.
78
+
79
+ ## Documentation
80
+
81
+ Full documentation, including configuration reference, every CLI command,
82
+ and framework integration recipes:
83
+
84
+ **https://github.com/mariokreitz/langsync**
85
+
86
+ ## License
87
+
88
+ [MIT](https://github.com/mariokreitz/langsync/blob/main/LICENSE) © Mario Kreitz
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ import '@langsync/shared/config';
package/dist/cli.js ADDED
@@ -0,0 +1,512 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { logger } from '@langsync/shared/logger';
4
+ import chalk3 from 'chalk';
5
+ import ora from 'ora';
6
+ import { relative, join, resolve, dirname } from 'path';
7
+ import { readFile, mkdir, writeFile, access } from 'fs/promises';
8
+ import '@langsync/shared/types';
9
+ import prompts from 'prompts';
10
+ import { loadConfig } from '@langsync/shared/config';
11
+ import { loadLocaleFiles, writeJson } from '@langsync/shared/fs';
12
+ import { syncTrees, validateLocales } from '@langsync/core';
13
+ import { exportToExcel, importFromExcel } from '@langsync/excel-engine';
14
+
15
+ function printBanner(version) {
16
+ const title = chalk3.bold.cyan("LangSync");
17
+ const tagline = chalk3.gray("Modern localization workflow tooling.");
18
+ const ver = chalk3.dim(`v${version}`);
19
+ console.log(`
20
+ ${title} ${ver}
21
+ ${tagline}
22
+ `);
23
+ }
24
+ function assertNodeVersion(minMajor) {
25
+ const current = process.versions.node;
26
+ const major = Number.parseInt(current.split(".")[0] ?? "0", 10);
27
+ if (major < minMajor) {
28
+ console.error(
29
+ chalk3.red(
30
+ `langsync requires Node.js >= ${String(minMajor)}. You are running ${current}. Please upgrade.`
31
+ )
32
+ );
33
+ process.exit(1);
34
+ }
35
+ }
36
+ var FRAMEWORK_SIGNATURES = [
37
+ { framework: "i18next", packages: ["i18next", "react-i18next", "vue-i18next"] },
38
+ { framework: "ngx-translate", packages: ["@ngx-translate/core", "@ngx-translate/http-loader"] },
39
+ { framework: "react-intl", packages: ["react-intl", "@formatjs/intl"] }
40
+ ];
41
+ async function detectFramework(cwd) {
42
+ const pkgPath = join(cwd, "package.json");
43
+ let pkg;
44
+ try {
45
+ pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
46
+ } catch {
47
+ return null;
48
+ }
49
+ const allDeps = {
50
+ ...pkg.dependencies,
51
+ ...pkg.devDependencies,
52
+ ...pkg.peerDependencies
53
+ };
54
+ for (const { framework, packages } of FRAMEWORK_SIGNATURES) {
55
+ if (packages.some((p) => p in allDeps)) return framework;
56
+ }
57
+ return null;
58
+ }
59
+ var FRAMEWORK_CHOICES = [
60
+ { title: "i18next", value: "i18next" },
61
+ { title: "ngx-translate (Angular)", value: "ngx-translate" },
62
+ { title: "react-intl", value: "react-intl" },
63
+ { title: "None / Custom", value: "none" }
64
+ ];
65
+ var DEFAULTS = {
66
+ input: "./src/i18n",
67
+ output: "./translations",
68
+ locales: "en,de",
69
+ defaultLocale: "en"
70
+ };
71
+ async function runInitPrompts(options) {
72
+ const framework = options.detectedFramework ?? "none";
73
+ if (options.yes) {
74
+ return {
75
+ input: DEFAULTS.input,
76
+ output: DEFAULTS.output,
77
+ locales: DEFAULTS.locales.split(",").map((l) => l.trim()),
78
+ defaultLocale: DEFAULTS.defaultLocale,
79
+ framework
80
+ };
81
+ }
82
+ const response = await prompts(
83
+ [
84
+ {
85
+ type: "text",
86
+ name: "input",
87
+ message: "Where are your source i18n files?",
88
+ initial: DEFAULTS.input
89
+ },
90
+ {
91
+ type: "text",
92
+ name: "output",
93
+ message: "Where should translation output go?",
94
+ initial: DEFAULTS.output
95
+ },
96
+ {
97
+ type: "text",
98
+ name: "locales",
99
+ message: "Which locales? (comma-separated)",
100
+ initial: DEFAULTS.locales,
101
+ format: (value) => value.split(",").map((l) => l.trim()).filter(Boolean),
102
+ validate: (value) => {
103
+ const list = value.split(",").map((l) => l.trim()).filter(Boolean);
104
+ return list.length > 0 ? true : "Please provide at least one locale.";
105
+ }
106
+ },
107
+ {
108
+ type: "text",
109
+ name: "defaultLocale",
110
+ message: "Default (reference) locale?",
111
+ initial: DEFAULTS.defaultLocale
112
+ },
113
+ {
114
+ type: "select",
115
+ name: "framework",
116
+ message: "Which i18n framework do you use?",
117
+ choices: FRAMEWORK_CHOICES,
118
+ initial: FRAMEWORK_CHOICES.findIndex((c) => c.value === framework)
119
+ }
120
+ ],
121
+ {
122
+ onCancel: () => {
123
+ throw new Error("Aborted by user.");
124
+ }
125
+ }
126
+ );
127
+ return response;
128
+ }
129
+ var CANDIDATE_CONFIG_FILES = [
130
+ "langsync.config.ts",
131
+ "langsync.config.js",
132
+ "langsync.config.mjs",
133
+ "langsync.config.json"
134
+ ];
135
+ async function pathExists(p) {
136
+ try {
137
+ await access(p);
138
+ return true;
139
+ } catch {
140
+ return false;
141
+ }
142
+ }
143
+ async function findExistingConfig(cwd) {
144
+ for (const file of CANDIDATE_CONFIG_FILES) {
145
+ const full = join(cwd, file);
146
+ if (await pathExists(full)) return full;
147
+ }
148
+ return null;
149
+ }
150
+ function renderTsConfig(answers) {
151
+ const frameworkLine = answers.framework === "none" ? "" : ` framework: '${answers.framework}',
152
+ `;
153
+ const localesArr = answers.locales.map((l) => `'${l}'`).join(", ");
154
+ return `import { defineConfig } from '@mariokreitz/langsync';
155
+
156
+ export default defineConfig({
157
+ input: '${answers.input}',
158
+ output: '${answers.output}',
159
+ locales: [${localesArr}],
160
+ defaultLocale: '${answers.defaultLocale}',
161
+ ${frameworkLine}});
162
+ `;
163
+ }
164
+ function renderJsonConfig(answers) {
165
+ const obj = {
166
+ input: answers.input,
167
+ output: answers.output,
168
+ locales: answers.locales,
169
+ defaultLocale: answers.defaultLocale
170
+ };
171
+ if (answers.framework !== "none") obj.framework = answers.framework;
172
+ return JSON.stringify(obj, null, 2) + "\n";
173
+ }
174
+ async function writeConfig(options) {
175
+ const { cwd, answers, format, force } = options;
176
+ const existing = await findExistingConfig(cwd);
177
+ if (existing && !force) {
178
+ throw new Error(
179
+ `A LangSync config already exists at ${existing}. Re-run with --force to overwrite.`
180
+ );
181
+ }
182
+ const configFilename = format === "ts" ? "langsync.config.ts" : "langsync.config.json";
183
+ const configPath = resolve(cwd, configFilename);
184
+ const content = format === "ts" ? renderTsConfig(answers) : renderJsonConfig(answers);
185
+ await mkdir(dirname(configPath), { recursive: true });
186
+ await writeFile(configPath, content, "utf-8");
187
+ const inputDir = resolve(cwd, answers.input);
188
+ await mkdir(inputDir, { recursive: true });
189
+ const createdLocaleFiles = [];
190
+ for (const locale of answers.locales) {
191
+ const localePath = join(inputDir, `${locale}.json`);
192
+ if (!await pathExists(localePath)) {
193
+ await writeFile(localePath, "{}\n", "utf-8");
194
+ createdLocaleFiles.push(localePath);
195
+ }
196
+ }
197
+ return { configPath, createdLocaleFiles };
198
+ }
199
+
200
+ // src/commands/init.ts
201
+ function registerInitCommand(program) {
202
+ program.command("init").description("Initialize a new LangSync configuration in the current project.").option("--force", "Overwrite an existing config file without asking.", false).option("--json", "Emit langsync.config.json instead of langsync.config.ts.", false).option("-y, --yes", "Accept all defaults; skip prompts.", false).action(async (flags) => {
203
+ const cwd = process.cwd();
204
+ const format = flags.json ? "json" : "ts";
205
+ try {
206
+ const detectSpinner = ora("Detecting i18n framework\u2026").start();
207
+ const detectedFramework = await detectFramework(cwd);
208
+ detectSpinner.succeed(
209
+ detectedFramework ? `Detected ${chalk3.cyan(detectedFramework)}` : "No known framework detected"
210
+ );
211
+ const answers = await runInitPrompts({
212
+ detectedFramework,
213
+ yes: flags.yes
214
+ });
215
+ const writeSpinner = ora("Writing config and scaffolding locale files\u2026").start();
216
+ const result = await writeConfig({
217
+ cwd,
218
+ answers,
219
+ format,
220
+ force: flags.force
221
+ });
222
+ writeSpinner.succeed("Files written");
223
+ console.log();
224
+ logger.success(`Created ${chalk3.bold(relative(cwd, result.configPath))}`);
225
+ for (const localeFile of result.createdLocaleFiles) {
226
+ logger.success(`Created ${chalk3.bold(relative(cwd, localeFile))}`);
227
+ }
228
+ console.log();
229
+ logger.info(`Next: run ${chalk3.bold("langsync validate")} to check your locales.`);
230
+ } catch (error) {
231
+ const message = error instanceof Error ? error.message : String(error);
232
+ logger.error(message);
233
+ process.exitCode = 1;
234
+ }
235
+ });
236
+ }
237
+ async function runSync(options) {
238
+ const loaded = await loadConfig(options.cwd);
239
+ if (!loaded) {
240
+ throw new Error("No LangSync config found. Run `langsync init` first.");
241
+ }
242
+ const { config } = loaded;
243
+ const referenceLocale = config.defaultLocale ?? config.locales[0];
244
+ const files = await loadLocaleFiles({
245
+ cwd: options.cwd,
246
+ inputDir: config.input,
247
+ locales: config.locales
248
+ });
249
+ const reference = files.find((f) => f.locale === referenceLocale);
250
+ if (!reference) {
251
+ throw new Error(`Could not find reference locale file for "${referenceLocale}".`);
252
+ }
253
+ const targets = files.filter((f) => f.locale !== referenceLocale);
254
+ const planned = [];
255
+ const written = [];
256
+ for (const target of targets) {
257
+ const merged = syncTrees(reference.translations, target.translations);
258
+ planned.push(target.path);
259
+ if (!options.dryRun) {
260
+ await writeJson(target.path, merged);
261
+ written.push(target.path);
262
+ }
263
+ }
264
+ return { referenceLocale, written, planned };
265
+ }
266
+
267
+ // src/commands/sync.ts
268
+ function registerSyncCommand(program) {
269
+ program.command("sync").description("Synchronize translation keys across all configured locales.").option("--dry-run", "Report planned changes without writing files.", false).action(async (flags) => {
270
+ try {
271
+ const cwd = process.cwd();
272
+ const { written, planned, referenceLocale } = await runSync({
273
+ cwd,
274
+ dryRun: flags.dryRun
275
+ });
276
+ const targets = flags.dryRun ? planned : written;
277
+ if (targets.length === 0) {
278
+ logger.info(`Nothing to sync against ${chalk3.cyan(referenceLocale)}.`);
279
+ } else {
280
+ const verb = flags.dryRun ? "Would update" : "Updated";
281
+ for (const path of targets) {
282
+ logger.success(`${verb} ${chalk3.bold(relative(cwd, path))}`);
283
+ }
284
+ }
285
+ } catch (error) {
286
+ const message = error instanceof Error ? error.message : String(error);
287
+ logger.error(message);
288
+ process.exitCode = 1;
289
+ }
290
+ });
291
+ }
292
+ async function runValidate(options) {
293
+ const loaded = await loadConfig(options.cwd);
294
+ if (!loaded) {
295
+ throw new Error("No LangSync config found. Run `langsync init` first.");
296
+ }
297
+ const { config } = loaded;
298
+ const referenceLocale = config.defaultLocale ?? config.locales[0];
299
+ const files = await loadLocaleFiles({
300
+ cwd: options.cwd,
301
+ inputDir: config.input,
302
+ locales: config.locales
303
+ });
304
+ const issues = validateLocales(files, referenceLocale);
305
+ const hasErrors = issues.some((i) => i.type === "missing" || i.type === "extra");
306
+ return {
307
+ referenceLocale,
308
+ issues,
309
+ exitCode: hasErrors ? 1 : 0
310
+ };
311
+ }
312
+
313
+ // src/commands/validate.ts
314
+ function registerValidateCommand(program) {
315
+ program.command("validate").description("Validate locale consistency, structure and missing keys.").option("--reporter <kind>", "Output format: pretty | json.", "pretty").action(async (flags) => {
316
+ try {
317
+ const { issues, exitCode, referenceLocale } = await runValidate({ cwd: process.cwd() });
318
+ if (flags.reporter === "json") {
319
+ console.log(JSON.stringify({ referenceLocale, issues }, null, 2));
320
+ } else {
321
+ if (issues.length === 0) {
322
+ logger.success(`All locales are consistent with ${chalk3.cyan(referenceLocale)}.`);
323
+ } else {
324
+ const byType = { missing: 0, extra: 0, empty: 0 };
325
+ for (const issue of issues) {
326
+ byType[issue.type]++;
327
+ const colored = issue.type === "empty" ? chalk3.yellow(issue.type) : chalk3.red(issue.type);
328
+ logger.info(`${colored} ${chalk3.cyan(issue.locale)} ${issue.key}`);
329
+ }
330
+ console.log();
331
+ logger.info(
332
+ `Summary: ${byType.missing} missing, ${byType.extra} extra, ${byType.empty} empty`
333
+ );
334
+ }
335
+ }
336
+ process.exitCode = exitCode;
337
+ } catch (error) {
338
+ const message = error instanceof Error ? error.message : String(error);
339
+ logger.error(message);
340
+ process.exitCode = 1;
341
+ }
342
+ });
343
+ }
344
+
345
+ // src/commands/find-missing/run.ts
346
+ async function runFindMissing(options) {
347
+ const { issues, referenceLocale } = await runValidate({ cwd: options.cwd });
348
+ const missingByLocale = {};
349
+ for (const issue of issues) {
350
+ if (issue.type !== "missing") continue;
351
+ (missingByLocale[issue.locale] ??= []).push(issue.key);
352
+ }
353
+ for (const locale of Object.keys(missingByLocale)) {
354
+ missingByLocale[locale].sort();
355
+ }
356
+ const exitCode = Object.keys(missingByLocale).length === 0 ? 0 : 1;
357
+ return { referenceLocale, missingByLocale, exitCode };
358
+ }
359
+
360
+ // src/commands/find-missing.ts
361
+ function registerFindMissingCommand(program) {
362
+ program.command("find-missing").description("Find missing translation keys across locales.").option("--reporter <kind>", "Output format: pretty | json.", "pretty").action(async (flags) => {
363
+ try {
364
+ const { referenceLocale, missingByLocale, exitCode } = await runFindMissing({
365
+ cwd: process.cwd()
366
+ });
367
+ if (flags.reporter === "json") {
368
+ console.log(JSON.stringify({ referenceLocale, missingByLocale }, null, 2));
369
+ } else if (Object.keys(missingByLocale).length === 0) {
370
+ logger.success(`No missing keys relative to ${chalk3.cyan(referenceLocale)}.`);
371
+ } else {
372
+ for (const [locale, keys] of Object.entries(missingByLocale)) {
373
+ logger.warn(`${chalk3.cyan(locale)} is missing ${keys.length} key(s):`);
374
+ for (const key of keys) console.log(` - ${key}`);
375
+ }
376
+ }
377
+ process.exitCode = exitCode;
378
+ } catch (error) {
379
+ const message = error instanceof Error ? error.message : String(error);
380
+ logger.error(message);
381
+ process.exitCode = 1;
382
+ }
383
+ });
384
+ }
385
+ var DEFAULT_FILE = "translations.xlsx";
386
+ var DEFAULT_SHEET = "Translations";
387
+ async function runExportExcel(options) {
388
+ const loaded = await loadConfig(options.cwd);
389
+ if (!loaded) {
390
+ throw new Error("No LangSync config found. Run `langsync init` first.");
391
+ }
392
+ const { config } = loaded;
393
+ const file = resolve(options.cwd, options.file ?? config.excel?.file ?? DEFAULT_FILE);
394
+ const sheetName = options.sheetName ?? config.excel?.sheetName ?? DEFAULT_SHEET;
395
+ const files = await loadLocaleFiles({
396
+ cwd: options.cwd,
397
+ inputDir: config.input,
398
+ locales: config.locales
399
+ });
400
+ await exportToExcel({
401
+ file,
402
+ sheetName,
403
+ locales: files.map((f) => ({ locale: f.locale, translations: f.translations }))
404
+ });
405
+ return { file, sheetName, locales: files.map((f) => f.locale) };
406
+ }
407
+
408
+ // src/commands/export.ts
409
+ function registerExportCommand(program) {
410
+ const cmd = program.command("export").description("Export translations to external formats.");
411
+ cmd.command("excel").description("Export translations to an Excel workbook.").option("--file <path>", "Output Excel file (overrides config).").option("--sheet <name>", "Worksheet name (overrides config).").action(async (flags) => {
412
+ try {
413
+ const cwd = process.cwd();
414
+ const { file, locales } = await runExportExcel({
415
+ cwd,
416
+ file: flags.file,
417
+ sheetName: flags.sheet
418
+ });
419
+ logger.success(
420
+ `Exported ${chalk3.cyan(String(locales.length))} locale(s) to ${chalk3.bold(
421
+ relative(cwd, file)
422
+ )}`
423
+ );
424
+ } catch (error) {
425
+ const message = error instanceof Error ? error.message : String(error);
426
+ logger.error(message);
427
+ process.exitCode = 1;
428
+ }
429
+ });
430
+ }
431
+ var DEFAULT_FILE2 = "translations.xlsx";
432
+ var DEFAULT_SHEET2 = "Translations";
433
+ async function runImportExcel(options) {
434
+ const loaded = await loadConfig(options.cwd);
435
+ if (!loaded) {
436
+ throw new Error("No LangSync config found. Run `langsync init` first.");
437
+ }
438
+ const { config } = loaded;
439
+ const file = resolve(options.cwd, options.file ?? config.excel?.file ?? DEFAULT_FILE2);
440
+ const sheetName = options.sheetName ?? config.excel?.sheetName ?? DEFAULT_SHEET2;
441
+ const inputAbs = resolve(options.cwd, config.input);
442
+ const configuredLocales = new Set(config.locales);
443
+ const result = await importFromExcel(file, sheetName);
444
+ const planned = [];
445
+ const written = [];
446
+ const skipped = [];
447
+ for (const entry of result.locales) {
448
+ if (!configuredLocales.has(entry.locale)) {
449
+ skipped.push(entry.locale);
450
+ continue;
451
+ }
452
+ const path = join(inputAbs, `${entry.locale}.json`);
453
+ planned.push(path);
454
+ if (!options.dryRun) {
455
+ await writeJson(path, entry.translations);
456
+ written.push(path);
457
+ }
458
+ }
459
+ return { file, sheetName, written, planned, skipped };
460
+ }
461
+
462
+ // src/commands/import.ts
463
+ function registerImportCommand(program) {
464
+ const cmd = program.command("import").description("Import translations from external formats.");
465
+ cmd.command("excel").description("Import translations from an Excel workbook.").option("--file <path>", "Input Excel file (overrides config).").option("--sheet <name>", "Worksheet name (overrides config).").option("--dry-run", "Report planned writes without touching disk.", false).action(async (flags) => {
466
+ try {
467
+ const cwd = process.cwd();
468
+ const { written, planned, skipped } = await runImportExcel({
469
+ cwd,
470
+ file: flags.file,
471
+ sheetName: flags.sheet,
472
+ dryRun: flags.dryRun
473
+ });
474
+ const targets = flags.dryRun ? planned : written;
475
+ const verb = flags.dryRun ? "Would write" : "Wrote";
476
+ for (const path of targets) {
477
+ logger.success(`${verb} ${chalk3.bold(relative(cwd, path))}`);
478
+ }
479
+ for (const locale of skipped) {
480
+ logger.warn(`Skipped ${chalk3.cyan(locale)} (not in configured locales).`);
481
+ }
482
+ } catch (error) {
483
+ const message = error instanceof Error ? error.message : String(error);
484
+ logger.error(message);
485
+ process.exitCode = 1;
486
+ }
487
+ });
488
+ }
489
+
490
+ // src/cli.ts
491
+ var VERSION = "0.0.0";
492
+ async function main() {
493
+ assertNodeVersion(22);
494
+ const program = new Command();
495
+ program.name("langsync").description("Modern localization workflow tooling for TypeScript applications.").version(VERSION, "-v, --version", "Output the current version.").hook("preAction", () => {
496
+ printBanner(VERSION);
497
+ });
498
+ registerInitCommand(program);
499
+ registerSyncCommand(program);
500
+ registerValidateCommand(program);
501
+ registerFindMissingCommand(program);
502
+ registerExportCommand(program);
503
+ registerImportCommand(program);
504
+ await program.parseAsync(process.argv);
505
+ }
506
+ main().catch((error) => {
507
+ const message = error instanceof Error ? error.message : String(error);
508
+ logger.error(message);
509
+ process.exit(1);
510
+ });
511
+ //# sourceMappingURL=cli.js.map
512
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/banner.ts","../src/ui/node-version.ts","../src/commands/init/detect-framework.ts","../src/commands/init/prompt.ts","../src/commands/init/write-config.ts","../src/commands/init.ts","../src/commands/sync/run.ts","../src/commands/sync.ts","../src/commands/validate/run.ts","../src/commands/validate.ts","../src/commands/find-missing/run.ts","../src/commands/find-missing.ts","../src/commands/export/run.ts","../src/commands/export.ts","../src/commands/import/run.ts","../src/commands/import.ts","../src/cli.ts"],"names":["chalk","join","logger","relative","loadConfig","loadLocaleFiles","resolve","DEFAULT_FILE","DEFAULT_SHEET","writeJson"],"mappings":"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","file":"cli.js","sourcesContent":["import chalk from 'chalk';\n\nexport function printBanner(version: string): void {\n const title = chalk.bold.cyan('LangSync');\n const tagline = chalk.gray('Modern localization workflow tooling.');\n const ver = chalk.dim(`v${version}`);\n console.log(`\\n ${title} ${ver}\\n ${tagline}\\n`);\n}\n","import chalk from 'chalk';\n\nexport function assertNodeVersion(minMajor: number): void {\n const current = process.versions.node;\n const major = Number.parseInt(current.split('.')[0] ?? '0', 10);\n if (major < minMajor) {\n console.error(\n chalk.red(\n `langsync requires Node.js >= ${String(minMajor)}. ` +\n `You are running ${current}. Please upgrade.`,\n ),\n );\n process.exit(1);\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type I18nFramework } from '@langsync/shared/types';\n\ninterface PackageJsonLike {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n}\n\nconst FRAMEWORK_SIGNATURES: readonly {\n framework: I18nFramework;\n packages: readonly string[];\n}[] = [\n { framework: 'i18next', packages: ['i18next', 'react-i18next', 'vue-i18next'] },\n { framework: 'ngx-translate', packages: ['@ngx-translate/core', '@ngx-translate/http-loader'] },\n { framework: 'react-intl', packages: ['react-intl', '@formatjs/intl'] },\n];\n\n/**\n * Detect the i18n framework used by the project at `cwd` by inspecting its `package.json`.\n * Returns `null` when no known framework dependency is present.\n */\nexport async function detectFramework(cwd: string): Promise<I18nFramework | null> {\n const pkgPath = join(cwd, 'package.json');\n let pkg: PackageJsonLike;\n try {\n pkg = JSON.parse(await readFile(pkgPath, 'utf-8')) as PackageJsonLike;\n } catch {\n return null;\n }\n\n const allDeps = {\n ...pkg.dependencies,\n ...pkg.devDependencies,\n ...pkg.peerDependencies,\n };\n\n for (const { framework, packages } of FRAMEWORK_SIGNATURES) {\n if (packages.some((p) => p in allDeps)) return framework;\n }\n return null;\n}\n","import prompts from 'prompts';\nimport { type I18nFramework } from '@langsync/shared/types';\n\nexport interface InitAnswers {\n input: string;\n output: string;\n locales: string[];\n defaultLocale: string;\n framework: I18nFramework | 'none';\n}\n\nexport interface InitPromptOptions {\n detectedFramework: I18nFramework | null;\n /** When true, skip prompts and use defaults (with detected framework). */\n yes?: boolean;\n}\n\nconst FRAMEWORK_CHOICES = [\n { title: 'i18next', value: 'i18next' as const },\n { title: 'ngx-translate (Angular)', value: 'ngx-translate' as const },\n { title: 'react-intl', value: 'react-intl' as const },\n { title: 'None / Custom', value: 'none' as const },\n];\n\nconst DEFAULTS = {\n input: './src/i18n',\n output: './translations',\n locales: 'en,de',\n defaultLocale: 'en',\n};\n\nexport async function runInitPrompts(options: InitPromptOptions): Promise<InitAnswers> {\n const framework: InitAnswers['framework'] = options.detectedFramework ?? 'none';\n\n if (options.yes) {\n return {\n input: DEFAULTS.input,\n output: DEFAULTS.output,\n locales: DEFAULTS.locales.split(',').map((l) => l.trim()),\n defaultLocale: DEFAULTS.defaultLocale,\n framework,\n };\n }\n\n const response = await prompts(\n [\n {\n type: 'text',\n name: 'input',\n message: 'Where are your source i18n files?',\n initial: DEFAULTS.input,\n },\n {\n type: 'text',\n name: 'output',\n message: 'Where should translation output go?',\n initial: DEFAULTS.output,\n },\n {\n type: 'text',\n name: 'locales',\n message: 'Which locales? (comma-separated)',\n initial: DEFAULTS.locales,\n format: (value: string) =>\n value\n .split(',')\n .map((l) => l.trim())\n .filter(Boolean),\n validate: (value: string) => {\n const list = value\n .split(',')\n .map((l) => l.trim())\n .filter(Boolean);\n return list.length > 0 ? true : 'Please provide at least one locale.';\n },\n },\n {\n type: 'text',\n name: 'defaultLocale',\n message: 'Default (reference) locale?',\n initial: DEFAULTS.defaultLocale,\n },\n {\n type: 'select',\n name: 'framework',\n message: 'Which i18n framework do you use?',\n choices: FRAMEWORK_CHOICES,\n initial: FRAMEWORK_CHOICES.findIndex((c) => c.value === framework),\n },\n ],\n {\n onCancel: () => {\n throw new Error('Aborted by user.');\n },\n },\n );\n\n // prompts() returns a typed-erased Answers record; trust our shape.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n return response as InitAnswers;\n}\n","import { mkdir, writeFile, access } from 'node:fs/promises';\nimport { dirname, join, resolve } from 'node:path';\nimport { type InitAnswers } from './prompt.js';\n\nexport type ConfigFormat = 'ts' | 'json';\n\nexport interface WriteConfigOptions {\n cwd: string;\n answers: InitAnswers;\n format: ConfigFormat;\n force: boolean;\n}\n\nexport interface WriteConfigResult {\n configPath: string;\n createdLocaleFiles: string[];\n}\n\nconst CANDIDATE_CONFIG_FILES = [\n 'langsync.config.ts',\n 'langsync.config.js',\n 'langsync.config.mjs',\n 'langsync.config.json',\n];\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await access(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function findExistingConfig(cwd: string): Promise<string | null> {\n for (const file of CANDIDATE_CONFIG_FILES) {\n const full = join(cwd, file);\n if (await pathExists(full)) return full;\n }\n return null;\n}\n\nfunction renderTsConfig(answers: InitAnswers): string {\n const frameworkLine =\n answers.framework === 'none' ? '' : ` framework: '${answers.framework}',\\n`;\n const localesArr = answers.locales.map((l) => `'${l}'`).join(', ');\n return `import { defineConfig } from '@mariokreitz/langsync';\n\nexport default defineConfig({\n input: '${answers.input}',\n output: '${answers.output}',\n locales: [${localesArr}],\n defaultLocale: '${answers.defaultLocale}',\n${frameworkLine}});\n`;\n}\n\nfunction renderJsonConfig(answers: InitAnswers): string {\n const obj: Record<string, unknown> = {\n input: answers.input,\n output: answers.output,\n locales: answers.locales,\n defaultLocale: answers.defaultLocale,\n };\n if (answers.framework !== 'none') obj.framework = answers.framework;\n return JSON.stringify(obj, null, 2) + '\\n';\n}\n\n/**\n * Write the LangSync config file and scaffold empty locale JSON stubs.\n * Refuses to overwrite an existing config unless `force` is set.\n */\nexport async function writeConfig(options: WriteConfigOptions): Promise<WriteConfigResult> {\n const { cwd, answers, format, force } = options;\n\n const existing = await findExistingConfig(cwd);\n if (existing && !force) {\n throw new Error(\n `A LangSync config already exists at ${existing}. Re-run with --force to overwrite.`,\n );\n }\n\n const configFilename = format === 'ts' ? 'langsync.config.ts' : 'langsync.config.json';\n const configPath = resolve(cwd, configFilename);\n const content = format === 'ts' ? renderTsConfig(answers) : renderJsonConfig(answers);\n\n await mkdir(dirname(configPath), { recursive: true });\n await writeFile(configPath, content, 'utf-8');\n\n // Scaffold empty locale stubs in the input directory.\n const inputDir = resolve(cwd, answers.input);\n await mkdir(inputDir, { recursive: true });\n const createdLocaleFiles: string[] = [];\n for (const locale of answers.locales) {\n const localePath = join(inputDir, `${locale}.json`);\n if (!(await pathExists(localePath))) {\n await writeFile(localePath, '{}\\n', 'utf-8');\n createdLocaleFiles.push(localePath);\n }\n }\n\n return { configPath, createdLocaleFiles };\n}\n","import { type Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { relative } from 'node:path';\nimport { logger } from '@langsync/shared/logger';\nimport { detectFramework } from './init/detect-framework.js';\nimport { runInitPrompts } from './init/prompt.js';\nimport { writeConfig, type ConfigFormat } from './init/write-config.js';\n\ninterface InitFlags {\n force: boolean;\n json: boolean;\n yes: boolean;\n}\n\nexport function registerInitCommand(program: Command): void {\n program\n .command('init')\n .description('Initialize a new LangSync configuration in the current project.')\n .option('--force', 'Overwrite an existing config file without asking.', false)\n .option('--json', 'Emit langsync.config.json instead of langsync.config.ts.', false)\n .option('-y, --yes', 'Accept all defaults; skip prompts.', false)\n .action(async (flags: InitFlags) => {\n const cwd = process.cwd();\n const format: ConfigFormat = flags.json ? 'json' : 'ts';\n\n try {\n const detectSpinner = ora('Detecting i18n framework…').start();\n const detectedFramework = await detectFramework(cwd);\n detectSpinner.succeed(\n detectedFramework\n ? `Detected ${chalk.cyan(detectedFramework)}`\n : 'No known framework detected',\n );\n\n // prompts owns stdio — must NOT be wrapped in a spinner.\n const answers = await runInitPrompts({\n detectedFramework,\n yes: flags.yes,\n });\n\n const writeSpinner = ora('Writing config and scaffolding locale files…').start();\n const result = await writeConfig({\n cwd,\n answers,\n format,\n force: flags.force,\n });\n writeSpinner.succeed('Files written');\n\n console.log();\n logger.success(`Created ${chalk.bold(relative(cwd, result.configPath))}`);\n for (const localeFile of result.createdLocaleFiles) {\n logger.success(`Created ${chalk.bold(relative(cwd, localeFile))}`);\n }\n console.log();\n logger.info(`Next: run ${chalk.bold('langsync validate')} to check your locales.`);\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n logger.error(message);\n process.exitCode = 1;\n }\n });\n}\n","import { loadConfig } from '@langsync/shared/config';\nimport { loadLocaleFiles, writeJson } from '@langsync/shared/fs';\nimport { syncTrees } from '@langsync/core';\n\nexport interface RunSyncOptions {\n cwd: string;\n dryRun?: boolean;\n}\n\nexport interface RunSyncResult {\n referenceLocale: string;\n written: string[];\n planned: string[];\n}\n\n/**\n * Synchronize keys from the reference locale into every other locale,\n * preserving existing translations and adding empty placeholders for new keys.\n */\nexport async function runSync(options: RunSyncOptions): Promise<RunSyncResult> {\n const loaded = await loadConfig(options.cwd);\n if (!loaded) {\n throw new Error('No LangSync config found. Run `langsync init` first.');\n }\n const { config } = loaded;\n const referenceLocale = config.defaultLocale ?? config.locales[0]!;\n\n const files = await loadLocaleFiles({\n cwd: options.cwd,\n inputDir: config.input,\n locales: config.locales,\n });\n\n const reference = files.find((f) => f.locale === referenceLocale);\n if (!reference) {\n throw new Error(`Could not find reference locale file for \"${referenceLocale}\".`);\n }\n\n const targets = files.filter((f) => f.locale !== referenceLocale);\n const planned: string[] = [];\n const written: string[] = [];\n\n for (const target of targets) {\n const merged = syncTrees(reference.translations, target.translations);\n planned.push(target.path);\n if (!options.dryRun) {\n await writeJson(target.path, merged);\n written.push(target.path);\n }\n }\n\n return { referenceLocale, written, planned };\n}\n","import { type Command } from 'commander';\nimport chalk from 'chalk';\nimport { relative } from 'node:path';\nimport { logger } from '@langsync/shared/logger';\nimport { runSync } from './sync/run.js';\n\ninterface SyncFlags {\n dryRun: boolean;\n}\n\nexport function registerSyncCommand(program: Command): void {\n program\n .command('sync')\n .description('Synchronize translation keys across all configured locales.')\n .option('--dry-run', 'Report planned changes without writing files.', false)\n .action(async (flags: SyncFlags) => {\n try {\n const cwd = process.cwd();\n const { written, planned, referenceLocale } = await runSync({\n cwd,\n dryRun: flags.dryRun,\n });\n\n const targets = flags.dryRun ? planned : written;\n if (targets.length === 0) {\n logger.info(`Nothing to sync against ${chalk.cyan(referenceLocale)}.`);\n } else {\n const verb = flags.dryRun ? 'Would update' : 'Updated';\n for (const path of targets) {\n logger.success(`${verb} ${chalk.bold(relative(cwd, path))}`);\n }\n }\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n logger.error(message);\n process.exitCode = 1;\n }\n });\n}\n","import { loadConfig } from '@langsync/shared/config';\nimport { loadLocaleFiles } from '@langsync/shared/fs';\nimport { validateLocales, type ValidationIssue } from '@langsync/core';\n\nexport interface RunValidateOptions {\n cwd: string;\n}\n\nexport interface RunValidateResult {\n referenceLocale: string;\n issues: ValidationIssue[];\n exitCode: 0 | 1;\n}\n\n/**\n * Validate the locale files configured in the LangSync config at `cwd`.\n *\n * - `missing` and `extra` issues are treated as errors (exit code 1).\n * - `empty` issues are treated as warnings only (exit code 0).\n */\nexport async function runValidate(options: RunValidateOptions): Promise<RunValidateResult> {\n const loaded = await loadConfig(options.cwd);\n if (!loaded) {\n throw new Error('No LangSync config found. Run `langsync init` first.');\n }\n\n const { config } = loaded;\n const referenceLocale = config.defaultLocale ?? config.locales[0]!;\n\n const files = await loadLocaleFiles({\n cwd: options.cwd,\n inputDir: config.input,\n locales: config.locales,\n });\n\n const issues = validateLocales(files, referenceLocale);\n const hasErrors = issues.some((i) => i.type === 'missing' || i.type === 'extra');\n\n return {\n referenceLocale,\n issues,\n exitCode: hasErrors ? 1 : 0,\n };\n}\n","import { type Command } from 'commander';\nimport chalk from 'chalk';\nimport { logger } from '@langsync/shared/logger';\nimport { runValidate } from './validate/run.js';\n\ninterface ValidateFlags {\n reporter: 'pretty' | 'json';\n}\n\nexport function registerValidateCommand(program: Command): void {\n program\n .command('validate')\n .description('Validate locale consistency, structure and missing keys.')\n .option('--reporter <kind>', 'Output format: pretty | json.', 'pretty')\n .action(async (flags: ValidateFlags) => {\n try {\n const { issues, exitCode, referenceLocale } = await runValidate({ cwd: process.cwd() });\n\n if (flags.reporter === 'json') {\n console.log(JSON.stringify({ referenceLocale, issues }, null, 2));\n } else {\n if (issues.length === 0) {\n logger.success(`All locales are consistent with ${chalk.cyan(referenceLocale)}.`);\n } else {\n const byType = { missing: 0, extra: 0, empty: 0 };\n for (const issue of issues) {\n byType[issue.type]++;\n const colored =\n issue.type === 'empty' ? chalk.yellow(issue.type) : chalk.red(issue.type);\n logger.info(`${colored} ${chalk.cyan(issue.locale)} ${issue.key}`);\n }\n console.log();\n logger.info(\n `Summary: ${byType.missing} missing, ${byType.extra} extra, ${byType.empty} empty`,\n );\n }\n }\n\n process.exitCode = exitCode;\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n logger.error(message);\n process.exitCode = 1;\n }\n });\n}\n","import { runValidate } from '../validate/run.js';\n\nexport interface RunFindMissingOptions {\n cwd: string;\n}\n\nexport interface RunFindMissingResult {\n referenceLocale: string;\n missingByLocale: Record<string, string[]>;\n exitCode: 0 | 1;\n}\n\n/**\n * Report missing translation keys per locale, relative to the reference locale.\n */\nexport async function runFindMissing(\n options: RunFindMissingOptions,\n): Promise<RunFindMissingResult> {\n const { issues, referenceLocale } = await runValidate({ cwd: options.cwd });\n\n const missingByLocale: Record<string, string[]> = {};\n for (const issue of issues) {\n if (issue.type !== 'missing') continue;\n (missingByLocale[issue.locale] ??= []).push(issue.key);\n }\n for (const locale of Object.keys(missingByLocale)) {\n missingByLocale[locale]!.sort();\n }\n\n const exitCode: 0 | 1 = Object.keys(missingByLocale).length === 0 ? 0 : 1;\n return { referenceLocale, missingByLocale, exitCode };\n}\n","import { type Command } from 'commander';\nimport chalk from 'chalk';\nimport { logger } from '@langsync/shared/logger';\nimport { runFindMissing } from './find-missing/run.js';\n\ninterface FindMissingFlags {\n reporter: 'pretty' | 'json';\n}\n\nexport function registerFindMissingCommand(program: Command): void {\n program\n .command('find-missing')\n .description('Find missing translation keys across locales.')\n .option('--reporter <kind>', 'Output format: pretty | json.', 'pretty')\n .action(async (flags: FindMissingFlags) => {\n try {\n const { referenceLocale, missingByLocale, exitCode } = await runFindMissing({\n cwd: process.cwd(),\n });\n\n if (flags.reporter === 'json') {\n console.log(JSON.stringify({ referenceLocale, missingByLocale }, null, 2));\n } else if (Object.keys(missingByLocale).length === 0) {\n logger.success(`No missing keys relative to ${chalk.cyan(referenceLocale)}.`);\n } else {\n for (const [locale, keys] of Object.entries(missingByLocale)) {\n logger.warn(`${chalk.cyan(locale)} is missing ${keys.length} key(s):`);\n for (const key of keys) console.log(` - ${key}`);\n }\n }\n\n process.exitCode = exitCode;\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n logger.error(message);\n process.exitCode = 1;\n }\n });\n}\n","import { resolve } from 'node:path';\nimport { loadConfig } from '@langsync/shared/config';\nimport { loadLocaleFiles } from '@langsync/shared/fs';\nimport { exportToExcel } from '@langsync/excel-engine';\n\nexport interface RunExportExcelOptions {\n cwd: string;\n /** Override the excel file path (relative to cwd). */\n file?: string;\n /** Override the worksheet name. */\n sheetName?: string;\n}\n\nexport interface RunExportExcelResult {\n file: string;\n sheetName: string;\n locales: string[];\n}\n\nconst DEFAULT_FILE = 'translations.xlsx';\nconst DEFAULT_SHEET = 'Translations';\n\n/**\n * Export all configured locales into a single Excel workbook.\n */\nexport async function runExportExcel(\n options: RunExportExcelOptions,\n): Promise<RunExportExcelResult> {\n const loaded = await loadConfig(options.cwd);\n if (!loaded) {\n throw new Error('No LangSync config found. Run `langsync init` first.');\n }\n const { config } = loaded;\n\n const file = resolve(options.cwd, options.file ?? config.excel?.file ?? DEFAULT_FILE);\n const sheetName = options.sheetName ?? config.excel?.sheetName ?? DEFAULT_SHEET;\n\n const files = await loadLocaleFiles({\n cwd: options.cwd,\n inputDir: config.input,\n locales: config.locales,\n });\n\n await exportToExcel({\n file,\n sheetName,\n locales: files.map((f) => ({ locale: f.locale, translations: f.translations })),\n });\n\n return { file, sheetName, locales: files.map((f) => f.locale) };\n}\n","import { type Command } from 'commander';\nimport chalk from 'chalk';\nimport { relative } from 'node:path';\nimport { logger } from '@langsync/shared/logger';\nimport { runExportExcel } from './export/run.js';\n\ninterface ExportExcelFlags {\n file?: string;\n sheet?: string;\n}\n\nexport function registerExportCommand(program: Command): void {\n const cmd = program.command('export').description('Export translations to external formats.');\n\n cmd\n .command('excel')\n .description('Export translations to an Excel workbook.')\n .option('--file <path>', 'Output Excel file (overrides config).')\n .option('--sheet <name>', 'Worksheet name (overrides config).')\n .action(async (flags: ExportExcelFlags) => {\n try {\n const cwd = process.cwd();\n const { file, locales } = await runExportExcel({\n cwd,\n file: flags.file,\n sheetName: flags.sheet,\n });\n logger.success(\n `Exported ${chalk.cyan(String(locales.length))} locale(s) to ${chalk.bold(\n relative(cwd, file),\n )}`,\n );\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n logger.error(message);\n process.exitCode = 1;\n }\n });\n}\n","import { join, resolve } from 'node:path';\nimport { loadConfig } from '@langsync/shared/config';\nimport { writeJson } from '@langsync/shared/fs';\nimport { importFromExcel } from '@langsync/excel-engine';\n\nexport interface RunImportExcelOptions {\n cwd: string;\n file?: string;\n sheetName?: string;\n dryRun?: boolean;\n}\n\nexport interface RunImportExcelResult {\n file: string;\n sheetName: string;\n written: string[];\n planned: string[];\n skipped: string[];\n}\n\nconst DEFAULT_FILE = 'translations.xlsx';\nconst DEFAULT_SHEET = 'Translations';\n\n/**\n * Import locales from an Excel workbook and write them back to the configured\n * input directory as JSON files. Locales not listed in the config are skipped.\n */\nexport async function runImportExcel(\n options: RunImportExcelOptions,\n): Promise<RunImportExcelResult> {\n const loaded = await loadConfig(options.cwd);\n if (!loaded) {\n throw new Error('No LangSync config found. Run `langsync init` first.');\n }\n const { config } = loaded;\n\n const file = resolve(options.cwd, options.file ?? config.excel?.file ?? DEFAULT_FILE);\n const sheetName = options.sheetName ?? config.excel?.sheetName ?? DEFAULT_SHEET;\n const inputAbs = resolve(options.cwd, config.input);\n const configuredLocales = new Set(config.locales);\n\n const result = await importFromExcel(file, sheetName);\n\n const planned: string[] = [];\n const written: string[] = [];\n const skipped: string[] = [];\n\n for (const entry of result.locales) {\n if (!configuredLocales.has(entry.locale)) {\n skipped.push(entry.locale);\n continue;\n }\n const path = join(inputAbs, `${entry.locale}.json`);\n planned.push(path);\n if (!options.dryRun) {\n await writeJson(path, entry.translations);\n written.push(path);\n }\n }\n\n return { file, sheetName, written, planned, skipped };\n}\n","import { type Command } from 'commander';\nimport chalk from 'chalk';\nimport { relative } from 'node:path';\nimport { logger } from '@langsync/shared/logger';\nimport { runImportExcel } from './import/run.js';\n\ninterface ImportExcelFlags {\n file?: string;\n sheet?: string;\n dryRun: boolean;\n}\n\nexport function registerImportCommand(program: Command): void {\n const cmd = program.command('import').description('Import translations from external formats.');\n\n cmd\n .command('excel')\n .description('Import translations from an Excel workbook.')\n .option('--file <path>', 'Input Excel file (overrides config).')\n .option('--sheet <name>', 'Worksheet name (overrides config).')\n .option('--dry-run', 'Report planned writes without touching disk.', false)\n .action(async (flags: ImportExcelFlags) => {\n try {\n const cwd = process.cwd();\n const { written, planned, skipped } = await runImportExcel({\n cwd,\n file: flags.file,\n sheetName: flags.sheet,\n dryRun: flags.dryRun,\n });\n\n const targets = flags.dryRun ? planned : written;\n const verb = flags.dryRun ? 'Would write' : 'Wrote';\n for (const path of targets) {\n logger.success(`${verb} ${chalk.bold(relative(cwd, path))}`);\n }\n for (const locale of skipped) {\n logger.warn(`Skipped ${chalk.cyan(locale)} (not in configured locales).`);\n }\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n logger.error(message);\n process.exitCode = 1;\n }\n });\n}\n","import { Command } from 'commander';\nimport { logger } from '@langsync/shared/logger';\nimport { printBanner } from './ui/banner.js';\nimport { assertNodeVersion } from './ui/node-version.js';\nimport { registerInitCommand } from './commands/init.js';\nimport { registerSyncCommand } from './commands/sync.js';\nimport { registerValidateCommand } from './commands/validate.js';\nimport { registerFindMissingCommand } from './commands/find-missing.js';\nimport { registerExportCommand } from './commands/export.js';\nimport { registerImportCommand } from './commands/import.js';\n\nconst VERSION = '0.0.0';\n\nasync function main(): Promise<void> {\n assertNodeVersion(22);\n\n const program = new Command();\n\n program\n .name('langsync')\n .description('Modern localization workflow tooling for TypeScript applications.')\n .version(VERSION, '-v, --version', 'Output the current version.')\n .hook('preAction', () => {\n printBanner(VERSION);\n });\n\n registerInitCommand(program);\n registerSyncCommand(program);\n registerValidateCommand(program);\n registerFindMissingCommand(program);\n registerExportCommand(program);\n registerImportCommand(program);\n\n await program.parseAsync(process.argv);\n}\n\nmain().catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n logger.error(message);\n process.exit(1);\n});\n"]}
@@ -0,0 +1 @@
1
+ export { LangSyncConfig, defineConfig } from '@langsync/shared/config';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ export { defineConfig } from '@langsync/shared/config';
3
+ //# sourceMappingURL=index.js.map
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourcesContent":[]}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@mariokreitz/langsync",
3
+ "version": "0.1.0",
4
+ "description": "Modern localization workflow tooling for TypeScript applications.",
5
+ "keywords": [
6
+ "i18n",
7
+ "localization",
8
+ "translation",
9
+ "excel",
10
+ "cli",
11
+ "i18next",
12
+ "ngx-translate",
13
+ "react-intl"
14
+ ],
15
+ "license": "MIT",
16
+ "author": "Mario Kreitz",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/mariokreitz/langsync.git",
20
+ "directory": "packages/cli"
21
+ },
22
+ "homepage": "https://github.com/mariokreitz/langsync#readme",
23
+ "bugs": {
24
+ "url": "https://github.com/mariokreitz/langsync/issues"
25
+ },
26
+ "type": "module",
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "bin": {
31
+ "langsync": "./dist/cli.js"
32
+ },
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "import": "./dist/index.js"
37
+ }
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "README.md",
42
+ "LICENSE"
43
+ ],
44
+ "engines": {
45
+ "node": ">=22"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "dependencies": {
51
+ "chalk": "^5.3.0",
52
+ "commander": "^12.1.0",
53
+ "listr2": "^8.2.5",
54
+ "ora": "^8.1.1",
55
+ "prompts": "^2.4.2",
56
+ "@langsync/shared": "0.1.0",
57
+ "@langsync/excel-engine": "0.1.0",
58
+ "@langsync/core": "0.1.0"
59
+ },
60
+ "devDependencies": {
61
+ "@types/prompts": "^2.4.9",
62
+ "memfs": "^4.15.1"
63
+ },
64
+ "scripts": {
65
+ "build": "tsup",
66
+ "dev": "tsup --watch",
67
+ "start": "node ./dist/cli.js",
68
+ "lint": "eslint src",
69
+ "typecheck": "tsc --noEmit",
70
+ "test": "vitest run --passWithNoTests",
71
+ "clean": "rm -rf dist .turbo"
72
+ }
73
+ }