@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,146 @@
1
+ 'use strict';
2
+
3
+ var errors = require('../lib/errors.cjs.js');
4
+ var generate = require('./generate.cjs.js');
5
+ var upload = require('./upload.cjs.js');
6
+ var download = require('./download.cjs.js');
7
+ var deploy = require('./deploy.cjs.js');
8
+ var status = require('./status.cjs.js');
9
+ var clean = require('./clean.cjs.js');
10
+ var sync = require('./sync.cjs.js');
11
+ var init = require('./init.cjs.js');
12
+ var setupMemsource = require('./setupMemsource.cjs.js');
13
+ var list = require('./list.cjs.js');
14
+
15
+ function registerCommands(program) {
16
+ const command = program.command("i18n [command]").description(
17
+ "Internationalization (i18n) management commands for translation workflows"
18
+ );
19
+ command.command("generate").description("Generate translation reference files from source code").requiredOption(
20
+ "--sprint <sprint>",
21
+ "Sprint value for filename (e.g., s3285). Format: <repo-name>-reference-<sprint>.json"
22
+ ).option(
23
+ "--source-dir <path>",
24
+ "Source directory to scan for translatable strings",
25
+ "src"
26
+ ).option(
27
+ "--output-dir <path>",
28
+ "Output directory for generated translation files",
29
+ "i18n"
30
+ ).option("--format <format>", "Output format (json, po)", "json").option(
31
+ "--include-pattern <pattern>",
32
+ "File pattern to include (glob)",
33
+ "**/*.{ts,tsx,js,jsx}"
34
+ ).option(
35
+ "--exclude-pattern <pattern>",
36
+ "File pattern to exclude (glob)",
37
+ "**/node_modules/**"
38
+ ).option("--extract-keys", "Extract translation keys from source code", true).option("--merge-existing", "Merge with existing translation files", false).option(
39
+ "--core-plugins",
40
+ "Generate core-plugins reference (Backstage plugins only) instead of RHDH-specific reference"
41
+ ).option(
42
+ "--output-filename <name>",
43
+ "Custom output filename (overrides sprint-based naming)"
44
+ ).option(
45
+ "--backstage-repo-path <path>",
46
+ "Path to Backstage repository root (for core-plugins mode). Defaults to checking BACKSTAGE_REPO_PATH env var or config"
47
+ ).action(wrapCommand(generate.generateCommand));
48
+ command.command("upload").description(
49
+ "Upload translation reference files to TMS (Translation Management System)"
50
+ ).option("--tms-url <url>", "TMS API URL").option("--tms-token <token>", "TMS API token").option("--project-id <id>", "TMS project ID").option("--source-file <path>", "Source translation file to upload").option(
51
+ "--upload-filename <name>",
52
+ "Custom filename for TMS upload (default: {repo-name}-{sprint}.json, extracts sprint from source filename)"
53
+ ).option(
54
+ "--target-languages <languages>",
55
+ "Comma-separated list of target languages"
56
+ ).option(
57
+ "--dry-run",
58
+ "Show what would be uploaded without actually uploading",
59
+ false
60
+ ).option("--force", "Force upload even if file has not changed", false).action(wrapCommand(upload.uploadCommand));
61
+ command.command("list").description("List available translation jobs from TMS").option("--project-id <id>", "TMS project ID").option("--languages <languages>", 'Filter by languages (e.g., "it,ja,fr")').option(
62
+ "--status <status>",
63
+ 'Filter by status (e.g., "COMPLETED", "ASSIGNED", "NEW")'
64
+ ).option("--format <format>", "Output format (table, json)", "table").action(wrapCommand(list.listCommand));
65
+ command.command("download").description("Download translated strings from TMS using Memsource CLI").option("--project-id <id>", "TMS project ID").option(
66
+ "--output-dir <path>",
67
+ "Output directory for downloaded translations",
68
+ "i18n/downloads"
69
+ ).option(
70
+ "--languages <languages>",
71
+ 'Comma-separated list of languages to download (e.g., "it,ja,fr")'
72
+ ).option(
73
+ "--job-ids <ids>",
74
+ 'Comma-separated list of specific job UIDs to download (use "i18n list" to see UIDs)'
75
+ ).option(
76
+ "--status <status>",
77
+ 'Filter by status (default: "COMPLETED"). Use "ALL" to download all statuses, or specific status like "ASSIGNED"',
78
+ "COMPLETED"
79
+ ).option(
80
+ "--include-incomplete",
81
+ "Include incomplete jobs (same as --status ALL)",
82
+ false
83
+ ).action(wrapCommand(download.downloadCommand));
84
+ command.command("deploy").description(
85
+ "Deploy downloaded translations to TypeScript translation files (it.ts, ja.ts, etc.)"
86
+ ).option(
87
+ "--source-dir <path>",
88
+ "Source directory containing downloaded translations (from Memsource)",
89
+ "i18n/downloads"
90
+ ).action(wrapCommand(deploy.deployCommand));
91
+ command.command("status").description("Show translation status and statistics").option("--source-dir <path>", "Source directory to analyze", "src").option("--i18n-dir <path>", "i18n directory to analyze", "i18n").option(
92
+ "--locales-dir <path>",
93
+ "Locales directory to analyze",
94
+ "src/locales"
95
+ ).option("--format <format>", "Output format (table, json)", "table").option("--include-stats", "Include detailed statistics", true).action(wrapCommand(status.statusCommand));
96
+ command.command("clean").description("Clean up temporary i18n files and caches").option("--i18n-dir <path>", "i18n directory to clean", "i18n").option("--cache-dir <path>", "Cache directory to clean", ".i18n-cache").option("--backup-dir <path>", "Backup directory to clean", ".i18n-backup").option("--force", "Force cleanup without confirmation", false).action(wrapCommand(clean.cleanCommand));
97
+ command.command("sync").description(
98
+ "Complete i18n workflow: generate \u2192 upload \u2192 download \u2192 deploy"
99
+ ).requiredOption(
100
+ "--sprint <sprint>",
101
+ "Sprint value for filename (e.g., s3285). Required for generate step."
102
+ ).option("--source-dir <path>", "Source directory to scan", "src").option("--output-dir <path>", "Output directory for i18n files", "i18n").option("--locales-dir <path>", "Target locales directory", "src/locales").option("--tms-url <url>", "TMS API URL").option("--tms-token <token>", "TMS API token").option("--project-id <id>", "TMS project ID").option(
103
+ "--languages <languages>",
104
+ "Comma-separated list of target languages"
105
+ ).option("--skip-upload", "Skip upload step", false).option("--skip-download", "Skip download step", false).option("--skip-deploy", "Skip deploy step", false).option("--dry-run", "Show what would be done without executing", false).action(wrapCommand(sync.syncCommand));
106
+ command.command("init").description("Initialize i18n configuration files").option(
107
+ "--setup-memsource",
108
+ "Also set up .memsourcerc file for Memsource CLI",
109
+ false
110
+ ).option(
111
+ "--memsource-venv <path>",
112
+ "Path to Memsource CLI virtual environment (will auto-detect or prompt if not provided)"
113
+ ).action(wrapCommand(init.initCommand));
114
+ command.command("setup-memsource").description(
115
+ "Set up .memsourcerc file for Memsource CLI (follows localization team instructions)"
116
+ ).option(
117
+ "--memsource-venv <path>",
118
+ "Path to Memsource CLI virtual environment (will auto-detect or prompt if not provided)"
119
+ ).option(
120
+ "--memsource-url <url>",
121
+ "Memsource URL",
122
+ "https://cloud.memsource.com/web"
123
+ ).option(
124
+ "--username <username>",
125
+ "Memsource username (will prompt if not provided and in interactive terminal)"
126
+ ).option(
127
+ "--password <password>",
128
+ "Memsource password (will prompt if not provided and in interactive terminal)"
129
+ ).option(
130
+ "--no-input",
131
+ "Disable interactive prompts (for automation/scripts)"
132
+ ).action(wrapCommand(setupMemsource.setupMemsourceCommand));
133
+ }
134
+ function wrapCommand(actionFunc) {
135
+ return async (opts) => {
136
+ try {
137
+ await actionFunc(opts);
138
+ process.exit(0);
139
+ } catch (error) {
140
+ errors.exitWithError(error);
141
+ }
142
+ };
143
+ }
144
+
145
+ exports.registerCommands = registerCommands;
146
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+
3
+ var os = require('node:os');
4
+ var path = require('node:path');
5
+ var chalk = require('chalk');
6
+ var fs = require('fs-extra');
7
+ var config = require('../lib/i18n/config.cjs.js');
8
+ var setupMemsource = require('./setupMemsource.cjs.js');
9
+
10
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
11
+
12
+ var os__default = /*#__PURE__*/_interopDefaultCompat(os);
13
+ var path__default = /*#__PURE__*/_interopDefaultCompat(path);
14
+ var chalk__default = /*#__PURE__*/_interopDefaultCompat(chalk);
15
+ var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
16
+
17
+ async function initCommand(opts) {
18
+ console.log(chalk__default.default.blue("\u{1F527} Initializing i18n configuration..."));
19
+ try {
20
+ await config.createDefaultConfigFile();
21
+ const memsourceRcPath = path__default.default.join(os__default.default.homedir(), ".memsourcerc");
22
+ const hasMemsourceRc = await fs__default.default.pathExists(memsourceRcPath);
23
+ if (!hasMemsourceRc) {
24
+ await config.createDefaultAuthFile();
25
+ }
26
+ console.log(chalk__default.default.green("\n\u2705 Configuration files created successfully!"));
27
+ console.log(chalk__default.default.yellow("\n\u{1F4DD} Next steps:"));
28
+ console.log("");
29
+ console.log(
30
+ chalk__default.default.cyan(" 1. Edit .i18n.config.json in your project root:")
31
+ );
32
+ console.log(
33
+ chalk__default.default.gray(
34
+ ' - Add your TMS URL (e.g., "https://cloud.memsource.com/web")'
35
+ )
36
+ );
37
+ console.log(chalk__default.default.gray(" - Add your TMS Project ID"));
38
+ console.log(
39
+ chalk__default.default.gray(
40
+ " - Adjust directories, languages, and patterns as needed"
41
+ )
42
+ );
43
+ console.log("");
44
+ if (hasMemsourceRc) {
45
+ console.log(
46
+ chalk__default.default.green(
47
+ " \u2713 ~/.memsourcerc found - authentication is already configured!"
48
+ )
49
+ );
50
+ console.log(
51
+ chalk__default.default.gray(" Make sure to source it before running commands:")
52
+ );
53
+ console.log(chalk__default.default.gray(" source ~/.memsourcerc"));
54
+ console.log("");
55
+ } else {
56
+ console.log(
57
+ chalk__default.default.cyan(" 2. Set up Memsource authentication (recommended):")
58
+ );
59
+ console.log(
60
+ chalk__default.default.gray(" Run: translations-cli i18n setup-memsource")
61
+ );
62
+ console.log(
63
+ chalk__default.default.gray(
64
+ " This creates ~/.memsourcerc following the localization team's format"
65
+ )
66
+ );
67
+ console.log(chalk__default.default.gray(" Then source it: source ~/.memsourcerc"));
68
+ console.log("");
69
+ console.log(chalk__default.default.cyan(" OR use ~/.i18n.auth.json (fallback):"));
70
+ console.log(chalk__default.default.gray(" - Add your TMS username and password"));
71
+ console.log(
72
+ chalk__default.default.gray(
73
+ " - Token can be left empty (will be generated or read from environment)"
74
+ )
75
+ );
76
+ console.log("");
77
+ }
78
+ console.log(chalk__default.default.cyan(" 3. Security reminder:"));
79
+ console.log(
80
+ chalk__default.default.gray(
81
+ " - Never commit ~/.i18n.auth.json or ~/.memsourcerc to git"
82
+ )
83
+ );
84
+ console.log(
85
+ chalk__default.default.gray(" - Add them to your global .gitignore if needed")
86
+ );
87
+ console.log("");
88
+ console.log(
89
+ chalk__default.default.blue("\u{1F4A1} For detailed instructions, see: docs/i18n-commands.md")
90
+ );
91
+ if (opts.setupMemsource) {
92
+ console.log(chalk__default.default.blue("\n\u{1F527} Setting up .memsourcerc file..."));
93
+ await setupMemsource.setupMemsourceCommand({
94
+ memsourceVenv: opts.memsourceVenv
95
+ });
96
+ } else if (!hasMemsourceRc) {
97
+ console.log(
98
+ chalk__default.default.yellow(
99
+ '\n\u{1F4A1} Tip: Run "translations-cli i18n setup-memsource" to set up .memsourcerc file'
100
+ )
101
+ );
102
+ console.log(
103
+ chalk__default.default.gray(
104
+ " This follows the localization team's instructions format."
105
+ )
106
+ );
107
+ }
108
+ } catch (error) {
109
+ console.error(chalk__default.default.red("\u274C Error creating config files:"), error);
110
+ throw error;
111
+ }
112
+ }
113
+
114
+ exports.initCommand = initCommand;
115
+ //# sourceMappingURL=init.cjs.js.map
@@ -0,0 +1,178 @@
1
+ 'use strict';
2
+
3
+ var chalk = require('chalk');
4
+ var config = require('../lib/i18n/config.cjs.js');
5
+ var exec = require('../lib/utils/exec.cjs.js');
6
+
7
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
+
9
+ var chalk__default = /*#__PURE__*/_interopDefaultCompat(chalk);
10
+
11
+ function buildListJobsArgs(projectId) {
12
+ return ["job", "list", "--project-id", projectId, "--format", "json"];
13
+ }
14
+ function listAllJobs(projectId) {
15
+ const listArgs = buildListJobsArgs(projectId);
16
+ const listOutput = exec.safeExecSyncOrThrow("memsource", listArgs, {
17
+ encoding: "utf-8",
18
+ env: { ...process.env }
19
+ });
20
+ const jobs = JSON.parse(listOutput);
21
+ return Array.isArray(jobs) ? jobs : [jobs];
22
+ }
23
+ function validateMemsourcePrerequisites() {
24
+ if (!exec.commandExists("memsource")) {
25
+ throw new Error(
26
+ "memsource CLI not found. Please ensure memsource-cli is installed and ~/.memsourcerc is sourced."
27
+ );
28
+ }
29
+ if (!process.env.MEMSOURCE_TOKEN) {
30
+ throw new Error(
31
+ "MEMSOURCE_TOKEN not found. Please source ~/.memsourcerc first: source ~/.memsourcerc"
32
+ );
33
+ }
34
+ }
35
+ function formatStatus(status) {
36
+ const statusMap = {
37
+ COMPLETED: chalk__default.default.green("\u2713 COMPLETED"),
38
+ ASSIGNED: chalk__default.default.yellow("\u25CB ASSIGNED"),
39
+ NEW: chalk__default.default.blue("\u25CB NEW"),
40
+ DECLINED: chalk__default.default.red("\u2717 DECLINED"),
41
+ CANCELLED: chalk__default.default.gray("\u2717 CANCELLED")
42
+ };
43
+ return statusMap[status] || status;
44
+ }
45
+ function displayJobsTable(jobs, languages, statusFilter) {
46
+ let filteredJobs = jobs;
47
+ if (languages && languages.length > 0) {
48
+ const languageSet = new Set(languages);
49
+ filteredJobs = filteredJobs.filter(
50
+ (job) => languageSet.has(job.target_lang)
51
+ );
52
+ }
53
+ if (statusFilter) {
54
+ filteredJobs = filteredJobs.filter(
55
+ (job) => job.status === statusFilter
56
+ );
57
+ }
58
+ if (filteredJobs.length === 0) {
59
+ console.log(chalk__default.default.yellow("No jobs found matching the criteria."));
60
+ return;
61
+ }
62
+ const jobsByFile = /* @__PURE__ */ new Map();
63
+ for (const job of filteredJobs) {
64
+ const filename = job.filename || "unknown";
65
+ if (!jobsByFile.has(filename)) {
66
+ jobsByFile.set(filename, []);
67
+ }
68
+ jobsByFile.get(filename).push(job);
69
+ }
70
+ console.log(chalk__default.default.blue("\n\u{1F4CB} Available Translation Jobs\n"));
71
+ console.log(
72
+ chalk__default.default.gray(
73
+ "Note: Display IDs (1, 2, 3...) shown in TMS UI are sequential and may not match the order here.\n"
74
+ )
75
+ );
76
+ console.log(
77
+ chalk__default.default.bold(
78
+ `${"Filename".padEnd(50)} ${"Language".padEnd(8)} ${"Status".padEnd(
79
+ 15
80
+ )} ${"Real UID (for download)".padEnd(30)}`
81
+ )
82
+ );
83
+ console.log(chalk__default.default.gray("-".repeat(120)));
84
+ for (const [filename, fileJobs] of Array.from(jobsByFile.entries()).sort(
85
+ (a, b) => a[0].localeCompare(b[0])
86
+ )) {
87
+ const sortedJobs = fileJobs.sort(
88
+ (a, b) => (a.target_lang || "").localeCompare(b.target_lang || "")
89
+ );
90
+ for (const job of sortedJobs) {
91
+ const status = formatStatus(job.status || "UNKNOWN");
92
+ const lang = (job.target_lang || "unknown").padEnd(8);
93
+ const uid = (job.uid || "unknown").padEnd(30);
94
+ const fileDisplay = filename.length > 48 ? `${filename.slice(0, 45)}...` : filename;
95
+ console.log(
96
+ `${fileDisplay.padEnd(50)} ${lang} ${status.padEnd(15)} ${chalk__default.default.cyan(
97
+ uid
98
+ )}`
99
+ );
100
+ }
101
+ }
102
+ console.log(chalk__default.default.gray("-".repeat(120)));
103
+ console.log(
104
+ chalk__default.default.gray(
105
+ `
106
+ Total: ${filteredJobs.length} job(s) | Use --languages or --status to filter`
107
+ )
108
+ );
109
+ console.log(
110
+ chalk__default.default.yellow(
111
+ "\n\u{1F4A1} Tip: Use the Real UID (not display ID) with --job-ids to download specific jobs."
112
+ )
113
+ );
114
+ console.log(
115
+ chalk__default.default.yellow(
116
+ ' Or use --languages "it,ja" to download all jobs for specific languages.'
117
+ )
118
+ );
119
+ }
120
+ function displayJobsJson(jobs, languages, statusFilter) {
121
+ let filteredJobs = jobs;
122
+ if (languages && languages.length > 0) {
123
+ const languageSet = new Set(languages);
124
+ filteredJobs = filteredJobs.filter(
125
+ (job) => languageSet.has(job.target_lang)
126
+ );
127
+ }
128
+ if (statusFilter) {
129
+ filteredJobs = filteredJobs.filter(
130
+ (job) => job.status === statusFilter
131
+ );
132
+ }
133
+ console.log(JSON.stringify(filteredJobs, null, 2));
134
+ }
135
+ async function listCommand(opts) {
136
+ console.log(chalk__default.default.blue("\u{1F4CB} Listing translation jobs from TMS..."));
137
+ const config$1 = await config.loadI18nConfig();
138
+ const mergedOpts = await config.mergeConfigWithOptions(config$1, opts);
139
+ const {
140
+ projectId,
141
+ languages,
142
+ status,
143
+ format = "table"
144
+ } = mergedOpts;
145
+ if (!projectId) {
146
+ console.error(chalk__default.default.red("\u274C Missing required TMS configuration:"));
147
+ console.error("");
148
+ console.error(chalk__default.default.yellow(" \u2717 Project ID"));
149
+ console.error(
150
+ chalk__default.default.gray(
151
+ " Set via: --project-id <id> or I18N_TMS_PROJECT_ID or .i18n.config.json"
152
+ )
153
+ );
154
+ process.exit(1);
155
+ }
156
+ if (!process.env.MEMSOURCE_TOKEN) {
157
+ console.error(chalk__default.default.red("\u274C MEMSOURCE_TOKEN not found"));
158
+ console.error(chalk__default.default.yellow(" Please source ~/.memsourcerc first:"));
159
+ console.error(chalk__default.default.gray(" source ~/.memsourcerc"));
160
+ process.exit(1);
161
+ }
162
+ try {
163
+ validateMemsourcePrerequisites();
164
+ const languageArray = languages && typeof languages === "string" ? languages.split(",").map((lang) => lang.trim()) : void 0;
165
+ const jobs = listAllJobs(projectId);
166
+ if (format === "json") {
167
+ displayJobsJson(jobs, languageArray, status);
168
+ } else {
169
+ displayJobsTable(jobs, languageArray, status);
170
+ }
171
+ } catch (error) {
172
+ console.error(chalk__default.default.red("\u274C Error listing jobs:"), error.message);
173
+ throw error;
174
+ }
175
+ }
176
+
177
+ exports.listCommand = listCommand;
178
+ //# sourceMappingURL=list.cjs.js.map