@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/cli.js
CHANGED
|
@@ -6,8 +6,12 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var
|
|
10
|
-
return
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
15
|
};
|
|
12
16
|
var __copyProps = (to, from, except, desc) => {
|
|
13
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -26,107 +30,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
30
|
mod
|
|
27
31
|
));
|
|
28
32
|
|
|
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
33
|
// src/utils/config.ts
|
|
103
|
-
var
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
};
|
|
34
|
+
var config_exports = {};
|
|
35
|
+
__export(config_exports, {
|
|
36
|
+
loadConfig: () => loadConfig,
|
|
37
|
+
saveConfig: () => saveConfig
|
|
38
|
+
});
|
|
130
39
|
async function loadConfig(configPath) {
|
|
131
40
|
const configFiles = [
|
|
132
41
|
configPath,
|
|
@@ -145,8 +54,8 @@ async function loadConfig(configPath) {
|
|
|
145
54
|
const content = await import_fs_extra.default.readFile(configFile, "utf-8");
|
|
146
55
|
config = JSON.parse(content);
|
|
147
56
|
} else {
|
|
148
|
-
delete require.cache[
|
|
149
|
-
config = require(
|
|
57
|
+
delete require.cache[import_node_path.default.resolve(configFile)];
|
|
58
|
+
config = require(import_node_path.default.resolve(configFile));
|
|
150
59
|
if (config.default) {
|
|
151
60
|
config = config.default;
|
|
152
61
|
}
|
|
@@ -162,9 +71,9 @@ async function loadConfig(configPath) {
|
|
|
162
71
|
const packageJsonPath = "package.json";
|
|
163
72
|
if (await import_fs_extra.default.pathExists(packageJsonPath)) {
|
|
164
73
|
try {
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
167
|
-
return mergeConfig(DEFAULT_CONFIG,
|
|
74
|
+
const packageJson = await import_fs_extra.default.readJson(packageJsonPath);
|
|
75
|
+
if (packageJson["intl-party"]) {
|
|
76
|
+
return mergeConfig(DEFAULT_CONFIG, packageJson["intl-party"]);
|
|
168
77
|
}
|
|
169
78
|
} catch {
|
|
170
79
|
}
|
|
@@ -188,20 +97,20 @@ async function autoDetectConfig() {
|
|
|
188
97
|
try {
|
|
189
98
|
const entries = await import_fs_extra.default.readdir(basePath);
|
|
190
99
|
const locales = entries.filter(
|
|
191
|
-
(entry) => import_fs_extra.default.statSync(
|
|
100
|
+
(entry) => import_fs_extra.default.statSync(import_node_path.default.join(basePath, entry)).isDirectory()
|
|
192
101
|
);
|
|
193
102
|
if (locales.length > 0) {
|
|
194
103
|
config.locales = locales;
|
|
195
104
|
config.translationPaths = {};
|
|
196
|
-
const firstLocaleDir =
|
|
105
|
+
const firstLocaleDir = import_node_path.default.join(basePath, locales[0]);
|
|
197
106
|
const namespaceFiles = await import_fs_extra.default.readdir(firstLocaleDir);
|
|
198
|
-
const namespaces = namespaceFiles.filter((file) => file.endsWith(".json")).map((file) =>
|
|
107
|
+
const namespaces = namespaceFiles.filter((file) => file.endsWith(".json")).map((file) => import_node_path.default.basename(file, ".json"));
|
|
199
108
|
if (namespaces.length > 0) {
|
|
200
109
|
config.namespaces = namespaces;
|
|
201
110
|
for (const locale of locales) {
|
|
202
111
|
config.translationPaths[locale] = {};
|
|
203
112
|
for (const namespace of namespaces) {
|
|
204
|
-
config.translationPaths[locale][namespace] =
|
|
113
|
+
config.translationPaths[locale][namespace] = import_node_path.default.join(
|
|
205
114
|
basePath,
|
|
206
115
|
locale,
|
|
207
116
|
`${namespace}.json`
|
|
@@ -218,7 +127,7 @@ async function autoDetectConfig() {
|
|
|
218
127
|
return config;
|
|
219
128
|
}
|
|
220
129
|
function mergeConfig(defaultConfig, userConfig) {
|
|
221
|
-
|
|
130
|
+
const merged = {
|
|
222
131
|
...defaultConfig,
|
|
223
132
|
...userConfig,
|
|
224
133
|
validation: {
|
|
@@ -238,13 +147,67 @@ function mergeConfig(defaultConfig, userConfig) {
|
|
|
238
147
|
...userConfig.translationPaths
|
|
239
148
|
}
|
|
240
149
|
};
|
|
150
|
+
if (userConfig.messages && !userConfig.outputDir) {
|
|
151
|
+
merged.outputDir = userConfig.messages;
|
|
152
|
+
}
|
|
153
|
+
if (userConfig.sourceDir && !userConfig.sourcePatterns) {
|
|
154
|
+
merged.sourcePatterns = [
|
|
155
|
+
import_node_path.default.join(userConfig.sourceDir, "**/*.{ts,tsx,js,jsx}")
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
return merged;
|
|
241
159
|
}
|
|
242
160
|
async function saveConfig(config, configPath = "intl-party.config.json") {
|
|
243
161
|
await import_fs_extra.default.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
244
162
|
}
|
|
163
|
+
var import_fs_extra, import_node_path, DEFAULT_CONFIG;
|
|
164
|
+
var init_config = __esm({
|
|
165
|
+
"src/utils/config.ts"() {
|
|
166
|
+
"use strict";
|
|
167
|
+
import_fs_extra = __toESM(require("fs-extra"));
|
|
168
|
+
import_node_path = __toESM(require("path"));
|
|
169
|
+
DEFAULT_CONFIG = {
|
|
170
|
+
locales: ["en", "es", "fr"],
|
|
171
|
+
defaultLocale: "en",
|
|
172
|
+
namespaces: ["common"],
|
|
173
|
+
translationPaths: {},
|
|
174
|
+
sourcePatterns: ["src/**/*.{ts,tsx,js,jsx}"],
|
|
175
|
+
outputDir: "./translations",
|
|
176
|
+
validation: {
|
|
177
|
+
strict: false,
|
|
178
|
+
logMissing: true,
|
|
179
|
+
throwOnMissing: false,
|
|
180
|
+
validateFormats: true
|
|
181
|
+
},
|
|
182
|
+
extraction: {
|
|
183
|
+
keyPrefix: "",
|
|
184
|
+
markExtracted: true,
|
|
185
|
+
sortKeys: true,
|
|
186
|
+
includeMetadata: false
|
|
187
|
+
},
|
|
188
|
+
sync: {
|
|
189
|
+
removeUnused: false,
|
|
190
|
+
addMissing: true,
|
|
191
|
+
preserveOrder: true
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// src/cli.ts
|
|
198
|
+
var import_commander2 = require("commander");
|
|
199
|
+
var import_chalk8 = __toESM(require("chalk"));
|
|
200
|
+
|
|
201
|
+
// src/commands/validate.ts
|
|
202
|
+
var import_chalk = __toESM(require("chalk"));
|
|
203
|
+
var import_ora = __toESM(require("ora"));
|
|
204
|
+
var import_fs_extra3 = __toESM(require("fs-extra"));
|
|
205
|
+
var import_core = require("@intl-party/core");
|
|
206
|
+
init_config();
|
|
245
207
|
|
|
246
208
|
// src/utils/translations.ts
|
|
247
209
|
var import_fs_extra2 = __toESM(require("fs-extra"));
|
|
210
|
+
var import_node_path2 = __toESM(require("path"));
|
|
248
211
|
async function loadTranslations(translationPaths, locales, namespaces) {
|
|
249
212
|
const translations = {};
|
|
250
213
|
for (const locale of locales) {
|
|
@@ -268,6 +231,21 @@ async function loadTranslations(translationPaths, locales, namespaces) {
|
|
|
268
231
|
}
|
|
269
232
|
return translations;
|
|
270
233
|
}
|
|
234
|
+
async function saveTranslations(translations, translationPaths) {
|
|
235
|
+
for (const [locale, localeTranslations] of Object.entries(translations)) {
|
|
236
|
+
for (const [namespace, namespaceTranslations] of Object.entries(
|
|
237
|
+
localeTranslations
|
|
238
|
+
)) {
|
|
239
|
+
const translationPath = translationPaths[locale]?.[namespace];
|
|
240
|
+
if (translationPath) {
|
|
241
|
+
await import_fs_extra2.default.ensureDir(import_node_path2.default.dirname(translationPath));
|
|
242
|
+
await import_fs_extra2.default.writeJson(translationPath, namespaceTranslations, {
|
|
243
|
+
spaces: 2
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
271
249
|
|
|
272
250
|
// src/commands/validate.ts
|
|
273
251
|
async function validateCommand(options) {
|
|
@@ -432,12 +410,23 @@ var import_chalk2 = __toESM(require("chalk"));
|
|
|
432
410
|
var import_ora2 = __toESM(require("ora"));
|
|
433
411
|
var import_glob = require("glob");
|
|
434
412
|
var import_fs_extra4 = __toESM(require("fs-extra"));
|
|
435
|
-
var
|
|
413
|
+
var import_node_path3 = __toESM(require("path"));
|
|
414
|
+
init_config();
|
|
436
415
|
async function extractCommand(options) {
|
|
437
|
-
const spinner = (0, import_ora2.default)("
|
|
416
|
+
const spinner = (0, import_ora2.default)("Loading configuration...").start();
|
|
417
|
+
let config;
|
|
418
|
+
try {
|
|
419
|
+
config = await loadConfig(options.config);
|
|
420
|
+
spinner.succeed("Configuration loaded");
|
|
421
|
+
} catch (error) {
|
|
422
|
+
spinner.fail("Failed to load configuration");
|
|
423
|
+
console.error(import_chalk2.default.red("Error:"), error instanceof Error ? error.message : error);
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
spinner.start("Extracting translation keys...");
|
|
438
427
|
try {
|
|
439
|
-
const sourcePatterns = options.source || ["src/**/*.{ts,tsx,js,jsx}"];
|
|
440
|
-
const outputDir = options.output || "./
|
|
428
|
+
const sourcePatterns = options.source || config.sourcePatterns || ["src/**/*.{ts,tsx,js,jsx}"];
|
|
429
|
+
const outputDir = options.output || config.outputDir || "./messages";
|
|
441
430
|
const files = await (0, import_glob.glob)(sourcePatterns);
|
|
442
431
|
spinner.succeed(`Found ${files.length} source files`);
|
|
443
432
|
const extractedKeys = /* @__PURE__ */ new Set();
|
|
@@ -454,7 +443,7 @@ async function extractCommand(options) {
|
|
|
454
443
|
});
|
|
455
444
|
return;
|
|
456
445
|
}
|
|
457
|
-
await writeExtractedKeys(Array.from(extractedKeys), outputDir, options);
|
|
446
|
+
await writeExtractedKeys(Array.from(extractedKeys), outputDir, config, options);
|
|
458
447
|
console.log(import_chalk2.default.green(`\u2713 Translation keys extracted to ${outputDir}`));
|
|
459
448
|
} catch (error) {
|
|
460
449
|
spinner.fail("Extraction failed");
|
|
@@ -472,6 +461,8 @@ function extractKeysFromContent(content) {
|
|
|
472
461
|
// t('key')
|
|
473
462
|
/useTranslations\(\)\(['"`]([^'"`]+)['"`]\)/g,
|
|
474
463
|
// useTranslations()('key')
|
|
464
|
+
/useTranslations\(['"`]([^'"`]+)['"`]\)\(['"`]([^'"`]+)['"`]\)/g,
|
|
465
|
+
// useTranslations('ns')('key')
|
|
475
466
|
/i18nKey=['"`]([^'"`]+)['"`]/g,
|
|
476
467
|
// i18nKey="key"
|
|
477
468
|
/\{\s*t\(['"`]([^'"`]+)['"`]\)\s*\}/g
|
|
@@ -480,12 +471,16 @@ function extractKeysFromContent(content) {
|
|
|
480
471
|
for (const pattern of patterns) {
|
|
481
472
|
let match;
|
|
482
473
|
while ((match = pattern.exec(content)) !== null) {
|
|
483
|
-
|
|
474
|
+
if (match[2]) {
|
|
475
|
+
keys.push(`${match[1]}.${match[2]}`);
|
|
476
|
+
} else {
|
|
477
|
+
keys.push(match[1]);
|
|
478
|
+
}
|
|
484
479
|
}
|
|
485
480
|
}
|
|
486
481
|
return keys;
|
|
487
482
|
}
|
|
488
|
-
async function writeExtractedKeys(keys, outputDir, options) {
|
|
483
|
+
async function writeExtractedKeys(keys, outputDir, config, options) {
|
|
489
484
|
await import_fs_extra4.default.ensureDir(outputDir);
|
|
490
485
|
const namespaces = { common: [] };
|
|
491
486
|
for (const key of keys) {
|
|
@@ -501,46 +496,273 @@ async function writeExtractedKeys(keys, outputDir, options) {
|
|
|
501
496
|
namespaces.common.push(key);
|
|
502
497
|
}
|
|
503
498
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
499
|
+
const locales = config.locales || ["en"];
|
|
500
|
+
for (const locale of locales) {
|
|
501
|
+
for (const [namespace, namespaceKeys] of Object.entries(namespaces)) {
|
|
502
|
+
if (namespaceKeys.length === 0) continue;
|
|
503
|
+
const filePath = import_node_path3.default.join(outputDir, locale, `${namespace}.json`);
|
|
504
|
+
await import_fs_extra4.default.ensureDir(import_node_path3.default.dirname(filePath));
|
|
505
|
+
let translations = {};
|
|
506
|
+
if ((options.update || locale !== config.defaultLocale) && await import_fs_extra4.default.pathExists(filePath)) {
|
|
507
|
+
try {
|
|
508
|
+
translations = await import_fs_extra4.default.readJson(filePath);
|
|
509
|
+
} catch {
|
|
510
|
+
}
|
|
513
511
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
512
|
+
for (const key of namespaceKeys) {
|
|
513
|
+
if (!translations[key]) {
|
|
514
|
+
translations[key] = locale === config.defaultLocale ? key : "";
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (config.extraction?.sortKeys !== false) {
|
|
518
|
+
const sortedTranslations = {};
|
|
519
|
+
Object.keys(translations).sort().forEach((k) => {
|
|
520
|
+
sortedTranslations[k] = translations[k];
|
|
521
|
+
});
|
|
522
|
+
translations = sortedTranslations;
|
|
518
523
|
}
|
|
524
|
+
await import_fs_extra4.default.writeJson(filePath, translations, { spaces: 2 });
|
|
519
525
|
}
|
|
520
|
-
await import_fs_extra4.default.writeJson(filePath, translations, { spaces: 2 });
|
|
521
526
|
}
|
|
522
527
|
}
|
|
523
528
|
|
|
524
529
|
// src/commands/sync.ts
|
|
525
530
|
var import_chalk3 = __toESM(require("chalk"));
|
|
531
|
+
var import_ora3 = __toESM(require("ora"));
|
|
532
|
+
var import_inquirer = __toESM(require("inquirer"));
|
|
533
|
+
init_config();
|
|
526
534
|
async function syncCommand(options) {
|
|
527
|
-
|
|
528
|
-
|
|
535
|
+
const spinner = (0, import_ora3.default)("Loading configuration...").start();
|
|
536
|
+
try {
|
|
537
|
+
const config = await loadConfig(options.config);
|
|
538
|
+
spinner.text = "Loading translations...";
|
|
539
|
+
const translations = await loadTranslations(
|
|
540
|
+
config.translationPaths,
|
|
541
|
+
config.locales,
|
|
542
|
+
config.namespaces
|
|
543
|
+
);
|
|
544
|
+
spinner.succeed("Configuration loaded");
|
|
545
|
+
const baseLocale = options.base || config.defaultLocale;
|
|
546
|
+
const targetLocales = options.target || config.locales.filter((l) => l !== baseLocale);
|
|
547
|
+
if (!config.locales.includes(baseLocale)) {
|
|
548
|
+
throw new Error(`Base locale '${baseLocale}' not found in configuration`);
|
|
549
|
+
}
|
|
550
|
+
const analysis = analyzeTranslations(
|
|
551
|
+
translations,
|
|
552
|
+
baseLocale,
|
|
553
|
+
targetLocales,
|
|
554
|
+
config.namespaces
|
|
555
|
+
);
|
|
556
|
+
if (options.verbose) {
|
|
557
|
+
displayAnalysis(analysis);
|
|
558
|
+
}
|
|
559
|
+
if (options.interactive && (analysis.missingKeys.length > 0 || analysis.unusedKeys.length > 0)) {
|
|
560
|
+
const shouldProceed = await confirmSync(analysis);
|
|
561
|
+
if (!shouldProceed) {
|
|
562
|
+
console.log(import_chalk3.default.yellow("Sync cancelled by user"));
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
const updatedTranslations = await performSync(
|
|
567
|
+
translations,
|
|
568
|
+
analysis,
|
|
569
|
+
baseLocale,
|
|
570
|
+
targetLocales,
|
|
571
|
+
config.namespaces,
|
|
572
|
+
options
|
|
573
|
+
);
|
|
574
|
+
spinner.start("Saving translations...");
|
|
575
|
+
await saveTranslations(updatedTranslations, config.translationPaths);
|
|
576
|
+
spinner.succeed("Translations synchronized successfully");
|
|
577
|
+
displaySummary(analysis, updatedTranslations);
|
|
578
|
+
} catch (error) {
|
|
579
|
+
spinner.fail("Sync failed");
|
|
580
|
+
console.error(
|
|
581
|
+
import_chalk3.default.red("Error:"),
|
|
582
|
+
error instanceof Error ? error.message : error
|
|
583
|
+
);
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
function analyzeTranslations(translations, baseLocale, targetLocales, namespaces) {
|
|
588
|
+
const missingKeys = [];
|
|
589
|
+
const unusedKeys = [];
|
|
590
|
+
const baseKeys = /* @__PURE__ */ new Set();
|
|
591
|
+
for (const namespace of namespaces) {
|
|
592
|
+
const baseTranslations = translations[baseLocale]?.[namespace] || {};
|
|
593
|
+
collectKeys(baseTranslations, "", baseKeys);
|
|
594
|
+
}
|
|
595
|
+
for (const locale of targetLocales) {
|
|
596
|
+
for (const namespace of namespaces) {
|
|
597
|
+
const targetTranslations = translations[locale]?.[namespace] || {};
|
|
598
|
+
const targetKeys = /* @__PURE__ */ new Set();
|
|
599
|
+
collectKeys(targetTranslations, "", targetKeys);
|
|
600
|
+
for (const key of baseKeys) {
|
|
601
|
+
if (!targetKeys.has(key)) {
|
|
602
|
+
missingKeys.push({ locale, namespace, key });
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
for (const locale of targetLocales) {
|
|
608
|
+
for (const namespace of namespaces) {
|
|
609
|
+
const targetTranslations = translations[locale]?.[namespace] || {};
|
|
610
|
+
const targetKeys = /* @__PURE__ */ new Set();
|
|
611
|
+
collectKeys(targetTranslations, "", targetKeys);
|
|
612
|
+
for (const key of targetKeys) {
|
|
613
|
+
if (!baseKeys.has(key)) {
|
|
614
|
+
unusedKeys.push({ locale, namespace, key });
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return {
|
|
620
|
+
missingKeys,
|
|
621
|
+
unusedKeys,
|
|
622
|
+
totalKeys: baseKeys.size,
|
|
623
|
+
missingCount: missingKeys.length,
|
|
624
|
+
unusedCount: unusedKeys.length
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
function collectKeys(obj, prefix, keys) {
|
|
628
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
629
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
630
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
631
|
+
collectKeys(value, fullKey, keys);
|
|
632
|
+
} else {
|
|
633
|
+
keys.add(fullKey);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function displayAnalysis(analysis) {
|
|
638
|
+
console.log(import_chalk3.default.bold("\n\u{1F4CA} Translation Analysis:"));
|
|
639
|
+
console.log(`Total keys in base locale: ${import_chalk3.default.blue(analysis.totalKeys)}`);
|
|
640
|
+
console.log(`Missing keys: ${import_chalk3.default.yellow(analysis.missingCount)}`);
|
|
641
|
+
console.log(`Unused keys: ${import_chalk3.default.red(analysis.unusedCount)}`);
|
|
642
|
+
if (analysis.missingKeys.length > 0) {
|
|
643
|
+
console.log(import_chalk3.default.yellow("\n\u26A0\uFE0F Missing Keys:"));
|
|
644
|
+
const grouped = groupKeysByLocale(analysis.missingKeys);
|
|
645
|
+
for (const [locale, keys] of Object.entries(grouped)) {
|
|
646
|
+
console.log(import_chalk3.default.gray(` ${locale}: ${keys.length} keys`));
|
|
647
|
+
if (keys.length <= 10) {
|
|
648
|
+
keys.forEach(
|
|
649
|
+
(key) => console.log(import_chalk3.default.gray(` - ${key.namespace}.${key.key}`))
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (analysis.unusedKeys.length > 0) {
|
|
655
|
+
console.log(import_chalk3.default.red("\n\u{1F5D1}\uFE0F Unused Keys:"));
|
|
656
|
+
const grouped = groupKeysByLocale(analysis.unusedKeys);
|
|
657
|
+
for (const [locale, keys] of Object.entries(grouped)) {
|
|
658
|
+
console.log(import_chalk3.default.gray(` ${locale}: ${keys.length} keys`));
|
|
659
|
+
if (keys.length <= 10) {
|
|
660
|
+
keys.forEach(
|
|
661
|
+
(key) => console.log(import_chalk3.default.gray(` - ${key.namespace}.${key.key}`))
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function groupKeysByLocale(keys) {
|
|
668
|
+
return keys.reduce(
|
|
669
|
+
(acc, key) => {
|
|
670
|
+
if (!acc[key.locale]) acc[key.locale] = [];
|
|
671
|
+
acc[key.locale].push(key);
|
|
672
|
+
return acc;
|
|
673
|
+
},
|
|
674
|
+
{}
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
async function confirmSync(analysis) {
|
|
678
|
+
const questions = [];
|
|
679
|
+
if (analysis.missingCount > 0) {
|
|
680
|
+
questions.push({
|
|
681
|
+
type: "confirm",
|
|
682
|
+
name: "addMissing",
|
|
683
|
+
message: `Add ${analysis.missingCount} missing translation keys?`,
|
|
684
|
+
default: true
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
if (analysis.unusedCount > 0) {
|
|
688
|
+
questions.push({
|
|
689
|
+
type: "confirm",
|
|
690
|
+
name: "removeUnused",
|
|
691
|
+
message: `Remove ${analysis.unusedCount} unused translation keys?`,
|
|
692
|
+
default: false
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
const answers = await import_inquirer.default.prompt(questions);
|
|
696
|
+
return answers.addMissing || answers.removeUnused;
|
|
697
|
+
}
|
|
698
|
+
async function performSync(translations, analysis, baseLocale, targetLocales, namespaces, options) {
|
|
699
|
+
const updatedTranslations = JSON.parse(JSON.stringify(translations));
|
|
700
|
+
if (analysis.missingKeys.length > 0 && (options.missingOnly || !options.missingOnly)) {
|
|
701
|
+
for (const missing of analysis.missingKeys) {
|
|
702
|
+
const baseValue = getNestedValue(
|
|
703
|
+
updatedTranslations[baseLocale]?.[missing.namespace] || {},
|
|
704
|
+
missing.key
|
|
705
|
+
);
|
|
706
|
+
setNestedValue(
|
|
707
|
+
updatedTranslations[missing.locale][missing.namespace],
|
|
708
|
+
missing.key,
|
|
709
|
+
baseValue
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if (analysis.unusedKeys.length > 0 && !options.missingOnly) {
|
|
714
|
+
for (const unused of analysis.unusedKeys) {
|
|
715
|
+
removeNestedValue(
|
|
716
|
+
updatedTranslations[unused.locale][unused.namespace],
|
|
717
|
+
unused.key
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return updatedTranslations;
|
|
722
|
+
}
|
|
723
|
+
function getNestedValue(obj, path6) {
|
|
724
|
+
return path6.split(".").reduce((current, key) => current?.[key], obj);
|
|
725
|
+
}
|
|
726
|
+
function setNestedValue(obj, path6, value) {
|
|
727
|
+
const keys = path6.split(".");
|
|
728
|
+
const lastKey = keys.pop();
|
|
729
|
+
const target = keys.reduce((current, key) => {
|
|
730
|
+
if (!current[key]) current[key] = {};
|
|
731
|
+
return current[key];
|
|
732
|
+
}, obj);
|
|
733
|
+
target[lastKey] = value;
|
|
734
|
+
}
|
|
735
|
+
function removeNestedValue(obj, path6) {
|
|
736
|
+
const keys = path6.split(".");
|
|
737
|
+
const lastKey = keys.pop();
|
|
738
|
+
const target = keys.reduce((current, key) => current?.[key], obj);
|
|
739
|
+
if (target) {
|
|
740
|
+
delete target[lastKey];
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
function displaySummary(analysis, translations) {
|
|
744
|
+
console.log(import_chalk3.default.bold.green("\n\u2705 Sync Complete!"));
|
|
745
|
+
console.log(`Added ${import_chalk3.default.green(analysis.missingCount)} missing keys`);
|
|
746
|
+
console.log(`Removed ${import_chalk3.default.red(analysis.unusedCount)} unused keys`);
|
|
747
|
+
console.log(
|
|
748
|
+
`Total translations: ${Object.keys(translations).length} locales`
|
|
749
|
+
);
|
|
529
750
|
}
|
|
530
751
|
|
|
531
752
|
// src/commands/init.ts
|
|
532
753
|
var import_chalk4 = __toESM(require("chalk"));
|
|
533
|
-
var
|
|
754
|
+
var import_ora4 = __toESM(require("ora"));
|
|
534
755
|
var import_fs_extra5 = __toESM(require("fs-extra"));
|
|
535
|
-
var
|
|
536
|
-
var
|
|
756
|
+
var import_node_path4 = __toESM(require("path"));
|
|
757
|
+
var import_inquirer2 = __toESM(require("inquirer"));
|
|
758
|
+
init_config();
|
|
537
759
|
async function initCommand(options) {
|
|
538
|
-
const spinner = (0,
|
|
760
|
+
const spinner = (0, import_ora4.default)("Initializing IntlParty configuration...").start();
|
|
539
761
|
try {
|
|
540
762
|
const configPath = "intl-party.config.json";
|
|
541
763
|
if (await import_fs_extra5.default.pathExists(configPath) && !options.force) {
|
|
542
764
|
spinner.stop();
|
|
543
|
-
const { overwrite } = await
|
|
765
|
+
const { overwrite } = await import_inquirer2.default.prompt([
|
|
544
766
|
{
|
|
545
767
|
type: "confirm",
|
|
546
768
|
name: "overwrite",
|
|
@@ -555,7 +777,7 @@ async function initCommand(options) {
|
|
|
555
777
|
}
|
|
556
778
|
spinner.start("Setting up configuration...");
|
|
557
779
|
spinner.stop();
|
|
558
|
-
const answers = await
|
|
780
|
+
const answers = await import_inquirer2.default.prompt([
|
|
559
781
|
{
|
|
560
782
|
type: "input",
|
|
561
783
|
name: "defaultLocale",
|
|
@@ -617,7 +839,7 @@ async function initCommand(options) {
|
|
|
617
839
|
for (const locale of answers.locales) {
|
|
618
840
|
config.translationPaths[locale] = {};
|
|
619
841
|
for (const namespace of answers.namespaces) {
|
|
620
|
-
config.translationPaths[locale][namespace] =
|
|
842
|
+
config.translationPaths[locale][namespace] = import_node_path4.default.join(
|
|
621
843
|
answers.translationsDir,
|
|
622
844
|
locale,
|
|
623
845
|
`${namespace}.json`
|
|
@@ -627,10 +849,10 @@ async function initCommand(options) {
|
|
|
627
849
|
spinner.start("Creating directory structure...");
|
|
628
850
|
await import_fs_extra5.default.ensureDir(answers.translationsDir);
|
|
629
851
|
for (const locale of answers.locales) {
|
|
630
|
-
const localeDir =
|
|
852
|
+
const localeDir = import_node_path4.default.join(answers.translationsDir, locale);
|
|
631
853
|
await import_fs_extra5.default.ensureDir(localeDir);
|
|
632
854
|
for (const namespace of answers.namespaces) {
|
|
633
|
-
const filePath =
|
|
855
|
+
const filePath = import_node_path4.default.join(localeDir, `${namespace}.json`);
|
|
634
856
|
if (!await import_fs_extra5.default.pathExists(filePath)) {
|
|
635
857
|
await import_fs_extra5.default.writeJson(filePath, {}, { spaces: 2 });
|
|
636
858
|
}
|
|
@@ -774,10 +996,11 @@ console.log(i18n.t('welcome'));
|
|
|
774
996
|
|
|
775
997
|
// src/commands/check.ts
|
|
776
998
|
var import_chalk5 = __toESM(require("chalk"));
|
|
777
|
-
var
|
|
999
|
+
var import_ora5 = __toESM(require("ora"));
|
|
1000
|
+
init_config();
|
|
778
1001
|
var import_core2 = require("@intl-party/core");
|
|
779
1002
|
async function checkCommand(options) {
|
|
780
|
-
const spinner = (0,
|
|
1003
|
+
const spinner = (0, import_ora5.default)("Loading configuration...").start();
|
|
781
1004
|
try {
|
|
782
1005
|
const config = await loadConfig(options.config);
|
|
783
1006
|
spinner.succeed("Configuration loaded");
|
|
@@ -899,46 +1122,927 @@ async function checkCommand(options) {
|
|
|
899
1122
|
}
|
|
900
1123
|
|
|
901
1124
|
// src/commands/generate.ts
|
|
1125
|
+
var import_fs_extra6 = __toESM(require("fs-extra"));
|
|
1126
|
+
var import_node_path5 = __toESM(require("path"));
|
|
1127
|
+
var import_crypto = __toESM(require("crypto"));
|
|
902
1128
|
var import_chalk6 = __toESM(require("chalk"));
|
|
1129
|
+
var import_ora6 = __toESM(require("ora"));
|
|
1130
|
+
var import_chokidar = require("chokidar");
|
|
1131
|
+
init_config();
|
|
1132
|
+
async function getMessageData(configPath, options) {
|
|
1133
|
+
let config;
|
|
1134
|
+
let locales;
|
|
1135
|
+
let namespaces;
|
|
1136
|
+
let translationPaths;
|
|
1137
|
+
if (configPath && await import_fs_extra6.default.pathExists(configPath)) {
|
|
1138
|
+
config = await loadConfig(configPath);
|
|
1139
|
+
locales = config.locales;
|
|
1140
|
+
namespaces = config.namespaces;
|
|
1141
|
+
translationPaths = config.translationPaths;
|
|
1142
|
+
} else {
|
|
1143
|
+
if (options?.verbose) {
|
|
1144
|
+
console.log(
|
|
1145
|
+
import_chalk6.default.gray("No config file found, auto-detecting from filesystem...")
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
const autoDetected = await autoDetectMessages(options);
|
|
1149
|
+
locales = autoDetected.locales;
|
|
1150
|
+
namespaces = autoDetected.namespaces;
|
|
1151
|
+
translationPaths = autoDetected.translationPaths;
|
|
1152
|
+
}
|
|
1153
|
+
if (config) {
|
|
1154
|
+
const nextjsConfig = config.shared;
|
|
1155
|
+
if (nextjsConfig && nextjsConfig.messagesPath && nextjsConfig.locales && nextjsConfig.namespaces) {
|
|
1156
|
+
translationPaths = {};
|
|
1157
|
+
for (const locale of nextjsConfig.locales) {
|
|
1158
|
+
translationPaths[locale] = {};
|
|
1159
|
+
for (const namespace of nextjsConfig.namespaces) {
|
|
1160
|
+
translationPaths[locale][namespace] = import_node_path5.default.join(
|
|
1161
|
+
process.cwd(),
|
|
1162
|
+
nextjsConfig.messagesPath,
|
|
1163
|
+
locale,
|
|
1164
|
+
`${namespace}.json`
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
locales = nextjsConfig.locales;
|
|
1169
|
+
namespaces = nextjsConfig.namespaces;
|
|
1170
|
+
if (options?.verbose) {
|
|
1171
|
+
console.log(
|
|
1172
|
+
import_chalk6.default.gray(
|
|
1173
|
+
`Using Next.js config: ${locales.length} locales, ${namespaces.length} namespaces`
|
|
1174
|
+
)
|
|
1175
|
+
);
|
|
1176
|
+
console.log(import_chalk6.default.gray(`Messages path: ${nextjsConfig.messagesPath}`));
|
|
1177
|
+
}
|
|
1178
|
+
} else if (config.messagesPath && config.locales && config.namespaces) {
|
|
1179
|
+
const standardConfig = config;
|
|
1180
|
+
translationPaths = {};
|
|
1181
|
+
for (const locale of standardConfig.locales) {
|
|
1182
|
+
translationPaths[locale] = {};
|
|
1183
|
+
for (const namespace of standardConfig.namespaces) {
|
|
1184
|
+
translationPaths[locale][namespace] = import_node_path5.default.join(
|
|
1185
|
+
process.cwd(),
|
|
1186
|
+
standardConfig.messagesPath,
|
|
1187
|
+
locale,
|
|
1188
|
+
`${namespace}.json`
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
locales = standardConfig.locales;
|
|
1193
|
+
namespaces = standardConfig.namespaces;
|
|
1194
|
+
if (options?.verbose) {
|
|
1195
|
+
console.log(
|
|
1196
|
+
import_chalk6.default.gray(
|
|
1197
|
+
`Using standard config: ${locales.length} locales, ${namespaces.length} namespaces`
|
|
1198
|
+
)
|
|
1199
|
+
);
|
|
1200
|
+
console.log(
|
|
1201
|
+
import_chalk6.default.gray(`Messages path: ${standardConfig.messagesPath}`)
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
const messages = await loadTranslations(
|
|
1207
|
+
translationPaths,
|
|
1208
|
+
locales,
|
|
1209
|
+
namespaces
|
|
1210
|
+
);
|
|
1211
|
+
const translationKeys = /* @__PURE__ */ new Set();
|
|
1212
|
+
const namespaceKeys = /* @__PURE__ */ new Set();
|
|
1213
|
+
for (const locale of locales) {
|
|
1214
|
+
for (const namespace of namespaces) {
|
|
1215
|
+
const extractKeys = (obj, prefix = "") => {
|
|
1216
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1217
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
1218
|
+
const namespaceKey = prefix ? key : fullKey;
|
|
1219
|
+
translationKeys.add(fullKey);
|
|
1220
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1221
|
+
extractKeys(value, fullKey);
|
|
1222
|
+
} else {
|
|
1223
|
+
const dotNotationKey = fullKey.replace(`${namespace}.`, "");
|
|
1224
|
+
namespaceKeys.add(dotNotationKey);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
if (messages[locale]?.[namespace]) {
|
|
1229
|
+
extractKeys(messages[locale][namespace], namespace);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
return {
|
|
1234
|
+
locales,
|
|
1235
|
+
namespaces,
|
|
1236
|
+
messages,
|
|
1237
|
+
translationKeys: Array.from(translationKeys).sort(),
|
|
1238
|
+
namespaceKeys: Array.from(namespaceKeys).sort()
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
function generateTypescriptTypes(data) {
|
|
1242
|
+
const { locales, namespaces, translationKeys, namespaceKeys } = data;
|
|
1243
|
+
const keyUnion = translationKeys.map((key) => `"${key}"`).join(" | ");
|
|
1244
|
+
const namespaceKeyUnion = namespaceKeys.map((key) => `"${key}"`).join(" | ");
|
|
1245
|
+
const namespaceTypes = namespaces.map((ns) => {
|
|
1246
|
+
const messageStructure = data.messages[locales[0]]?.[ns];
|
|
1247
|
+
if (!messageStructure) return ` "${ns}": {};`;
|
|
1248
|
+
const generateNestedInterface = (obj, indent = 2) => {
|
|
1249
|
+
const spaces = " ".repeat(indent);
|
|
1250
|
+
const entries = Object.entries(obj);
|
|
1251
|
+
if (entries.length === 0) return "{}";
|
|
1252
|
+
const interfaceLines = entries.map(([key, value]) => {
|
|
1253
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1254
|
+
return `${spaces}"${key}": ${generateNestedInterface(value, indent + 1)};`;
|
|
1255
|
+
} else {
|
|
1256
|
+
return `${spaces}"${key}": string;`;
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
return `{
|
|
1260
|
+
${interfaceLines.join("\n")}
|
|
1261
|
+
${" ".repeat(indent - 1)}}`;
|
|
1262
|
+
};
|
|
1263
|
+
return ` "${ns}": ${generateNestedInterface(messageStructure)};`;
|
|
1264
|
+
}).join("\n");
|
|
1265
|
+
const localeTypes = locales.map((locale) => {
|
|
1266
|
+
return ` "${locale}": {
|
|
1267
|
+
${namespaceTypes}
|
|
1268
|
+
};`;
|
|
1269
|
+
}).join("\n");
|
|
1270
|
+
return `// Generated by @intl-party/cli - do not edit
|
|
1271
|
+
// This file contains type-safe definitions for your translations
|
|
1272
|
+
|
|
1273
|
+
export type TranslationKey = ${keyUnion};
|
|
1274
|
+
|
|
1275
|
+
export type NamespaceTranslationKey = ${namespaceKeyUnion};
|
|
1276
|
+
|
|
1277
|
+
export type TranslationNamespace = ${namespaces.map((ns) => `"${ns}"`).join(" | ")};
|
|
1278
|
+
|
|
1279
|
+
export type Locale = ${locales.map((locale) => `"${locale}"`).join(" | ")};
|
|
1280
|
+
|
|
1281
|
+
export interface Translations {
|
|
1282
|
+
${localeTypes}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
export interface NamespaceTranslations {
|
|
1286
|
+
${namespaceTypes}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// Helper type for getting translation value type
|
|
1290
|
+
export type TranslationValue<T extends TranslationKey> = string;
|
|
1291
|
+
|
|
1292
|
+
// Helper type for getting namespace translations
|
|
1293
|
+
export type GetNamespaceTranslations<N extends TranslationNamespace> =
|
|
1294
|
+
Translations[Locale][N];
|
|
1295
|
+
|
|
1296
|
+
// Type-safe translation function signature
|
|
1297
|
+
export interface TranslationFunction {
|
|
1298
|
+
<T extends TranslationKey>(key: T, options?: Record<string, any>): TranslationValue<T>;
|
|
1299
|
+
<T extends TranslationNamespace>(namespace: T): GetNamespaceTranslations<T>;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// Default messages object (for runtime usage)
|
|
1303
|
+
export const defaultMessages: Translations = ${JSON.stringify(data.messages, null, 2)} as const;
|
|
1304
|
+
`;
|
|
1305
|
+
}
|
|
1306
|
+
function generateClientMessages(data) {
|
|
1307
|
+
return `// Generated by @intl-party/cli - do not edit
|
|
1308
|
+
// This file contains runtime message data for the client package
|
|
1309
|
+
|
|
1310
|
+
import type { Translations } from './translations.generated';
|
|
1311
|
+
|
|
1312
|
+
export const messages: Translations = ${JSON.stringify(data.messages, null, 2)} as const;
|
|
1313
|
+
|
|
1314
|
+
// Re-export for convenience
|
|
1315
|
+
export { messages as defaultMessages } from './translations.generated';
|
|
1316
|
+
`;
|
|
1317
|
+
}
|
|
1318
|
+
function generateJavaScriptMessages(data) {
|
|
1319
|
+
return `// Generated by @intl-party/cli - do not edit
|
|
1320
|
+
// This file contains runtime message data for easy imports
|
|
1321
|
+
|
|
1322
|
+
export const defaultMessages = ${JSON.stringify(data.messages, null, 2)};
|
|
1323
|
+
|
|
1324
|
+
// Export individual locale messages for convenience
|
|
1325
|
+
${data.locales.map((locale) => `export const ${locale}Messages = defaultMessages.${locale};`).join("\n")}
|
|
1326
|
+
`;
|
|
1327
|
+
}
|
|
1328
|
+
function generateClientIndex() {
|
|
1329
|
+
return `// Generated by @intl-party/cli - do not edit
|
|
1330
|
+
// This file is the main entry point for the client package
|
|
1331
|
+
|
|
1332
|
+
export * from './translations.generated';
|
|
1333
|
+
export * from './messages.generated';
|
|
1334
|
+
`;
|
|
1335
|
+
}
|
|
1336
|
+
function generateJsonSchemas(data) {
|
|
1337
|
+
const schemas = {};
|
|
1338
|
+
for (const locale of data.locales) {
|
|
1339
|
+
for (const namespace of data.namespaces) {
|
|
1340
|
+
const messages = data.messages[locale]?.[namespace] || {};
|
|
1341
|
+
const schema = createJsonSchema(messages);
|
|
1342
|
+
const schemaName = `${locale}_${namespace}`;
|
|
1343
|
+
schemas[schemaName] = {
|
|
1344
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
1345
|
+
$id: `#/schemas/${schemaName}`,
|
|
1346
|
+
title: `Translation schema for ${locale}/${namespace}`,
|
|
1347
|
+
description: `JSON schema for translation keys in ${namespace} namespace for ${locale} locale`,
|
|
1348
|
+
type: "object",
|
|
1349
|
+
properties: schema.properties,
|
|
1350
|
+
required: schema.required,
|
|
1351
|
+
additionalProperties: false
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
const combinedSchema = {
|
|
1356
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
1357
|
+
$id: "#/schemas/translations",
|
|
1358
|
+
title: "IntlParty Translation Schema",
|
|
1359
|
+
description: "JSON schema for all IntlParty translations",
|
|
1360
|
+
type: "object",
|
|
1361
|
+
properties: {},
|
|
1362
|
+
definitions: {}
|
|
1363
|
+
};
|
|
1364
|
+
for (const locale of data.locales) {
|
|
1365
|
+
combinedSchema.properties[locale] = {
|
|
1366
|
+
type: "object",
|
|
1367
|
+
description: `Translations for ${locale} locale`,
|
|
1368
|
+
properties: {}
|
|
1369
|
+
};
|
|
1370
|
+
for (const namespace of data.namespaces) {
|
|
1371
|
+
const schemaName = `${locale}_${namespace}`;
|
|
1372
|
+
combinedSchema.properties[locale].properties[namespace] = {
|
|
1373
|
+
$ref: `#/definitions/${schemaName}`
|
|
1374
|
+
};
|
|
1375
|
+
combinedSchema.definitions[schemaName] = schemas[schemaName];
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return JSON.stringify(
|
|
1379
|
+
{
|
|
1380
|
+
schemas,
|
|
1381
|
+
combined: combinedSchema,
|
|
1382
|
+
metadata: {
|
|
1383
|
+
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1384
|
+
locales: data.locales,
|
|
1385
|
+
namespaces: data.namespaces,
|
|
1386
|
+
version: "1.0.0"
|
|
1387
|
+
}
|
|
1388
|
+
},
|
|
1389
|
+
null,
|
|
1390
|
+
2
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
function createJsonSchema(obj, prefix = "") {
|
|
1394
|
+
const properties = {};
|
|
1395
|
+
const required = [];
|
|
1396
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1397
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
1398
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1399
|
+
const nestedSchema = createJsonSchema(value, fullKey);
|
|
1400
|
+
properties[key] = {
|
|
1401
|
+
type: "object",
|
|
1402
|
+
properties: nestedSchema.properties,
|
|
1403
|
+
required: nestedSchema.required,
|
|
1404
|
+
description: `Translation key: ${fullKey}`
|
|
1405
|
+
};
|
|
1406
|
+
} else {
|
|
1407
|
+
properties[key] = {
|
|
1408
|
+
type: "string",
|
|
1409
|
+
description: `Translation key: ${fullKey}`,
|
|
1410
|
+
examples: [value]
|
|
1411
|
+
};
|
|
1412
|
+
required.push(key);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
return { properties, required };
|
|
1416
|
+
}
|
|
1417
|
+
function generateDocumentation(data) {
|
|
1418
|
+
const lines = [];
|
|
1419
|
+
lines.push("# Translation Documentation");
|
|
1420
|
+
lines.push("");
|
|
1421
|
+
lines.push(`Generated on: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
1422
|
+
lines.push(`Locales: ${data.locales.join(", ")}`);
|
|
1423
|
+
lines.push(`Namespaces: ${data.namespaces.join(", ")}`);
|
|
1424
|
+
lines.push("");
|
|
1425
|
+
lines.push("## Table of Contents");
|
|
1426
|
+
for (const locale of data.locales) {
|
|
1427
|
+
lines.push(`- [${locale.toUpperCase()}](#${locale.toLowerCase()})`);
|
|
1428
|
+
}
|
|
1429
|
+
lines.push("");
|
|
1430
|
+
for (const locale of data.locales) {
|
|
1431
|
+
lines.push(`## ${locale.toUpperCase()}`);
|
|
1432
|
+
lines.push("");
|
|
1433
|
+
for (const namespace of data.namespaces) {
|
|
1434
|
+
const messages = data.messages[locale]?.[namespace] || {};
|
|
1435
|
+
if (Object.keys(messages).length === 0) {
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
lines.push(`### ${namespace} namespace`);
|
|
1439
|
+
lines.push("");
|
|
1440
|
+
generateNamespaceDocumentation(messages, lines, "");
|
|
1441
|
+
}
|
|
1442
|
+
lines.push("");
|
|
1443
|
+
}
|
|
1444
|
+
lines.push("## Summary");
|
|
1445
|
+
lines.push("");
|
|
1446
|
+
const totalKeys = Object.values(data.messages).flatMap((locale) => Object.values(locale)).reduce((total, namespace) => {
|
|
1447
|
+
return total + countKeys(namespace);
|
|
1448
|
+
}, 0);
|
|
1449
|
+
lines.push(`- **Total locales**: ${data.locales.length}`);
|
|
1450
|
+
lines.push(`- **Total namespaces**: ${data.namespaces.length}`);
|
|
1451
|
+
lines.push(`- **Total translation keys**: ${totalKeys}`);
|
|
1452
|
+
lines.push("");
|
|
1453
|
+
lines.push("### Keys by Locale");
|
|
1454
|
+
lines.push("");
|
|
1455
|
+
for (const locale of data.locales) {
|
|
1456
|
+
const localeKeys = Object.values(data.messages[locale] || {}).reduce(
|
|
1457
|
+
(total, namespace) => total + countKeys(namespace),
|
|
1458
|
+
0
|
|
1459
|
+
);
|
|
1460
|
+
lines.push(`- **${locale}**: ${localeKeys} keys`);
|
|
1461
|
+
}
|
|
1462
|
+
return lines.join("\n");
|
|
1463
|
+
}
|
|
1464
|
+
function generateNamespaceDocumentation(obj, lines, prefix) {
|
|
1465
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1466
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
1467
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1468
|
+
lines.push(`#### ${fullKey}`);
|
|
1469
|
+
lines.push("");
|
|
1470
|
+
lines.push("Nested translation object containing:");
|
|
1471
|
+
lines.push("");
|
|
1472
|
+
const nestedKeys = Object.keys(value);
|
|
1473
|
+
for (const nestedKey of nestedKeys) {
|
|
1474
|
+
lines.push(`- \`${fullKey}.${nestedKey}\``);
|
|
1475
|
+
}
|
|
1476
|
+
lines.push("");
|
|
1477
|
+
generateNamespaceDocumentation(value, lines, fullKey);
|
|
1478
|
+
} else {
|
|
1479
|
+
lines.push(`#### ${fullKey}`);
|
|
1480
|
+
lines.push("");
|
|
1481
|
+
lines.push(`**Translation**: \`"${value}"\``);
|
|
1482
|
+
lines.push("");
|
|
1483
|
+
const interpolationMatches = String(value).match(/\{\{([^}]+)\}\}/g);
|
|
1484
|
+
if (interpolationMatches) {
|
|
1485
|
+
lines.push("**Interpolation variables**:");
|
|
1486
|
+
lines.push("");
|
|
1487
|
+
const variables = interpolationMatches.map(
|
|
1488
|
+
(match) => match.replace(/[{}]/g, "")
|
|
1489
|
+
);
|
|
1490
|
+
const uniqueVariables = [...new Set(variables)];
|
|
1491
|
+
for (const variable of uniqueVariables) {
|
|
1492
|
+
lines.push(`- \`${variable}\``);
|
|
1493
|
+
}
|
|
1494
|
+
lines.push("");
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
function countKeys(obj) {
|
|
1500
|
+
let count = 0;
|
|
1501
|
+
for (const value of Object.values(obj)) {
|
|
1502
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1503
|
+
count += countKeys(value);
|
|
1504
|
+
} else {
|
|
1505
|
+
count += 1;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
return count;
|
|
1509
|
+
}
|
|
1510
|
+
async function autoDetectMessages(options) {
|
|
1511
|
+
const cwd = process.cwd();
|
|
1512
|
+
const possibleMessageDirs = [
|
|
1513
|
+
import_node_path5.default.join(cwd, "messages"),
|
|
1514
|
+
import_node_path5.default.join(cwd, "locales"),
|
|
1515
|
+
import_node_path5.default.join(cwd, "translations"),
|
|
1516
|
+
import_node_path5.default.join(cwd, "i18n")
|
|
1517
|
+
];
|
|
1518
|
+
let messagesDir = null;
|
|
1519
|
+
for (const dir of possibleMessageDirs) {
|
|
1520
|
+
if (await import_fs_extra6.default.pathExists(dir)) {
|
|
1521
|
+
messagesDir = dir;
|
|
1522
|
+
break;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
if (!messagesDir) {
|
|
1526
|
+
throw new Error(
|
|
1527
|
+
"No messages directory found. Expected one of: messages/, locales/, translations/, i18n/"
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
if (options?.verbose) {
|
|
1531
|
+
console.log(import_chalk6.default.gray(`Found messages directory: ${messagesDir}`));
|
|
1532
|
+
}
|
|
1533
|
+
const localeDirs = await import_fs_extra6.default.readdir(messagesDir);
|
|
1534
|
+
const detectedLocales = localeDirs.filter(async (dir) => {
|
|
1535
|
+
const localePath = import_node_path5.default.join(messagesDir, dir);
|
|
1536
|
+
const stat = await import_fs_extra6.default.stat(localePath);
|
|
1537
|
+
return stat.isDirectory();
|
|
1538
|
+
});
|
|
1539
|
+
const validLocales = [];
|
|
1540
|
+
for (const dir of localeDirs) {
|
|
1541
|
+
const localePath = import_node_path5.default.join(messagesDir, dir);
|
|
1542
|
+
const stat = await import_fs_extra6.default.stat(localePath);
|
|
1543
|
+
if (stat.isDirectory()) {
|
|
1544
|
+
validLocales.push(dir);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
if (validLocales.length === 0) {
|
|
1548
|
+
throw new Error(`No locale directories found in ${messagesDir}`);
|
|
1549
|
+
}
|
|
1550
|
+
const firstLocale = validLocales[0];
|
|
1551
|
+
const firstLocalePath = import_node_path5.default.join(messagesDir, firstLocale);
|
|
1552
|
+
const namespaceFiles = await import_fs_extra6.default.readdir(firstLocalePath);
|
|
1553
|
+
const detectedNamespaces = namespaceFiles.filter((file) => file.endsWith(".json")).map((file) => import_node_path5.default.basename(file, ".json"));
|
|
1554
|
+
if (detectedNamespaces.length === 0) {
|
|
1555
|
+
throw new Error(`No JSON files found in ${firstLocalePath}`);
|
|
1556
|
+
}
|
|
1557
|
+
const translationPaths = {};
|
|
1558
|
+
for (const locale of validLocales) {
|
|
1559
|
+
translationPaths[locale] = {};
|
|
1560
|
+
for (const namespace of detectedNamespaces) {
|
|
1561
|
+
translationPaths[locale][namespace] = import_node_path5.default.join(
|
|
1562
|
+
messagesDir,
|
|
1563
|
+
locale,
|
|
1564
|
+
`${namespace}.json`
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
if (options?.verbose) {
|
|
1569
|
+
console.log(
|
|
1570
|
+
import_chalk6.default.gray(
|
|
1571
|
+
`Auto-detected ${validLocales.length} locales: ${validLocales.join(", ")}`
|
|
1572
|
+
)
|
|
1573
|
+
);
|
|
1574
|
+
console.log(
|
|
1575
|
+
import_chalk6.default.gray(
|
|
1576
|
+
`Auto-detected ${detectedNamespaces.length} namespaces: ${detectedNamespaces.join(", ")}`
|
|
1577
|
+
)
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
return {
|
|
1581
|
+
locales: validLocales,
|
|
1582
|
+
namespaces: detectedNamespaces,
|
|
1583
|
+
translationPaths
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
function generateCacheHash(data) {
|
|
1587
|
+
const content = JSON.stringify({
|
|
1588
|
+
locales: data.locales.sort(),
|
|
1589
|
+
namespaces: data.namespaces.sort(),
|
|
1590
|
+
messages: data.messages
|
|
1591
|
+
});
|
|
1592
|
+
return import_crypto.default.createHash("md5").update(content).digest("hex");
|
|
1593
|
+
}
|
|
1594
|
+
async function writeGeneratedFiles(data, outputDir, options) {
|
|
1595
|
+
await import_fs_extra6.default.ensureDir(outputDir);
|
|
1596
|
+
const cacheHash = generateCacheHash(data);
|
|
1597
|
+
const cacheFilePath = import_node_path5.default.join(outputDir, ".intl-party-cache");
|
|
1598
|
+
let shouldRegenerate = true;
|
|
1599
|
+
if (await import_fs_extra6.default.pathExists(cacheFilePath)) {
|
|
1600
|
+
const existingHash = await import_fs_extra6.default.readFile(cacheFilePath, "utf-8");
|
|
1601
|
+
if (existingHash === cacheHash) {
|
|
1602
|
+
shouldRegenerate = false;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
if (!shouldRegenerate && !options.watch && !options.client) {
|
|
1606
|
+
if (options.verbose) {
|
|
1607
|
+
console.log(import_chalk6.default.gray("No changes detected, skipping generation"));
|
|
1608
|
+
}
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
if (options.types !== false) {
|
|
1612
|
+
const typesContent = generateTypescriptTypes(data);
|
|
1613
|
+
const typesFilePath = import_node_path5.default.join(outputDir, "translations.generated.ts");
|
|
1614
|
+
await import_fs_extra6.default.writeFile(typesFilePath, typesContent);
|
|
1615
|
+
const jsContent = generateJavaScriptMessages(data);
|
|
1616
|
+
const jsFilePath = import_node_path5.default.join(outputDir, "messages.generated.js");
|
|
1617
|
+
await import_fs_extra6.default.writeFile(jsFilePath, jsContent);
|
|
1618
|
+
if (options.verbose) {
|
|
1619
|
+
console.log(import_chalk6.default.green(`\u2713 Generated types: ${typesFilePath}`));
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
if (options.schemas) {
|
|
1623
|
+
const schemasContent = generateJsonSchemas(data);
|
|
1624
|
+
const schemasFilePath = import_node_path5.default.join(outputDir, "schemas.json");
|
|
1625
|
+
await import_fs_extra6.default.writeFile(schemasFilePath, schemasContent);
|
|
1626
|
+
if (options.verbose) {
|
|
1627
|
+
console.log(import_chalk6.default.green(`\u2713 Generated schemas: ${schemasFilePath}`));
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
if (options.docs) {
|
|
1631
|
+
const docsContent = generateDocumentation(data);
|
|
1632
|
+
const docsFilePath = import_node_path5.default.join(outputDir, "translations.md");
|
|
1633
|
+
await import_fs_extra6.default.writeFile(docsFilePath, docsContent);
|
|
1634
|
+
if (options.verbose) {
|
|
1635
|
+
console.log(import_chalk6.default.green(`\u2713 Generated documentation: ${docsFilePath}`));
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
await import_fs_extra6.default.writeFile(cacheFilePath, cacheHash);
|
|
1639
|
+
}
|
|
1640
|
+
async function generateClientPackage(data, options) {
|
|
1641
|
+
console.log(import_chalk6.default.blue("\u{1F527} Generating client package files..."));
|
|
1642
|
+
if (options.verbose) {
|
|
1643
|
+
console.log(import_chalk6.default.gray(`Data: ${JSON.stringify(data, null, 2)}`));
|
|
1644
|
+
}
|
|
1645
|
+
let rootDir = process.cwd();
|
|
1646
|
+
while (rootDir !== import_node_path5.default.dirname(rootDir)) {
|
|
1647
|
+
const packageJsonPath = import_node_path5.default.join(rootDir, "package.json");
|
|
1648
|
+
if (await import_fs_extra6.default.pathExists(packageJsonPath)) {
|
|
1649
|
+
const packageJson = await import_fs_extra6.default.readJson(packageJsonPath);
|
|
1650
|
+
if (packageJson.name === "@intl-party/monorepo") {
|
|
1651
|
+
break;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
rootDir = import_node_path5.default.dirname(rootDir);
|
|
1655
|
+
}
|
|
1656
|
+
const clientDir = import_node_path5.default.join(rootDir, "packages/client/generated");
|
|
1657
|
+
await import_fs_extra6.default.ensureDir(clientDir);
|
|
1658
|
+
const typesContent = generateTypescriptTypes(data);
|
|
1659
|
+
const typesFilePath = import_node_path5.default.join(clientDir, "translations.generated.ts");
|
|
1660
|
+
if (options.verbose) {
|
|
1661
|
+
console.log(import_chalk6.default.gray(`Writing types to: ${typesFilePath}`));
|
|
1662
|
+
}
|
|
1663
|
+
await import_fs_extra6.default.writeFile(typesFilePath, typesContent);
|
|
1664
|
+
const messagesContent = generateClientMessages(data);
|
|
1665
|
+
const messagesFilePath = import_node_path5.default.join(clientDir, "messages.generated.ts");
|
|
1666
|
+
if (options.verbose) {
|
|
1667
|
+
console.log(import_chalk6.default.gray(`Writing messages to: ${messagesFilePath}`));
|
|
1668
|
+
}
|
|
1669
|
+
await import_fs_extra6.default.writeFile(messagesFilePath, messagesContent);
|
|
1670
|
+
const indexContent = generateClientIndex();
|
|
1671
|
+
const indexPath = import_node_path5.default.join(clientDir, "index.generated.ts");
|
|
1672
|
+
if (options.verbose) {
|
|
1673
|
+
console.log(import_chalk6.default.gray(`Writing index to: ${indexPath}`));
|
|
1674
|
+
}
|
|
1675
|
+
await import_fs_extra6.default.writeFile(indexPath, indexContent);
|
|
1676
|
+
if (options.verbose) {
|
|
1677
|
+
console.log(
|
|
1678
|
+
import_chalk6.default.green(`\u2713 Generated client package files in ${clientDir}`)
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
async function setupWatcher(configPath, outputDir, options) {
|
|
1683
|
+
const config = await loadConfig(configPath);
|
|
1684
|
+
const { translationPaths } = config;
|
|
1685
|
+
const watchPaths = [];
|
|
1686
|
+
for (const localePaths of Object.values(translationPaths)) {
|
|
1687
|
+
watchPaths.push(...Object.values(localePaths));
|
|
1688
|
+
}
|
|
1689
|
+
console.log(
|
|
1690
|
+
import_chalk6.default.blue(`\u{1F440} Watching for changes in ${watchPaths.length} files...`)
|
|
1691
|
+
);
|
|
1692
|
+
const watcher = (0, import_chokidar.watch)(watchPaths, {
|
|
1693
|
+
ignoreInitial: true,
|
|
1694
|
+
persistent: true
|
|
1695
|
+
});
|
|
1696
|
+
watcher.on("change", async (filePath) => {
|
|
1697
|
+
console.log(import_chalk6.default.yellow(`\u{1F4DD} File changed: ${filePath}`));
|
|
1698
|
+
try {
|
|
1699
|
+
const data = await getMessageData(configPath);
|
|
1700
|
+
await writeGeneratedFiles(data, outputDir, options);
|
|
1701
|
+
console.log(import_chalk6.default.green("\u2713 Regenerated translations"));
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
console.error(import_chalk6.default.red("\u2717 Failed to regenerate:"), error);
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
1706
|
+
process.on("SIGINT", () => {
|
|
1707
|
+
watcher.close();
|
|
1708
|
+
console.log(import_chalk6.default.gray("\n\u{1F44B} Stopped watching"));
|
|
1709
|
+
process.exit(0);
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
903
1712
|
async function generateCommand(options) {
|
|
904
|
-
|
|
905
|
-
|
|
1713
|
+
const spinner = (0, import_ora6.default)("Loading translation data...").start();
|
|
1714
|
+
if (options.verbose) {
|
|
1715
|
+
console.log(
|
|
1716
|
+
import_chalk6.default.gray(`Debug options: ${JSON.stringify(options, null, 2)}`)
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
try {
|
|
1720
|
+
const data = await getMessageData(options.config, options);
|
|
1721
|
+
spinner.succeed(
|
|
1722
|
+
`Loaded ${data.locales.length} locales, ${data.namespaces.length} namespaces, ${data.translationKeys.length} keys`
|
|
1723
|
+
);
|
|
1724
|
+
const outputDir = options.output || "./node_modules/.intl-party";
|
|
1725
|
+
spinner.start("Generating files...");
|
|
1726
|
+
await writeGeneratedFiles(data, outputDir, options);
|
|
1727
|
+
spinner.succeed("Files generated successfully");
|
|
1728
|
+
console.log(import_chalk6.default.green(`\u2713 Generated translation files in ${outputDir}`));
|
|
1729
|
+
if (options.client) {
|
|
1730
|
+
await generateClientPackage(data, options);
|
|
1731
|
+
}
|
|
1732
|
+
if (options.watch) {
|
|
1733
|
+
await setupWatcher(options.config, outputDir, options);
|
|
1734
|
+
}
|
|
1735
|
+
} catch (error) {
|
|
1736
|
+
spinner.fail("Generation failed");
|
|
1737
|
+
console.error(
|
|
1738
|
+
import_chalk6.default.red("Error:"),
|
|
1739
|
+
error instanceof Error ? error.message : error
|
|
1740
|
+
);
|
|
1741
|
+
process.exit(1);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// src/commands/nextjs.ts
|
|
1746
|
+
var import_commander = require("commander");
|
|
1747
|
+
var import_node_fs = require("fs");
|
|
1748
|
+
var import_node_path6 = require("path");
|
|
1749
|
+
var import_chalk7 = __toESM(require("chalk"));
|
|
1750
|
+
async function initializeNextjsProject(force = false) {
|
|
1751
|
+
console.log(import_chalk7.default.cyan("\u{1F680} Initializing IntlParty for Next.js..."));
|
|
1752
|
+
if ((0, import_node_fs.existsSync)("intl-party.config.ts") || (0, import_node_fs.existsSync)("intl-party.config.js")) {
|
|
1753
|
+
if (!force) {
|
|
1754
|
+
console.log(import_chalk7.default.yellow("\u26A0\uFE0F IntlParty already initialized!"));
|
|
1755
|
+
return true;
|
|
1756
|
+
}
|
|
1757
|
+
console.log(import_chalk7.default.yellow("\u26A0\uFE0F Force flag set, overwriting existing files..."));
|
|
1758
|
+
}
|
|
1759
|
+
const hasSrcDir = (0, import_node_fs.existsSync)("src");
|
|
1760
|
+
const baseDir = hasSrcDir ? "src" : ".";
|
|
1761
|
+
const appDir = (0, import_node_path6.join)(baseDir, "app");
|
|
1762
|
+
const configContent = `// IntlParty configuration for Next.js
|
|
1763
|
+
export default {
|
|
1764
|
+
locales: ["en", "es", "fr"],
|
|
1765
|
+
defaultLocale: "en",
|
|
1766
|
+
messages: "./messages",
|
|
1767
|
+
// localePrefix defaults to "never" for clean URLs
|
|
1768
|
+
// cookieName defaults to "INTL_LOCALE"
|
|
1769
|
+
};`;
|
|
1770
|
+
(0, import_node_fs.writeFileSync)("intl-party.config.ts", configContent);
|
|
1771
|
+
console.log(import_chalk7.default.green("\u2705 Created intl-party.config.ts"));
|
|
1772
|
+
const messagesDir = "messages";
|
|
1773
|
+
if (!(0, import_node_fs.existsSync)(messagesDir)) {
|
|
1774
|
+
(0, import_node_fs.mkdirSync)(messagesDir, { recursive: true });
|
|
1775
|
+
}
|
|
1776
|
+
const locales = ["en", "es", "fr"];
|
|
1777
|
+
const sampleMessagesBase = {
|
|
1778
|
+
welcome: "Welcome to IntlParty!",
|
|
1779
|
+
description: "A modern i18n solution for Next.js",
|
|
1780
|
+
navigation: {
|
|
1781
|
+
home: "Home",
|
|
1782
|
+
about: "About",
|
|
1783
|
+
contact: "Contact"
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
locales.forEach((locale) => {
|
|
1787
|
+
const localeDir = (0, import_node_path6.join)(messagesDir, locale);
|
|
1788
|
+
if (!(0, import_node_fs.existsSync)(localeDir)) {
|
|
1789
|
+
(0, import_node_fs.mkdirSync)(localeDir, { recursive: true });
|
|
1790
|
+
}
|
|
1791
|
+
const messages = {
|
|
1792
|
+
...sampleMessagesBase,
|
|
1793
|
+
navigation: { ...sampleMessagesBase.navigation }
|
|
1794
|
+
};
|
|
1795
|
+
if (locale === "es") {
|
|
1796
|
+
messages.welcome = "\xA1Bienvenido a IntlParty!";
|
|
1797
|
+
messages.description = "Una soluci\xF3n i18n moderna para Next.js";
|
|
1798
|
+
messages.navigation.home = "Inicio";
|
|
1799
|
+
messages.navigation.about = "Acerca de";
|
|
1800
|
+
messages.navigation.contact = "Contacto";
|
|
1801
|
+
} else if (locale === "fr") {
|
|
1802
|
+
messages.welcome = "Bienvenue chez IntlParty !";
|
|
1803
|
+
messages.description = "Une solution i18n moderne pour Next.js";
|
|
1804
|
+
messages.navigation.home = "Accueil";
|
|
1805
|
+
messages.navigation.about = "\xC0 propos";
|
|
1806
|
+
messages.navigation.contact = "Contact";
|
|
1807
|
+
}
|
|
1808
|
+
(0, import_node_fs.writeFileSync)(
|
|
1809
|
+
(0, import_node_path6.join)(localeDir, "common.json"),
|
|
1810
|
+
JSON.stringify(messages, null, 2)
|
|
1811
|
+
);
|
|
1812
|
+
});
|
|
1813
|
+
console.log(import_chalk7.default.green("\u2705 Created sample message files in ./messages"));
|
|
1814
|
+
const middlewarePath = hasSrcDir ? "src/middleware.ts" : "middleware.ts";
|
|
1815
|
+
const middlewareContent = `import { createSetup } from "@intl-party/nextjs";
|
|
1816
|
+
import config from "${hasSrcDir ? "../" : "./"}intl-party.config";
|
|
1817
|
+
|
|
1818
|
+
const { middleware, middlewareConfig } = createSetup(config);
|
|
1819
|
+
|
|
1820
|
+
export { middleware };
|
|
1821
|
+
export const config = middlewareConfig;`;
|
|
1822
|
+
(0, import_node_fs.writeFileSync)(middlewarePath, middlewareContent);
|
|
1823
|
+
console.log(import_chalk7.default.green(`\u2705 Created ${middlewarePath}`));
|
|
1824
|
+
if ((0, import_node_fs.existsSync)("next.config.js")) {
|
|
1825
|
+
const nextConfigIntlPath = "next.config.intl-party.js";
|
|
1826
|
+
const nextConfigContent = `const { createNextConfigWithIntl } = require("@intl-party/nextjs");
|
|
1827
|
+
|
|
1828
|
+
/** @type {import('next').NextConfig} */
|
|
1829
|
+
const nextConfig = {
|
|
1830
|
+
reactStrictMode: true,
|
|
1831
|
+
};
|
|
1832
|
+
|
|
1833
|
+
module.exports = createNextConfigWithIntl(
|
|
1834
|
+
{
|
|
1835
|
+
i18nConfig: require("./intl-party.config").default,
|
|
1836
|
+
autoGenerate: true,
|
|
1837
|
+
watchMode: true,
|
|
1838
|
+
},
|
|
1839
|
+
nextConfig
|
|
1840
|
+
);`;
|
|
1841
|
+
(0, import_node_fs.writeFileSync)(nextConfigIntlPath, nextConfigContent);
|
|
1842
|
+
console.log(
|
|
1843
|
+
import_chalk7.default.green(
|
|
1844
|
+
`\u2705 Created ${nextConfigIntlPath} (merge with your next.config.js)`
|
|
1845
|
+
)
|
|
1846
|
+
);
|
|
1847
|
+
}
|
|
1848
|
+
if (!(0, import_node_fs.existsSync)(appDir)) {
|
|
1849
|
+
(0, import_node_fs.mkdirSync)(appDir, { recursive: true });
|
|
1850
|
+
}
|
|
1851
|
+
const layoutContent = `import { createSetup } from "@intl-party/nextjs";
|
|
1852
|
+
import config from "${hasSrcDir ? "../../" : "../"}intl-party.config";
|
|
1853
|
+
|
|
1854
|
+
const { getLocale, getMessages, Provider } = createSetup(config);
|
|
1855
|
+
|
|
1856
|
+
export default async function RootLayout({
|
|
1857
|
+
children,
|
|
1858
|
+
}: {
|
|
1859
|
+
children: React.ReactNode;
|
|
1860
|
+
}) {
|
|
1861
|
+
const locale = await getLocale();
|
|
1862
|
+
const messages = await getMessages(locale);
|
|
1863
|
+
|
|
1864
|
+
return (
|
|
1865
|
+
<html lang={locale}>
|
|
1866
|
+
<body>
|
|
1867
|
+
<Provider locale={locale} initialMessages={messages}>
|
|
1868
|
+
{children}
|
|
1869
|
+
</Provider>
|
|
1870
|
+
</body>
|
|
1871
|
+
</html>
|
|
1872
|
+
);
|
|
1873
|
+
}`;
|
|
1874
|
+
(0, import_node_fs.writeFileSync)((0, import_node_path6.join)(appDir, "layout.intl-party.tsx"), layoutContent);
|
|
1875
|
+
const pageContent = `import { useTranslations } from "@intl-party/nextjs";
|
|
1876
|
+
|
|
1877
|
+
export default function HomePage() {
|
|
1878
|
+
const t = useTranslations("common");
|
|
1879
|
+
|
|
1880
|
+
return (
|
|
1881
|
+
<div style={{ padding: "2rem", fontFamily: "system-ui, sans-serif" }}>
|
|
1882
|
+
<h1>{t("welcome")}</h1>
|
|
1883
|
+
<p>{t("description")}</p>
|
|
1884
|
+
|
|
1885
|
+
<nav style={{ display: "flex", gap: "1rem", marginTop: "1rem" }}>
|
|
1886
|
+
<a href="/">{t("navigation.home")}</a>
|
|
1887
|
+
<a href="/about">{t("navigation.about")}</a>
|
|
1888
|
+
<a href="/contact">{t("navigation.contact")}</a>
|
|
1889
|
+
</nav>
|
|
1890
|
+
</div>
|
|
1891
|
+
);
|
|
1892
|
+
}`;
|
|
1893
|
+
(0, import_node_fs.writeFileSync)((0, import_node_path6.join)(appDir, "page.intl-party.tsx"), pageContent);
|
|
1894
|
+
console.log(import_chalk7.default.green(`\u2705 Created example files in ${appDir}`));
|
|
1895
|
+
if ((0, import_node_fs.existsSync)(".gitignore")) {
|
|
1896
|
+
const gitignore = (0, import_node_fs.readFileSync)(".gitignore", "utf-8");
|
|
1897
|
+
if (!gitignore.includes(".intl-party")) {
|
|
1898
|
+
console.log(import_chalk7.default.blue("\n\u{1F4A1} Tip: Add .intl-party/ to your .gitignore"));
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
console.log(import_chalk7.default.blue("\n\u{1F4A1} Tip: Make sure to install the dependency:"));
|
|
1902
|
+
console.log(import_chalk7.default.white(" npm install @intl-party/nextjs"));
|
|
1903
|
+
console.log(import_chalk7.default.green("\n\u{1F389} IntlParty initialized successfully!"));
|
|
1904
|
+
console.log("\n\u{1F4DD} Next steps:");
|
|
1905
|
+
console.log("1. Review the generated configuration files");
|
|
1906
|
+
console.log(
|
|
1907
|
+
`2. Move/merge ${appDir}/layout.intl-party.tsx into your RootLayout`
|
|
1908
|
+
);
|
|
1909
|
+
console.log("3. Add translations to your message files in ./messages");
|
|
1910
|
+
console.log("4. Run your development server");
|
|
1911
|
+
console.log(`
|
|
1912
|
+
\u{1F4DA} For more information, visit: https://intl-party.ai/docs`);
|
|
1913
|
+
return true;
|
|
906
1914
|
}
|
|
1915
|
+
var nextjsCommand = new import_commander.Command("nextjs").description("Next.js specific commands for IntlParty").option("--init", "Initialize IntlParty in a Next.js project").option("--force", "Force overwrite existing files").action(async (options) => {
|
|
1916
|
+
if (options.init) {
|
|
1917
|
+
await initializeNextjsProject(options.force);
|
|
1918
|
+
} else {
|
|
1919
|
+
console.log("Please specify an action. Use --init to initialize.");
|
|
1920
|
+
}
|
|
1921
|
+
});
|
|
1922
|
+
|
|
1923
|
+
// package.json
|
|
1924
|
+
var package_default = {
|
|
1925
|
+
name: "@intl-party/cli",
|
|
1926
|
+
version: "1.1.1",
|
|
1927
|
+
description: "Command-line interface for IntlParty - validation, extraction, and management tools",
|
|
1928
|
+
main: "dist/index.js",
|
|
1929
|
+
types: "dist/index.d.ts",
|
|
1930
|
+
bin: {
|
|
1931
|
+
"intl-party": "./dist/cli.js",
|
|
1932
|
+
ip: "./dist/cli.js"
|
|
1933
|
+
},
|
|
1934
|
+
files: [
|
|
1935
|
+
"dist"
|
|
1936
|
+
],
|
|
1937
|
+
scripts: {
|
|
1938
|
+
build: "tsup src/cli.ts src/index.ts --format cjs",
|
|
1939
|
+
dev: "tsup src/cli.ts src/index.ts --format cjs --dts --watch",
|
|
1940
|
+
test: "vitest --run",
|
|
1941
|
+
"test:watch": "vitest",
|
|
1942
|
+
lint: "eslint src --ext .ts",
|
|
1943
|
+
typecheck: "tsc --noEmit",
|
|
1944
|
+
clean: "rm -rf dist"
|
|
1945
|
+
},
|
|
1946
|
+
keywords: [
|
|
1947
|
+
"cli",
|
|
1948
|
+
"i18n",
|
|
1949
|
+
"internationalization",
|
|
1950
|
+
"validation",
|
|
1951
|
+
"extraction",
|
|
1952
|
+
"typescript"
|
|
1953
|
+
],
|
|
1954
|
+
author: "RodrigoEspinosa",
|
|
1955
|
+
license: "MIT",
|
|
1956
|
+
homepage: "https://github.com/RodrigoEspinosa/intl-party#readme",
|
|
1957
|
+
bugs: {
|
|
1958
|
+
url: "https://github.com/RodrigoEspinosa/intl-party/issues"
|
|
1959
|
+
},
|
|
1960
|
+
dependencies: {
|
|
1961
|
+
"@intl-party/core": "workspace:*",
|
|
1962
|
+
commander: "^11.1.0",
|
|
1963
|
+
chalk: "^5.3.0",
|
|
1964
|
+
chokidar: "^3.5.3",
|
|
1965
|
+
glob: "^10.3.10",
|
|
1966
|
+
"fs-extra": "^11.2.0",
|
|
1967
|
+
ora: "^7.0.1",
|
|
1968
|
+
inquirer: "^9.2.12"
|
|
1969
|
+
},
|
|
1970
|
+
devDependencies: {
|
|
1971
|
+
"@types/fs-extra": "^11.0.4",
|
|
1972
|
+
"@types/inquirer": "^9.0.7",
|
|
1973
|
+
"@types/node": "^20.10.0",
|
|
1974
|
+
eslint: "^8.55.0",
|
|
1975
|
+
jsdom: "^23.0.1",
|
|
1976
|
+
tsup: "^8.0.1",
|
|
1977
|
+
typescript: "^5.3.0",
|
|
1978
|
+
vitest: "^1.0.0"
|
|
1979
|
+
},
|
|
1980
|
+
repository: {
|
|
1981
|
+
type: "git",
|
|
1982
|
+
url: "https://github.com/RodrigoEspinosa/intl-party.git",
|
|
1983
|
+
directory: "packages/cli"
|
|
1984
|
+
},
|
|
1985
|
+
engines: {
|
|
1986
|
+
node: ">=18.0.0"
|
|
1987
|
+
}
|
|
1988
|
+
};
|
|
907
1989
|
|
|
908
1990
|
// src/cli.ts
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
import_commander.program.command("extract").description("extract translation keys from source code").option("-s, --source <patterns...>", "source file patterns", [
|
|
1991
|
+
import_commander2.program.name("intl-party").description("CLI for IntlParty internationalization library").version(package_default.version);
|
|
1992
|
+
import_commander2.program.option("-c, --config <path>", "path to config file", "intl-party.config.js").option("-v, --verbose", "verbose output").option("--no-color", "disable colored output");
|
|
1993
|
+
import_commander2.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);
|
|
1994
|
+
import_commander2.program.command("extract").description("extract translation keys from source code").option("-s, --source <patterns...>", "source file patterns", [
|
|
914
1995
|
"src/**/*.{ts,tsx,js,jsx}"
|
|
915
1996
|
]).option(
|
|
916
1997
|
"-o, --output <dir>",
|
|
917
1998
|
"output directory for extracted keys",
|
|
918
|
-
"./
|
|
1999
|
+
"./messages"
|
|
919
2000
|
).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
|
-
|
|
921
|
-
|
|
2001
|
+
import_commander2.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);
|
|
2002
|
+
import_commander2.program.command("init").description("initialize intl-party configuration and structure").option("--force", "overwrite existing configuration").option(
|
|
922
2003
|
"--template <template>",
|
|
923
2004
|
"template to use (nextjs|react|vanilla)",
|
|
924
2005
|
"react"
|
|
925
2006
|
).action(initCommand);
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
2007
|
+
import_commander2.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);
|
|
2008
|
+
import_commander2.program.command("check-config").description("validate your intl-party configuration").option("-c, --config <path>", "path to config file").action(async (options) => {
|
|
2009
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
2010
|
+
const spinner = (await import("ora")).default("Validating configuration...").start();
|
|
2011
|
+
try {
|
|
2012
|
+
const config = await loadConfig2(options.config);
|
|
2013
|
+
spinner.succeed("Configuration is valid!");
|
|
2014
|
+
if (options.verbose) {
|
|
2015
|
+
console.log(JSON.stringify(config, null, 2));
|
|
2016
|
+
}
|
|
2017
|
+
} catch (error) {
|
|
2018
|
+
spinner.fail("Configuration is invalid");
|
|
2019
|
+
console.error(
|
|
2020
|
+
import_chalk8.default.red("Error:"),
|
|
2021
|
+
error instanceof Error ? error.message : error
|
|
2022
|
+
);
|
|
2023
|
+
process.exit(1);
|
|
2024
|
+
}
|
|
2025
|
+
});
|
|
2026
|
+
import_commander2.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("-c, --client", "generate client package files").option(
|
|
2027
|
+
"-o, --output <dir>",
|
|
2028
|
+
"output directory for generated files",
|
|
2029
|
+
"./node_modules/.intl-party"
|
|
2030
|
+
).option("--watch", "watch for changes and regenerate").option("--verbose", "verbose output").action(generateCommand);
|
|
2031
|
+
import_commander2.program.addCommand(nextjsCommand);
|
|
2032
|
+
import_commander2.program.command("completion").description("generate shell completion scripts").option("--shell <shell>", "shell type (bash|zsh|fish)", "bash").action((_options) => {
|
|
929
2033
|
console.log("Shell completion not implemented yet");
|
|
930
2034
|
});
|
|
931
|
-
|
|
2035
|
+
import_commander2.program.exitOverride((err) => {
|
|
932
2036
|
if (err.code === "commander.help") {
|
|
933
2037
|
process.exit(0);
|
|
934
2038
|
}
|
|
935
2039
|
if (err.code === "commander.version") {
|
|
936
2040
|
process.exit(0);
|
|
937
2041
|
}
|
|
938
|
-
console.error(
|
|
2042
|
+
console.error(import_chalk8.default.red("Error:"), err.message);
|
|
939
2043
|
process.exit(1);
|
|
940
2044
|
});
|
|
941
|
-
|
|
2045
|
+
import_commander2.program.parse();
|
|
942
2046
|
if (!process.argv.slice(2).length) {
|
|
943
|
-
|
|
2047
|
+
import_commander2.program.outputHelp();
|
|
944
2048
|
}
|