@intl-party/cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,944 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __commonJS = (cb, mod) => function __require() {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+
29
+ // package.json
30
+ var require_package = __commonJS({
31
+ "package.json"(exports2, module2) {
32
+ module2.exports = {
33
+ name: "@intl-party/cli",
34
+ version: "1.0.0",
35
+ description: "Command-line interface for IntlParty - validation, extraction, and management tools",
36
+ main: "dist/index.js",
37
+ types: "dist/index.d.ts",
38
+ bin: {
39
+ "intl-party": "./dist/cli.js",
40
+ ip: "./dist/cli.js"
41
+ },
42
+ files: [
43
+ "dist"
44
+ ],
45
+ scripts: {
46
+ build: "tsup src/cli.ts src/index.ts --format cjs",
47
+ dev: "tsup src/cli.ts src/index.ts --format cjs --dts --watch",
48
+ test: "vitest",
49
+ "test:watch": "vitest --watch",
50
+ lint: "eslint src --ext .ts",
51
+ typecheck: "tsc --noEmit",
52
+ clean: "rm -rf dist"
53
+ },
54
+ keywords: [
55
+ "cli",
56
+ "i18n",
57
+ "internationalization",
58
+ "validation",
59
+ "extraction",
60
+ "typescript"
61
+ ],
62
+ author: "IntlParty Team",
63
+ license: "MIT",
64
+ dependencies: {
65
+ "@intl-party/core": "workspace:*",
66
+ commander: "^11.1.0",
67
+ chalk: "^5.3.0",
68
+ glob: "^10.3.10",
69
+ "fs-extra": "^11.2.0",
70
+ ora: "^7.0.1",
71
+ inquirer: "^9.2.12"
72
+ },
73
+ devDependencies: {
74
+ "@types/fs-extra": "^11.0.4",
75
+ "@types/inquirer": "^9.0.7",
76
+ "@types/node": "^20.10.0",
77
+ eslint: "^8.55.0",
78
+ jsdom: "^23.0.1",
79
+ tsup: "^8.0.1",
80
+ typescript: "^5.3.0",
81
+ vitest: "^1.0.0"
82
+ },
83
+ repository: {
84
+ type: "git",
85
+ url: "https://github.com/intl-party/intl-party.git",
86
+ directory: "packages/cli"
87
+ }
88
+ };
89
+ }
90
+ });
91
+
92
+ // src/cli.ts
93
+ var import_commander = require("commander");
94
+ var import_chalk7 = __toESM(require("chalk"));
95
+
96
+ // src/commands/validate.ts
97
+ var import_chalk = __toESM(require("chalk"));
98
+ var import_ora = __toESM(require("ora"));
99
+ var import_fs_extra3 = __toESM(require("fs-extra"));
100
+ var import_core = require("@intl-party/core");
101
+
102
+ // src/utils/config.ts
103
+ var import_fs_extra = __toESM(require("fs-extra"));
104
+ var import_path = __toESM(require("path"));
105
+ var DEFAULT_CONFIG = {
106
+ locales: ["en", "es", "fr"],
107
+ defaultLocale: "en",
108
+ namespaces: ["common"],
109
+ translationPaths: {},
110
+ sourcePatterns: ["src/**/*.{ts,tsx,js,jsx}"],
111
+ outputDir: "./translations",
112
+ validation: {
113
+ strict: false,
114
+ logMissing: true,
115
+ throwOnMissing: false,
116
+ validateFormats: true
117
+ },
118
+ extraction: {
119
+ keyPrefix: "",
120
+ markExtracted: true,
121
+ sortKeys: true,
122
+ includeMetadata: false
123
+ },
124
+ sync: {
125
+ removeUnused: false,
126
+ addMissing: true,
127
+ preserveOrder: true
128
+ }
129
+ };
130
+ async function loadConfig(configPath) {
131
+ const configFiles = [
132
+ configPath,
133
+ "intl-party.config.js",
134
+ "intl-party.config.ts",
135
+ "intl-party.config.json",
136
+ ".intl-party.config.js",
137
+ ".intl-party.config.ts",
138
+ ".intl-party.config.json"
139
+ ].filter(Boolean);
140
+ for (const configFile of configFiles) {
141
+ if (configFile && await import_fs_extra.default.pathExists(configFile)) {
142
+ try {
143
+ let config;
144
+ if (configFile.endsWith(".json")) {
145
+ const content = await import_fs_extra.default.readFile(configFile, "utf-8");
146
+ config = JSON.parse(content);
147
+ } else {
148
+ delete require.cache[import_path.default.resolve(configFile)];
149
+ config = require(import_path.default.resolve(configFile));
150
+ if (config.default) {
151
+ config = config.default;
152
+ }
153
+ }
154
+ return mergeConfig(DEFAULT_CONFIG, config);
155
+ } catch (error) {
156
+ throw new Error(
157
+ `Failed to load config from ${configFile}: ${error instanceof Error ? error.message : error}`
158
+ );
159
+ }
160
+ }
161
+ }
162
+ const packageJsonPath = "package.json";
163
+ if (await import_fs_extra.default.pathExists(packageJsonPath)) {
164
+ try {
165
+ const packageJson2 = await import_fs_extra.default.readJson(packageJsonPath);
166
+ if (packageJson2["intl-party"]) {
167
+ return mergeConfig(DEFAULT_CONFIG, packageJson2["intl-party"]);
168
+ }
169
+ } catch {
170
+ }
171
+ }
172
+ const autoDetected = await autoDetectConfig();
173
+ return mergeConfig(DEFAULT_CONFIG, autoDetected);
174
+ }
175
+ async function autoDetectConfig() {
176
+ const config = {};
177
+ const commonPaths = [
178
+ "translations",
179
+ "locales",
180
+ "i18n",
181
+ "public/locales",
182
+ "src/locales",
183
+ "src/translations",
184
+ "assets/locales"
185
+ ];
186
+ for (const basePath of commonPaths) {
187
+ if (await import_fs_extra.default.pathExists(basePath)) {
188
+ try {
189
+ const entries = await import_fs_extra.default.readdir(basePath);
190
+ const locales = entries.filter(
191
+ (entry) => import_fs_extra.default.statSync(import_path.default.join(basePath, entry)).isDirectory()
192
+ );
193
+ if (locales.length > 0) {
194
+ config.locales = locales;
195
+ config.translationPaths = {};
196
+ const firstLocaleDir = import_path.default.join(basePath, locales[0]);
197
+ const namespaceFiles = await import_fs_extra.default.readdir(firstLocaleDir);
198
+ const namespaces = namespaceFiles.filter((file) => file.endsWith(".json")).map((file) => import_path.default.basename(file, ".json"));
199
+ if (namespaces.length > 0) {
200
+ config.namespaces = namespaces;
201
+ for (const locale of locales) {
202
+ config.translationPaths[locale] = {};
203
+ for (const namespace of namespaces) {
204
+ config.translationPaths[locale][namespace] = import_path.default.join(
205
+ basePath,
206
+ locale,
207
+ `${namespace}.json`
208
+ );
209
+ }
210
+ }
211
+ }
212
+ break;
213
+ }
214
+ } catch {
215
+ }
216
+ }
217
+ }
218
+ return config;
219
+ }
220
+ function mergeConfig(defaultConfig, userConfig) {
221
+ return {
222
+ ...defaultConfig,
223
+ ...userConfig,
224
+ validation: {
225
+ ...defaultConfig.validation,
226
+ ...userConfig.validation
227
+ },
228
+ extraction: {
229
+ ...defaultConfig.extraction,
230
+ ...userConfig.extraction
231
+ },
232
+ sync: {
233
+ ...defaultConfig.sync,
234
+ ...userConfig.sync
235
+ },
236
+ translationPaths: {
237
+ ...defaultConfig.translationPaths,
238
+ ...userConfig.translationPaths
239
+ }
240
+ };
241
+ }
242
+ async function saveConfig(config, configPath = "intl-party.config.json") {
243
+ await import_fs_extra.default.writeFile(configPath, JSON.stringify(config, null, 2));
244
+ }
245
+
246
+ // src/utils/translations.ts
247
+ var import_fs_extra2 = __toESM(require("fs-extra"));
248
+ async function loadTranslations(translationPaths, locales, namespaces) {
249
+ const translations = {};
250
+ for (const locale of locales) {
251
+ translations[locale] = {};
252
+ for (const namespace of namespaces) {
253
+ const translationPath = translationPaths[locale]?.[namespace];
254
+ if (translationPath && await import_fs_extra2.default.pathExists(translationPath)) {
255
+ try {
256
+ const content = await import_fs_extra2.default.readJson(translationPath);
257
+ translations[locale][namespace] = content;
258
+ } catch (error) {
259
+ console.warn(
260
+ `Failed to load ${locale}/${namespace} from ${translationPath}`
261
+ );
262
+ translations[locale][namespace] = {};
263
+ }
264
+ } else {
265
+ translations[locale][namespace] = {};
266
+ }
267
+ }
268
+ }
269
+ return translations;
270
+ }
271
+
272
+ // src/commands/validate.ts
273
+ async function validateCommand(options) {
274
+ const spinner = (0, import_ora.default)("Loading configuration...").start();
275
+ try {
276
+ const config = await loadConfig(options.config);
277
+ spinner.succeed("Configuration loaded");
278
+ const targetLocales = options.locales || config.locales;
279
+ const targetNamespaces = options.namespaces || config.namespaces;
280
+ spinner.start("Loading translations...");
281
+ const translations = await loadTranslations(
282
+ config.translationPaths,
283
+ targetLocales,
284
+ targetNamespaces
285
+ );
286
+ spinner.succeed("Translations loaded");
287
+ spinner.start("Validating translations...");
288
+ const validationConfig = {
289
+ strict: options.strict || config.validation?.strict || false,
290
+ logMissing: options.verbose || false,
291
+ throwOnMissing: false,
292
+ validateFormats: true
293
+ };
294
+ const result = (0, import_core.validateTranslations)(
295
+ translations,
296
+ targetLocales,
297
+ targetNamespaces,
298
+ validationConfig
299
+ );
300
+ spinner.stop();
301
+ await outputResults(result, options);
302
+ if (!result.valid) {
303
+ process.exit(1);
304
+ }
305
+ } catch (error) {
306
+ spinner.fail("Validation failed");
307
+ console.error(
308
+ import_chalk.default.red("Error:"),
309
+ error instanceof Error ? error.message : error
310
+ );
311
+ process.exit(1);
312
+ }
313
+ }
314
+ async function outputResults(result, options) {
315
+ if (options.format === "json") {
316
+ const output = JSON.stringify(result, null, 2);
317
+ if (options.output) {
318
+ await import_fs_extra3.default.writeFile(options.output, output);
319
+ console.log(import_chalk.default.green("\u2713"), `Results written to ${options.output}`);
320
+ } else {
321
+ console.log(output);
322
+ }
323
+ return;
324
+ }
325
+ if (options.format === "junit") {
326
+ const junitXml = generateJUnitXML(result);
327
+ if (options.output) {
328
+ await import_fs_extra3.default.writeFile(options.output, junitXml);
329
+ console.log(
330
+ import_chalk.default.green("\u2713"),
331
+ `JUnit report written to ${options.output}`
332
+ );
333
+ } else {
334
+ console.log(junitXml);
335
+ }
336
+ return;
337
+ }
338
+ if (result.valid) {
339
+ console.log(import_chalk.default.green("\u2713 All translations are valid!"));
340
+ if (result.warnings.length > 0) {
341
+ console.log(import_chalk.default.yellow(`
342
+ \u26A0 ${result.warnings.length} warning(s):`));
343
+ result.warnings.forEach((warning, index) => {
344
+ console.log(
345
+ ` ${index + 1}. ${import_chalk.default.yellow(warning.type)}: ${warning.message}`
346
+ );
347
+ if (options.verbose) {
348
+ console.log(
349
+ ` Locale: ${warning.locale}, Namespace: ${warning.namespace}, Key: ${warning.key}`
350
+ );
351
+ }
352
+ });
353
+ }
354
+ } else {
355
+ console.log(
356
+ import_chalk.default.red(`\u2717 Validation failed with ${result.errors.length} error(s):`)
357
+ );
358
+ result.errors.forEach((error, index) => {
359
+ console.log(` ${index + 1}. ${import_chalk.default.red(error.type)}: ${error.message}`);
360
+ if (options.verbose) {
361
+ console.log(
362
+ ` Locale: ${error.locale}, Namespace: ${error.namespace}, Key: ${error.key}`
363
+ );
364
+ }
365
+ });
366
+ if (result.warnings.length > 0) {
367
+ console.log(import_chalk.default.yellow(`
368
+ \u26A0 ${result.warnings.length} warning(s):`));
369
+ result.warnings.forEach((warning, index) => {
370
+ console.log(
371
+ ` ${index + 1}. ${import_chalk.default.yellow(warning.type)}: ${warning.message}`
372
+ );
373
+ if (options.verbose) {
374
+ console.log(
375
+ ` Locale: ${warning.locale}, Namespace: ${warning.namespace}, Key: ${warning.key}`
376
+ );
377
+ }
378
+ });
379
+ }
380
+ }
381
+ const totalIssues = result.errors.length + result.warnings.length;
382
+ if (totalIssues > 0) {
383
+ console.log(
384
+ `
385
+ Summary: ${result.errors.length} errors, ${result.warnings.length} warnings`
386
+ );
387
+ }
388
+ }
389
+ function generateJUnitXML(result) {
390
+ const totalTests = 1;
391
+ const failures = result.valid ? 0 : 1;
392
+ const errors = result.errors.length;
393
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>
394
+ `;
395
+ xml += `<testsuites name="intl-party-validation" tests="${totalTests}" failures="${failures}" errors="${errors}">
396
+ `;
397
+ xml += ` <testsuite name="translation-validation" tests="${totalTests}" failures="${failures}" errors="${errors}">
398
+ `;
399
+ if (result.valid) {
400
+ xml += ` <testcase name="validation" classname="translations" />
401
+ `;
402
+ } else {
403
+ xml += ` <testcase name="validation" classname="translations">
404
+ `;
405
+ xml += ` <failure message="Translation validation failed">
406
+ `;
407
+ xml += ` <![CDATA[
408
+ `;
409
+ result.errors.forEach((error) => {
410
+ xml += `${error.type}: ${error.message}
411
+ `;
412
+ xml += ` Locale: ${error.locale}, Namespace: ${error.namespace}, Key: ${error.key}
413
+
414
+ `;
415
+ });
416
+ xml += ` ]]>
417
+ `;
418
+ xml += ` </failure>
419
+ `;
420
+ xml += ` </testcase>
421
+ `;
422
+ }
423
+ xml += ` </testsuite>
424
+ `;
425
+ xml += `</testsuites>
426
+ `;
427
+ return xml;
428
+ }
429
+
430
+ // src/commands/extract.ts
431
+ var import_chalk2 = __toESM(require("chalk"));
432
+ var import_ora2 = __toESM(require("ora"));
433
+ var import_glob = require("glob");
434
+ var import_fs_extra4 = __toESM(require("fs-extra"));
435
+ var import_path2 = __toESM(require("path"));
436
+ async function extractCommand(options) {
437
+ const spinner = (0, import_ora2.default)("Extracting translation keys...").start();
438
+ try {
439
+ const sourcePatterns = options.source || ["src/**/*.{ts,tsx,js,jsx}"];
440
+ const outputDir = options.output || "./translations";
441
+ const files = await (0, import_glob.glob)(sourcePatterns);
442
+ spinner.succeed(`Found ${files.length} source files`);
443
+ const extractedKeys = /* @__PURE__ */ new Set();
444
+ for (const file of files) {
445
+ const content = await import_fs_extra4.default.readFile(file, "utf-8");
446
+ const keys = extractKeysFromContent(content);
447
+ keys.forEach((key) => extractedKeys.add(key));
448
+ }
449
+ spinner.succeed(`Extracted ${extractedKeys.size} unique translation keys`);
450
+ if (options.dryRun) {
451
+ console.log("\nExtracted keys:");
452
+ Array.from(extractedKeys).sort().forEach((key) => {
453
+ console.log(` ${import_chalk2.default.cyan(key)}`);
454
+ });
455
+ return;
456
+ }
457
+ await writeExtractedKeys(Array.from(extractedKeys), outputDir, options);
458
+ console.log(import_chalk2.default.green(`\u2713 Translation keys extracted to ${outputDir}`));
459
+ } catch (error) {
460
+ spinner.fail("Extraction failed");
461
+ console.error(
462
+ import_chalk2.default.red("Error:"),
463
+ error instanceof Error ? error.message : error
464
+ );
465
+ process.exit(1);
466
+ }
467
+ }
468
+ function extractKeysFromContent(content) {
469
+ const keys = [];
470
+ const patterns = [
471
+ /t\(['"`]([^'"`]+)['"`]\)/g,
472
+ // t('key')
473
+ /useTranslations\(\)\(['"`]([^'"`]+)['"`]\)/g,
474
+ // useTranslations()('key')
475
+ /i18nKey=['"`]([^'"`]+)['"`]/g,
476
+ // i18nKey="key"
477
+ /\{\s*t\(['"`]([^'"`]+)['"`]\)\s*\}/g
478
+ // { t('key') }
479
+ ];
480
+ for (const pattern of patterns) {
481
+ let match;
482
+ while ((match = pattern.exec(content)) !== null) {
483
+ keys.push(match[1]);
484
+ }
485
+ }
486
+ return keys;
487
+ }
488
+ async function writeExtractedKeys(keys, outputDir, options) {
489
+ await import_fs_extra4.default.ensureDir(outputDir);
490
+ const namespaces = { common: [] };
491
+ for (const key of keys) {
492
+ const parts = key.split(".");
493
+ if (parts.length > 1) {
494
+ const namespace = parts[0];
495
+ const keyWithoutNamespace = parts.slice(1).join(".");
496
+ if (!namespaces[namespace]) {
497
+ namespaces[namespace] = [];
498
+ }
499
+ namespaces[namespace].push(keyWithoutNamespace);
500
+ } else {
501
+ namespaces.common.push(key);
502
+ }
503
+ }
504
+ for (const [namespace, namespaceKeys] of Object.entries(namespaces)) {
505
+ if (namespaceKeys.length === 0) continue;
506
+ const filePath = import_path2.default.join(outputDir, "en", `${namespace}.json`);
507
+ await import_fs_extra4.default.ensureDir(import_path2.default.dirname(filePath));
508
+ let translations = {};
509
+ if (options.update && await import_fs_extra4.default.pathExists(filePath)) {
510
+ try {
511
+ translations = await import_fs_extra4.default.readJson(filePath);
512
+ } catch {
513
+ }
514
+ }
515
+ for (const key of namespaceKeys) {
516
+ if (!translations[key]) {
517
+ translations[key] = key;
518
+ }
519
+ }
520
+ await import_fs_extra4.default.writeJson(filePath, translations, { spaces: 2 });
521
+ }
522
+ }
523
+
524
+ // src/commands/sync.ts
525
+ var import_chalk3 = __toESM(require("chalk"));
526
+ async function syncCommand(options) {
527
+ console.log(import_chalk3.default.blue("Sync command not yet implemented"));
528
+ console.log("Options:", options);
529
+ }
530
+
531
+ // src/commands/init.ts
532
+ var import_chalk4 = __toESM(require("chalk"));
533
+ var import_ora3 = __toESM(require("ora"));
534
+ var import_fs_extra5 = __toESM(require("fs-extra"));
535
+ var import_path3 = __toESM(require("path"));
536
+ var import_inquirer = __toESM(require("inquirer"));
537
+ async function initCommand(options) {
538
+ const spinner = (0, import_ora3.default)("Initializing IntlParty configuration...").start();
539
+ try {
540
+ const configPath = "intl-party.config.json";
541
+ if (await import_fs_extra5.default.pathExists(configPath) && !options.force) {
542
+ spinner.stop();
543
+ const { overwrite } = await import_inquirer.default.prompt([
544
+ {
545
+ type: "confirm",
546
+ name: "overwrite",
547
+ message: "Configuration file already exists. Overwrite?",
548
+ default: false
549
+ }
550
+ ]);
551
+ if (!overwrite) {
552
+ console.log(import_chalk4.default.yellow("Initialization cancelled."));
553
+ return;
554
+ }
555
+ }
556
+ spinner.start("Setting up configuration...");
557
+ spinner.stop();
558
+ const answers = await import_inquirer.default.prompt([
559
+ {
560
+ type: "input",
561
+ name: "defaultLocale",
562
+ message: "Default locale:",
563
+ default: "en"
564
+ },
565
+ {
566
+ type: "input",
567
+ name: "locales",
568
+ message: "Supported locales (comma-separated):",
569
+ default: "en,es,fr",
570
+ filter: (input) => input.split(",").map((l) => l.trim())
571
+ },
572
+ {
573
+ type: "input",
574
+ name: "namespaces",
575
+ message: "Translation namespaces (comma-separated):",
576
+ default: "common",
577
+ filter: (input) => input.split(",").map((n) => n.trim())
578
+ },
579
+ {
580
+ type: "input",
581
+ name: "translationsDir",
582
+ message: "Translations directory:",
583
+ default: "./translations"
584
+ },
585
+ {
586
+ type: "input",
587
+ name: "sourceDir",
588
+ message: "Source directory:",
589
+ default: "./src"
590
+ }
591
+ ]);
592
+ const config = {
593
+ locales: answers.locales,
594
+ defaultLocale: answers.defaultLocale,
595
+ namespaces: answers.namespaces,
596
+ translationPaths: {},
597
+ sourcePatterns: [`${answers.sourceDir}/**/*.{ts,tsx,js,jsx}`],
598
+ outputDir: answers.translationsDir,
599
+ validation: {
600
+ strict: false,
601
+ logMissing: true,
602
+ throwOnMissing: false,
603
+ validateFormats: true
604
+ },
605
+ extraction: {
606
+ keyPrefix: "",
607
+ markExtracted: true,
608
+ sortKeys: true,
609
+ includeMetadata: false
610
+ },
611
+ sync: {
612
+ removeUnused: false,
613
+ addMissing: true,
614
+ preserveOrder: true
615
+ }
616
+ };
617
+ for (const locale of answers.locales) {
618
+ config.translationPaths[locale] = {};
619
+ for (const namespace of answers.namespaces) {
620
+ config.translationPaths[locale][namespace] = import_path3.default.join(
621
+ answers.translationsDir,
622
+ locale,
623
+ `${namespace}.json`
624
+ );
625
+ }
626
+ }
627
+ spinner.start("Creating directory structure...");
628
+ await import_fs_extra5.default.ensureDir(answers.translationsDir);
629
+ for (const locale of answers.locales) {
630
+ const localeDir = import_path3.default.join(answers.translationsDir, locale);
631
+ await import_fs_extra5.default.ensureDir(localeDir);
632
+ for (const namespace of answers.namespaces) {
633
+ const filePath = import_path3.default.join(localeDir, `${namespace}.json`);
634
+ if (!await import_fs_extra5.default.pathExists(filePath)) {
635
+ await import_fs_extra5.default.writeJson(filePath, {}, { spaces: 2 });
636
+ }
637
+ }
638
+ }
639
+ await saveConfig(config, configPath);
640
+ if (options.template) {
641
+ await createTemplateFiles(options.template, answers);
642
+ }
643
+ spinner.succeed("IntlParty configuration initialized successfully!");
644
+ console.log(import_chalk4.default.green("\n\u2713 Configuration created:"), configPath);
645
+ console.log(
646
+ import_chalk4.default.green("\u2713 Translation directories created:"),
647
+ answers.translationsDir
648
+ );
649
+ if (options.template) {
650
+ console.log(import_chalk4.default.green("\u2713 Template files created"));
651
+ }
652
+ console.log(import_chalk4.default.blue("\nNext steps:"));
653
+ console.log("1. Add translations to your JSON files");
654
+ console.log(
655
+ "2. Run",
656
+ import_chalk4.default.cyan("intl-party extract"),
657
+ "to extract keys from your code"
658
+ );
659
+ console.log(
660
+ "3. Run",
661
+ import_chalk4.default.cyan("intl-party validate"),
662
+ "to check your translations"
663
+ );
664
+ } catch (error) {
665
+ spinner.fail("Initialization failed");
666
+ console.error(
667
+ import_chalk4.default.red("Error:"),
668
+ error instanceof Error ? error.message : error
669
+ );
670
+ process.exit(1);
671
+ }
672
+ }
673
+ async function createTemplateFiles(template, config) {
674
+ switch (template) {
675
+ case "nextjs":
676
+ await createNextJSTemplate(config);
677
+ break;
678
+ case "react":
679
+ await createReactTemplate(config);
680
+ break;
681
+ case "vanilla":
682
+ await createVanillaTemplate(config);
683
+ break;
684
+ default:
685
+ console.log(import_chalk4.default.yellow(`Unknown template: ${template}`));
686
+ }
687
+ }
688
+ async function createNextJSTemplate(config) {
689
+ const middlewareContent = `import { createI18nMiddleware } from '@intl-party/nextjs/middleware';
690
+
691
+ export default createI18nMiddleware({
692
+ locales: ${JSON.stringify(config.locales)},
693
+ defaultLocale: '${config.defaultLocale}',
694
+ localePrefix: 'as-needed'
695
+ });
696
+
697
+ export const config = {
698
+ matcher: [
699
+ '/((?!api|_next|_vercel|favicon.ico).*)'
700
+ ]
701
+ };
702
+ `;
703
+ const layoutContent = `import { AppI18nProvider } from '@intl-party/nextjs/app';
704
+
705
+ export default function RootLayout({
706
+ children,
707
+ params: { locale }
708
+ }: {
709
+ children: React.ReactNode;
710
+ params: { locale: string };
711
+ }) {
712
+ return (
713
+ <html lang={locale}>
714
+ <body>
715
+ <AppI18nProvider locale={locale} config={{
716
+ locales: ${JSON.stringify(config.locales)},
717
+ defaultLocale: '${config.defaultLocale}',
718
+ namespaces: ${JSON.stringify(config.namespaces)}
719
+ }}>
720
+ {children}
721
+ </AppI18nProvider>
722
+ </body>
723
+ </html>
724
+ );
725
+ }
726
+ `;
727
+ await import_fs_extra5.default.writeFile("middleware.ts", middlewareContent);
728
+ await import_fs_extra5.default.ensureDir("app/[locale]");
729
+ await import_fs_extra5.default.writeFile("app/[locale]/layout.tsx", layoutContent);
730
+ }
731
+ async function createReactTemplate(config) {
732
+ const appContent = `import { I18nProvider } from '@intl-party/react';
733
+ import { createI18n } from '@intl-party/core';
734
+
735
+ const i18n = createI18n({
736
+ locales: ${JSON.stringify(config.locales)},
737
+ defaultLocale: '${config.defaultLocale}',
738
+ namespaces: ${JSON.stringify(config.namespaces)}
739
+ });
740
+
741
+ function App() {
742
+ return (
743
+ <I18nProvider i18n={i18n}>
744
+ <div className="App">
745
+ <h1>IntlParty React App</h1>
746
+ {/* Your app content */}
747
+ </div>
748
+ </I18nProvider>
749
+ );
750
+ }
751
+
752
+ export default App;
753
+ `;
754
+ await import_fs_extra5.default.writeFile("src/App.tsx", appContent);
755
+ }
756
+ async function createVanillaTemplate(config) {
757
+ const indexContent = `import { createI18n } from '@intl-party/core';
758
+
759
+ const i18n = createI18n({
760
+ locales: ${JSON.stringify(config.locales)},
761
+ defaultLocale: '${config.defaultLocale}',
762
+ namespaces: ${JSON.stringify(config.namespaces)}
763
+ });
764
+
765
+ // Load translations
766
+ // Add your translation loading logic here
767
+
768
+ // Use translations
769
+ console.log(i18n.t('welcome'));
770
+ `;
771
+ await import_fs_extra5.default.ensureDir("src");
772
+ await import_fs_extra5.default.writeFile("src/index.ts", indexContent);
773
+ }
774
+
775
+ // src/commands/check.ts
776
+ var import_chalk5 = __toESM(require("chalk"));
777
+ var import_ora4 = __toESM(require("ora"));
778
+ var import_core2 = require("@intl-party/core");
779
+ async function checkCommand(options) {
780
+ const spinner = (0, import_ora4.default)("Loading configuration...").start();
781
+ try {
782
+ const config = await loadConfig(options.config);
783
+ spinner.succeed("Configuration loaded");
784
+ spinner.start("Loading translations...");
785
+ const translations = await loadTranslations(
786
+ config.translationPaths,
787
+ config.locales,
788
+ config.namespaces
789
+ );
790
+ spinner.succeed("Translations loaded");
791
+ const issues = [];
792
+ if (options.missing !== false) {
793
+ spinner.start("Checking for missing translations...");
794
+ const validationResult = (0, import_core2.validateTranslations)(
795
+ translations,
796
+ config.locales,
797
+ config.namespaces,
798
+ { strict: true }
799
+ );
800
+ validationResult.errors.forEach((error) => {
801
+ if (error.type === "missing_key" || error.type === "missing_namespace") {
802
+ issues.push({
803
+ type: "missing",
804
+ severity: "error",
805
+ message: error.message,
806
+ locale: error.locale,
807
+ namespace: error.namespace,
808
+ key: error.key
809
+ });
810
+ }
811
+ });
812
+ spinner.succeed(
813
+ `Missing translations check completed (${issues.length} issues found)`
814
+ );
815
+ }
816
+ if (options.formatErrors !== false) {
817
+ spinner.start("Checking for format errors...");
818
+ const validationResult = (0, import_core2.validateTranslations)(
819
+ translations,
820
+ config.locales,
821
+ config.namespaces,
822
+ { validateFormats: true }
823
+ );
824
+ validationResult.errors.forEach((error) => {
825
+ if (error.type === "invalid_format") {
826
+ issues.push({
827
+ type: "format",
828
+ severity: "error",
829
+ message: error.message,
830
+ locale: error.locale,
831
+ namespace: error.namespace,
832
+ key: error.key
833
+ });
834
+ }
835
+ });
836
+ spinner.succeed("Format errors check completed");
837
+ }
838
+ if (options.unused) {
839
+ spinner.start("Checking for unused keys...");
840
+ spinner.succeed("Unused keys check completed");
841
+ }
842
+ if (options.duplicates) {
843
+ spinner.start("Checking for duplicate keys...");
844
+ spinner.succeed("Duplicate keys check completed");
845
+ }
846
+ if (issues.length === 0) {
847
+ console.log(import_chalk5.default.green("\u2713 No issues found!"));
848
+ return;
849
+ }
850
+ console.log(import_chalk5.default.red(`
851
+ \u2717 Found ${issues.length} issue(s):
852
+ `));
853
+ const groupedIssues = issues.reduce(
854
+ (acc, issue) => {
855
+ if (!acc[issue.type]) {
856
+ acc[issue.type] = [];
857
+ }
858
+ acc[issue.type].push(issue);
859
+ return acc;
860
+ },
861
+ {}
862
+ );
863
+ for (const [type, typeIssues] of Object.entries(groupedIssues)) {
864
+ console.log(
865
+ import_chalk5.default.bold(`${type.toUpperCase()} ISSUES (${typeIssues.length}):`)
866
+ );
867
+ typeIssues.forEach((issue, index) => {
868
+ const icon = issue.severity === "error" ? import_chalk5.default.red("\u2717") : import_chalk5.default.yellow("\u26A0");
869
+ const location = issue.locale && issue.namespace ? import_chalk5.default.gray(
870
+ `[${issue.locale}/${issue.namespace}${issue.key ? `/${issue.key}` : ""}]`
871
+ ) : "";
872
+ console.log(` ${index + 1}. ${icon} ${issue.message} ${location}`);
873
+ });
874
+ console.log();
875
+ }
876
+ if (options.fix) {
877
+ spinner.start("Attempting to fix issues...");
878
+ let fixedCount = 0;
879
+ if (fixedCount > 0) {
880
+ spinner.succeed(`Fixed ${fixedCount} issue(s)`);
881
+ } else {
882
+ spinner.info("No issues could be automatically fixed");
883
+ }
884
+ }
885
+ const errorCount = issues.filter(
886
+ (issue) => issue.severity === "error"
887
+ ).length;
888
+ if (errorCount > 0) {
889
+ process.exit(1);
890
+ }
891
+ } catch (error) {
892
+ spinner.fail("Check failed");
893
+ console.error(
894
+ import_chalk5.default.red("Error:"),
895
+ error instanceof Error ? error.message : error
896
+ );
897
+ process.exit(1);
898
+ }
899
+ }
900
+
901
+ // src/commands/generate.ts
902
+ var import_chalk6 = __toESM(require("chalk"));
903
+ async function generateCommand(options) {
904
+ console.log(import_chalk6.default.blue("Generate command not yet implemented"));
905
+ console.log("Options:", options);
906
+ }
907
+
908
+ // src/cli.ts
909
+ var packageJson = require_package();
910
+ import_commander.program.name("intl-party").description("CLI for IntlParty internationalization library").version(packageJson.version);
911
+ import_commander.program.option("-c, --config <path>", "path to config file", "intl-party.config.js").option("-v, --verbose", "verbose output").option("--no-color", "disable colored output");
912
+ import_commander.program.command("validate").description("validate translation files for completeness and consistency").option("-l, --locales <locales...>", "specific locales to validate").option("-n, --namespaces <namespaces...>", "specific namespaces to validate").option("--strict", "enable strict validation mode").option("--format <format>", "output format (text|json|junit)", "text").option("--output <file>", "output file path").action(validateCommand);
913
+ import_commander.program.command("extract").description("extract translation keys from source code").option("-s, --source <patterns...>", "source file patterns", [
914
+ "src/**/*.{ts,tsx,js,jsx}"
915
+ ]).option(
916
+ "-o, --output <dir>",
917
+ "output directory for extracted keys",
918
+ "./translations"
919
+ ).option("--dry-run", "show what would be extracted without writing files").option("--update", "update existing translation files with new keys").option("--remove-unused", "remove unused translation keys").action(extractCommand);
920
+ import_commander.program.command("sync").description("synchronize translations across locales").option("-b, --base <locale>", "base locale to sync from", "en").option("-t, --target <locales...>", "target locales to sync to").option("--missing-only", "only add missing keys, don't remove extras").option("--interactive", "interactive mode for conflict resolution").action(syncCommand);
921
+ import_commander.program.command("init").description("initialize intl-party configuration and structure").option("--force", "overwrite existing configuration").option(
922
+ "--template <template>",
923
+ "template to use (nextjs|react|vanilla)",
924
+ "react"
925
+ ).action(initCommand);
926
+ import_commander.program.command("check").description("check for issues in translations and configuration").option("--missing", "check for missing translations").option("--unused", "check for unused translation keys").option("--duplicates", "check for duplicate keys").option("--format-errors", "check for format errors in translations").option("--fix", "automatically fix issues where possible").action(checkCommand);
927
+ import_commander.program.command("generate").description("generate TypeScript definitions and other files").option("-t, --types", "generate TypeScript type definitions").option("-s, --schemas", "generate JSON schemas").option("-d, --docs", "generate documentation").option("--watch", "watch for changes and regenerate").action(generateCommand);
928
+ import_commander.program.command("completion").description("generate shell completion scripts").option("--shell <shell>", "shell type (bash|zsh|fish)", "bash").action((options) => {
929
+ console.log("Shell completion not implemented yet");
930
+ });
931
+ import_commander.program.exitOverride((err) => {
932
+ if (err.code === "commander.help") {
933
+ process.exit(0);
934
+ }
935
+ if (err.code === "commander.version") {
936
+ process.exit(0);
937
+ }
938
+ console.error(import_chalk7.default.red("Error:"), err.message);
939
+ process.exit(1);
940
+ });
941
+ import_commander.program.parse();
942
+ if (!process.argv.slice(2).length) {
943
+ import_commander.program.outputHelp();
944
+ }