gtx-cli 2.4.0 → 2.4.2
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/CHANGELOG.md +15 -0
- package/dist/api/checkFileTranslations.js +32 -12
- package/dist/api/downloadFileBatch.js +14 -1
- package/dist/cli/commands/translate.js +2 -2
- package/dist/config/generateSettings.d.ts +2 -2
- package/dist/config/generateSettings.js +27 -28
- package/dist/formats/files/translate.js +1 -1
- package/dist/formats/yaml/mergeYaml.d.ts +1 -1
- package/dist/formats/yaml/mergeYaml.js +6 -1
- package/dist/types/index.d.ts +2 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# gtx-cli
|
|
2
2
|
|
|
3
|
+
## 2.4.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#753](https://github.com/generaltranslation/gt/pull/753) [`bd0bc26`](https://github.com/generaltranslation/gt/commit/bd0bc265192d5b51618a537a92122cd6eeae6e4d) Thanks [@ErnestM1234](https://github.com/ErnestM1234)! - fix: avoid downloading files when using --publish flag
|
|
8
|
+
|
|
9
|
+
## 2.4.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#751](https://github.com/generaltranslation/gt/pull/751) [`7114780`](https://github.com/generaltranslation/gt/commit/71147803bf3e4cf21556ffb9b5f77756e283a32a) Thanks [@SamEggert](https://github.com/SamEggert)! - transform for yaml files -- retrieve file format in downloadFileBatch
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [[`7114780`](https://github.com/generaltranslation/gt/commit/71147803bf3e4cf21556ffb9b5f77756e283a32a)]:
|
|
16
|
+
- generaltranslation@7.7.1
|
|
17
|
+
|
|
3
18
|
## 2.4.0
|
|
4
19
|
|
|
5
20
|
### Minor Changes
|
|
@@ -103,7 +103,9 @@ function prepareFileQueryData(data, locales) {
|
|
|
103
103
|
*/
|
|
104
104
|
function generateStatusSuffixText(downloadStatus, fileQueryData) {
|
|
105
105
|
// Simple progress indicator
|
|
106
|
-
const progressText = chalk.green(`[${downloadStatus.downloaded.size +
|
|
106
|
+
const progressText = chalk.green(`[${downloadStatus.downloaded.size +
|
|
107
|
+
downloadStatus.failed.size +
|
|
108
|
+
downloadStatus.skipped.size}/${fileQueryData.length}]`) + ` translations completed`;
|
|
107
109
|
// Get terminal height to adapt our output
|
|
108
110
|
const terminalHeight = process.stdout.rows || 24; // Default to 24 if undefined
|
|
109
111
|
// If terminal is very small, just show the basic progress
|
|
@@ -120,6 +122,7 @@ function generateStatusSuffixText(downloadStatus, fileQueryData) {
|
|
|
120
122
|
completed: new Set(),
|
|
121
123
|
pending: new Set([item.locale]),
|
|
122
124
|
failed: new Set(),
|
|
125
|
+
skipped: new Set(),
|
|
123
126
|
});
|
|
124
127
|
}
|
|
125
128
|
else {
|
|
@@ -143,6 +146,14 @@ function generateStatusSuffixText(downloadStatus, fileQueryData) {
|
|
|
143
146
|
status.failed.add(locale);
|
|
144
147
|
}
|
|
145
148
|
}
|
|
149
|
+
for (const fileLocale of downloadStatus.skipped) {
|
|
150
|
+
const [fileName, locale] = fileLocale.split(':');
|
|
151
|
+
const status = fileStatus.get(fileName);
|
|
152
|
+
if (status) {
|
|
153
|
+
status.pending.delete(locale);
|
|
154
|
+
status.skipped.add(locale);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
146
157
|
// Calculate how many files we can show based on terminal height
|
|
147
158
|
const filesArray = Array.from(fileStatus.entries());
|
|
148
159
|
const maxFilesToShow = Math.min(filesArray.length, terminalHeight - 3 // Header + progress + buffer
|
|
@@ -159,6 +170,13 @@ function generateStatusSuffixText(downloadStatus, fileQueryData) {
|
|
|
159
170
|
.join(', ');
|
|
160
171
|
localeStatuses.push(chalk.green(`${completedCodes}`));
|
|
161
172
|
}
|
|
173
|
+
// Add (translated but not downloaded) skipped locales
|
|
174
|
+
if (status.skipped.size > 0) {
|
|
175
|
+
const skippedCodes = Array.from(status.skipped)
|
|
176
|
+
.map((locale) => getLocaleProperties(locale).code)
|
|
177
|
+
.join(', ');
|
|
178
|
+
localeStatuses.push(chalk.green(`${skippedCodes}`));
|
|
179
|
+
}
|
|
162
180
|
// Add failed locales
|
|
163
181
|
if (status.failed.size > 0) {
|
|
164
182
|
const failedCodes = Array.from(status.failed)
|
|
@@ -230,17 +248,19 @@ async function checkTranslationDeployment(fileQueryData, downloadStatus, spinner
|
|
|
230
248
|
};
|
|
231
249
|
})
|
|
232
250
|
.filter((file) => file !== null);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
251
|
+
if (batchFiles.length > 0) {
|
|
252
|
+
const batchResult = await downloadFileBatch(batchFiles, options, 3, 1000, Boolean(forceDownload));
|
|
253
|
+
// Process results
|
|
254
|
+
batchFiles.forEach((file) => {
|
|
255
|
+
const { translationId, fileLocale } = file;
|
|
256
|
+
if (batchResult.successful.includes(translationId)) {
|
|
257
|
+
downloadStatus.downloaded.add(fileLocale);
|
|
258
|
+
}
|
|
259
|
+
else if (batchResult.failed.includes(translationId)) {
|
|
260
|
+
downloadStatus.failed.add(fileLocale);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
244
264
|
}
|
|
245
265
|
// Force a refresh of the spinner display
|
|
246
266
|
const statusText = generateStatusSuffixText(downloadStatus, fileQueryData);
|
|
@@ -8,6 +8,7 @@ import { mergeJson } from '../formats/json/mergeJson.js';
|
|
|
8
8
|
import mergeYaml from '../formats/yaml/mergeYaml.js';
|
|
9
9
|
import { getDownloadedVersions, saveDownloadedVersions, } from '../fs/config/downloadedVersions.js';
|
|
10
10
|
import { recordDownloaded } from '../state/recentDownloads.js';
|
|
11
|
+
import stringify from 'fast-json-stable-stringify';
|
|
11
12
|
/**
|
|
12
13
|
* Downloads multiple translation files in a single batch request
|
|
13
14
|
* @param files - Array of files to download with their output paths
|
|
@@ -92,10 +93,22 @@ export async function downloadFileBatch(files, options, maxRetries = 3, retryDel
|
|
|
92
93
|
translatedContent: file.data,
|
|
93
94
|
targetLocale: locale,
|
|
94
95
|
},
|
|
95
|
-
])[0];
|
|
96
|
+
], options.defaultLocale)[0];
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
}
|
|
100
|
+
// If the file is a GTJSON file, stable sort the order and format the data
|
|
101
|
+
if (file.fileFormat === 'GTJSON') {
|
|
102
|
+
try {
|
|
103
|
+
const jsonData = JSON.parse(data);
|
|
104
|
+
const sortedData = stringify(jsonData); // stably sort with fast-json-stable-stringify
|
|
105
|
+
const sortedJsonData = JSON.parse(sortedData);
|
|
106
|
+
data = JSON.stringify(sortedJsonData, null, 2); // format the data
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
logWarning(`Failed to sort GTJson file: ${file.id}: ` + error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
99
112
|
// Write the file to disk
|
|
100
113
|
await fs.promises.writeFile(outputPath, data);
|
|
101
114
|
// Track as downloaded
|
|
@@ -10,12 +10,12 @@ import { noFilesError, noVersionIdError } from '../../console/index.js';
|
|
|
10
10
|
import localizeStaticImports from '../../utils/localizeStaticImports.js';
|
|
11
11
|
// Downloads translations that were completed
|
|
12
12
|
export async function handleTranslate(options, settings, filesTranslationResponse) {
|
|
13
|
-
if (filesTranslationResponse
|
|
13
|
+
if (filesTranslationResponse) {
|
|
14
14
|
const { resolvedPaths, placeholderPaths, transformPaths } = settings.files;
|
|
15
15
|
const fileMapping = createFileMapping(resolvedPaths, placeholderPaths, transformPaths, settings.locales, settings.defaultLocale);
|
|
16
16
|
const { data } = filesTranslationResponse;
|
|
17
17
|
// Check for remaining translations
|
|
18
|
-
await checkFileTranslations(data, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale][sourcePath] ?? null, settings, options.force, options.forceDownload);
|
|
18
|
+
await checkFileTranslations(data, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale]?.[sourcePath] ?? null, settings, options.force, options.forceDownload);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
// Downloads translations that were originally staged
|
|
@@ -2,8 +2,8 @@ import { Settings } from '../types/index.js';
|
|
|
2
2
|
export declare const DEFAULT_SRC_PATTERNS: string[];
|
|
3
3
|
/**
|
|
4
4
|
* Generates settings from any
|
|
5
|
-
* @param
|
|
5
|
+
* @param flags - The CLI flags to generate settings from
|
|
6
6
|
* @param cwd - The current working directory
|
|
7
7
|
* @returns The generated settings
|
|
8
8
|
*/
|
|
9
|
-
export declare function generateSettings(
|
|
9
|
+
export declare function generateSettings(flags: Record<string, any>, cwd?: string): Promise<Settings>;
|
|
@@ -20,24 +20,24 @@ export const DEFAULT_SRC_PATTERNS = [
|
|
|
20
20
|
];
|
|
21
21
|
/**
|
|
22
22
|
* Generates settings from any
|
|
23
|
-
* @param
|
|
23
|
+
* @param flags - The CLI flags to generate settings from
|
|
24
24
|
* @param cwd - The current working directory
|
|
25
25
|
* @returns The generated settings
|
|
26
26
|
*/
|
|
27
|
-
export async function generateSettings(
|
|
27
|
+
export async function generateSettings(flags, cwd = process.cwd()) {
|
|
28
28
|
// Load config file
|
|
29
29
|
let gtConfig = {};
|
|
30
|
-
if (
|
|
31
|
-
|
|
30
|
+
if (flags.config && !flags.config.endsWith('.json')) {
|
|
31
|
+
flags.config = `${flags.config}.json`;
|
|
32
32
|
}
|
|
33
|
-
if (
|
|
34
|
-
gtConfig = loadConfig(
|
|
33
|
+
if (flags.config) {
|
|
34
|
+
gtConfig = loadConfig(flags.config);
|
|
35
35
|
}
|
|
36
36
|
else {
|
|
37
37
|
const config = resolveConfig(cwd);
|
|
38
38
|
if (config) {
|
|
39
39
|
gtConfig = config.config;
|
|
40
|
-
|
|
40
|
+
flags.config = config.path;
|
|
41
41
|
}
|
|
42
42
|
else {
|
|
43
43
|
gtConfig = {};
|
|
@@ -45,43 +45,43 @@ export async function generateSettings(options, cwd = process.cwd()) {
|
|
|
45
45
|
}
|
|
46
46
|
// Warn if apiKey is present in gt.config.json
|
|
47
47
|
if (gtConfig.apiKey) {
|
|
48
|
-
warnApiKeyInConfig(
|
|
48
|
+
warnApiKeyInConfig(flags.config);
|
|
49
49
|
process.exit(1);
|
|
50
50
|
}
|
|
51
51
|
const projectIdEnv = resolveProjectId();
|
|
52
52
|
// Resolve mismatched projectIds
|
|
53
53
|
if (gtConfig.projectId &&
|
|
54
|
-
|
|
55
|
-
gtConfig.projectId !==
|
|
56
|
-
logErrorAndExit(`Project ID mismatch between ${chalk.green(gtConfig.projectId)} and ${chalk.green(
|
|
54
|
+
flags.projectId &&
|
|
55
|
+
gtConfig.projectId !== flags.projectId) {
|
|
56
|
+
logErrorAndExit(`Project ID mismatch between ${chalk.green(gtConfig.projectId)} and ${chalk.green(flags.projectId)}! Please use the same projectId in all configs.`);
|
|
57
57
|
}
|
|
58
58
|
else if (gtConfig.projectId &&
|
|
59
59
|
projectIdEnv &&
|
|
60
60
|
gtConfig.projectId !== projectIdEnv) {
|
|
61
61
|
logErrorAndExit(`Project ID mismatch between ${chalk.green(gtConfig.projectId)} and ${chalk.green(projectIdEnv)}! Please use the same projectId in all configs.`);
|
|
62
62
|
}
|
|
63
|
-
if (
|
|
64
|
-
!
|
|
63
|
+
if (flags.options?.docsUrlPattern &&
|
|
64
|
+
!flags.options?.docsUrlPattern.includes('[locale]')) {
|
|
65
65
|
logErrorAndExit('Failed to localize static urls: URL pattern must include "[locale]" to denote the location of the locale');
|
|
66
66
|
}
|
|
67
|
-
if (
|
|
68
|
-
!
|
|
67
|
+
if (flags.options?.docsImportPattern &&
|
|
68
|
+
!flags.options?.docsImportPattern.includes('[locale]')) {
|
|
69
69
|
logErrorAndExit('Failed to localize static imports: Import pattern must include "[locale]" to denote the location of the locale');
|
|
70
70
|
}
|
|
71
|
-
if (
|
|
72
|
-
for (const file of
|
|
71
|
+
if (flags.options?.copyFiles) {
|
|
72
|
+
for (const file of flags.options.copyFiles) {
|
|
73
73
|
if (!file.includes('[locale]')) {
|
|
74
74
|
logErrorAndExit('Failed to copy files: File path must include "[locale]" to denote the location of the locale');
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
// merge options
|
|
79
|
-
const mergedOptions = { ...gtConfig, ...
|
|
79
|
+
const mergedOptions = { ...gtConfig, ...flags };
|
|
80
80
|
// Add defaultLocale if not provided
|
|
81
81
|
mergedOptions.defaultLocale =
|
|
82
82
|
mergedOptions.defaultLocale || libraryDefaultLocale;
|
|
83
83
|
// merge locales
|
|
84
|
-
mergedOptions.locales = Array.from(new Set([...(gtConfig.locales || []), ...(
|
|
84
|
+
mergedOptions.locales = Array.from(new Set([...(gtConfig.locales || []), ...(flags.locales || [])]));
|
|
85
85
|
// Separate defaultLocale from locales
|
|
86
86
|
mergedOptions.locales = mergedOptions.locales.filter((locale) => locale !== mergedOptions.defaultLocale);
|
|
87
87
|
// Add apiKey if not provided
|
|
@@ -104,7 +104,7 @@ export async function generateSettings(options, cwd = process.cwd()) {
|
|
|
104
104
|
// For human review, always stage the project
|
|
105
105
|
mergedOptions.stageTranslations = mergedOptions.stageTranslations ?? false;
|
|
106
106
|
// Add publish if not provided
|
|
107
|
-
mergedOptions.publish = (gtConfig.publish ||
|
|
107
|
+
mergedOptions.publish = (gtConfig.publish || flags.publish) ?? false;
|
|
108
108
|
// Populate src if not provided
|
|
109
109
|
mergedOptions.src = mergedOptions.src || DEFAULT_SRC_PATTERNS;
|
|
110
110
|
// Resolve all glob patterns in the files object
|
|
@@ -115,21 +115,20 @@ export async function generateSettings(options, cwd = process.cwd()) {
|
|
|
115
115
|
.map(([key]) => key);
|
|
116
116
|
mergedOptions.files = mergedOptions.files
|
|
117
117
|
? resolveFiles(mergedOptions.files, mergedOptions.defaultLocale, mergedOptions.locales, cwd, compositePatterns)
|
|
118
|
-
:
|
|
118
|
+
: { resolvedPaths: {}, placeholderPaths: {}, transformPaths: {} };
|
|
119
119
|
mergedOptions.options = {
|
|
120
120
|
...(mergedOptions.options || {}),
|
|
121
121
|
experimentalLocalizeStaticImports: gtConfig.options?.experimentalLocalizeStaticImports ||
|
|
122
|
-
|
|
122
|
+
flags.experimentalLocalizeStaticImports,
|
|
123
123
|
experimentalLocalizeStaticUrls: gtConfig.options?.experimentalLocalizeStaticUrls ||
|
|
124
|
-
|
|
124
|
+
flags.experimentalLocalizeStaticUrls,
|
|
125
125
|
experimentalHideDefaultLocale: gtConfig.options?.experimentalHideDefaultLocale ||
|
|
126
|
-
|
|
126
|
+
flags.experimentalHideDefaultLocale,
|
|
127
127
|
experimentalFlattenJsonFiles: gtConfig.options?.experimentalFlattenJsonFiles ||
|
|
128
|
-
|
|
128
|
+
flags.experimentalFlattenJsonFiles,
|
|
129
129
|
experimentalClearLocaleDirs: gtConfig.options?.experimentalClearLocaleDirs ||
|
|
130
|
-
|
|
131
|
-
clearLocaleDirsExclude: gtConfig.options?.clearLocaleDirsExclude ||
|
|
132
|
-
options.clearLocaleDirsExclude,
|
|
130
|
+
flags.experimentalClearLocaleDirs,
|
|
131
|
+
clearLocaleDirsExclude: gtConfig.options?.clearLocaleDirsExclude || flags.clearLocaleDirsExclude,
|
|
133
132
|
};
|
|
134
133
|
// Add additional options if provided
|
|
135
134
|
if (mergedOptions.options) {
|
|
@@ -112,7 +112,7 @@ export async function aggregateFiles(settings) {
|
|
|
112
112
|
allFiles.push(...files);
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
-
if (allFiles.length === 0) {
|
|
115
|
+
if (allFiles.length === 0 && !settings.publish) {
|
|
116
116
|
logError('No files to translate were found. Please check your configuration and try again.');
|
|
117
117
|
}
|
|
118
118
|
return allFiles;
|
|
@@ -2,4 +2,4 @@ import { AdditionalOptions } from '../../types/index.js';
|
|
|
2
2
|
export default function mergeYaml(originalContent: string, inputPath: string, options: AdditionalOptions, targets: {
|
|
3
3
|
translatedContent: string;
|
|
4
4
|
targetLocale: string;
|
|
5
|
-
}[]): string[];
|
|
5
|
+
}[], defaultLocale: string): string[];
|
|
@@ -2,7 +2,8 @@ import JSONPointer from 'jsonpointer';
|
|
|
2
2
|
import { exit, logError } from '../../console/logging.js';
|
|
3
3
|
import { validateYamlSchema } from './utils.js';
|
|
4
4
|
import YAML from 'yaml';
|
|
5
|
-
|
|
5
|
+
import { applyTransformations } from '../json/mergeJson.js';
|
|
6
|
+
export default function mergeYaml(originalContent, inputPath, options, targets, defaultLocale) {
|
|
6
7
|
const yamlSchema = validateYamlSchema(options, inputPath);
|
|
7
8
|
if (!yamlSchema) {
|
|
8
9
|
return targets.map((target) => target.translatedContent);
|
|
@@ -46,6 +47,10 @@ export default function mergeYaml(originalContent, inputPath, options, targets)
|
|
|
46
47
|
// Silently ignore invalid or non-existent JSON pointers
|
|
47
48
|
}
|
|
48
49
|
}
|
|
50
|
+
// Apply transformations if they exist
|
|
51
|
+
if (yamlSchema.transform) {
|
|
52
|
+
applyTransformations(mergedYaml, yamlSchema.transform, target.targetLocale, defaultLocale);
|
|
53
|
+
}
|
|
49
54
|
output.push(YAML.stringify(mergedYaml));
|
|
50
55
|
}
|
|
51
56
|
if (!output.length) {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -120,7 +120,7 @@ export type Settings = {
|
|
|
120
120
|
resolvedPaths: ResolvedFiles;
|
|
121
121
|
placeholderPaths: ResolvedFiles;
|
|
122
122
|
transformPaths: TransformFiles;
|
|
123
|
-
}
|
|
123
|
+
};
|
|
124
124
|
stageTranslations: boolean;
|
|
125
125
|
publish: boolean;
|
|
126
126
|
_versionId?: string;
|
|
@@ -163,6 +163,7 @@ export type JsonSchema = {
|
|
|
163
163
|
export type YamlSchema = {
|
|
164
164
|
preset?: 'mintlify';
|
|
165
165
|
include?: string[];
|
|
166
|
+
transform?: TransformOptions;
|
|
166
167
|
};
|
|
167
168
|
export type SourceObjectOptions = {
|
|
168
169
|
type: 'array' | 'object';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gtx-cli",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": "dist/main.js",
|
|
6
6
|
"files": [
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"dotenv": "^16.4.5",
|
|
74
74
|
"esbuild": "^0.25.4",
|
|
75
75
|
"fast-glob": "^3.3.3",
|
|
76
|
+
"fast-json-stable-stringify": "^2.1.0",
|
|
76
77
|
"form-data": "^4.0.4",
|
|
77
78
|
"gt-remark": "^1.0.1",
|
|
78
79
|
"json-pointer": "^0.6.2",
|
|
@@ -91,7 +92,7 @@
|
|
|
91
92
|
"unified": "^11.0.5",
|
|
92
93
|
"unist-util-visit": "^5.0.0",
|
|
93
94
|
"yaml": "^2.8.0",
|
|
94
|
-
"generaltranslation": "7.7.
|
|
95
|
+
"generaltranslation": "7.7.1"
|
|
95
96
|
},
|
|
96
97
|
"devDependencies": {
|
|
97
98
|
"@babel/types": "^7.28.4",
|