gtx-cli 2.3.10 → 2.3.11
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 +6 -0
- package/dist/api/checkFileTranslations.d.ts +1 -1
- package/dist/api/checkFileTranslations.js +11 -5
- package/dist/api/downloadFileBatch.d.ts +3 -1
- package/dist/api/downloadFileBatch.js +41 -1
- package/dist/cli/commands/translate.js +3 -3
- package/dist/cli/flags.js +1 -0
- package/dist/fs/config/downloadedVersions.d.ts +12 -0
- package/dist/fs/config/downloadedVersions.js +30 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# gtx-cli
|
|
2
2
|
|
|
3
|
+
## 2.3.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#732](https://github.com/generaltranslation/gt/pull/732) [`bcd8272`](https://github.com/generaltranslation/gt/commit/bcd8272576ff02432e39cf1887a48b4f566eb752) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - Added freezing when fetching translations for unmodified source files. This will keep any local changes until retranslation is triggered or --force-download is used
|
|
8
|
+
|
|
3
9
|
## 2.3.10
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -20,4 +20,4 @@ export declare function checkFileTranslations(data: {
|
|
|
20
20
|
versionId: string;
|
|
21
21
|
fileName: string;
|
|
22
22
|
};
|
|
23
|
-
}, locales: string[], timeoutDuration: number, resolveOutputPath: (sourcePath: string, locale: string) => string | null, options: Settings, forceRetranslation?: boolean): Promise<boolean>;
|
|
23
|
+
}, locales: string[], timeoutDuration: number, resolveOutputPath: (sourcePath: string, locale: string) => string | null, options: Settings, forceRetranslation?: boolean, forceDownload?: boolean): Promise<boolean>;
|
|
@@ -16,7 +16,7 @@ import path from 'node:path';
|
|
|
16
16
|
* @param timeoutDuration - The timeout duration for the wait in seconds
|
|
17
17
|
* @returns True if all translations are deployed, false otherwise
|
|
18
18
|
*/
|
|
19
|
-
export async function checkFileTranslations(data, locales, timeoutDuration, resolveOutputPath, options, forceRetranslation) {
|
|
19
|
+
export async function checkFileTranslations(data, locales, timeoutDuration, resolveOutputPath, options, forceRetranslation, forceDownload) {
|
|
20
20
|
const startTime = Date.now();
|
|
21
21
|
console.log();
|
|
22
22
|
const spinner = await createOraSpinner();
|
|
@@ -49,7 +49,7 @@ export async function checkFileTranslations(data, locales, timeoutDuration, reso
|
|
|
49
49
|
};
|
|
50
50
|
// Do first check immediately, but skip if force retranslation is enabled
|
|
51
51
|
if (!forceRetranslation) {
|
|
52
|
-
const initialCheck = await checkTranslationDeployment(fileQueryData, downloadStatus, spinner, resolveOutputPath, options);
|
|
52
|
+
const initialCheck = await checkTranslationDeployment(fileQueryData, downloadStatus, spinner, resolveOutputPath, options, forceDownload);
|
|
53
53
|
if (initialCheck) {
|
|
54
54
|
spinner.succeed(chalk.green('Files translated!'));
|
|
55
55
|
return true;
|
|
@@ -62,7 +62,7 @@ export async function checkFileTranslations(data, locales, timeoutDuration, reso
|
|
|
62
62
|
// Start the interval aligned with the original request time
|
|
63
63
|
setTimeout(() => {
|
|
64
64
|
intervalCheck = setInterval(async () => {
|
|
65
|
-
const isDeployed = await checkTranslationDeployment(fileQueryData, downloadStatus, spinner, resolveOutputPath, options);
|
|
65
|
+
const isDeployed = await checkTranslationDeployment(fileQueryData, downloadStatus, spinner, resolveOutputPath, options, forceDownload);
|
|
66
66
|
const elapsed = Date.now() - startTime;
|
|
67
67
|
if (isDeployed || elapsed >= timeoutDuration * 1000) {
|
|
68
68
|
clearInterval(intervalCheck);
|
|
@@ -186,7 +186,7 @@ function generateStatusSuffixText(downloadStatus, fileQueryData) {
|
|
|
186
186
|
/**
|
|
187
187
|
* Checks translation status and downloads ready files
|
|
188
188
|
*/
|
|
189
|
-
async function checkTranslationDeployment(fileQueryData, downloadStatus, spinner, resolveOutputPath, options) {
|
|
189
|
+
async function checkTranslationDeployment(fileQueryData, downloadStatus, spinner, resolveOutputPath, options, forceDownload) {
|
|
190
190
|
try {
|
|
191
191
|
// Only query for files that haven't been downloaded yet
|
|
192
192
|
const currentQueryData = fileQueryData.filter((item) => !downloadStatus.downloaded.has(`${item.fileName}:${item.locale}`) &&
|
|
@@ -202,6 +202,11 @@ async function checkTranslationDeployment(fileQueryData, downloadStatus, spinner
|
|
|
202
202
|
// Filter for ready translations
|
|
203
203
|
const readyTranslations = translations.filter((translation) => translation.isReady && translation.fileName);
|
|
204
204
|
if (readyTranslations.length > 0) {
|
|
205
|
+
// Build version map by fileName:locale for this batch
|
|
206
|
+
const versionMap = new Map(fileQueryData.map((item) => [
|
|
207
|
+
`${item.fileName}:${gt.resolveAliasLocale(item.locale)}`,
|
|
208
|
+
item.versionId,
|
|
209
|
+
]));
|
|
205
210
|
// Prepare batch download data
|
|
206
211
|
const batchFiles = readyTranslations
|
|
207
212
|
.map((translation) => {
|
|
@@ -220,10 +225,11 @@ async function checkTranslationDeployment(fileQueryData, downloadStatus, spinner
|
|
|
220
225
|
outputPath,
|
|
221
226
|
locale,
|
|
222
227
|
fileLocale: `${fileName}:${locale}`,
|
|
228
|
+
versionId: versionMap.get(`${fileName}:${locale}`),
|
|
223
229
|
};
|
|
224
230
|
})
|
|
225
231
|
.filter((file) => file !== null);
|
|
226
|
-
const batchResult = await downloadFileBatch(batchFiles, options);
|
|
232
|
+
const batchResult = await downloadFileBatch(batchFiles, options, 3, 1000, Boolean(forceDownload));
|
|
227
233
|
// Process results
|
|
228
234
|
batchFiles.forEach((file) => {
|
|
229
235
|
const { translationId, fileLocale } = file;
|
|
@@ -5,6 +5,8 @@ export type BatchedFiles = Array<{
|
|
|
5
5
|
inputPath: string;
|
|
6
6
|
locale: string;
|
|
7
7
|
fileLocale: string;
|
|
8
|
+
fileId?: string;
|
|
9
|
+
versionId?: string;
|
|
8
10
|
}>;
|
|
9
11
|
export type DownloadFileBatchResult = {
|
|
10
12
|
successful: string[];
|
|
@@ -17,4 +19,4 @@ export type DownloadFileBatchResult = {
|
|
|
17
19
|
* @param retryDelay - Delay between retries in milliseconds
|
|
18
20
|
* @returns Object containing successful and failed file IDs
|
|
19
21
|
*/
|
|
20
|
-
export declare function downloadFileBatch(files: BatchedFiles, options: Settings, maxRetries?: number, retryDelay?: number): Promise<DownloadFileBatchResult>;
|
|
22
|
+
export declare function downloadFileBatch(files: BatchedFiles, options: Settings, maxRetries?: number, retryDelay?: number, forceDownload?: boolean): Promise<DownloadFileBatchResult>;
|
|
@@ -6,6 +6,7 @@ import { validateJsonSchema } from '../formats/json/utils.js';
|
|
|
6
6
|
import { validateYamlSchema } from '../formats/yaml/utils.js';
|
|
7
7
|
import { mergeJson } from '../formats/json/mergeJson.js';
|
|
8
8
|
import mergeYaml from '../formats/yaml/mergeYaml.js';
|
|
9
|
+
import { getDownloadedVersions, saveDownloadedVersions, } from '../fs/config/downloadedVersions.js';
|
|
9
10
|
/**
|
|
10
11
|
* Downloads multiple translation files in a single batch request
|
|
11
12
|
* @param files - Array of files to download with their output paths
|
|
@@ -13,17 +14,22 @@ import mergeYaml from '../formats/yaml/mergeYaml.js';
|
|
|
13
14
|
* @param retryDelay - Delay between retries in milliseconds
|
|
14
15
|
* @returns Object containing successful and failed file IDs
|
|
15
16
|
*/
|
|
16
|
-
export async function downloadFileBatch(files, options, maxRetries = 3, retryDelay = 1000) {
|
|
17
|
+
export async function downloadFileBatch(files, options, maxRetries = 3, retryDelay = 1000, forceDownload = false) {
|
|
18
|
+
// Local record of what version was last downloaded for each fileName:locale
|
|
19
|
+
const downloadedVersions = getDownloadedVersions(options.configDirectory);
|
|
20
|
+
let didUpdateDownloadedLock = false;
|
|
17
21
|
let retries = 0;
|
|
18
22
|
const fileIds = files.map((file) => file.translationId);
|
|
19
23
|
const result = { successful: [], failed: [] };
|
|
20
24
|
// Create a map of translationId to outputPath for easier lookup
|
|
21
25
|
const outputPathMap = new Map(files.map((file) => [file.translationId, file.outputPath]));
|
|
22
26
|
const inputPathMap = new Map(files.map((file) => [file.translationId, file.inputPath]));
|
|
27
|
+
const fileIdMap = new Map(files.map((file) => [file.translationId, file.fileId]));
|
|
23
28
|
const localeMap = new Map(files.map((file) => [
|
|
24
29
|
file.translationId,
|
|
25
30
|
gt.resolveAliasLocale(file.locale),
|
|
26
31
|
]));
|
|
32
|
+
const versionMap = new Map(files.map((file) => [file.translationId, file.versionId]));
|
|
27
33
|
while (retries <= maxRetries) {
|
|
28
34
|
try {
|
|
29
35
|
// Download the files
|
|
@@ -36,6 +42,8 @@ export async function downloadFileBatch(files, options, maxRetries = 3, retryDel
|
|
|
36
42
|
const outputPath = outputPathMap.get(translationId);
|
|
37
43
|
const inputPath = inputPathMap.get(translationId);
|
|
38
44
|
const locale = localeMap.get(translationId);
|
|
45
|
+
const fileId = fileIdMap.get(translationId);
|
|
46
|
+
const versionId = versionMap.get(translationId);
|
|
39
47
|
if (!outputPath || !inputPath) {
|
|
40
48
|
logWarning(`No input/output path found for file: ${translationId}`);
|
|
41
49
|
result.failed.push(translationId);
|
|
@@ -46,6 +54,18 @@ export async function downloadFileBatch(files, options, maxRetries = 3, retryDel
|
|
|
46
54
|
if (!fs.existsSync(dir)) {
|
|
47
55
|
fs.mkdirSync(dir, { recursive: true });
|
|
48
56
|
}
|
|
57
|
+
// If a local translation already exists for the same source version, skip overwrite
|
|
58
|
+
const keyId = fileId || inputPath;
|
|
59
|
+
const downloadedKey = `${keyId}:${locale}`;
|
|
60
|
+
const alreadyDownloadedVersion = downloadedVersions.entries[downloadedKey]?.versionId;
|
|
61
|
+
const fileExists = fs.existsSync(outputPath);
|
|
62
|
+
if (!forceDownload &&
|
|
63
|
+
fileExists &&
|
|
64
|
+
versionId &&
|
|
65
|
+
alreadyDownloadedVersion === versionId) {
|
|
66
|
+
result.successful.push(translationId);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
49
69
|
let data = file.data;
|
|
50
70
|
if (options.options?.jsonSchema && locale) {
|
|
51
71
|
const jsonSchema = validateJsonSchema(options.options, inputPath);
|
|
@@ -78,6 +98,15 @@ export async function downloadFileBatch(files, options, maxRetries = 3, retryDel
|
|
|
78
98
|
// Write the file to disk
|
|
79
99
|
await fs.promises.writeFile(outputPath, data);
|
|
80
100
|
result.successful.push(translationId);
|
|
101
|
+
if (versionId) {
|
|
102
|
+
downloadedVersions.entries[downloadedKey] = {
|
|
103
|
+
versionId,
|
|
104
|
+
fileId: fileId || undefined,
|
|
105
|
+
fileName: inputPath,
|
|
106
|
+
updatedAt: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
didUpdateDownloadedLock = true;
|
|
109
|
+
}
|
|
81
110
|
}
|
|
82
111
|
catch (error) {
|
|
83
112
|
logError(`Error saving file ${file.id}: ` + error);
|
|
@@ -91,6 +120,11 @@ export async function downloadFileBatch(files, options, maxRetries = 3, retryDel
|
|
|
91
120
|
result.failed.push(fileId);
|
|
92
121
|
}
|
|
93
122
|
}
|
|
123
|
+
// Persist any updates to the downloaded map at the end of a successful cycle
|
|
124
|
+
if (didUpdateDownloadedLock) {
|
|
125
|
+
saveDownloadedVersions(options.configDirectory, downloadedVersions);
|
|
126
|
+
didUpdateDownloadedLock = false;
|
|
127
|
+
}
|
|
94
128
|
return result;
|
|
95
129
|
}
|
|
96
130
|
catch (error) {
|
|
@@ -100,6 +134,9 @@ export async function downloadFileBatch(files, options, maxRetries = 3, retryDel
|
|
|
100
134
|
error);
|
|
101
135
|
// Mark all files as failed
|
|
102
136
|
result.failed = [...fileIds];
|
|
137
|
+
if (didUpdateDownloadedLock) {
|
|
138
|
+
saveDownloadedVersions(options.configDirectory, downloadedVersions);
|
|
139
|
+
}
|
|
103
140
|
return result;
|
|
104
141
|
}
|
|
105
142
|
// Increment retry counter and wait before next attempt
|
|
@@ -109,5 +146,8 @@ export async function downloadFileBatch(files, options, maxRetries = 3, retryDel
|
|
|
109
146
|
}
|
|
110
147
|
// Mark all files as failed if we get here
|
|
111
148
|
result.failed = [...fileIds];
|
|
149
|
+
if (didUpdateDownloadedLock) {
|
|
150
|
+
saveDownloadedVersions(options.configDirectory, downloadedVersions);
|
|
151
|
+
}
|
|
112
152
|
return result;
|
|
113
153
|
}
|
|
@@ -15,7 +15,7 @@ export async function handleTranslate(options, settings, filesTranslationRespons
|
|
|
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);
|
|
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
|
|
@@ -33,8 +33,8 @@ export async function handleDownload(options, settings) {
|
|
|
33
33
|
const fileMapping = createFileMapping(resolvedPaths, placeholderPaths, transformPaths, settings.locales, settings.defaultLocale);
|
|
34
34
|
const stagedVersionData = await getStagedVersions(settings.configDirectory);
|
|
35
35
|
// Check for remaining translations
|
|
36
|
-
await checkFileTranslations(stagedVersionData, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale][sourcePath] ?? null, settings, false // force is not applicable for downloading staged translations
|
|
37
|
-
);
|
|
36
|
+
await checkFileTranslations(stagedVersionData, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale][sourcePath] ?? null, settings, false, // force is not applicable for downloading staged translations
|
|
37
|
+
options.forceDownload);
|
|
38
38
|
}
|
|
39
39
|
export async function postProcessTranslations(settings) {
|
|
40
40
|
// Localize static urls (/docs -> /[locale]/docs) and preserve anchor IDs for non-default locales
|
package/dist/cli/flags.js
CHANGED
|
@@ -26,6 +26,7 @@ export function attachTranslateFlags(command) {
|
|
|
26
26
|
.option('--experimental-flatten-json-files', 'Triggering this will flatten the json files into a single file. This is useful for projects that have a lot of json files.', false)
|
|
27
27
|
.option('--experimental-localize-static-imports', 'Triggering this will run a script after the cli tool that localizes all static imports in content files. Currently only supported for md and mdx files.', false)
|
|
28
28
|
.option('--force', 'Force a retranslation, invalidating all existing cached translations if they exist.', false)
|
|
29
|
+
.option('--force-download', 'Force download and overwrite local files, bypassing downloaded-versions checks.', false)
|
|
29
30
|
.option('--experimental-clear-locale-dirs', 'Clear locale directories before downloading new translations', false);
|
|
30
31
|
return command;
|
|
31
32
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type DownloadedVersionEntry = {
|
|
2
|
+
versionId: string;
|
|
3
|
+
fileId?: string;
|
|
4
|
+
fileName?: string;
|
|
5
|
+
updatedAt?: string;
|
|
6
|
+
};
|
|
7
|
+
export type DownloadedVersions = {
|
|
8
|
+
version: number;
|
|
9
|
+
entries: Record<string, DownloadedVersionEntry>;
|
|
10
|
+
};
|
|
11
|
+
export declare function getDownloadedVersions(configDirectory: string): DownloadedVersions;
|
|
12
|
+
export declare function saveDownloadedVersions(configDirectory: string, lock: DownloadedVersions): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { logError } from '../../console/logging.js';
|
|
4
|
+
const DOWNLOADED_VERSIONS_FILE = 'downloaded-versions.json';
|
|
5
|
+
export function getDownloadedVersions(configDirectory) {
|
|
6
|
+
try {
|
|
7
|
+
const filepath = path.join(configDirectory, DOWNLOADED_VERSIONS_FILE);
|
|
8
|
+
if (!fs.existsSync(filepath))
|
|
9
|
+
return { version: 1, entries: {} };
|
|
10
|
+
const raw = JSON.parse(fs.readFileSync(filepath, 'utf8'));
|
|
11
|
+
if (raw && typeof raw === 'object' && raw.version && raw.entries) {
|
|
12
|
+
return raw;
|
|
13
|
+
}
|
|
14
|
+
return { version: 1, entries: {} };
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
logError(`An error occurred while getting downloaded versions: ${error}`);
|
|
18
|
+
return { version: 1, entries: {} };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function saveDownloadedVersions(configDirectory, lock) {
|
|
22
|
+
try {
|
|
23
|
+
const filepath = path.join(configDirectory, DOWNLOADED_VERSIONS_FILE);
|
|
24
|
+
fs.mkdirSync(configDirectory, { recursive: true });
|
|
25
|
+
fs.writeFileSync(filepath, JSON.stringify(lock, null, 2));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
logError(`An error occurred while updating ${DOWNLOADED_VERSIONS_FILE}: ${error}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export type TranslateFlags = {
|
|
|
41
41
|
stageTranslations?: boolean;
|
|
42
42
|
publish?: boolean;
|
|
43
43
|
force?: boolean;
|
|
44
|
+
forceDownload?: boolean;
|
|
44
45
|
experimentalLocalizeStaticUrls?: boolean;
|
|
45
46
|
experimentalHideDefaultLocale?: boolean;
|
|
46
47
|
experimentalFlattenJsonFiles?: boolean;
|