@red-hat-developer-hub/translations-cli 0.0.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.
@@ -0,0 +1,1294 @@
1
+ 'use strict';
2
+
3
+ var path = require('node:path');
4
+ var chalk = require('chalk');
5
+ var fs = require('fs-extra');
6
+ var glob = require('glob');
7
+ var extractKeys = require('../lib/i18n/extractKeys.cjs.js');
8
+ var generateFiles = require('../lib/i18n/generateFiles.cjs.js');
9
+ var mergeFiles = require('../lib/i18n/mergeFiles.cjs.js');
10
+ var config = require('../lib/i18n/config.cjs.js');
11
+ var exec = require('../lib/utils/exec.cjs.js');
12
+ var deployTranslations = require('../lib/i18n/deployTranslations.cjs.js');
13
+
14
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
15
+
16
+ var path__default = /*#__PURE__*/_interopDefaultCompat(path);
17
+ var chalk__default = /*#__PURE__*/_interopDefaultCompat(chalk);
18
+ var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
19
+ var glob__default = /*#__PURE__*/_interopDefaultCompat(glob);
20
+
21
+ function detectRepoName(repoPath) {
22
+ const targetPath = repoPath || process.cwd();
23
+ try {
24
+ const gitRepoUrl = exec.safeExecSyncOrThrow(
25
+ "git",
26
+ ["config", "--get", "remote.origin.url"],
27
+ {
28
+ cwd: targetPath
29
+ }
30
+ );
31
+ if (gitRepoUrl) {
32
+ let repoName = gitRepoUrl.replace(/\.git$/, "");
33
+ const lastSlashIndex = repoName.lastIndexOf("/");
34
+ if (lastSlashIndex >= 0) {
35
+ repoName = repoName.substring(lastSlashIndex + 1);
36
+ }
37
+ if (repoName) {
38
+ return repoName;
39
+ }
40
+ }
41
+ } catch {
42
+ }
43
+ return path__default.default.basename(targetPath);
44
+ }
45
+ function isNestedStructure(data) {
46
+ if (typeof data !== "object" || data === null) return false;
47
+ const firstKey = Object.keys(data)[0];
48
+ if (!firstKey) return false;
49
+ const firstValue = data[firstKey];
50
+ return typeof firstValue === "object" && firstValue !== null && "en" in firstValue;
51
+ }
52
+ const LANGUAGE_CODES = [
53
+ "de",
54
+ "es",
55
+ "fr",
56
+ "it",
57
+ "ja",
58
+ "ko",
59
+ "pt",
60
+ "zh",
61
+ "ru",
62
+ "ar",
63
+ "hi",
64
+ "nl",
65
+ "pl",
66
+ "sv",
67
+ "tr",
68
+ "uk",
69
+ "vi"
70
+ ];
71
+ function isLanguageFile(fileName) {
72
+ const isNonEnglishLanguage = LANGUAGE_CODES.some((code) => {
73
+ if (fileName === code) return true;
74
+ if (fileName.endsWith(`-${code}`)) return true;
75
+ if (fileName.includes(`.${code}.`) || fileName.includes(`-${code}-`)) {
76
+ return true;
77
+ }
78
+ return false;
79
+ });
80
+ return isNonEnglishLanguage && !fileName.includes("-en") && fileName !== "en";
81
+ }
82
+ function isEnglishReferenceFile(filePath, content) {
83
+ const fileName = path__default.default.basename(filePath, path__default.default.extname(filePath));
84
+ const fullFileName = path__default.default.basename(filePath);
85
+ if (isLanguageFile(fileName)) {
86
+ return false;
87
+ }
88
+ const hasCreateTranslationRef = content.includes("createTranslationRef") && (content.includes("from '@backstage/core-plugin-api/alpha'") || content.includes("from '@backstage/frontend-plugin-api'"));
89
+ const isEnglishFile = fullFileName.endsWith("-en.ts") || fullFileName.endsWith("-en.tsx") || fullFileName === "en.ts" || fullFileName === "en.tsx" || fileName.endsWith("-en") || fileName === "en";
90
+ const hasCreateTranslationMessagesWithRef = isEnglishFile && content.includes("createTranslationMessages") && content.includes("ref:") && (content.includes("from '@backstage/core-plugin-api/alpha'") || content.includes("from '@backstage/frontend-plugin-api'"));
91
+ return hasCreateTranslationRef || hasCreateTranslationMessagesWithRef;
92
+ }
93
+ async function findEnglishReferenceFiles(sourceDir, includePattern, excludePattern) {
94
+ const allSourceFiles = glob__default.default.sync(includePattern, {
95
+ cwd: sourceDir,
96
+ ignore: excludePattern,
97
+ absolute: true
98
+ });
99
+ const sourceFiles = [];
100
+ for (const filePath of allSourceFiles) {
101
+ try {
102
+ const content = await fs__default.default.readFile(filePath, "utf-8");
103
+ if (isEnglishReferenceFile(filePath, content)) {
104
+ sourceFiles.push(filePath);
105
+ }
106
+ } catch {
107
+ continue;
108
+ }
109
+ }
110
+ return sourceFiles;
111
+ }
112
+ async function findJsonTranslationFiles(sourceDir) {
113
+ const repoRoot = process.cwd();
114
+ const jsonPatterns = [
115
+ // Patterns relative to sourceDir
116
+ "**/translations/**/*.json",
117
+ "packages/app/src/translations/**/*.json",
118
+ "packages/app/translations/**/*.json",
119
+ // Patterns at repo root level (for RHDH repo)
120
+ "translations/**/*-en.json",
121
+ "translations/**/*en*.json"
122
+ ];
123
+ const jsonFiles = [];
124
+ for (const pattern of jsonPatterns) {
125
+ try {
126
+ const filesFromSource = glob__default.default.sync(pattern, {
127
+ cwd: sourceDir,
128
+ ignore: [
129
+ "**/node_modules/**",
130
+ "**/dist/**",
131
+ "**/build/**",
132
+ "**/test/**"
133
+ ],
134
+ absolute: true
135
+ });
136
+ jsonFiles.push(...filesFromSource);
137
+ if (pattern.startsWith("translations/")) {
138
+ const filesFromRoot = glob__default.default.sync(pattern, {
139
+ cwd: repoRoot,
140
+ ignore: [
141
+ "**/node_modules/**",
142
+ "**/dist/**",
143
+ "**/build/**",
144
+ "**/test/**"
145
+ ],
146
+ absolute: true
147
+ });
148
+ jsonFiles.push(...filesFromRoot);
149
+ }
150
+ } catch {
151
+ continue;
152
+ }
153
+ }
154
+ const uniqueFiles = Array.from(new Set(jsonFiles));
155
+ const englishJsonFiles = [];
156
+ for (const filePath of uniqueFiles) {
157
+ const fileName = path__default.default.basename(filePath, ".json").toLowerCase();
158
+ if (fileName.includes("en") || fileName.includes("reference")) {
159
+ if (!isLanguageFile(fileName.replace(/en|reference/gi, ""))) {
160
+ englishJsonFiles.push(filePath);
161
+ }
162
+ }
163
+ }
164
+ return englishJsonFiles;
165
+ }
166
+ function pathContainsTranslationsOrI18n(filePath) {
167
+ if (filePath.includes("/translations/") || filePath.includes("/i18n/")) {
168
+ return true;
169
+ }
170
+ const fileName = path__default.default.basename(filePath).toLowerCase();
171
+ const translationFileNames = [
172
+ "translation.ts",
173
+ "ref.ts",
174
+ "translationref.ts",
175
+ "messages.ts",
176
+ "data.json",
177
+ "alpha.ts"
178
+ ];
179
+ if (translationFileNames.some(
180
+ (name) => fileName === name || fileName.endsWith(name)
181
+ )) {
182
+ return true;
183
+ }
184
+ return false;
185
+ }
186
+ function isNonEnglishLanguageFile(filePath) {
187
+ const languageCodes = [
188
+ "de",
189
+ // German
190
+ "fr",
191
+ // French
192
+ "es",
193
+ // Spanish
194
+ "it",
195
+ // Italian
196
+ "ja",
197
+ // Japanese
198
+ "zh",
199
+ // Chinese
200
+ "pt",
201
+ // Portuguese
202
+ "ru",
203
+ // Russian
204
+ "ko",
205
+ // Korean
206
+ "nl",
207
+ // Dutch
208
+ "sv",
209
+ // Swedish
210
+ "pl",
211
+ // Polish
212
+ "cs",
213
+ // Czech
214
+ "tr",
215
+ // Turkish
216
+ "ar",
217
+ // Arabic
218
+ "he",
219
+ // Hebrew
220
+ "hi"
221
+ // Hindi
222
+ ];
223
+ const fileName = filePath.toLowerCase();
224
+ for (const lang of languageCodes) {
225
+ if (fileName.includes(`.${lang}.`) || fileName.includes(`-${lang}.`) || fileName.includes(`_${lang}.`)) {
226
+ return true;
227
+ }
228
+ if (fileName.includes(`/translations/${lang}/`) || fileName.includes(`/i18n/${lang}/`) || fileName.includes(`\\translations\\${lang}\\`) || fileName.includes(`\\i18n\\${lang}\\`)) {
229
+ return true;
230
+ }
231
+ }
232
+ return false;
233
+ }
234
+ async function findBackstagePluginTranslationRefs(backstageRepoPath) {
235
+ if (!await fs__default.default.pathExists(backstageRepoPath)) {
236
+ console.warn(
237
+ chalk__default.default.yellow(
238
+ `\u26A0\uFE0F Backstage repository path does not exist: ${backstageRepoPath}`
239
+ )
240
+ );
241
+ return [];
242
+ }
243
+ const patterns = [
244
+ // plugins/ directory structure (simpler structure: plugins/{name}/src/translation.ts)
245
+ "plugins/*/src/translation.ts",
246
+ "plugins/*/src/ref.ts",
247
+ "plugins/*/src/translationRef.ts",
248
+ "plugins/*/src/messages.ts",
249
+ "plugins/*/src/data.json",
250
+ "plugins/*/src/**/translation.ts",
251
+ "plugins/*/src/**/ref.ts",
252
+ "plugins/*/src/**/translationRef.ts",
253
+ "plugins/*/src/**/messages.ts",
254
+ "plugins/*/src/**/data.json",
255
+ "plugins/*/src/**/alpha.ts",
256
+ // plugins/ directory structure (packages subdirectory: plugins/*/packages/plugin-*/...)
257
+ "plugins/*/packages/plugin-*/**/translation.ts",
258
+ "plugins/*/packages/plugin-*/**/ref.ts",
259
+ "plugins/*/packages/plugin-*/**/translationRef.ts",
260
+ "plugins/*/packages/plugin-*/**/messages.ts",
261
+ "plugins/*/packages/plugin-*/**/data.json",
262
+ "plugins/*/packages/core-*/**/translation.ts",
263
+ "plugins/*/packages/core-*/**/ref.ts",
264
+ "plugins/*/packages/core-*/**/translationRef.ts",
265
+ "plugins/*/packages/core-*/**/messages.ts",
266
+ "plugins/*/packages/core-*/**/data.json",
267
+ // packages/ directory structure
268
+ "packages/plugin-*/**/translation.ts",
269
+ "packages/plugin-*/**/ref.ts",
270
+ "packages/plugin-*/**/translationRef.ts",
271
+ "packages/plugin-*/**/messages.ts",
272
+ "packages/plugin-*/**/data.json",
273
+ "packages/core-*/**/translation.ts",
274
+ "packages/core-*/**/ref.ts",
275
+ "packages/core-*/**/translationRef.ts",
276
+ "packages/core-*/**/messages.ts",
277
+ "packages/core-*/**/data.json",
278
+ // workspaces/ directory structure
279
+ "workspaces/*/packages/plugin-*/**/translation.ts",
280
+ "workspaces/*/packages/plugin-*/**/ref.ts",
281
+ "workspaces/*/packages/plugin-*/**/translationRef.ts",
282
+ "workspaces/*/packages/plugin-*/**/messages.ts",
283
+ "workspaces/*/packages/plugin-*/**/data.json",
284
+ "workspaces/*/packages/core-*/**/translation.ts",
285
+ "workspaces/*/packages/core-*/**/ref.ts",
286
+ "workspaces/*/packages/core-*/**/translationRef.ts",
287
+ "workspaces/*/packages/core-*/**/messages.ts",
288
+ "workspaces/*/packages/core-*/**/data.json",
289
+ // Also check for alpha.ts files (some plugins export refs from alpha.ts)
290
+ "plugins/*/packages/plugin-*/**/alpha.ts",
291
+ "plugins/*/packages/core-*/**/alpha.ts",
292
+ "packages/plugin-*/**/alpha.ts",
293
+ "packages/core-*/**/alpha.ts",
294
+ "workspaces/*/packages/plugin-*/**/alpha.ts",
295
+ "workspaces/*/packages/core-*/**/alpha.ts"
296
+ ];
297
+ const pluginRefFiles = [];
298
+ for (const pattern of patterns) {
299
+ try {
300
+ const files = glob__default.default.sync(pattern, {
301
+ cwd: backstageRepoPath,
302
+ ignore: [
303
+ "**/build/**",
304
+ "**/dist/**",
305
+ "**/node_modules/**",
306
+ "**/*.test.ts",
307
+ "**/*.spec.ts",
308
+ "**/*.test.d.ts",
309
+ "**/*.spec.d.ts"
310
+ ],
311
+ absolute: true
312
+ });
313
+ const filteredFiles = files.filter(
314
+ (file) => pathContainsTranslationsOrI18n(file) && !isNonEnglishLanguageFile(file)
315
+ );
316
+ pluginRefFiles.push(...filteredFiles);
317
+ } catch {
318
+ continue;
319
+ }
320
+ }
321
+ return Array.from(new Set(pluginRefFiles));
322
+ }
323
+ function extractBackstagePluginName(filePath) {
324
+ const normalizePluginName = (name) => {
325
+ return name.endsWith("-react") ? name.replace(/-react$/, "") : name;
326
+ };
327
+ const simplePluginsMatch = /plugins\/([^/]+)\//.exec(filePath);
328
+ if (simplePluginsMatch && filePath.includes("/src/")) {
329
+ return normalizePluginName(simplePluginsMatch[1]);
330
+ }
331
+ const pluginsMatch = /plugins\/([^/]+)\/packages\/plugin-([^/]+)/.exec(
332
+ filePath
333
+ );
334
+ if (pluginsMatch) {
335
+ return normalizePluginName(pluginsMatch[2]);
336
+ }
337
+ const packagesPluginMatch = /packages\/plugin-([^/]+)/.exec(filePath);
338
+ if (packagesPluginMatch) {
339
+ return normalizePluginName(packagesPluginMatch[1]);
340
+ }
341
+ const packagesCoreMatch = /packages\/core-([^/]+)/.exec(filePath);
342
+ if (packagesCoreMatch) {
343
+ return `core-${packagesCoreMatch[1]}`;
344
+ }
345
+ const workspacesMatch = /workspaces\/([^/]+)\/packages\/plugin-([^/]+)/.exec(
346
+ filePath
347
+ );
348
+ if (workspacesMatch) {
349
+ return normalizePluginName(workspacesMatch[2]);
350
+ }
351
+ const nodeModulesPluginMatch = /@backstage\/plugin-([^/]+)/.exec(filePath);
352
+ if (nodeModulesPluginMatch) {
353
+ return normalizePluginName(nodeModulesPluginMatch[1]);
354
+ }
355
+ const nodeModulesCoreMatch = /@backstage\/core-([^/]+)/.exec(filePath);
356
+ if (nodeModulesCoreMatch) {
357
+ return `core-${nodeModulesCoreMatch[1]}`;
358
+ }
359
+ return null;
360
+ }
361
+ function buildPackageName(pluginName) {
362
+ return pluginName.startsWith("core-") ? `@backstage/${pluginName}` : `@backstage/plugin-${pluginName}`;
363
+ }
364
+ async function checkPackageJsonDependencies(packageJsonPath, packageName) {
365
+ if (!await fs__default.default.pathExists(packageJsonPath)) {
366
+ return false;
367
+ }
368
+ try {
369
+ const packageJson = await fs__default.default.readJson(packageJsonPath);
370
+ const allDeps = {
371
+ ...packageJson.dependencies,
372
+ ...packageJson.devDependencies,
373
+ ...packageJson.peerDependencies
374
+ };
375
+ return packageName in allDeps;
376
+ } catch {
377
+ return false;
378
+ }
379
+ }
380
+ function buildPackagePatterns(pluginName, packageName) {
381
+ const patterns = [
382
+ packageName,
383
+ `backstage-plugin-${pluginName}`,
384
+ `backstage/core-${pluginName.replace("core-", "")}`,
385
+ `backstage-plugin-${pluginName}-react`,
386
+ `plugin-${pluginName}-react`,
387
+ packageName.replace("@backstage/", "")
388
+ ];
389
+ if (pluginName === "home" || pluginName === "home-react") {
390
+ patterns.push("dynamic-home-page");
391
+ patterns.push("backstage-plugin-dynamic-home-page");
392
+ }
393
+ return patterns;
394
+ }
395
+ function buildPluginIdPatterns(pluginName) {
396
+ return [
397
+ `backstage.plugin-${pluginName}`,
398
+ `backstage.core-${pluginName.replace("core-", "")}`
399
+ ];
400
+ }
401
+ function matchesPackagePattern(line, pattern) {
402
+ if (line.includes("backend") || line.includes("module")) {
403
+ return false;
404
+ }
405
+ if (line.includes(pattern)) {
406
+ return true;
407
+ }
408
+ return line.includes(`./dynamic-plugins/dist/${pattern}`) || line.includes(`dynamic-plugins/dist/${pattern}`);
409
+ }
410
+ function matchesPluginIdPattern(trimmedLine, pattern) {
411
+ return trimmedLine.includes(`"${pattern}"`) || trimmedLine.includes(`'${pattern}'`);
412
+ }
413
+ function parseDynamicPluginsFile(content, packagePatterns, pluginIdPatterns) {
414
+ const lines = content.split("\n");
415
+ const pluginEntries = [];
416
+ for (let i = 0; i < lines.length; i++) {
417
+ const line = lines[i];
418
+ const trimmedLine = line.trim();
419
+ const matchesPackage = packagePatterns.some(
420
+ (pattern) => matchesPackagePattern(line, pattern)
421
+ );
422
+ const matchesPluginId = pluginIdPatterns.some(
423
+ (pattern) => matchesPluginIdPattern(trimmedLine, pattern)
424
+ );
425
+ if (matchesPackage || matchesPluginId && trimmedLine.startsWith("backstage.")) {
426
+ pluginEntries.push({ startLine: i, disabled: null });
427
+ }
428
+ for (const entry of pluginEntries) {
429
+ if (i >= entry.startLine && i < entry.startLine + 10) {
430
+ if (trimmedLine.startsWith("disabled:")) {
431
+ entry.disabled = trimmedLine.includes("disabled: false") ? false : trimmedLine.includes("disabled: true");
432
+ }
433
+ }
434
+ }
435
+ }
436
+ return pluginEntries.some(
437
+ (entry) => entry.disabled === false || entry.disabled === null
438
+ );
439
+ }
440
+ async function checkDynamicPlugins(repoRoot, pluginName, packageName) {
441
+ const dynamicPluginsPath = path__default.default.join(
442
+ repoRoot,
443
+ "dynamic-plugins.default.yaml"
444
+ );
445
+ if (!await fs__default.default.pathExists(dynamicPluginsPath)) {
446
+ return false;
447
+ }
448
+ try {
449
+ const content = await fs__default.default.readFile(dynamicPluginsPath, "utf-8");
450
+ const packagePatterns = buildPackagePatterns(pluginName, packageName);
451
+ const pluginIdPatterns = buildPluginIdPatterns(pluginName);
452
+ return parseDynamicPluginsFile(content, packagePatterns, pluginIdPatterns);
453
+ } catch {
454
+ return false;
455
+ }
456
+ }
457
+ async function checkSourceCodeImports(repoRoot, packageName) {
458
+ const searchPatterns = [
459
+ `packages/app/src/**/*.{ts,tsx,js,jsx}`,
460
+ `packages/app/src/**/*.tsx`,
461
+ `src/**/*.{ts,tsx,js,jsx}`
462
+ ];
463
+ for (const pattern of searchPatterns) {
464
+ try {
465
+ const files = glob__default.default.sync(pattern, {
466
+ cwd: repoRoot,
467
+ ignore: [
468
+ "**/node_modules/**",
469
+ "**/dist/**",
470
+ "**/build/**",
471
+ "**/*.test.*",
472
+ "**/*.spec.*"
473
+ ],
474
+ absolute: true
475
+ });
476
+ for (const file of files.slice(0, 50)) {
477
+ try {
478
+ const content = await fs__default.default.readFile(file, "utf-8");
479
+ if (content.includes(`from '${packageName}'`) || content.includes(`from "${packageName}"`) || content.includes(`require('${packageName}')`) || content.includes(`require("${packageName}")`) || content.includes(`'${packageName}'`) || content.includes(`"${packageName}"`)) {
480
+ return true;
481
+ }
482
+ } catch {
483
+ continue;
484
+ }
485
+ }
486
+ } catch {
487
+ continue;
488
+ }
489
+ }
490
+ return false;
491
+ }
492
+ async function isPluginUsedInRhdh(pluginName, repoRoot) {
493
+ const packageName = buildPackageName(pluginName);
494
+ const nodeModulesPath = path__default.default.join(repoRoot, "node_modules", packageName);
495
+ if (!await fs__default.default.pathExists(nodeModulesPath)) {
496
+ return false;
497
+ }
498
+ const packageJsonPath = path__default.default.join(repoRoot, "package.json");
499
+ if (await checkPackageJsonDependencies(packageJsonPath, packageName)) {
500
+ return true;
501
+ }
502
+ const appPackageJsonPath = path__default.default.join(
503
+ repoRoot,
504
+ "packages",
505
+ "app",
506
+ "package.json"
507
+ );
508
+ if (await checkPackageJsonDependencies(appPackageJsonPath, packageName)) {
509
+ return true;
510
+ }
511
+ if (await checkDynamicPlugins(repoRoot, pluginName, packageName)) {
512
+ return true;
513
+ }
514
+ if (await checkSourceCodeImports(repoRoot, packageName)) {
515
+ return true;
516
+ }
517
+ return false;
518
+ }
519
+ function findLanguageData(pluginData, isCorePlugins) {
520
+ if ("en" in pluginData && typeof pluginData.en === "object" && pluginData.en !== null) {
521
+ return pluginData.en;
522
+ }
523
+ if (isCorePlugins) {
524
+ for (const [, langData] of Object.entries(pluginData)) {
525
+ if (typeof langData === "object" && langData !== null) {
526
+ return langData;
527
+ }
528
+ }
529
+ } else {
530
+ for (const [lang, langData] of Object.entries(pluginData)) {
531
+ if (typeof langData === "object" && langData !== null && lang !== "en") {
532
+ return langData;
533
+ }
534
+ }
535
+ }
536
+ return null;
537
+ }
538
+ function extractKeysFromLanguageData(languageData, isCorePlugins, hasEnglish) {
539
+ const keys = {};
540
+ for (const [key, value] of Object.entries(languageData)) {
541
+ if (typeof value === "string") {
542
+ keys[key] = isCorePlugins && !hasEnglish ? key : value;
543
+ }
544
+ }
545
+ return keys;
546
+ }
547
+ function extractNestedStructure(data, isCorePlugins) {
548
+ const result = {};
549
+ for (const [pluginName, pluginData] of Object.entries(data)) {
550
+ if (typeof pluginData !== "object" || pluginData === null) {
551
+ continue;
552
+ }
553
+ const languageData = findLanguageData(
554
+ pluginData,
555
+ isCorePlugins
556
+ );
557
+ if (languageData) {
558
+ const hasEnglish = "en" in pluginData;
559
+ result[pluginName] = extractKeysFromLanguageData(
560
+ languageData,
561
+ isCorePlugins,
562
+ hasEnglish
563
+ );
564
+ }
565
+ }
566
+ return result;
567
+ }
568
+ function extractFlatStructure(data, filePath) {
569
+ const translations = typeof data === "object" && data !== null && "translations" in data ? data.translations : data;
570
+ if (typeof translations !== "object" || translations === null) {
571
+ return {};
572
+ }
573
+ const pluginName = detectPluginName(filePath) || "translations";
574
+ const result = {};
575
+ result[pluginName] = {};
576
+ for (const [key, value] of Object.entries(translations)) {
577
+ if (typeof value === "string") {
578
+ result[pluginName][key] = value;
579
+ }
580
+ }
581
+ return result;
582
+ }
583
+ async function extractKeysFromJsonFile(filePath, isCorePlugins = false) {
584
+ try {
585
+ const content = await fs__default.default.readFile(filePath, "utf-8");
586
+ const data = JSON.parse(content);
587
+ if (typeof data === "object" && data !== null) {
588
+ const nestedResult = extractNestedStructure(
589
+ data,
590
+ isCorePlugins
591
+ );
592
+ if (Object.keys(nestedResult).length > 0) {
593
+ return nestedResult;
594
+ }
595
+ }
596
+ return extractFlatStructure(data, filePath);
597
+ } catch (error) {
598
+ console.warn(
599
+ chalk__default.default.yellow(
600
+ `\u26A0\uFE0F Warning: Could not extract keys from JSON file ${filePath}: ${error}`
601
+ )
602
+ );
603
+ return {};
604
+ }
605
+ }
606
+ function detectPluginName(filePath) {
607
+ const workspaceRegex = /workspaces\/([^/]+)\/plugins\/([^/]+)/;
608
+ const workspaceMatch = workspaceRegex.exec(filePath);
609
+ if (workspaceMatch) {
610
+ return workspaceMatch[2];
611
+ }
612
+ const translationsRegex = /translations\/([^/]+)\//;
613
+ const translationsMatch = translationsRegex.exec(filePath);
614
+ if (translationsMatch) {
615
+ return translationsMatch[1];
616
+ }
617
+ const dirName = path__default.default.dirname(filePath);
618
+ const parentDir = path__default.default.basename(dirName);
619
+ if (parentDir === "translations" || parentDir.includes("translation")) {
620
+ const grandParentDir = path__default.default.basename(path__default.default.dirname(dirName));
621
+ return grandParentDir;
622
+ }
623
+ return parentDir;
624
+ }
625
+ const INVALID_PLUGIN_NAMES = /* @__PURE__ */ new Set([
626
+ "dist",
627
+ "build",
628
+ "node_modules",
629
+ "packages",
630
+ "src",
631
+ "lib",
632
+ "components",
633
+ "utils"
634
+ ]);
635
+ function validatePluginName(pluginName, filePath) {
636
+ if (!pluginName) {
637
+ console.warn(
638
+ chalk__default.default.yellow(
639
+ `\u26A0\uFE0F Warning: Could not determine plugin name for ${path__default.default.relative(
640
+ process.cwd(),
641
+ filePath
642
+ )}, skipping`
643
+ )
644
+ );
645
+ return false;
646
+ }
647
+ if (INVALID_PLUGIN_NAMES.has(pluginName.toLowerCase())) {
648
+ console.warn(
649
+ chalk__default.default.yellow(
650
+ `\u26A0\uFE0F Warning: Skipping invalid plugin name "${pluginName}" from ${path__default.default.relative(
651
+ process.cwd(),
652
+ filePath
653
+ )}`
654
+ )
655
+ );
656
+ return false;
657
+ }
658
+ return true;
659
+ }
660
+ function mergeKeysIntoPluginGroup(pluginGroups, pluginName, keys, filePath) {
661
+ if (!pluginGroups[pluginName]) {
662
+ pluginGroups[pluginName] = {};
663
+ }
664
+ const overwrittenKeys = [];
665
+ for (const [key, value] of Object.entries(keys)) {
666
+ const stringValue = String(value);
667
+ if (pluginGroups[pluginName][key] && pluginGroups[pluginName][key] !== stringValue) {
668
+ overwrittenKeys.push(key);
669
+ }
670
+ pluginGroups[pluginName][key] = stringValue;
671
+ }
672
+ if (overwrittenKeys.length > 0) {
673
+ console.warn(
674
+ chalk__default.default.yellow(
675
+ `\u26A0\uFE0F Warning: ${overwrittenKeys.length} keys were overwritten in plugin "${pluginName}" from ${path__default.default.relative(
676
+ process.cwd(),
677
+ filePath
678
+ )}`
679
+ )
680
+ );
681
+ }
682
+ }
683
+ async function processSourceFile(filePath, pluginGroups) {
684
+ try {
685
+ const content = await fs__default.default.readFile(filePath, "utf-8");
686
+ const extractResult = extractKeys.extractTranslationKeys(content, filePath);
687
+ const keys = extractResult.keys;
688
+ const pluginName = extractResult.pluginId || detectPluginName(filePath);
689
+ if (!validatePluginName(pluginName, filePath)) {
690
+ return;
691
+ }
692
+ mergeKeysIntoPluginGroup(pluginGroups, pluginName, keys, filePath);
693
+ } catch (error) {
694
+ console.warn(
695
+ chalk__default.default.yellow(`\u26A0\uFE0F Warning: Could not process ${filePath}: ${error}`)
696
+ );
697
+ }
698
+ }
699
+ function convertToStructuredData(pluginGroups) {
700
+ const structuredData = {};
701
+ for (const [pluginName, keys] of Object.entries(pluginGroups)) {
702
+ structuredData[pluginName] = { en: keys };
703
+ }
704
+ return structuredData;
705
+ }
706
+ async function extractAndGroupKeys(sourceFiles) {
707
+ const pluginGroups = {};
708
+ for (const filePath of sourceFiles) {
709
+ await processSourceFile(filePath, pluginGroups);
710
+ }
711
+ return convertToStructuredData(pluginGroups);
712
+ }
713
+ async function generateOrMergeFiles(translationKeys, outputPath, formatStr, mergeExisting) {
714
+ if (mergeExisting && await fs__default.default.pathExists(outputPath)) {
715
+ console.log(chalk__default.default.yellow(`\u{1F504} Merging with existing ${outputPath}...`));
716
+ await mergeFiles.mergeTranslationFiles(translationKeys, outputPath, formatStr);
717
+ } else {
718
+ console.log(chalk__default.default.yellow(`\u{1F4DD} Generating ${outputPath}...`));
719
+ await generateFiles.generateTranslationFiles(translationKeys, outputPath, formatStr);
720
+ }
721
+ }
722
+ async function validateGeneratedFile(outputPath, formatStr) {
723
+ if (formatStr !== "json") {
724
+ return;
725
+ }
726
+ console.log(chalk__default.default.yellow(`\u{1F50D} Validating generated file...`));
727
+ const { validateTranslationFile } = await import('../lib/i18n/validateFile.cjs.js');
728
+ const isValid = await validateTranslationFile(outputPath);
729
+ if (!isValid) {
730
+ throw new Error(`Generated file failed validation: ${outputPath}`);
731
+ }
732
+ console.log(chalk__default.default.green(`\u2705 Generated file is valid`));
733
+ }
734
+ function displaySummary(translationKeys) {
735
+ console.log(chalk__default.default.blue("\n\u{1F4CB} Included Plugins Summary:"));
736
+ console.log(chalk__default.default.gray("\u2500".repeat(60)));
737
+ const plugins = Object.entries(translationKeys).map(([pluginName, pluginData]) => ({
738
+ name: pluginName,
739
+ keyCount: Object.keys(pluginData.en || {}).length
740
+ })).sort((a, b) => a.name.localeCompare(b.name));
741
+ let totalKeys = 0;
742
+ for (const plugin of plugins) {
743
+ const keyLabel = plugin.keyCount === 1 ? "key" : "keys";
744
+ console.log(
745
+ chalk__default.default.cyan(
746
+ ` \u2022 ${plugin.name.padEnd(35)} ${chalk__default.default.yellow(
747
+ plugin.keyCount.toString().padStart(4)
748
+ )} ${keyLabel}`
749
+ )
750
+ );
751
+ totalKeys += plugin.keyCount;
752
+ }
753
+ console.log(chalk__default.default.gray("\u2500".repeat(60)));
754
+ const pluginLabel = plugins.length === 1 ? "plugin" : "plugins";
755
+ const totalKeyLabel = totalKeys === 1 ? "key" : "keys";
756
+ console.log(
757
+ chalk__default.default.cyan(
758
+ ` Total: ${chalk__default.default.yellow(
759
+ plugins.length.toString()
760
+ )} ${pluginLabel}, ${chalk__default.default.yellow(
761
+ totalKeys.toString()
762
+ )} ${totalKeyLabel}`
763
+ )
764
+ );
765
+ console.log("");
766
+ }
767
+ function validateSprintOptions(outputFilename, sprint) {
768
+ if (!outputFilename && !sprint) {
769
+ throw new Error(
770
+ "--sprint is required. Please provide a sprint value (e.g., --sprint s3285)"
771
+ );
772
+ }
773
+ if (sprint && !/^s?\d+$/i.test(sprint)) {
774
+ throw new Error(
775
+ `Invalid sprint format: "${sprint}". Sprint should be in format "s3285" or "3285"`
776
+ );
777
+ }
778
+ }
779
+ function getBackstageRepoPath(opts, config) {
780
+ return opts.backstageRepoPath || config.backstageRepoPath || process.env.BACKSTAGE_REPO_PATH || null;
781
+ }
782
+ function mapPluginName(pluginName, refFile) {
783
+ let mapped = pluginName.replace(/^plugin-/, "");
784
+ if (pluginName === "plugin-home-react" || refFile.includes("home-react")) {
785
+ mapped = "home-react";
786
+ } else if (pluginName === "plugin-home") {
787
+ mapped = "home";
788
+ }
789
+ return mapped;
790
+ }
791
+ async function extractKeysFromDataJson(refFile) {
792
+ const keys = {};
793
+ const jsonContent = await fs__default.default.readJson(refFile);
794
+ if (typeof jsonContent === "object" && jsonContent !== null) {
795
+ for (const [key, value] of Object.entries(jsonContent)) {
796
+ if (typeof value === "string") {
797
+ keys[key] = value;
798
+ } else if (typeof value === "object" && value !== null && "defaultMessage" in value) {
799
+ keys[key] = value.defaultMessage;
800
+ }
801
+ }
802
+ }
803
+ const pluginName = extractBackstagePluginName(refFile);
804
+ return { keys, pluginName };
805
+ }
806
+ async function extractKeysFromRefFile(refFile) {
807
+ if (refFile.endsWith("data.json")) {
808
+ return extractKeysFromDataJson(refFile);
809
+ }
810
+ const content = await fs__default.default.readFile(refFile, "utf-8");
811
+ const extractResult = extractKeys.extractTranslationKeys(content, refFile);
812
+ const pluginName = extractResult.pluginId || extractBackstagePluginName(refFile);
813
+ return { keys: extractResult.keys, pluginName };
814
+ }
815
+ async function checkPluginUsage(pluginRefFiles, rhdhRepoPath) {
816
+ const usedPlugins = /* @__PURE__ */ new Set();
817
+ const unusedPlugins = /* @__PURE__ */ new Set();
818
+ for (const refFile of pluginRefFiles) {
819
+ try {
820
+ const pluginName = extractBackstagePluginName(refFile);
821
+ if (!pluginName) continue;
822
+ const mappedPluginName = mapPluginName(pluginName, refFile);
823
+ const isUsed = await isPluginUsedInRhdh(mappedPluginName, rhdhRepoPath);
824
+ if (isUsed) {
825
+ usedPlugins.add(mappedPluginName);
826
+ } else {
827
+ unusedPlugins.add(mappedPluginName);
828
+ }
829
+ } catch {
830
+ continue;
831
+ }
832
+ }
833
+ return { usedPlugins, unusedPlugins };
834
+ }
835
+ async function extractKeysFromCorePluginRefs(pluginRefFiles, shouldFilterByUsage, usedPlugins, communityPluginsRoot) {
836
+ const structuredData = {};
837
+ const isCommunityPluginsRepo = communityPluginsRoot && pluginRefFiles.some(
838
+ (file) => file.includes("workspaces/") && file.includes("/plugins/") && !file.includes("/packages/")
839
+ );
840
+ let filteredCount = 0;
841
+ for (const refFile of pluginRefFiles) {
842
+ try {
843
+ const { keys, pluginName } = await extractKeysFromRefFile(refFile);
844
+ if (!pluginName || Object.keys(keys).length === 0) {
845
+ continue;
846
+ }
847
+ const mappedPluginName = mapPluginName(pluginName, refFile);
848
+ if (isCommunityPluginsRepo && communityPluginsRoot) {
849
+ if (!deployTranslations.isRedHatOwnedPlugin(mappedPluginName, communityPluginsRoot)) {
850
+ filteredCount++;
851
+ continue;
852
+ }
853
+ }
854
+ if (shouldFilterByUsage && !usedPlugins.has(mappedPluginName)) {
855
+ continue;
856
+ }
857
+ if (!structuredData[mappedPluginName]) {
858
+ structuredData[mappedPluginName] = { en: {} };
859
+ }
860
+ for (const [key, value] of Object.entries(keys)) {
861
+ if (!structuredData[mappedPluginName].en[key]) {
862
+ structuredData[mappedPluginName].en[key] = value;
863
+ }
864
+ }
865
+ } catch (error) {
866
+ console.warn(
867
+ chalk__default.default.yellow(
868
+ `\u26A0\uFE0F Warning: Could not extract from ${refFile}: ${error}`
869
+ )
870
+ );
871
+ }
872
+ }
873
+ if (isCommunityPluginsRepo && filteredCount > 0) {
874
+ console.log(
875
+ chalk__default.default.gray(
876
+ ` Filtered out ${filteredCount} non-Red Hat owned plugin(s) from community-plugins repo`
877
+ )
878
+ );
879
+ }
880
+ return structuredData;
881
+ }
882
+ function filterTranslatedFiles(files, outputDirPath, outputFileName) {
883
+ return files.filter((file) => {
884
+ const fileName = path__default.default.basename(file);
885
+ if (file.startsWith(outputDirPath) && fileName === outputFileName) {
886
+ return false;
887
+ }
888
+ if (fileName.includes("reference") || fileName.includes("core-plugins-reference")) {
889
+ return false;
890
+ }
891
+ return true;
892
+ });
893
+ }
894
+ async function findCorePluginsTranslatedFiles(repoRoot, outputDir) {
895
+ const corePluginsJsonPatterns = [
896
+ "translations/core-plugins*.json",
897
+ "translations/**/core-plugins*.json"
898
+ ];
899
+ const translatedFiles = [];
900
+ const outputDirPath = path__default.default.resolve(repoRoot, String(outputDir || "i18n"));
901
+ const outputFileName = "core-plugins-reference.json";
902
+ for (const pattern of corePluginsJsonPatterns) {
903
+ try {
904
+ const files = glob__default.default.sync(pattern, {
905
+ cwd: repoRoot,
906
+ ignore: [
907
+ "**/node_modules/**",
908
+ "**/dist/**",
909
+ "**/build/**",
910
+ "**/test/**"
911
+ ],
912
+ absolute: true
913
+ });
914
+ const filteredFiles = filterTranslatedFiles(
915
+ files,
916
+ outputDirPath,
917
+ outputFileName
918
+ );
919
+ translatedFiles.push(...filteredFiles);
920
+ } catch {
921
+ }
922
+ }
923
+ return translatedFiles;
924
+ }
925
+ async function extractKeysFromTranslatedFiles(translatedFiles, repoRoot) {
926
+ const structuredData = {};
927
+ for (const translatedFile of translatedFiles) {
928
+ console.log(
929
+ chalk__default.default.gray(` Processing: ${path__default.default.relative(repoRoot, translatedFile)}`)
930
+ );
931
+ const translatedKeys = await extractKeysFromJsonFile(translatedFile, true);
932
+ const totalKeys = Object.values(translatedKeys).reduce(
933
+ (sum, keys) => sum + Object.keys(keys).length,
934
+ 0
935
+ );
936
+ const pluginCount = Object.keys(translatedKeys).length;
937
+ console.log(
938
+ chalk__default.default.gray(
939
+ ` Extracted ${totalKeys} keys from ${pluginCount} plugin${pluginCount !== 1 ? "s" : ""}`
940
+ )
941
+ );
942
+ for (const [pluginName, pluginKeys] of Object.entries(translatedKeys)) {
943
+ if (!structuredData[pluginName]) {
944
+ structuredData[pluginName] = {};
945
+ }
946
+ for (const [key, value] of Object.entries(pluginKeys)) {
947
+ if (!structuredData[pluginName][key]) {
948
+ structuredData[pluginName][key] = value;
949
+ }
950
+ }
951
+ }
952
+ }
953
+ return structuredData;
954
+ }
955
+ async function processCorePlugins(backstageRepoPath, repoRoot, outputDir) {
956
+ console.log(
957
+ chalk__default.default.yellow(
958
+ `\u{1F4C1} Scanning Backstage plugin packages in: ${backstageRepoPath}`
959
+ )
960
+ );
961
+ const pluginRefFiles = await findBackstagePluginTranslationRefs(
962
+ backstageRepoPath
963
+ );
964
+ console.log(
965
+ chalk__default.default.gray(
966
+ `Found ${pluginRefFiles.length} Backstage plugin translation ref files`
967
+ )
968
+ );
969
+ const structuredData = {};
970
+ if (pluginRefFiles.length === 0) {
971
+ return structuredData;
972
+ }
973
+ const rhdhRepoPath = process.env.RHDH_REPO_PATH || null;
974
+ const shouldFilterByUsage = Boolean(rhdhRepoPath);
975
+ let usedPlugins = /* @__PURE__ */ new Set();
976
+ if (shouldFilterByUsage) {
977
+ console.log(
978
+ chalk__default.default.yellow(`\u{1F50D} Checking which plugins are actually used in RHDH...`)
979
+ );
980
+ const { usedPlugins: checkedUsed, unusedPlugins } = await checkPluginUsage(
981
+ pluginRefFiles,
982
+ rhdhRepoPath
983
+ );
984
+ usedPlugins = checkedUsed;
985
+ if (unusedPlugins.size > 0) {
986
+ console.log(
987
+ chalk__default.default.gray(
988
+ ` Skipping ${unusedPlugins.size} unused plugin(s): ${Array.from(
989
+ unusedPlugins
990
+ ).slice(0, 5).join(", ")}${unusedPlugins.size > 5 ? "..." : ""}`
991
+ )
992
+ );
993
+ }
994
+ console.log(
995
+ chalk__default.default.gray(` Found ${usedPlugins.size} plugin(s) actually used in RHDH`)
996
+ );
997
+ } else {
998
+ console.log(
999
+ chalk__default.default.yellow(
1000
+ `\u{1F4E6} Extracting all Backstage core plugins (RHDH_REPO_PATH not set, skipping usage filter)...`
1001
+ )
1002
+ );
1003
+ }
1004
+ const isCommunityPluginsRepo = pluginRefFiles.some(
1005
+ (file) => file.includes("workspaces/") && file.includes("/plugins/") && !file.includes("/packages/")
1006
+ );
1007
+ const communityPluginsRoot = isCommunityPluginsRepo ? backstageRepoPath : null;
1008
+ if (isCommunityPluginsRepo) {
1009
+ console.log(
1010
+ chalk__default.default.yellow(
1011
+ `\u{1F50D} Detected community-plugins repo - filtering to Red Hat owned plugins only...`
1012
+ )
1013
+ );
1014
+ }
1015
+ const refData = await extractKeysFromCorePluginRefs(
1016
+ pluginRefFiles,
1017
+ shouldFilterByUsage,
1018
+ usedPlugins,
1019
+ communityPluginsRoot
1020
+ );
1021
+ Object.assign(structuredData, refData);
1022
+ const pluginCount = Object.keys(structuredData).length;
1023
+ if (isCommunityPluginsRepo) {
1024
+ console.log(
1025
+ chalk__default.default.green(
1026
+ `\u2705 Extracted keys from ${pluginCount} Red Hat owned plugin(s) in community-plugins repo`
1027
+ )
1028
+ );
1029
+ } else {
1030
+ console.log(
1031
+ chalk__default.default.green(
1032
+ `\u2705 Extracted keys from ${usedPlugins.size} Backstage plugin packages used in RHDH`
1033
+ )
1034
+ );
1035
+ }
1036
+ const translatedFiles = await findCorePluginsTranslatedFiles(
1037
+ repoRoot,
1038
+ outputDir
1039
+ );
1040
+ if (translatedFiles.length > 0) {
1041
+ console.log(
1042
+ chalk__default.default.yellow(
1043
+ `\u{1F4C1} Scanning ${translatedFiles.length} existing core-plugins file(s) to extract translation keys...`
1044
+ )
1045
+ );
1046
+ const translatedData = await extractKeysFromTranslatedFiles(
1047
+ translatedFiles,
1048
+ repoRoot
1049
+ );
1050
+ let filteredTranslatedCount = 0;
1051
+ for (const [pluginName, pluginKeys] of Object.entries(translatedData)) {
1052
+ if (isCommunityPluginsRepo && communityPluginsRoot) {
1053
+ if (!deployTranslations.isRedHatOwnedPlugin(pluginName, communityPluginsRoot)) {
1054
+ filteredTranslatedCount++;
1055
+ continue;
1056
+ }
1057
+ }
1058
+ if (!structuredData[pluginName]) {
1059
+ structuredData[pluginName] = { en: {} };
1060
+ }
1061
+ for (const [key, value] of Object.entries(pluginKeys)) {
1062
+ if (!structuredData[pluginName].en[key]) {
1063
+ structuredData[pluginName].en[key] = value;
1064
+ }
1065
+ }
1066
+ }
1067
+ if (isCommunityPluginsRepo && filteredTranslatedCount > 0) {
1068
+ console.log(
1069
+ chalk__default.default.gray(
1070
+ ` Filtered out ${filteredTranslatedCount} non-Red Hat owned plugin(s) from translated files`
1071
+ )
1072
+ );
1073
+ }
1074
+ console.log(
1075
+ chalk__default.default.green(
1076
+ `\u2705 Extracted keys from ${translatedFiles.length} existing core-plugins translation file(s)`
1077
+ )
1078
+ );
1079
+ }
1080
+ return structuredData;
1081
+ }
1082
+ function filterRhdhPlugins(rhdhData) {
1083
+ const backstageCorePlugins = /* @__PURE__ */ new Set([
1084
+ "home",
1085
+ "catalog-graph",
1086
+ "api-docs",
1087
+ "kubernetes",
1088
+ "kubernetes-cluster",
1089
+ "techdocs",
1090
+ "home-react",
1091
+ "catalog-react",
1092
+ "org",
1093
+ "search-react",
1094
+ "kubernetes-react",
1095
+ "scaffolder-react"
1096
+ ]);
1097
+ const rhdhPlugins = {};
1098
+ for (const [pluginName, pluginData] of Object.entries(rhdhData)) {
1099
+ if (!backstageCorePlugins.has(pluginName) || pluginName === "catalog" || pluginName === "scaffolder" || pluginName === "search" || pluginName === "core-components" || pluginName === "catalog-import" || pluginName === "user-settings") {
1100
+ rhdhPlugins[pluginName] = pluginData;
1101
+ }
1102
+ }
1103
+ return rhdhPlugins;
1104
+ }
1105
+ async function processRhdhPlugins(sourceDir, includePattern, excludePattern) {
1106
+ console.log(chalk__default.default.yellow(`\u{1F4C1} Scanning ${sourceDir} for translation keys...`));
1107
+ const allSourceFiles = glob__default.default.sync(includePattern, {
1108
+ cwd: sourceDir,
1109
+ ignore: excludePattern,
1110
+ absolute: true
1111
+ });
1112
+ const sourceFiles = await findEnglishReferenceFiles(
1113
+ sourceDir,
1114
+ includePattern,
1115
+ excludePattern
1116
+ );
1117
+ console.log(
1118
+ chalk__default.default.gray(
1119
+ `Found ${allSourceFiles.length} files, ${sourceFiles.length} are English reference files`
1120
+ )
1121
+ );
1122
+ const rhdhData = await extractAndGroupKeys(sourceFiles);
1123
+ console.log(chalk__default.default.yellow(`\u{1F4C1} Scanning for JSON translation files...`));
1124
+ const jsonFiles = await findJsonTranslationFiles(sourceDir);
1125
+ console.log(chalk__default.default.gray(`Found ${jsonFiles.length} JSON translation files`));
1126
+ if (jsonFiles.length > 0) {
1127
+ for (const jsonFile of jsonFiles) {
1128
+ const jsonKeys = await extractKeysFromJsonFile(jsonFile);
1129
+ for (const [pluginName, keys] of Object.entries(jsonKeys)) {
1130
+ if (!rhdhData[pluginName]) {
1131
+ rhdhData[pluginName] = { en: {} };
1132
+ }
1133
+ for (const [key, value] of Object.entries(keys)) {
1134
+ if (!rhdhData[pluginName].en[key]) {
1135
+ rhdhData[pluginName].en[key] = value;
1136
+ }
1137
+ }
1138
+ }
1139
+ }
1140
+ console.log(
1141
+ chalk__default.default.green(
1142
+ `\u2705 Merged keys from ${jsonFiles.length} JSON translation files`
1143
+ )
1144
+ );
1145
+ }
1146
+ return filterRhdhPlugins(rhdhData);
1147
+ }
1148
+ function generateFilename(outputFilename, sprint, corePlugins, backstageRepoPath) {
1149
+ if (outputFilename) {
1150
+ return outputFilename.replace(/\.json$/, "");
1151
+ }
1152
+ if (!sprint) {
1153
+ throw new Error(
1154
+ "Sprint value is required. Please provide --sprint option (e.g., --sprint s3285)"
1155
+ );
1156
+ }
1157
+ const normalizedSprint = sprint.startsWith("s") || sprint.startsWith("S") ? sprint.toLowerCase() : `s${sprint}`;
1158
+ const repoName = corePlugins && backstageRepoPath ? detectRepoName(backstageRepoPath) : detectRepoName();
1159
+ return `${repoName.toLowerCase()}-${normalizedSprint}`;
1160
+ }
1161
+ function getOutputDirectory(corePlugins, backstageRepoPath, outputDir) {
1162
+ if (corePlugins && backstageRepoPath) {
1163
+ return path__default.default.join(backstageRepoPath, "i18n");
1164
+ }
1165
+ return String(outputDir || "i18n");
1166
+ }
1167
+ async function generateCommand(opts) {
1168
+ const corePlugins = Boolean(opts.corePlugins) || opts.corePlugins === "true";
1169
+ const outputFilename = opts.outputFilename;
1170
+ const sprint = opts.sprint;
1171
+ validateSprintOptions(outputFilename, sprint);
1172
+ if (corePlugins) {
1173
+ console.log(
1174
+ chalk__default.default.blue("\u{1F30D} Generating core-plugins translation reference file...")
1175
+ );
1176
+ } else {
1177
+ console.log(chalk__default.default.blue("\u{1F30D} Generating RHDH translation reference file..."));
1178
+ }
1179
+ const config$1 = await config.loadI18nConfig();
1180
+ const mergedOpts = await config.mergeConfigWithOptions(config$1, opts);
1181
+ const {
1182
+ sourceDir = "src",
1183
+ outputDir = "i18n",
1184
+ format = "json",
1185
+ includePattern = "**/*.{ts,tsx,js,jsx}",
1186
+ excludePattern = "**/node_modules/**",
1187
+ extractKeys = true,
1188
+ mergeExisting = false
1189
+ } = mergedOpts;
1190
+ try {
1191
+ await fs__default.default.ensureDir(outputDir);
1192
+ const translationKeys = {};
1193
+ const backstageRepoPath = getBackstageRepoPath(opts, config$1);
1194
+ if (extractKeys) {
1195
+ const repoRoot = process.cwd();
1196
+ let structuredData;
1197
+ if (corePlugins) {
1198
+ if (!backstageRepoPath) {
1199
+ console.error(
1200
+ chalk__default.default.red(
1201
+ "\u274C Backstage repository path is required for --core-plugins mode"
1202
+ )
1203
+ );
1204
+ console.error(
1205
+ chalk__default.default.yellow(
1206
+ ' Please provide one of:\n 1. --backstage-repo-path <path>\n 2. Add "backstageRepoPath" to .i18n.config.json\n 3. Set BACKSTAGE_REPO_PATH environment variable'
1207
+ )
1208
+ );
1209
+ process.exit(1);
1210
+ }
1211
+ structuredData = await processCorePlugins(
1212
+ backstageRepoPath,
1213
+ repoRoot,
1214
+ outputDir
1215
+ );
1216
+ console.log(
1217
+ chalk__default.default.gray(
1218
+ ` Including all ${Object.keys(structuredData).length} core plugins (including React versions with unique translations)`
1219
+ )
1220
+ );
1221
+ } else {
1222
+ structuredData = await processRhdhPlugins(
1223
+ sourceDir,
1224
+ includePattern,
1225
+ excludePattern
1226
+ );
1227
+ }
1228
+ const totalKeys = Object.values(structuredData).reduce(
1229
+ (sum, pluginData) => sum + Object.keys(pluginData.en || {}).length,
1230
+ 0
1231
+ );
1232
+ console.log(
1233
+ chalk__default.default.green(
1234
+ `\u2705 Extracted ${totalKeys} translation keys from ${Object.keys(structuredData).length} plugins`
1235
+ )
1236
+ );
1237
+ Object.assign(translationKeys, structuredData);
1238
+ }
1239
+ const formatStr = String(format || "json");
1240
+ const filename = generateFilename(
1241
+ outputFilename,
1242
+ sprint,
1243
+ corePlugins,
1244
+ backstageRepoPath
1245
+ );
1246
+ const finalOutputDir = getOutputDirectory(
1247
+ corePlugins,
1248
+ backstageRepoPath,
1249
+ outputDir
1250
+ );
1251
+ if (corePlugins && backstageRepoPath) {
1252
+ await fs__default.default.ensureDir(finalOutputDir);
1253
+ }
1254
+ const outputPath = path__default.default.join(finalOutputDir, `${filename}.${formatStr}`);
1255
+ await generateOrMergeFiles(
1256
+ translationKeys,
1257
+ outputPath,
1258
+ formatStr,
1259
+ mergeExisting
1260
+ );
1261
+ await validateGeneratedFile(outputPath, formatStr);
1262
+ if (extractKeys && isNestedStructure(translationKeys)) {
1263
+ displaySummary(
1264
+ translationKeys
1265
+ );
1266
+ }
1267
+ console.log(
1268
+ chalk__default.default.green(`\u2705 Translation reference files generated successfully!`)
1269
+ );
1270
+ console.log(chalk__default.default.gray(` Output: ${outputPath}`));
1271
+ if (extractKeys && isNestedStructure(translationKeys)) {
1272
+ const totalKeys = Object.values(
1273
+ translationKeys
1274
+ ).reduce(
1275
+ (sum, pluginData) => sum + Object.keys(pluginData.en || {}).length,
1276
+ 0
1277
+ );
1278
+ console.log(
1279
+ chalk__default.default.gray(` Plugins: ${Object.keys(translationKeys).length}`)
1280
+ );
1281
+ console.log(chalk__default.default.gray(` Keys: ${totalKeys}`));
1282
+ } else {
1283
+ console.log(
1284
+ chalk__default.default.gray(` Keys: ${Object.keys(translationKeys).length}`)
1285
+ );
1286
+ }
1287
+ } catch (error) {
1288
+ console.error(chalk__default.default.red("\u274C Error generating translation files:"), error);
1289
+ throw error;
1290
+ }
1291
+ }
1292
+
1293
+ exports.generateCommand = generateCommand;
1294
+ //# sourceMappingURL=generate.cjs.js.map