@intl-party/cli 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +2 -2
- package/README.md +413 -0
- package/dist/cli.js +1267 -163
- package/dist/index.js +909 -46
- package/package.json +14 -6
package/dist/index.js
CHANGED
|
@@ -51,7 +51,7 @@ var import_core = require("@intl-party/core");
|
|
|
51
51
|
|
|
52
52
|
// src/utils/config.ts
|
|
53
53
|
var import_fs_extra = __toESM(require("fs-extra"));
|
|
54
|
-
var
|
|
54
|
+
var import_node_path = __toESM(require("path"));
|
|
55
55
|
var DEFAULT_CONFIG = {
|
|
56
56
|
locales: ["en", "es", "fr"],
|
|
57
57
|
defaultLocale: "en",
|
|
@@ -95,8 +95,8 @@ async function loadConfig(configPath) {
|
|
|
95
95
|
const content = await import_fs_extra.default.readFile(configFile, "utf-8");
|
|
96
96
|
config = JSON.parse(content);
|
|
97
97
|
} else {
|
|
98
|
-
delete require.cache[
|
|
99
|
-
config = require(
|
|
98
|
+
delete require.cache[import_node_path.default.resolve(configFile)];
|
|
99
|
+
config = require(import_node_path.default.resolve(configFile));
|
|
100
100
|
if (config.default) {
|
|
101
101
|
config = config.default;
|
|
102
102
|
}
|
|
@@ -138,20 +138,20 @@ async function autoDetectConfig() {
|
|
|
138
138
|
try {
|
|
139
139
|
const entries = await import_fs_extra.default.readdir(basePath);
|
|
140
140
|
const locales = entries.filter(
|
|
141
|
-
(entry) => import_fs_extra.default.statSync(
|
|
141
|
+
(entry) => import_fs_extra.default.statSync(import_node_path.default.join(basePath, entry)).isDirectory()
|
|
142
142
|
);
|
|
143
143
|
if (locales.length > 0) {
|
|
144
144
|
config.locales = locales;
|
|
145
145
|
config.translationPaths = {};
|
|
146
|
-
const firstLocaleDir =
|
|
146
|
+
const firstLocaleDir = import_node_path.default.join(basePath, locales[0]);
|
|
147
147
|
const namespaceFiles = await import_fs_extra.default.readdir(firstLocaleDir);
|
|
148
|
-
const namespaces = namespaceFiles.filter((file) => file.endsWith(".json")).map((file) =>
|
|
148
|
+
const namespaces = namespaceFiles.filter((file) => file.endsWith(".json")).map((file) => import_node_path.default.basename(file, ".json"));
|
|
149
149
|
if (namespaces.length > 0) {
|
|
150
150
|
config.namespaces = namespaces;
|
|
151
151
|
for (const locale of locales) {
|
|
152
152
|
config.translationPaths[locale] = {};
|
|
153
153
|
for (const namespace of namespaces) {
|
|
154
|
-
config.translationPaths[locale][namespace] =
|
|
154
|
+
config.translationPaths[locale][namespace] = import_node_path.default.join(
|
|
155
155
|
basePath,
|
|
156
156
|
locale,
|
|
157
157
|
`${namespace}.json`
|
|
@@ -168,7 +168,7 @@ async function autoDetectConfig() {
|
|
|
168
168
|
return config;
|
|
169
169
|
}
|
|
170
170
|
function mergeConfig(defaultConfig, userConfig) {
|
|
171
|
-
|
|
171
|
+
const merged = {
|
|
172
172
|
...defaultConfig,
|
|
173
173
|
...userConfig,
|
|
174
174
|
validation: {
|
|
@@ -188,6 +188,15 @@ function mergeConfig(defaultConfig, userConfig) {
|
|
|
188
188
|
...userConfig.translationPaths
|
|
189
189
|
}
|
|
190
190
|
};
|
|
191
|
+
if (userConfig.messages && !userConfig.outputDir) {
|
|
192
|
+
merged.outputDir = userConfig.messages;
|
|
193
|
+
}
|
|
194
|
+
if (userConfig.sourceDir && !userConfig.sourcePatterns) {
|
|
195
|
+
merged.sourcePatterns = [
|
|
196
|
+
import_node_path.default.join(userConfig.sourceDir, "**/*.{ts,tsx,js,jsx}")
|
|
197
|
+
];
|
|
198
|
+
}
|
|
199
|
+
return merged;
|
|
191
200
|
}
|
|
192
201
|
async function saveConfig(config, configPath = "intl-party.config.json") {
|
|
193
202
|
await import_fs_extra.default.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
@@ -195,7 +204,7 @@ async function saveConfig(config, configPath = "intl-party.config.json") {
|
|
|
195
204
|
|
|
196
205
|
// src/utils/translations.ts
|
|
197
206
|
var import_fs_extra2 = __toESM(require("fs-extra"));
|
|
198
|
-
var
|
|
207
|
+
var import_node_path2 = __toESM(require("path"));
|
|
199
208
|
async function loadTranslations(translationPaths, locales, namespaces) {
|
|
200
209
|
const translations = {};
|
|
201
210
|
for (const locale of locales) {
|
|
@@ -226,7 +235,7 @@ async function saveTranslations(translations, translationPaths) {
|
|
|
226
235
|
)) {
|
|
227
236
|
const translationPath = translationPaths[locale]?.[namespace];
|
|
228
237
|
if (translationPath) {
|
|
229
|
-
await import_fs_extra2.default.ensureDir(
|
|
238
|
+
await import_fs_extra2.default.ensureDir(import_node_path2.default.dirname(translationPath));
|
|
230
239
|
await import_fs_extra2.default.writeJson(translationPath, namespaceTranslations, {
|
|
231
240
|
spaces: 2
|
|
232
241
|
});
|
|
@@ -398,12 +407,22 @@ var import_chalk2 = __toESM(require("chalk"));
|
|
|
398
407
|
var import_ora2 = __toESM(require("ora"));
|
|
399
408
|
var import_glob = require("glob");
|
|
400
409
|
var import_fs_extra4 = __toESM(require("fs-extra"));
|
|
401
|
-
var
|
|
410
|
+
var import_node_path3 = __toESM(require("path"));
|
|
402
411
|
async function extractCommand(options) {
|
|
403
|
-
const spinner = (0, import_ora2.default)("
|
|
412
|
+
const spinner = (0, import_ora2.default)("Loading configuration...").start();
|
|
413
|
+
let config;
|
|
404
414
|
try {
|
|
405
|
-
|
|
406
|
-
|
|
415
|
+
config = await loadConfig(options.config);
|
|
416
|
+
spinner.succeed("Configuration loaded");
|
|
417
|
+
} catch (error) {
|
|
418
|
+
spinner.fail("Failed to load configuration");
|
|
419
|
+
console.error(import_chalk2.default.red("Error:"), error instanceof Error ? error.message : error);
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
spinner.start("Extracting translation keys...");
|
|
423
|
+
try {
|
|
424
|
+
const sourcePatterns = options.source || config.sourcePatterns || ["src/**/*.{ts,tsx,js,jsx}"];
|
|
425
|
+
const outputDir = options.output || config.outputDir || "./messages";
|
|
407
426
|
const files = await (0, import_glob.glob)(sourcePatterns);
|
|
408
427
|
spinner.succeed(`Found ${files.length} source files`);
|
|
409
428
|
const extractedKeys = /* @__PURE__ */ new Set();
|
|
@@ -420,7 +439,7 @@ async function extractCommand(options) {
|
|
|
420
439
|
});
|
|
421
440
|
return;
|
|
422
441
|
}
|
|
423
|
-
await writeExtractedKeys(Array.from(extractedKeys), outputDir, options);
|
|
442
|
+
await writeExtractedKeys(Array.from(extractedKeys), outputDir, config, options);
|
|
424
443
|
console.log(import_chalk2.default.green(`\u2713 Translation keys extracted to ${outputDir}`));
|
|
425
444
|
} catch (error) {
|
|
426
445
|
spinner.fail("Extraction failed");
|
|
@@ -438,6 +457,8 @@ function extractKeysFromContent(content) {
|
|
|
438
457
|
// t('key')
|
|
439
458
|
/useTranslations\(\)\(['"`]([^'"`]+)['"`]\)/g,
|
|
440
459
|
// useTranslations()('key')
|
|
460
|
+
/useTranslations\(['"`]([^'"`]+)['"`]\)\(['"`]([^'"`]+)['"`]\)/g,
|
|
461
|
+
// useTranslations('ns')('key')
|
|
441
462
|
/i18nKey=['"`]([^'"`]+)['"`]/g,
|
|
442
463
|
// i18nKey="key"
|
|
443
464
|
/\{\s*t\(['"`]([^'"`]+)['"`]\)\s*\}/g
|
|
@@ -446,12 +467,16 @@ function extractKeysFromContent(content) {
|
|
|
446
467
|
for (const pattern of patterns) {
|
|
447
468
|
let match;
|
|
448
469
|
while ((match = pattern.exec(content)) !== null) {
|
|
449
|
-
|
|
470
|
+
if (match[2]) {
|
|
471
|
+
keys.push(`${match[1]}.${match[2]}`);
|
|
472
|
+
} else {
|
|
473
|
+
keys.push(match[1]);
|
|
474
|
+
}
|
|
450
475
|
}
|
|
451
476
|
}
|
|
452
477
|
return keys;
|
|
453
478
|
}
|
|
454
|
-
async function writeExtractedKeys(keys, outputDir, options) {
|
|
479
|
+
async function writeExtractedKeys(keys, outputDir, config, options) {
|
|
455
480
|
await import_fs_extra4.default.ensureDir(outputDir);
|
|
456
481
|
const namespaces = { common: [] };
|
|
457
482
|
for (const key of keys) {
|
|
@@ -467,46 +492,271 @@ async function writeExtractedKeys(keys, outputDir, options) {
|
|
|
467
492
|
namespaces.common.push(key);
|
|
468
493
|
}
|
|
469
494
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
495
|
+
const locales = config.locales || ["en"];
|
|
496
|
+
for (const locale of locales) {
|
|
497
|
+
for (const [namespace, namespaceKeys] of Object.entries(namespaces)) {
|
|
498
|
+
if (namespaceKeys.length === 0) continue;
|
|
499
|
+
const filePath = import_node_path3.default.join(outputDir, locale, `${namespace}.json`);
|
|
500
|
+
await import_fs_extra4.default.ensureDir(import_node_path3.default.dirname(filePath));
|
|
501
|
+
let translations = {};
|
|
502
|
+
if ((options.update || locale !== config.defaultLocale) && await import_fs_extra4.default.pathExists(filePath)) {
|
|
503
|
+
try {
|
|
504
|
+
translations = await import_fs_extra4.default.readJson(filePath);
|
|
505
|
+
} catch {
|
|
506
|
+
}
|
|
479
507
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
508
|
+
for (const key of namespaceKeys) {
|
|
509
|
+
if (!translations[key]) {
|
|
510
|
+
translations[key] = locale === config.defaultLocale ? key : "";
|
|
511
|
+
}
|
|
484
512
|
}
|
|
513
|
+
if (config.extraction?.sortKeys !== false) {
|
|
514
|
+
const sortedTranslations = {};
|
|
515
|
+
Object.keys(translations).sort().forEach((k) => {
|
|
516
|
+
sortedTranslations[k] = translations[k];
|
|
517
|
+
});
|
|
518
|
+
translations = sortedTranslations;
|
|
519
|
+
}
|
|
520
|
+
await import_fs_extra4.default.writeJson(filePath, translations, { spaces: 2 });
|
|
485
521
|
}
|
|
486
|
-
await import_fs_extra4.default.writeJson(filePath, translations, { spaces: 2 });
|
|
487
522
|
}
|
|
488
523
|
}
|
|
489
524
|
|
|
490
525
|
// src/commands/sync.ts
|
|
491
526
|
var import_chalk3 = __toESM(require("chalk"));
|
|
527
|
+
var import_ora3 = __toESM(require("ora"));
|
|
528
|
+
var import_inquirer = __toESM(require("inquirer"));
|
|
492
529
|
async function syncCommand(options) {
|
|
493
|
-
|
|
494
|
-
|
|
530
|
+
const spinner = (0, import_ora3.default)("Loading configuration...").start();
|
|
531
|
+
try {
|
|
532
|
+
const config = await loadConfig(options.config);
|
|
533
|
+
spinner.text = "Loading translations...";
|
|
534
|
+
const translations = await loadTranslations(
|
|
535
|
+
config.translationPaths,
|
|
536
|
+
config.locales,
|
|
537
|
+
config.namespaces
|
|
538
|
+
);
|
|
539
|
+
spinner.succeed("Configuration loaded");
|
|
540
|
+
const baseLocale = options.base || config.defaultLocale;
|
|
541
|
+
const targetLocales = options.target || config.locales.filter((l) => l !== baseLocale);
|
|
542
|
+
if (!config.locales.includes(baseLocale)) {
|
|
543
|
+
throw new Error(`Base locale '${baseLocale}' not found in configuration`);
|
|
544
|
+
}
|
|
545
|
+
const analysis = analyzeTranslations(
|
|
546
|
+
translations,
|
|
547
|
+
baseLocale,
|
|
548
|
+
targetLocales,
|
|
549
|
+
config.namespaces
|
|
550
|
+
);
|
|
551
|
+
if (options.verbose) {
|
|
552
|
+
displayAnalysis(analysis);
|
|
553
|
+
}
|
|
554
|
+
if (options.interactive && (analysis.missingKeys.length > 0 || analysis.unusedKeys.length > 0)) {
|
|
555
|
+
const shouldProceed = await confirmSync(analysis);
|
|
556
|
+
if (!shouldProceed) {
|
|
557
|
+
console.log(import_chalk3.default.yellow("Sync cancelled by user"));
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
const updatedTranslations = await performSync(
|
|
562
|
+
translations,
|
|
563
|
+
analysis,
|
|
564
|
+
baseLocale,
|
|
565
|
+
targetLocales,
|
|
566
|
+
config.namespaces,
|
|
567
|
+
options
|
|
568
|
+
);
|
|
569
|
+
spinner.start("Saving translations...");
|
|
570
|
+
await saveTranslations(updatedTranslations, config.translationPaths);
|
|
571
|
+
spinner.succeed("Translations synchronized successfully");
|
|
572
|
+
displaySummary(analysis, updatedTranslations);
|
|
573
|
+
} catch (error) {
|
|
574
|
+
spinner.fail("Sync failed");
|
|
575
|
+
console.error(
|
|
576
|
+
import_chalk3.default.red("Error:"),
|
|
577
|
+
error instanceof Error ? error.message : error
|
|
578
|
+
);
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function analyzeTranslations(translations, baseLocale, targetLocales, namespaces) {
|
|
583
|
+
const missingKeys = [];
|
|
584
|
+
const unusedKeys = [];
|
|
585
|
+
const baseKeys = /* @__PURE__ */ new Set();
|
|
586
|
+
for (const namespace of namespaces) {
|
|
587
|
+
const baseTranslations = translations[baseLocale]?.[namespace] || {};
|
|
588
|
+
collectKeys(baseTranslations, "", baseKeys);
|
|
589
|
+
}
|
|
590
|
+
for (const locale of targetLocales) {
|
|
591
|
+
for (const namespace of namespaces) {
|
|
592
|
+
const targetTranslations = translations[locale]?.[namespace] || {};
|
|
593
|
+
const targetKeys = /* @__PURE__ */ new Set();
|
|
594
|
+
collectKeys(targetTranslations, "", targetKeys);
|
|
595
|
+
for (const key of baseKeys) {
|
|
596
|
+
if (!targetKeys.has(key)) {
|
|
597
|
+
missingKeys.push({ locale, namespace, key });
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
for (const locale of targetLocales) {
|
|
603
|
+
for (const namespace of namespaces) {
|
|
604
|
+
const targetTranslations = translations[locale]?.[namespace] || {};
|
|
605
|
+
const targetKeys = /* @__PURE__ */ new Set();
|
|
606
|
+
collectKeys(targetTranslations, "", targetKeys);
|
|
607
|
+
for (const key of targetKeys) {
|
|
608
|
+
if (!baseKeys.has(key)) {
|
|
609
|
+
unusedKeys.push({ locale, namespace, key });
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
missingKeys,
|
|
616
|
+
unusedKeys,
|
|
617
|
+
totalKeys: baseKeys.size,
|
|
618
|
+
missingCount: missingKeys.length,
|
|
619
|
+
unusedCount: unusedKeys.length
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
function collectKeys(obj, prefix, keys) {
|
|
623
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
624
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
625
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
626
|
+
collectKeys(value, fullKey, keys);
|
|
627
|
+
} else {
|
|
628
|
+
keys.add(fullKey);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function displayAnalysis(analysis) {
|
|
633
|
+
console.log(import_chalk3.default.bold("\n\u{1F4CA} Translation Analysis:"));
|
|
634
|
+
console.log(`Total keys in base locale: ${import_chalk3.default.blue(analysis.totalKeys)}`);
|
|
635
|
+
console.log(`Missing keys: ${import_chalk3.default.yellow(analysis.missingCount)}`);
|
|
636
|
+
console.log(`Unused keys: ${import_chalk3.default.red(analysis.unusedCount)}`);
|
|
637
|
+
if (analysis.missingKeys.length > 0) {
|
|
638
|
+
console.log(import_chalk3.default.yellow("\n\u26A0\uFE0F Missing Keys:"));
|
|
639
|
+
const grouped = groupKeysByLocale(analysis.missingKeys);
|
|
640
|
+
for (const [locale, keys] of Object.entries(grouped)) {
|
|
641
|
+
console.log(import_chalk3.default.gray(` ${locale}: ${keys.length} keys`));
|
|
642
|
+
if (keys.length <= 10) {
|
|
643
|
+
keys.forEach(
|
|
644
|
+
(key) => console.log(import_chalk3.default.gray(` - ${key.namespace}.${key.key}`))
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (analysis.unusedKeys.length > 0) {
|
|
650
|
+
console.log(import_chalk3.default.red("\n\u{1F5D1}\uFE0F Unused Keys:"));
|
|
651
|
+
const grouped = groupKeysByLocale(analysis.unusedKeys);
|
|
652
|
+
for (const [locale, keys] of Object.entries(grouped)) {
|
|
653
|
+
console.log(import_chalk3.default.gray(` ${locale}: ${keys.length} keys`));
|
|
654
|
+
if (keys.length <= 10) {
|
|
655
|
+
keys.forEach(
|
|
656
|
+
(key) => console.log(import_chalk3.default.gray(` - ${key.namespace}.${key.key}`))
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
function groupKeysByLocale(keys) {
|
|
663
|
+
return keys.reduce(
|
|
664
|
+
(acc, key) => {
|
|
665
|
+
if (!acc[key.locale]) acc[key.locale] = [];
|
|
666
|
+
acc[key.locale].push(key);
|
|
667
|
+
return acc;
|
|
668
|
+
},
|
|
669
|
+
{}
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
async function confirmSync(analysis) {
|
|
673
|
+
const questions = [];
|
|
674
|
+
if (analysis.missingCount > 0) {
|
|
675
|
+
questions.push({
|
|
676
|
+
type: "confirm",
|
|
677
|
+
name: "addMissing",
|
|
678
|
+
message: `Add ${analysis.missingCount} missing translation keys?`,
|
|
679
|
+
default: true
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
if (analysis.unusedCount > 0) {
|
|
683
|
+
questions.push({
|
|
684
|
+
type: "confirm",
|
|
685
|
+
name: "removeUnused",
|
|
686
|
+
message: `Remove ${analysis.unusedCount} unused translation keys?`,
|
|
687
|
+
default: false
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
const answers = await import_inquirer.default.prompt(questions);
|
|
691
|
+
return answers.addMissing || answers.removeUnused;
|
|
692
|
+
}
|
|
693
|
+
async function performSync(translations, analysis, baseLocale, targetLocales, namespaces, options) {
|
|
694
|
+
const updatedTranslations = JSON.parse(JSON.stringify(translations));
|
|
695
|
+
if (analysis.missingKeys.length > 0 && (options.missingOnly || !options.missingOnly)) {
|
|
696
|
+
for (const missing of analysis.missingKeys) {
|
|
697
|
+
const baseValue = getNestedValue(
|
|
698
|
+
updatedTranslations[baseLocale]?.[missing.namespace] || {},
|
|
699
|
+
missing.key
|
|
700
|
+
);
|
|
701
|
+
setNestedValue(
|
|
702
|
+
updatedTranslations[missing.locale][missing.namespace],
|
|
703
|
+
missing.key,
|
|
704
|
+
baseValue
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
if (analysis.unusedKeys.length > 0 && !options.missingOnly) {
|
|
709
|
+
for (const unused of analysis.unusedKeys) {
|
|
710
|
+
removeNestedValue(
|
|
711
|
+
updatedTranslations[unused.locale][unused.namespace],
|
|
712
|
+
unused.key
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return updatedTranslations;
|
|
717
|
+
}
|
|
718
|
+
function getNestedValue(obj, path6) {
|
|
719
|
+
return path6.split(".").reduce((current, key) => current?.[key], obj);
|
|
720
|
+
}
|
|
721
|
+
function setNestedValue(obj, path6, value) {
|
|
722
|
+
const keys = path6.split(".");
|
|
723
|
+
const lastKey = keys.pop();
|
|
724
|
+
const target = keys.reduce((current, key) => {
|
|
725
|
+
if (!current[key]) current[key] = {};
|
|
726
|
+
return current[key];
|
|
727
|
+
}, obj);
|
|
728
|
+
target[lastKey] = value;
|
|
729
|
+
}
|
|
730
|
+
function removeNestedValue(obj, path6) {
|
|
731
|
+
const keys = path6.split(".");
|
|
732
|
+
const lastKey = keys.pop();
|
|
733
|
+
const target = keys.reduce((current, key) => current?.[key], obj);
|
|
734
|
+
if (target) {
|
|
735
|
+
delete target[lastKey];
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function displaySummary(analysis, translations) {
|
|
739
|
+
console.log(import_chalk3.default.bold.green("\n\u2705 Sync Complete!"));
|
|
740
|
+
console.log(`Added ${import_chalk3.default.green(analysis.missingCount)} missing keys`);
|
|
741
|
+
console.log(`Removed ${import_chalk3.default.red(analysis.unusedCount)} unused keys`);
|
|
742
|
+
console.log(
|
|
743
|
+
`Total translations: ${Object.keys(translations).length} locales`
|
|
744
|
+
);
|
|
495
745
|
}
|
|
496
746
|
|
|
497
747
|
// src/commands/init.ts
|
|
498
748
|
var import_chalk4 = __toESM(require("chalk"));
|
|
499
|
-
var
|
|
749
|
+
var import_ora4 = __toESM(require("ora"));
|
|
500
750
|
var import_fs_extra5 = __toESM(require("fs-extra"));
|
|
501
|
-
var
|
|
502
|
-
var
|
|
751
|
+
var import_node_path4 = __toESM(require("path"));
|
|
752
|
+
var import_inquirer2 = __toESM(require("inquirer"));
|
|
503
753
|
async function initCommand(options) {
|
|
504
|
-
const spinner = (0,
|
|
754
|
+
const spinner = (0, import_ora4.default)("Initializing IntlParty configuration...").start();
|
|
505
755
|
try {
|
|
506
756
|
const configPath = "intl-party.config.json";
|
|
507
757
|
if (await import_fs_extra5.default.pathExists(configPath) && !options.force) {
|
|
508
758
|
spinner.stop();
|
|
509
|
-
const { overwrite } = await
|
|
759
|
+
const { overwrite } = await import_inquirer2.default.prompt([
|
|
510
760
|
{
|
|
511
761
|
type: "confirm",
|
|
512
762
|
name: "overwrite",
|
|
@@ -521,7 +771,7 @@ async function initCommand(options) {
|
|
|
521
771
|
}
|
|
522
772
|
spinner.start("Setting up configuration...");
|
|
523
773
|
spinner.stop();
|
|
524
|
-
const answers = await
|
|
774
|
+
const answers = await import_inquirer2.default.prompt([
|
|
525
775
|
{
|
|
526
776
|
type: "input",
|
|
527
777
|
name: "defaultLocale",
|
|
@@ -583,7 +833,7 @@ async function initCommand(options) {
|
|
|
583
833
|
for (const locale of answers.locales) {
|
|
584
834
|
config.translationPaths[locale] = {};
|
|
585
835
|
for (const namespace of answers.namespaces) {
|
|
586
|
-
config.translationPaths[locale][namespace] =
|
|
836
|
+
config.translationPaths[locale][namespace] = import_node_path4.default.join(
|
|
587
837
|
answers.translationsDir,
|
|
588
838
|
locale,
|
|
589
839
|
`${namespace}.json`
|
|
@@ -593,10 +843,10 @@ async function initCommand(options) {
|
|
|
593
843
|
spinner.start("Creating directory structure...");
|
|
594
844
|
await import_fs_extra5.default.ensureDir(answers.translationsDir);
|
|
595
845
|
for (const locale of answers.locales) {
|
|
596
|
-
const localeDir =
|
|
846
|
+
const localeDir = import_node_path4.default.join(answers.translationsDir, locale);
|
|
597
847
|
await import_fs_extra5.default.ensureDir(localeDir);
|
|
598
848
|
for (const namespace of answers.namespaces) {
|
|
599
|
-
const filePath =
|
|
849
|
+
const filePath = import_node_path4.default.join(localeDir, `${namespace}.json`);
|
|
600
850
|
if (!await import_fs_extra5.default.pathExists(filePath)) {
|
|
601
851
|
await import_fs_extra5.default.writeJson(filePath, {}, { spaces: 2 });
|
|
602
852
|
}
|
|
@@ -740,10 +990,10 @@ console.log(i18n.t('welcome'));
|
|
|
740
990
|
|
|
741
991
|
// src/commands/check.ts
|
|
742
992
|
var import_chalk5 = __toESM(require("chalk"));
|
|
743
|
-
var
|
|
993
|
+
var import_ora5 = __toESM(require("ora"));
|
|
744
994
|
var import_core2 = require("@intl-party/core");
|
|
745
995
|
async function checkCommand(options) {
|
|
746
|
-
const spinner = (0,
|
|
996
|
+
const spinner = (0, import_ora5.default)("Loading configuration...").start();
|
|
747
997
|
try {
|
|
748
998
|
const config = await loadConfig(options.config);
|
|
749
999
|
spinner.succeed("Configuration loaded");
|
|
@@ -865,10 +1115,623 @@ async function checkCommand(options) {
|
|
|
865
1115
|
}
|
|
866
1116
|
|
|
867
1117
|
// src/commands/generate.ts
|
|
1118
|
+
var import_fs_extra6 = __toESM(require("fs-extra"));
|
|
1119
|
+
var import_node_path5 = __toESM(require("path"));
|
|
1120
|
+
var import_crypto = __toESM(require("crypto"));
|
|
868
1121
|
var import_chalk6 = __toESM(require("chalk"));
|
|
1122
|
+
var import_ora6 = __toESM(require("ora"));
|
|
1123
|
+
var import_chokidar = require("chokidar");
|
|
1124
|
+
async function getMessageData(configPath, options) {
|
|
1125
|
+
let config;
|
|
1126
|
+
let locales;
|
|
1127
|
+
let namespaces;
|
|
1128
|
+
let translationPaths;
|
|
1129
|
+
if (configPath && await import_fs_extra6.default.pathExists(configPath)) {
|
|
1130
|
+
config = await loadConfig(configPath);
|
|
1131
|
+
locales = config.locales;
|
|
1132
|
+
namespaces = config.namespaces;
|
|
1133
|
+
translationPaths = config.translationPaths;
|
|
1134
|
+
} else {
|
|
1135
|
+
if (options?.verbose) {
|
|
1136
|
+
console.log(
|
|
1137
|
+
import_chalk6.default.gray("No config file found, auto-detecting from filesystem...")
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
const autoDetected = await autoDetectMessages(options);
|
|
1141
|
+
locales = autoDetected.locales;
|
|
1142
|
+
namespaces = autoDetected.namespaces;
|
|
1143
|
+
translationPaths = autoDetected.translationPaths;
|
|
1144
|
+
}
|
|
1145
|
+
if (config) {
|
|
1146
|
+
const nextjsConfig = config.shared;
|
|
1147
|
+
if (nextjsConfig && nextjsConfig.messagesPath && nextjsConfig.locales && nextjsConfig.namespaces) {
|
|
1148
|
+
translationPaths = {};
|
|
1149
|
+
for (const locale of nextjsConfig.locales) {
|
|
1150
|
+
translationPaths[locale] = {};
|
|
1151
|
+
for (const namespace of nextjsConfig.namespaces) {
|
|
1152
|
+
translationPaths[locale][namespace] = import_node_path5.default.join(
|
|
1153
|
+
process.cwd(),
|
|
1154
|
+
nextjsConfig.messagesPath,
|
|
1155
|
+
locale,
|
|
1156
|
+
`${namespace}.json`
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
locales = nextjsConfig.locales;
|
|
1161
|
+
namespaces = nextjsConfig.namespaces;
|
|
1162
|
+
if (options?.verbose) {
|
|
1163
|
+
console.log(
|
|
1164
|
+
import_chalk6.default.gray(
|
|
1165
|
+
`Using Next.js config: ${locales.length} locales, ${namespaces.length} namespaces`
|
|
1166
|
+
)
|
|
1167
|
+
);
|
|
1168
|
+
console.log(import_chalk6.default.gray(`Messages path: ${nextjsConfig.messagesPath}`));
|
|
1169
|
+
}
|
|
1170
|
+
} else if (config.messagesPath && config.locales && config.namespaces) {
|
|
1171
|
+
const standardConfig = config;
|
|
1172
|
+
translationPaths = {};
|
|
1173
|
+
for (const locale of standardConfig.locales) {
|
|
1174
|
+
translationPaths[locale] = {};
|
|
1175
|
+
for (const namespace of standardConfig.namespaces) {
|
|
1176
|
+
translationPaths[locale][namespace] = import_node_path5.default.join(
|
|
1177
|
+
process.cwd(),
|
|
1178
|
+
standardConfig.messagesPath,
|
|
1179
|
+
locale,
|
|
1180
|
+
`${namespace}.json`
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
locales = standardConfig.locales;
|
|
1185
|
+
namespaces = standardConfig.namespaces;
|
|
1186
|
+
if (options?.verbose) {
|
|
1187
|
+
console.log(
|
|
1188
|
+
import_chalk6.default.gray(
|
|
1189
|
+
`Using standard config: ${locales.length} locales, ${namespaces.length} namespaces`
|
|
1190
|
+
)
|
|
1191
|
+
);
|
|
1192
|
+
console.log(
|
|
1193
|
+
import_chalk6.default.gray(`Messages path: ${standardConfig.messagesPath}`)
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
const messages = await loadTranslations(
|
|
1199
|
+
translationPaths,
|
|
1200
|
+
locales,
|
|
1201
|
+
namespaces
|
|
1202
|
+
);
|
|
1203
|
+
const translationKeys = /* @__PURE__ */ new Set();
|
|
1204
|
+
const namespaceKeys = /* @__PURE__ */ new Set();
|
|
1205
|
+
for (const locale of locales) {
|
|
1206
|
+
for (const namespace of namespaces) {
|
|
1207
|
+
const extractKeys = (obj, prefix = "") => {
|
|
1208
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1209
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
1210
|
+
const namespaceKey = prefix ? key : fullKey;
|
|
1211
|
+
translationKeys.add(fullKey);
|
|
1212
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1213
|
+
extractKeys(value, fullKey);
|
|
1214
|
+
} else {
|
|
1215
|
+
const dotNotationKey = fullKey.replace(`${namespace}.`, "");
|
|
1216
|
+
namespaceKeys.add(dotNotationKey);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
if (messages[locale]?.[namespace]) {
|
|
1221
|
+
extractKeys(messages[locale][namespace], namespace);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return {
|
|
1226
|
+
locales,
|
|
1227
|
+
namespaces,
|
|
1228
|
+
messages,
|
|
1229
|
+
translationKeys: Array.from(translationKeys).sort(),
|
|
1230
|
+
namespaceKeys: Array.from(namespaceKeys).sort()
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
function generateTypescriptTypes(data) {
|
|
1234
|
+
const { locales, namespaces, translationKeys, namespaceKeys } = data;
|
|
1235
|
+
const keyUnion = translationKeys.map((key) => `"${key}"`).join(" | ");
|
|
1236
|
+
const namespaceKeyUnion = namespaceKeys.map((key) => `"${key}"`).join(" | ");
|
|
1237
|
+
const namespaceTypes = namespaces.map((ns) => {
|
|
1238
|
+
const messageStructure = data.messages[locales[0]]?.[ns];
|
|
1239
|
+
if (!messageStructure) return ` "${ns}": {};`;
|
|
1240
|
+
const generateNestedInterface = (obj, indent = 2) => {
|
|
1241
|
+
const spaces = " ".repeat(indent);
|
|
1242
|
+
const entries = Object.entries(obj);
|
|
1243
|
+
if (entries.length === 0) return "{}";
|
|
1244
|
+
const interfaceLines = entries.map(([key, value]) => {
|
|
1245
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1246
|
+
return `${spaces}"${key}": ${generateNestedInterface(value, indent + 1)};`;
|
|
1247
|
+
} else {
|
|
1248
|
+
return `${spaces}"${key}": string;`;
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
return `{
|
|
1252
|
+
${interfaceLines.join("\n")}
|
|
1253
|
+
${" ".repeat(indent - 1)}}`;
|
|
1254
|
+
};
|
|
1255
|
+
return ` "${ns}": ${generateNestedInterface(messageStructure)};`;
|
|
1256
|
+
}).join("\n");
|
|
1257
|
+
const localeTypes = locales.map((locale) => {
|
|
1258
|
+
return ` "${locale}": {
|
|
1259
|
+
${namespaceTypes}
|
|
1260
|
+
};`;
|
|
1261
|
+
}).join("\n");
|
|
1262
|
+
return `// Generated by @intl-party/cli - do not edit
|
|
1263
|
+
// This file contains type-safe definitions for your translations
|
|
1264
|
+
|
|
1265
|
+
export type TranslationKey = ${keyUnion};
|
|
1266
|
+
|
|
1267
|
+
export type NamespaceTranslationKey = ${namespaceKeyUnion};
|
|
1268
|
+
|
|
1269
|
+
export type TranslationNamespace = ${namespaces.map((ns) => `"${ns}"`).join(" | ")};
|
|
1270
|
+
|
|
1271
|
+
export type Locale = ${locales.map((locale) => `"${locale}"`).join(" | ")};
|
|
1272
|
+
|
|
1273
|
+
export interface Translations {
|
|
1274
|
+
${localeTypes}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
export interface NamespaceTranslations {
|
|
1278
|
+
${namespaceTypes}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// Helper type for getting translation value type
|
|
1282
|
+
export type TranslationValue<T extends TranslationKey> = string;
|
|
1283
|
+
|
|
1284
|
+
// Helper type for getting namespace translations
|
|
1285
|
+
export type GetNamespaceTranslations<N extends TranslationNamespace> =
|
|
1286
|
+
Translations[Locale][N];
|
|
1287
|
+
|
|
1288
|
+
// Type-safe translation function signature
|
|
1289
|
+
export interface TranslationFunction {
|
|
1290
|
+
<T extends TranslationKey>(key: T, options?: Record<string, any>): TranslationValue<T>;
|
|
1291
|
+
<T extends TranslationNamespace>(namespace: T): GetNamespaceTranslations<T>;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// Default messages object (for runtime usage)
|
|
1295
|
+
export const defaultMessages: Translations = ${JSON.stringify(data.messages, null, 2)} as const;
|
|
1296
|
+
`;
|
|
1297
|
+
}
|
|
1298
|
+
function generateClientMessages(data) {
|
|
1299
|
+
return `// Generated by @intl-party/cli - do not edit
|
|
1300
|
+
// This file contains runtime message data for the client package
|
|
1301
|
+
|
|
1302
|
+
import type { Translations } from './translations.generated';
|
|
1303
|
+
|
|
1304
|
+
export const messages: Translations = ${JSON.stringify(data.messages, null, 2)} as const;
|
|
1305
|
+
|
|
1306
|
+
// Re-export for convenience
|
|
1307
|
+
export { messages as defaultMessages } from './translations.generated';
|
|
1308
|
+
`;
|
|
1309
|
+
}
|
|
1310
|
+
function generateJavaScriptMessages(data) {
|
|
1311
|
+
return `// Generated by @intl-party/cli - do not edit
|
|
1312
|
+
// This file contains runtime message data for easy imports
|
|
1313
|
+
|
|
1314
|
+
export const defaultMessages = ${JSON.stringify(data.messages, null, 2)};
|
|
1315
|
+
|
|
1316
|
+
// Export individual locale messages for convenience
|
|
1317
|
+
${data.locales.map((locale) => `export const ${locale}Messages = defaultMessages.${locale};`).join("\n")}
|
|
1318
|
+
`;
|
|
1319
|
+
}
|
|
1320
|
+
function generateClientIndex() {
|
|
1321
|
+
return `// Generated by @intl-party/cli - do not edit
|
|
1322
|
+
// This file is the main entry point for the client package
|
|
1323
|
+
|
|
1324
|
+
export * from './translations.generated';
|
|
1325
|
+
export * from './messages.generated';
|
|
1326
|
+
`;
|
|
1327
|
+
}
|
|
1328
|
+
function generateJsonSchemas(data) {
|
|
1329
|
+
const schemas = {};
|
|
1330
|
+
for (const locale of data.locales) {
|
|
1331
|
+
for (const namespace of data.namespaces) {
|
|
1332
|
+
const messages = data.messages[locale]?.[namespace] || {};
|
|
1333
|
+
const schema = createJsonSchema(messages);
|
|
1334
|
+
const schemaName = `${locale}_${namespace}`;
|
|
1335
|
+
schemas[schemaName] = {
|
|
1336
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
1337
|
+
$id: `#/schemas/${schemaName}`,
|
|
1338
|
+
title: `Translation schema for ${locale}/${namespace}`,
|
|
1339
|
+
description: `JSON schema for translation keys in ${namespace} namespace for ${locale} locale`,
|
|
1340
|
+
type: "object",
|
|
1341
|
+
properties: schema.properties,
|
|
1342
|
+
required: schema.required,
|
|
1343
|
+
additionalProperties: false
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
const combinedSchema = {
|
|
1348
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
1349
|
+
$id: "#/schemas/translations",
|
|
1350
|
+
title: "IntlParty Translation Schema",
|
|
1351
|
+
description: "JSON schema for all IntlParty translations",
|
|
1352
|
+
type: "object",
|
|
1353
|
+
properties: {},
|
|
1354
|
+
definitions: {}
|
|
1355
|
+
};
|
|
1356
|
+
for (const locale of data.locales) {
|
|
1357
|
+
combinedSchema.properties[locale] = {
|
|
1358
|
+
type: "object",
|
|
1359
|
+
description: `Translations for ${locale} locale`,
|
|
1360
|
+
properties: {}
|
|
1361
|
+
};
|
|
1362
|
+
for (const namespace of data.namespaces) {
|
|
1363
|
+
const schemaName = `${locale}_${namespace}`;
|
|
1364
|
+
combinedSchema.properties[locale].properties[namespace] = {
|
|
1365
|
+
$ref: `#/definitions/${schemaName}`
|
|
1366
|
+
};
|
|
1367
|
+
combinedSchema.definitions[schemaName] = schemas[schemaName];
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
return JSON.stringify(
|
|
1371
|
+
{
|
|
1372
|
+
schemas,
|
|
1373
|
+
combined: combinedSchema,
|
|
1374
|
+
metadata: {
|
|
1375
|
+
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1376
|
+
locales: data.locales,
|
|
1377
|
+
namespaces: data.namespaces,
|
|
1378
|
+
version: "1.0.0"
|
|
1379
|
+
}
|
|
1380
|
+
},
|
|
1381
|
+
null,
|
|
1382
|
+
2
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
function createJsonSchema(obj, prefix = "") {
|
|
1386
|
+
const properties = {};
|
|
1387
|
+
const required = [];
|
|
1388
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1389
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
1390
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1391
|
+
const nestedSchema = createJsonSchema(value, fullKey);
|
|
1392
|
+
properties[key] = {
|
|
1393
|
+
type: "object",
|
|
1394
|
+
properties: nestedSchema.properties,
|
|
1395
|
+
required: nestedSchema.required,
|
|
1396
|
+
description: `Translation key: ${fullKey}`
|
|
1397
|
+
};
|
|
1398
|
+
} else {
|
|
1399
|
+
properties[key] = {
|
|
1400
|
+
type: "string",
|
|
1401
|
+
description: `Translation key: ${fullKey}`,
|
|
1402
|
+
examples: [value]
|
|
1403
|
+
};
|
|
1404
|
+
required.push(key);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
return { properties, required };
|
|
1408
|
+
}
|
|
1409
|
+
function generateDocumentation(data) {
|
|
1410
|
+
const lines = [];
|
|
1411
|
+
lines.push("# Translation Documentation");
|
|
1412
|
+
lines.push("");
|
|
1413
|
+
lines.push(`Generated on: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
1414
|
+
lines.push(`Locales: ${data.locales.join(", ")}`);
|
|
1415
|
+
lines.push(`Namespaces: ${data.namespaces.join(", ")}`);
|
|
1416
|
+
lines.push("");
|
|
1417
|
+
lines.push("## Table of Contents");
|
|
1418
|
+
for (const locale of data.locales) {
|
|
1419
|
+
lines.push(`- [${locale.toUpperCase()}](#${locale.toLowerCase()})`);
|
|
1420
|
+
}
|
|
1421
|
+
lines.push("");
|
|
1422
|
+
for (const locale of data.locales) {
|
|
1423
|
+
lines.push(`## ${locale.toUpperCase()}`);
|
|
1424
|
+
lines.push("");
|
|
1425
|
+
for (const namespace of data.namespaces) {
|
|
1426
|
+
const messages = data.messages[locale]?.[namespace] || {};
|
|
1427
|
+
if (Object.keys(messages).length === 0) {
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
lines.push(`### ${namespace} namespace`);
|
|
1431
|
+
lines.push("");
|
|
1432
|
+
generateNamespaceDocumentation(messages, lines, "");
|
|
1433
|
+
}
|
|
1434
|
+
lines.push("");
|
|
1435
|
+
}
|
|
1436
|
+
lines.push("## Summary");
|
|
1437
|
+
lines.push("");
|
|
1438
|
+
const totalKeys = Object.values(data.messages).flatMap((locale) => Object.values(locale)).reduce((total, namespace) => {
|
|
1439
|
+
return total + countKeys(namespace);
|
|
1440
|
+
}, 0);
|
|
1441
|
+
lines.push(`- **Total locales**: ${data.locales.length}`);
|
|
1442
|
+
lines.push(`- **Total namespaces**: ${data.namespaces.length}`);
|
|
1443
|
+
lines.push(`- **Total translation keys**: ${totalKeys}`);
|
|
1444
|
+
lines.push("");
|
|
1445
|
+
lines.push("### Keys by Locale");
|
|
1446
|
+
lines.push("");
|
|
1447
|
+
for (const locale of data.locales) {
|
|
1448
|
+
const localeKeys = Object.values(data.messages[locale] || {}).reduce(
|
|
1449
|
+
(total, namespace) => total + countKeys(namespace),
|
|
1450
|
+
0
|
|
1451
|
+
);
|
|
1452
|
+
lines.push(`- **${locale}**: ${localeKeys} keys`);
|
|
1453
|
+
}
|
|
1454
|
+
return lines.join("\n");
|
|
1455
|
+
}
|
|
1456
|
+
function generateNamespaceDocumentation(obj, lines, prefix) {
|
|
1457
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1458
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
1459
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1460
|
+
lines.push(`#### ${fullKey}`);
|
|
1461
|
+
lines.push("");
|
|
1462
|
+
lines.push("Nested translation object containing:");
|
|
1463
|
+
lines.push("");
|
|
1464
|
+
const nestedKeys = Object.keys(value);
|
|
1465
|
+
for (const nestedKey of nestedKeys) {
|
|
1466
|
+
lines.push(`- \`${fullKey}.${nestedKey}\``);
|
|
1467
|
+
}
|
|
1468
|
+
lines.push("");
|
|
1469
|
+
generateNamespaceDocumentation(value, lines, fullKey);
|
|
1470
|
+
} else {
|
|
1471
|
+
lines.push(`#### ${fullKey}`);
|
|
1472
|
+
lines.push("");
|
|
1473
|
+
lines.push(`**Translation**: \`"${value}"\``);
|
|
1474
|
+
lines.push("");
|
|
1475
|
+
const interpolationMatches = String(value).match(/\{\{([^}]+)\}\}/g);
|
|
1476
|
+
if (interpolationMatches) {
|
|
1477
|
+
lines.push("**Interpolation variables**:");
|
|
1478
|
+
lines.push("");
|
|
1479
|
+
const variables = interpolationMatches.map(
|
|
1480
|
+
(match) => match.replace(/[{}]/g, "")
|
|
1481
|
+
);
|
|
1482
|
+
const uniqueVariables = [...new Set(variables)];
|
|
1483
|
+
for (const variable of uniqueVariables) {
|
|
1484
|
+
lines.push(`- \`${variable}\``);
|
|
1485
|
+
}
|
|
1486
|
+
lines.push("");
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
function countKeys(obj) {
|
|
1492
|
+
let count = 0;
|
|
1493
|
+
for (const value of Object.values(obj)) {
|
|
1494
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1495
|
+
count += countKeys(value);
|
|
1496
|
+
} else {
|
|
1497
|
+
count += 1;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return count;
|
|
1501
|
+
}
|
|
1502
|
+
async function autoDetectMessages(options) {
|
|
1503
|
+
const cwd = process.cwd();
|
|
1504
|
+
const possibleMessageDirs = [
|
|
1505
|
+
import_node_path5.default.join(cwd, "messages"),
|
|
1506
|
+
import_node_path5.default.join(cwd, "locales"),
|
|
1507
|
+
import_node_path5.default.join(cwd, "translations"),
|
|
1508
|
+
import_node_path5.default.join(cwd, "i18n")
|
|
1509
|
+
];
|
|
1510
|
+
let messagesDir = null;
|
|
1511
|
+
for (const dir of possibleMessageDirs) {
|
|
1512
|
+
if (await import_fs_extra6.default.pathExists(dir)) {
|
|
1513
|
+
messagesDir = dir;
|
|
1514
|
+
break;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
if (!messagesDir) {
|
|
1518
|
+
throw new Error(
|
|
1519
|
+
"No messages directory found. Expected one of: messages/, locales/, translations/, i18n/"
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
if (options?.verbose) {
|
|
1523
|
+
console.log(import_chalk6.default.gray(`Found messages directory: ${messagesDir}`));
|
|
1524
|
+
}
|
|
1525
|
+
const localeDirs = await import_fs_extra6.default.readdir(messagesDir);
|
|
1526
|
+
const detectedLocales = localeDirs.filter(async (dir) => {
|
|
1527
|
+
const localePath = import_node_path5.default.join(messagesDir, dir);
|
|
1528
|
+
const stat = await import_fs_extra6.default.stat(localePath);
|
|
1529
|
+
return stat.isDirectory();
|
|
1530
|
+
});
|
|
1531
|
+
const validLocales = [];
|
|
1532
|
+
for (const dir of localeDirs) {
|
|
1533
|
+
const localePath = import_node_path5.default.join(messagesDir, dir);
|
|
1534
|
+
const stat = await import_fs_extra6.default.stat(localePath);
|
|
1535
|
+
if (stat.isDirectory()) {
|
|
1536
|
+
validLocales.push(dir);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (validLocales.length === 0) {
|
|
1540
|
+
throw new Error(`No locale directories found in ${messagesDir}`);
|
|
1541
|
+
}
|
|
1542
|
+
const firstLocale = validLocales[0];
|
|
1543
|
+
const firstLocalePath = import_node_path5.default.join(messagesDir, firstLocale);
|
|
1544
|
+
const namespaceFiles = await import_fs_extra6.default.readdir(firstLocalePath);
|
|
1545
|
+
const detectedNamespaces = namespaceFiles.filter((file) => file.endsWith(".json")).map((file) => import_node_path5.default.basename(file, ".json"));
|
|
1546
|
+
if (detectedNamespaces.length === 0) {
|
|
1547
|
+
throw new Error(`No JSON files found in ${firstLocalePath}`);
|
|
1548
|
+
}
|
|
1549
|
+
const translationPaths = {};
|
|
1550
|
+
for (const locale of validLocales) {
|
|
1551
|
+
translationPaths[locale] = {};
|
|
1552
|
+
for (const namespace of detectedNamespaces) {
|
|
1553
|
+
translationPaths[locale][namespace] = import_node_path5.default.join(
|
|
1554
|
+
messagesDir,
|
|
1555
|
+
locale,
|
|
1556
|
+
`${namespace}.json`
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
if (options?.verbose) {
|
|
1561
|
+
console.log(
|
|
1562
|
+
import_chalk6.default.gray(
|
|
1563
|
+
`Auto-detected ${validLocales.length} locales: ${validLocales.join(", ")}`
|
|
1564
|
+
)
|
|
1565
|
+
);
|
|
1566
|
+
console.log(
|
|
1567
|
+
import_chalk6.default.gray(
|
|
1568
|
+
`Auto-detected ${detectedNamespaces.length} namespaces: ${detectedNamespaces.join(", ")}`
|
|
1569
|
+
)
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
return {
|
|
1573
|
+
locales: validLocales,
|
|
1574
|
+
namespaces: detectedNamespaces,
|
|
1575
|
+
translationPaths
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
function generateCacheHash(data) {
|
|
1579
|
+
const content = JSON.stringify({
|
|
1580
|
+
locales: data.locales.sort(),
|
|
1581
|
+
namespaces: data.namespaces.sort(),
|
|
1582
|
+
messages: data.messages
|
|
1583
|
+
});
|
|
1584
|
+
return import_crypto.default.createHash("md5").update(content).digest("hex");
|
|
1585
|
+
}
|
|
1586
|
+
async function writeGeneratedFiles(data, outputDir, options) {
|
|
1587
|
+
await import_fs_extra6.default.ensureDir(outputDir);
|
|
1588
|
+
const cacheHash = generateCacheHash(data);
|
|
1589
|
+
const cacheFilePath = import_node_path5.default.join(outputDir, ".intl-party-cache");
|
|
1590
|
+
let shouldRegenerate = true;
|
|
1591
|
+
if (await import_fs_extra6.default.pathExists(cacheFilePath)) {
|
|
1592
|
+
const existingHash = await import_fs_extra6.default.readFile(cacheFilePath, "utf-8");
|
|
1593
|
+
if (existingHash === cacheHash) {
|
|
1594
|
+
shouldRegenerate = false;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
if (!shouldRegenerate && !options.watch && !options.client) {
|
|
1598
|
+
if (options.verbose) {
|
|
1599
|
+
console.log(import_chalk6.default.gray("No changes detected, skipping generation"));
|
|
1600
|
+
}
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
if (options.types !== false) {
|
|
1604
|
+
const typesContent = generateTypescriptTypes(data);
|
|
1605
|
+
const typesFilePath = import_node_path5.default.join(outputDir, "translations.generated.ts");
|
|
1606
|
+
await import_fs_extra6.default.writeFile(typesFilePath, typesContent);
|
|
1607
|
+
const jsContent = generateJavaScriptMessages(data);
|
|
1608
|
+
const jsFilePath = import_node_path5.default.join(outputDir, "messages.generated.js");
|
|
1609
|
+
await import_fs_extra6.default.writeFile(jsFilePath, jsContent);
|
|
1610
|
+
if (options.verbose) {
|
|
1611
|
+
console.log(import_chalk6.default.green(`\u2713 Generated types: ${typesFilePath}`));
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
if (options.schemas) {
|
|
1615
|
+
const schemasContent = generateJsonSchemas(data);
|
|
1616
|
+
const schemasFilePath = import_node_path5.default.join(outputDir, "schemas.json");
|
|
1617
|
+
await import_fs_extra6.default.writeFile(schemasFilePath, schemasContent);
|
|
1618
|
+
if (options.verbose) {
|
|
1619
|
+
console.log(import_chalk6.default.green(`\u2713 Generated schemas: ${schemasFilePath}`));
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
if (options.docs) {
|
|
1623
|
+
const docsContent = generateDocumentation(data);
|
|
1624
|
+
const docsFilePath = import_node_path5.default.join(outputDir, "translations.md");
|
|
1625
|
+
await import_fs_extra6.default.writeFile(docsFilePath, docsContent);
|
|
1626
|
+
if (options.verbose) {
|
|
1627
|
+
console.log(import_chalk6.default.green(`\u2713 Generated documentation: ${docsFilePath}`));
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
await import_fs_extra6.default.writeFile(cacheFilePath, cacheHash);
|
|
1631
|
+
}
|
|
1632
|
+
async function generateClientPackage(data, options) {
|
|
1633
|
+
console.log(import_chalk6.default.blue("\u{1F527} Generating client package files..."));
|
|
1634
|
+
if (options.verbose) {
|
|
1635
|
+
console.log(import_chalk6.default.gray(`Data: ${JSON.stringify(data, null, 2)}`));
|
|
1636
|
+
}
|
|
1637
|
+
let rootDir = process.cwd();
|
|
1638
|
+
while (rootDir !== import_node_path5.default.dirname(rootDir)) {
|
|
1639
|
+
const packageJsonPath = import_node_path5.default.join(rootDir, "package.json");
|
|
1640
|
+
if (await import_fs_extra6.default.pathExists(packageJsonPath)) {
|
|
1641
|
+
const packageJson = await import_fs_extra6.default.readJson(packageJsonPath);
|
|
1642
|
+
if (packageJson.name === "@intl-party/monorepo") {
|
|
1643
|
+
break;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
rootDir = import_node_path5.default.dirname(rootDir);
|
|
1647
|
+
}
|
|
1648
|
+
const clientDir = import_node_path5.default.join(rootDir, "packages/client/generated");
|
|
1649
|
+
await import_fs_extra6.default.ensureDir(clientDir);
|
|
1650
|
+
const typesContent = generateTypescriptTypes(data);
|
|
1651
|
+
const typesFilePath = import_node_path5.default.join(clientDir, "translations.generated.ts");
|
|
1652
|
+
if (options.verbose) {
|
|
1653
|
+
console.log(import_chalk6.default.gray(`Writing types to: ${typesFilePath}`));
|
|
1654
|
+
}
|
|
1655
|
+
await import_fs_extra6.default.writeFile(typesFilePath, typesContent);
|
|
1656
|
+
const messagesContent = generateClientMessages(data);
|
|
1657
|
+
const messagesFilePath = import_node_path5.default.join(clientDir, "messages.generated.ts");
|
|
1658
|
+
if (options.verbose) {
|
|
1659
|
+
console.log(import_chalk6.default.gray(`Writing messages to: ${messagesFilePath}`));
|
|
1660
|
+
}
|
|
1661
|
+
await import_fs_extra6.default.writeFile(messagesFilePath, messagesContent);
|
|
1662
|
+
const indexContent = generateClientIndex();
|
|
1663
|
+
const indexPath = import_node_path5.default.join(clientDir, "index.generated.ts");
|
|
1664
|
+
if (options.verbose) {
|
|
1665
|
+
console.log(import_chalk6.default.gray(`Writing index to: ${indexPath}`));
|
|
1666
|
+
}
|
|
1667
|
+
await import_fs_extra6.default.writeFile(indexPath, indexContent);
|
|
1668
|
+
if (options.verbose) {
|
|
1669
|
+
console.log(
|
|
1670
|
+
import_chalk6.default.green(`\u2713 Generated client package files in ${clientDir}`)
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
async function setupWatcher(configPath, outputDir, options) {
|
|
1675
|
+
const config = await loadConfig(configPath);
|
|
1676
|
+
const { translationPaths } = config;
|
|
1677
|
+
const watchPaths = [];
|
|
1678
|
+
for (const localePaths of Object.values(translationPaths)) {
|
|
1679
|
+
watchPaths.push(...Object.values(localePaths));
|
|
1680
|
+
}
|
|
1681
|
+
console.log(
|
|
1682
|
+
import_chalk6.default.blue(`\u{1F440} Watching for changes in ${watchPaths.length} files...`)
|
|
1683
|
+
);
|
|
1684
|
+
const watcher = (0, import_chokidar.watch)(watchPaths, {
|
|
1685
|
+
ignoreInitial: true,
|
|
1686
|
+
persistent: true
|
|
1687
|
+
});
|
|
1688
|
+
watcher.on("change", async (filePath) => {
|
|
1689
|
+
console.log(import_chalk6.default.yellow(`\u{1F4DD} File changed: ${filePath}`));
|
|
1690
|
+
try {
|
|
1691
|
+
const data = await getMessageData(configPath);
|
|
1692
|
+
await writeGeneratedFiles(data, outputDir, options);
|
|
1693
|
+
console.log(import_chalk6.default.green("\u2713 Regenerated translations"));
|
|
1694
|
+
} catch (error) {
|
|
1695
|
+
console.error(import_chalk6.default.red("\u2717 Failed to regenerate:"), error);
|
|
1696
|
+
}
|
|
1697
|
+
});
|
|
1698
|
+
process.on("SIGINT", () => {
|
|
1699
|
+
watcher.close();
|
|
1700
|
+
console.log(import_chalk6.default.gray("\n\u{1F44B} Stopped watching"));
|
|
1701
|
+
process.exit(0);
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
869
1704
|
async function generateCommand(options) {
|
|
870
|
-
|
|
871
|
-
|
|
1705
|
+
const spinner = (0, import_ora6.default)("Loading translation data...").start();
|
|
1706
|
+
if (options.verbose) {
|
|
1707
|
+
console.log(
|
|
1708
|
+
import_chalk6.default.gray(`Debug options: ${JSON.stringify(options, null, 2)}`)
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
try {
|
|
1712
|
+
const data = await getMessageData(options.config, options);
|
|
1713
|
+
spinner.succeed(
|
|
1714
|
+
`Loaded ${data.locales.length} locales, ${data.namespaces.length} namespaces, ${data.translationKeys.length} keys`
|
|
1715
|
+
);
|
|
1716
|
+
const outputDir = options.output || "./node_modules/.intl-party";
|
|
1717
|
+
spinner.start("Generating files...");
|
|
1718
|
+
await writeGeneratedFiles(data, outputDir, options);
|
|
1719
|
+
spinner.succeed("Files generated successfully");
|
|
1720
|
+
console.log(import_chalk6.default.green(`\u2713 Generated translation files in ${outputDir}`));
|
|
1721
|
+
if (options.client) {
|
|
1722
|
+
await generateClientPackage(data, options);
|
|
1723
|
+
}
|
|
1724
|
+
if (options.watch) {
|
|
1725
|
+
await setupWatcher(options.config, outputDir, options);
|
|
1726
|
+
}
|
|
1727
|
+
} catch (error) {
|
|
1728
|
+
spinner.fail("Generation failed");
|
|
1729
|
+
console.error(
|
|
1730
|
+
import_chalk6.default.red("Error:"),
|
|
1731
|
+
error instanceof Error ? error.message : error
|
|
1732
|
+
);
|
|
1733
|
+
process.exit(1);
|
|
1734
|
+
}
|
|
872
1735
|
}
|
|
873
1736
|
// Annotate the CommonJS export names for ESM import in node:
|
|
874
1737
|
0 && (module.exports = {
|