@better-intl/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,571 @@
1
+ 'use strict';
2
+
3
+ var promises = require('fs/promises');
4
+ var path = require('path');
5
+ var compiler = require('@better-intl/compiler');
6
+ var ink = require('ink');
7
+ var react = require('react');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+ var core = require('@better-intl/core');
10
+
11
+ // src/commands.ts
12
+ async function resolveFiles(patterns, exclude, cwd) {
13
+ const files = [];
14
+ for (const pattern of patterns) {
15
+ for await (const entry of promises.glob(pattern, { cwd })) {
16
+ const filePath = path.resolve(cwd, entry);
17
+ const rel = path.relative(cwd, filePath);
18
+ const isExcluded = exclude.some((ex) => {
19
+ const regex = new RegExp(
20
+ ex.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*")
21
+ );
22
+ return regex.test(rel);
23
+ });
24
+ if (!isExcluded) {
25
+ files.push(filePath);
26
+ }
27
+ }
28
+ }
29
+ return [...new Set(files)];
30
+ }
31
+ async function extractAllMessages(config, cwd) {
32
+ const files = await resolveFiles(config.include, config.exclude, cwd);
33
+ const allMessages = [];
34
+ for (const file of files) {
35
+ const source = await promises.readFile(file, "utf-8");
36
+ const relPath = path.relative(cwd, file);
37
+ allMessages.push(...compiler.extract(source, { filePath: relPath }));
38
+ }
39
+ return allMessages;
40
+ }
41
+ async function loadAllCatalogs(config, cwd) {
42
+ const catalogs = {};
43
+ for (const locale of config.locales) {
44
+ const catalogPath = path.resolve(
45
+ cwd,
46
+ config.catalogs.replace("{locale}", locale)
47
+ );
48
+ try {
49
+ catalogs[locale] = JSON.parse(await promises.readFile(catalogPath, "utf-8"));
50
+ } catch {
51
+ catalogs[locale] = {};
52
+ }
53
+ }
54
+ return catalogs;
55
+ }
56
+ async function getTranslationsData(config, cwd) {
57
+ const allMessages = await extractAllMessages(config, cwd);
58
+ const catalogs = await loadAllCatalogs(config, cwd);
59
+ const extractedIds = new Set(allMessages.map((m) => m.id));
60
+ const manualKeys = /* @__PURE__ */ new Set();
61
+ for (const messages of Object.values(catalogs)) {
62
+ for (const key of Object.keys(messages)) {
63
+ if (!extractedIds.has(key)) {
64
+ manualKeys.add(key);
65
+ }
66
+ }
67
+ }
68
+ const extracted = allMessages.map((msg) => {
69
+ const translations = {};
70
+ for (const locale of config.locales) {
71
+ if (msg.id in (catalogs[locale] ?? {})) {
72
+ translations[locale] = catalogs[locale][msg.id];
73
+ }
74
+ }
75
+ return {
76
+ id: msg.id,
77
+ source: `${msg.filePath}:${msg.line} <${msg.elementType}>`,
78
+ default: msg.defaultMessage,
79
+ translations
80
+ };
81
+ });
82
+ const manual = [...manualKeys].map((key) => {
83
+ const translations = {};
84
+ for (const locale of config.locales) {
85
+ if (key in (catalogs[locale] ?? {})) {
86
+ translations[locale] = catalogs[locale][key];
87
+ }
88
+ }
89
+ return { key, translations };
90
+ });
91
+ return {
92
+ locales: config.locales,
93
+ defaultLocale: config.defaultLocale,
94
+ extracted,
95
+ manual
96
+ };
97
+ }
98
+ function truncate(text, max) {
99
+ return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
100
+ }
101
+ function pad(text, width) {
102
+ return text.length >= width ? text : text + " ".repeat(width - text.length);
103
+ }
104
+ function Header({ data }) {
105
+ const totalKeys = data.extracted.length + data.manual.length;
106
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", marginBottom: 1, children: [
107
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { bold: true, children: [
108
+ "better-intl ",
109
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "gray", children: "dashboard" })
110
+ ] }),
111
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "gray", children: [
112
+ totalKeys,
113
+ " keys \xB7 ",
114
+ data.locales.length,
115
+ " locales \xB7",
116
+ " ",
117
+ data.extracted.length,
118
+ " extracted \xB7 ",
119
+ data.manual.length,
120
+ " manual"
121
+ ] })
122
+ ] });
123
+ }
124
+ function StatusIcon({ has, isDefault }) {
125
+ if (isDefault) return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "gray", children: "\u2500" });
126
+ if (has) return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "green", children: "\u2713" });
127
+ return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "red", children: "\u2717" });
128
+ }
129
+ function ExtractedTable({ data }) {
130
+ if (data.extracted.length === 0) return null;
131
+ const sourceWidth = 28;
132
+ const defaultWidth = 22;
133
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", marginBottom: 1, children: [
134
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, color: "gray", children: "EXTRACTED FROM SOURCE" }),
135
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "gray", children: [
136
+ pad("Source", sourceWidth),
137
+ " ",
138
+ pad("Default", defaultWidth),
139
+ " ",
140
+ data.locales.map((l) => pad(l, 8)).join("")
141
+ ] }) }),
142
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "gray", children: "\u2500".repeat(sourceWidth + defaultWidth + data.locales.length * 8 + 2) }),
143
+ data.extracted.map((msg) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { children: [
144
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "cyan", children: [
145
+ pad(truncate(msg.source, sourceWidth), sourceWidth),
146
+ " "
147
+ ] }),
148
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "gray", children: [
149
+ pad(truncate(msg.default, defaultWidth), defaultWidth),
150
+ " "
151
+ ] }),
152
+ data.locales.map((locale) => /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { width: 8, children: /* @__PURE__ */ jsxRuntime.jsx(
153
+ StatusIcon,
154
+ {
155
+ has: !!msg.translations[locale],
156
+ isDefault: locale === data.defaultLocale
157
+ }
158
+ ) }, locale))
159
+ ] }, msg.id))
160
+ ] });
161
+ }
162
+ function ManualTable({ data }) {
163
+ if (data.manual.length === 0) return null;
164
+ const keyWidth = 32;
165
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", marginBottom: 1, children: [
166
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, color: "gray", children: "MANUAL KEYS" }),
167
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "gray", children: [
168
+ pad("Key", keyWidth),
169
+ " ",
170
+ data.locales.map((l) => pad(l, 8)).join("")
171
+ ] }) }),
172
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "gray", children: "\u2500".repeat(keyWidth + data.locales.length * 8 + 1) }),
173
+ data.manual.map((msg) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { children: [
174
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "magenta", children: [
175
+ pad(truncate(msg.key, keyWidth), keyWidth),
176
+ " "
177
+ ] }),
178
+ data.locales.map((locale) => /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { width: 8, children: /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { has: !!msg.translations[locale], isDefault: false }) }, locale))
179
+ ] }, msg.key))
180
+ ] });
181
+ }
182
+ function Coverage({ data }) {
183
+ const totalKeys = data.extracted.length + data.manual.length;
184
+ if (totalKeys === 0) return null;
185
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
186
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, color: "gray", children: "COVERAGE" }),
187
+ data.locales.map((locale) => {
188
+ const isDefault = locale === data.defaultLocale;
189
+ const translated = isDefault ? totalKeys : data.extracted.filter((m) => m.translations[locale]).length + data.manual.filter((m) => m.translations[locale]).length;
190
+ const pct = Math.round(translated / totalKeys * 100);
191
+ const barWidth = 20;
192
+ const filled = Math.round(pct / 100 * barWidth);
193
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(barWidth - filled);
194
+ const color = pct === 100 ? "green" : pct >= 50 ? "yellow" : "red";
195
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { gap: 1, children: [
196
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: pad(locale, 8) }),
197
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color, children: bar }),
198
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color, bold: true, children: [
199
+ pct,
200
+ "%"
201
+ ] }),
202
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "gray", children: [
203
+ "(",
204
+ translated,
205
+ "/",
206
+ totalKeys,
207
+ ")"
208
+ ] })
209
+ ] }, locale);
210
+ })
211
+ ] });
212
+ }
213
+ function App({ config, cwd }) {
214
+ const { exit } = ink.useApp();
215
+ const [data, setData] = react.useState(null);
216
+ const [error, setError] = react.useState(null);
217
+ ink.useInput((input) => {
218
+ if (input === "q") exit();
219
+ });
220
+ react.useEffect(() => {
221
+ getTranslationsData(config, cwd).then(setData).catch((err) => setError(String(err)));
222
+ }, [config, cwd]);
223
+ if (error) {
224
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", padding: 1, children: [
225
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "red", bold: true, children: "Error loading translations" }),
226
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "red", children: error })
227
+ ] });
228
+ }
229
+ if (!data) {
230
+ return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { padding: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "gray", children: "Loading translations\u2026" }) });
231
+ }
232
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", padding: 1, children: [
233
+ /* @__PURE__ */ jsxRuntime.jsx(Header, { data }),
234
+ /* @__PURE__ */ jsxRuntime.jsx(ExtractedTable, { data }),
235
+ /* @__PURE__ */ jsxRuntime.jsx(ManualTable, { data }),
236
+ /* @__PURE__ */ jsxRuntime.jsx(Coverage, { data }),
237
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Press q to exit" }) })
238
+ ] });
239
+ }
240
+ async function devCommand(config, cwd) {
241
+ const instance = ink.render(/* @__PURE__ */ jsxRuntime.jsx(App, { config, cwd }));
242
+ await instance.waitUntilExit();
243
+ }
244
+
245
+ // src/commands.ts
246
+ function log(msg) {
247
+ console.log(` ${msg}`);
248
+ }
249
+ function logHeader(msg) {
250
+ console.log(`
251
+ \u2726 ${msg}
252
+ `);
253
+ }
254
+ async function resolveFiles2(patterns, exclude, cwd) {
255
+ const files = [];
256
+ for (const pattern of patterns) {
257
+ for await (const entry of promises.glob(pattern, { cwd })) {
258
+ const filePath = path.resolve(cwd, entry);
259
+ const rel = path.relative(cwd, filePath);
260
+ const isExcluded = exclude.some((ex) => {
261
+ const regex = new RegExp(
262
+ ex.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*")
263
+ );
264
+ return regex.test(rel);
265
+ });
266
+ if (!isExcluded) {
267
+ files.push(filePath);
268
+ }
269
+ }
270
+ }
271
+ return [...new Set(files)];
272
+ }
273
+ async function extractAllMessages2(config, cwd) {
274
+ const files = await resolveFiles2(config.include, config.exclude, cwd);
275
+ const allMessages = [];
276
+ for (const file of files) {
277
+ const source = await promises.readFile(file, "utf-8");
278
+ const relPath = path.relative(cwd, file);
279
+ allMessages.push(...compiler.extract(source, { filePath: relPath }));
280
+ }
281
+ return allMessages;
282
+ }
283
+ async function loadAllCatalogs2(config, cwd) {
284
+ const catalogs = {};
285
+ for (const locale of config.locales) {
286
+ const catalogPath = path.resolve(
287
+ cwd,
288
+ config.catalogs.replace("{locale}", locale)
289
+ );
290
+ try {
291
+ catalogs[locale] = JSON.parse(await promises.readFile(catalogPath, "utf-8"));
292
+ } catch {
293
+ catalogs[locale] = {};
294
+ }
295
+ }
296
+ return catalogs;
297
+ }
298
+ async function extractCommand(config, cwd) {
299
+ logHeader("Extracting messages...");
300
+ const allMessages = await extractAllMessages2(config, cwd);
301
+ log(`Extracted ${allMessages.length} messages`);
302
+ for (const locale of config.locales) {
303
+ const catalogPath = path.resolve(
304
+ cwd,
305
+ config.catalogs.replace("{locale}", locale)
306
+ );
307
+ await promises.mkdir(path.dirname(catalogPath), { recursive: true });
308
+ let existing = {};
309
+ try {
310
+ existing = JSON.parse(await promises.readFile(catalogPath, "utf-8"));
311
+ } catch {
312
+ }
313
+ if (locale === config.defaultLocale) {
314
+ compiler.generateDefaultCatalog(allMessages);
315
+ const { messages, added, removed } = compiler.mergeCatalog(existing, allMessages);
316
+ await promises.writeFile(catalogPath, `${JSON.stringify(messages, null, 2)}
317
+ `);
318
+ log(
319
+ `${locale}: ${Object.keys(messages).length} keys (${added.length} added, ${removed.length} removed)`
320
+ );
321
+ } else {
322
+ const { messages, added } = compiler.mergeCatalog(existing, allMessages);
323
+ await promises.writeFile(catalogPath, `${JSON.stringify(messages, null, 2)}
324
+ `);
325
+ log(
326
+ `${locale}: ${Object.keys(messages).length} keys (${added.length} need translation)`
327
+ );
328
+ }
329
+ }
330
+ logHeader("Done!");
331
+ }
332
+ async function validateCommand(config, cwd) {
333
+ logHeader("Validating catalogs...");
334
+ const defaultPath = path.resolve(
335
+ cwd,
336
+ config.catalogs.replace("{locale}", config.defaultLocale)
337
+ );
338
+ let defaultCatalog;
339
+ try {
340
+ defaultCatalog = JSON.parse(await promises.readFile(defaultPath, "utf-8"));
341
+ } catch {
342
+ console.error(` \u2717 Default catalog not found: ${defaultPath}`);
343
+ console.error(" Run 'i18n extract' first.");
344
+ process.exitCode = 1;
345
+ return;
346
+ }
347
+ let hasErrors = false;
348
+ for (const locale of config.locales) {
349
+ if (locale === config.defaultLocale) continue;
350
+ const catalogPath = path.resolve(
351
+ cwd,
352
+ config.catalogs.replace("{locale}", locale)
353
+ );
354
+ try {
355
+ const catalog = JSON.parse(
356
+ await promises.readFile(catalogPath, "utf-8")
357
+ );
358
+ const missing = compiler.findMissingKeys(defaultCatalog, catalog);
359
+ if (missing.length > 0) {
360
+ log(`\u2717 ${locale}: ${missing.length} missing keys`);
361
+ for (const key of missing.slice(0, 5)) {
362
+ log(` - ${key}: "${defaultCatalog[key]}"`);
363
+ }
364
+ if (missing.length > 5) {
365
+ log(` ... and ${missing.length - 5} more`);
366
+ }
367
+ hasErrors = true;
368
+ } else {
369
+ log(`\u2713 ${locale}: complete`);
370
+ }
371
+ } catch {
372
+ log(`\u2717 ${locale}: catalog not found`);
373
+ hasErrors = true;
374
+ }
375
+ }
376
+ if (hasErrors) {
377
+ process.exitCode = 1;
378
+ } else {
379
+ logHeader("All catalogs valid!");
380
+ }
381
+ }
382
+ async function deadKeysCommand(config, cwd) {
383
+ logHeader("Finding dead keys...");
384
+ const allMessages = await extractAllMessages2(config, cwd);
385
+ const catalogs = await loadAllCatalogs2(config, cwd);
386
+ for (const locale of config.locales) {
387
+ const catalog = catalogs[locale];
388
+ const dead = compiler.findDeadKeys(catalog, allMessages);
389
+ if (dead.length > 0) {
390
+ log(`${locale}: ${dead.length} dead keys`);
391
+ for (const key of dead) {
392
+ log(` - ${key}`);
393
+ }
394
+ } else {
395
+ log(`${locale}: no dead keys`);
396
+ }
397
+ }
398
+ }
399
+ async function missingCommand(config, cwd) {
400
+ logHeader("Finding missing translations...");
401
+ const defaultPath = path.resolve(
402
+ cwd,
403
+ config.catalogs.replace("{locale}", config.defaultLocale)
404
+ );
405
+ let defaultCatalog;
406
+ try {
407
+ defaultCatalog = JSON.parse(await promises.readFile(defaultPath, "utf-8"));
408
+ } catch {
409
+ console.error(" Default catalog not found. Run 'i18n extract' first.");
410
+ process.exitCode = 1;
411
+ return;
412
+ }
413
+ for (const locale of config.locales) {
414
+ if (locale === config.defaultLocale) continue;
415
+ const catalogPath = path.resolve(
416
+ cwd,
417
+ config.catalogs.replace("{locale}", locale)
418
+ );
419
+ try {
420
+ const catalog = JSON.parse(
421
+ await promises.readFile(catalogPath, "utf-8")
422
+ );
423
+ const missing = compiler.findMissingKeys(defaultCatalog, catalog);
424
+ log(`${locale}: ${missing.length} missing`);
425
+ } catch {
426
+ log(`${locale}: catalog not found (all keys missing)`);
427
+ }
428
+ }
429
+ }
430
+ async function compileCommand(config, cwd) {
431
+ logHeader("Compiling catalogs...");
432
+ const outDir = path.resolve(cwd, ".better-intl");
433
+ await promises.mkdir(outDir, { recursive: true });
434
+ for (const locale of config.locales) {
435
+ const catalogPath = path.resolve(
436
+ cwd,
437
+ config.catalogs.replace("{locale}", locale)
438
+ );
439
+ try {
440
+ const catalog = JSON.parse(
441
+ await promises.readFile(catalogPath, "utf-8")
442
+ );
443
+ const compiled = `export default ${JSON.stringify(catalog)};
444
+ `;
445
+ const outPath = path.resolve(outDir, `${locale}.js`);
446
+ await promises.writeFile(outPath, compiled);
447
+ log(`${locale}: ${Object.keys(catalog).length} keys \u2192 ${outPath}`);
448
+ } catch {
449
+ log(`${locale}: catalog not found, skipping`);
450
+ }
451
+ }
452
+ logHeader("Done!");
453
+ }
454
+ async function showCommand(config, cwd) {
455
+ logHeader("Translation Status");
456
+ const allMessages = await extractAllMessages2(config, cwd);
457
+ const catalogs = await loadAllCatalogs2(config, cwd);
458
+ const extractedIds = new Set(allMessages.map((m) => m.id));
459
+ const manualKeys = /* @__PURE__ */ new Set();
460
+ for (const messages of Object.values(catalogs)) {
461
+ for (const key of Object.keys(messages)) {
462
+ if (!extractedIds.has(key)) {
463
+ manualKeys.add(key);
464
+ }
465
+ }
466
+ }
467
+ if (allMessages.length > 0) {
468
+ log("Extracted messages (from source)");
469
+ log("\u2500".repeat(76));
470
+ const localeHeaders = config.locales.map((l) => padRight(l, 20)).join("");
471
+ log(
472
+ ` ${padRight("Source", 28)} ${padRight("Default", 22)} ${localeHeaders}`
473
+ );
474
+ log("\u2500".repeat(76));
475
+ for (const msg of allMessages) {
476
+ const source = truncate2(
477
+ `${msg.filePath}:${msg.line} <${msg.elementType}>`,
478
+ 26
479
+ );
480
+ const defaultText = truncate2(msg.defaultMessage, 20);
481
+ const localeCells = config.locales.map((locale) => {
482
+ const catalog = catalogs[locale] ?? {};
483
+ if (!(msg.id in catalog)) return padRight("\u2717 MISSING", 20);
484
+ if (locale === config.defaultLocale) return padRight("\u2713", 20);
485
+ const val = catalog[msg.id];
486
+ if (val === msg.defaultMessage) return padRight("~ untranslated", 20);
487
+ return padRight(`\u2713 ${truncate2(val, 16)}`, 20);
488
+ }).join("");
489
+ log(
490
+ ` ${padRight(source, 28)} ${padRight(defaultText, 22)} ${localeCells}`
491
+ );
492
+ }
493
+ log("\u2500".repeat(76));
494
+ const extractedCounts = config.locales.map((locale) => {
495
+ const catalog = catalogs[locale] ?? {};
496
+ const found = allMessages.filter((m) => m.id in catalog).length;
497
+ return `${locale}: ${found}/${allMessages.length}`;
498
+ }).join(" ");
499
+ log(` ${allMessages.length} extracted | ${extractedCounts}`);
500
+ console.log();
501
+ }
502
+ if (manualKeys.size > 0) {
503
+ log("Manual keys");
504
+ log("\u2500".repeat(76));
505
+ const localeHeaders = config.locales.map((l) => padRight(l, 24)).join("");
506
+ log(` ${padRight("Key", 24)} ${localeHeaders}`);
507
+ log("\u2500".repeat(76));
508
+ for (const key of manualKeys) {
509
+ const localeCells = config.locales.map((locale) => {
510
+ const catalog = catalogs[locale] ?? {};
511
+ if (!(key in catalog)) return padRight("\u2717 MISSING", 24);
512
+ return padRight(truncate2(catalog[key], 22), 24);
513
+ }).join("");
514
+ log(` ${padRight(key, 24)} ${localeCells}`);
515
+ }
516
+ log("\u2500".repeat(76));
517
+ const manualCounts = config.locales.map((locale) => {
518
+ const catalog = catalogs[locale] ?? {};
519
+ const found = [...manualKeys].filter((k) => k in catalog).length;
520
+ return `${locale}: ${found}/${manualKeys.size}`;
521
+ }).join(" ");
522
+ log(` ${manualKeys.size} manual | ${manualCounts}`);
523
+ console.log();
524
+ }
525
+ const total = allMessages.length + manualKeys.size;
526
+ logHeader(`Total: ${total} keys, ${config.locales.length} locales`);
527
+ }
528
+ function truncate2(text, maxLen) {
529
+ if (text.length <= maxLen) return text;
530
+ return `${text.slice(0, maxLen - 3)}...`;
531
+ }
532
+ function padRight(text, width) {
533
+ if (text.length >= width) return text.slice(0, width);
534
+ return text + " ".repeat(width - text.length);
535
+ }
536
+ var CONFIG_FILES = [
537
+ "better-intl.config.json",
538
+ "better-intl.config.js",
539
+ ".better-intlrc.json"
540
+ ];
541
+ async function loadConfig(cwd = process.cwd()) {
542
+ for (const file of CONFIG_FILES) {
543
+ const path$1 = path.resolve(cwd, file);
544
+ try {
545
+ const raw = await promises.readFile(path$1, "utf-8");
546
+ const parsed = JSON.parse(raw);
547
+ return { ...core.DEFAULT_CONFIG, ...parsed };
548
+ } catch {
549
+ }
550
+ }
551
+ try {
552
+ const pkgPath = path.resolve(cwd, "package.json");
553
+ const pkg = JSON.parse(await promises.readFile(pkgPath, "utf-8"));
554
+ if (pkg["better-intl"]) {
555
+ return { ...core.DEFAULT_CONFIG, ...pkg["better-intl"] };
556
+ }
557
+ } catch {
558
+ }
559
+ return core.DEFAULT_CONFIG;
560
+ }
561
+
562
+ exports.compileCommand = compileCommand;
563
+ exports.deadKeysCommand = deadKeysCommand;
564
+ exports.devCommand = devCommand;
565
+ exports.extractCommand = extractCommand;
566
+ exports.loadConfig = loadConfig;
567
+ exports.missingCommand = missingCommand;
568
+ exports.showCommand = showCommand;
569
+ exports.validateCommand = validateCommand;
570
+ //# sourceMappingURL=index.cjs.map
571
+ //# sourceMappingURL=index.cjs.map