gtx-cli 1.2.30-alpha.32 → 1.2.30-alpha.34
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 +18 -0
- package/dist/api/checkFileTranslations.d.ts +1 -1
- package/dist/api/checkFileTranslations.js +8 -7
- package/dist/api/downloadFile.d.ts +1 -1
- package/dist/api/downloadFile.js +3 -2
- package/dist/api/downloadFileBatch.d.ts +1 -1
- package/dist/api/downloadFileBatch.js +3 -2
- package/dist/api/fetchTranslations.d.ts +1 -1
- package/dist/api/fetchTranslations.js +3 -2
- package/dist/api/sendFiles.js +2 -2
- package/dist/api/sendUpdates.js +2 -1
- package/dist/api/waitForUpdates.d.ts +1 -1
- package/dist/api/waitForUpdates.js +3 -2
- package/dist/cli/react.js +14 -1
- package/dist/config/generateSettings.js +24 -12
- package/dist/config/resolveConfig.d.ts +4 -0
- package/dist/config/resolveConfig.js +19 -0
- package/dist/formats/files/translate.d.ts +4 -0
- package/dist/formats/files/translate.js +7 -5
- package/dist/fs/config/loadConfig.d.ts +1 -1
- package/dist/fs/config/loadConfig.js +1 -1
- package/dist/fs/config/parseFilesConfig.js +13 -12
- package/dist/fs/config/setupConfig.d.ts +1 -0
- package/dist/fs/config/setupConfig.js +1 -0
- package/dist/translation/translate.js +2 -2
- package/dist/types/index.d.ts +3 -0
- package/dist/utils/flattenJsonFiles.d.ts +2 -0
- package/dist/utils/flattenJsonFiles.js +36 -0
- package/dist/utils/headers.d.ts +1 -0
- package/dist/utils/headers.js +14 -0
- package/dist/utils/localizeStaticUrls.d.ts +15 -0
- package/dist/utils/localizeStaticUrls.js +78 -0
- package/dist/utils/sanitizeFileContent.d.ts +6 -0
- package/dist/utils/sanitizeFileContent.js +29 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# gtx-cli
|
|
2
2
|
|
|
3
|
+
## 1.2.34
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#428](https://github.com/generaltranslation/gt/pull/428) [`54036f5`](https://github.com/generaltranslation/gt/commit/54036f54308bdb9f9e6dcec93871e004dcf1be4c) Thanks [@ErnestM1234](https://github.com/ErnestM1234)! - feat: add experimental options to translate
|
|
8
|
+
|
|
9
|
+
## 1.2.33
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#426](https://github.com/generaltranslation/gt/pull/426) [`ce57545`](https://github.com/generaltranslation/gt/commit/ce575454301185c663cfb93345d3058c9ceb25dd) Thanks [@brian-lou](https://github.com/brian-lou)! - Improve file pattern matching
|
|
14
|
+
|
|
15
|
+
## 1.2.31
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [#423](https://github.com/generaltranslation/gt/pull/423) [`0ed08c7`](https://github.com/generaltranslation/gt/commit/0ed08c7bb1e63c99296b74138e4d44b718681fc8) Thanks [@brian-lou](https://github.com/brian-lou)! - Add setting configuration options
|
|
20
|
+
|
|
3
21
|
## 1.2.30
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @param timeoutDuration - The timeout duration for the wait in seconds
|
|
9
9
|
* @returns True if all translations are deployed, false otherwise
|
|
10
10
|
*/
|
|
11
|
-
export declare function checkFileTranslations(apiKey: string, baseUrl: string, data: {
|
|
11
|
+
export declare function checkFileTranslations(projectId: string, apiKey: string, baseUrl: string, data: {
|
|
12
12
|
[key: string]: {
|
|
13
13
|
versionId: string;
|
|
14
14
|
fileName: string;
|
|
@@ -3,6 +3,7 @@ import { createOraSpinner, logError } from '../console/logging.js';
|
|
|
3
3
|
import { getLocaleProperties } from 'generaltranslation';
|
|
4
4
|
import { downloadFile } from './downloadFile.js';
|
|
5
5
|
import { downloadFileBatch } from './downloadFileBatch.js';
|
|
6
|
+
import { getAuthHeaders } from '../utils/headers.js';
|
|
6
7
|
/**
|
|
7
8
|
* Checks the status of translations for a given version ID
|
|
8
9
|
* @param apiKey - The API key for the General Translation API
|
|
@@ -13,7 +14,7 @@ import { downloadFileBatch } from './downloadFileBatch.js';
|
|
|
13
14
|
* @param timeoutDuration - The timeout duration for the wait in seconds
|
|
14
15
|
* @returns True if all translations are deployed, false otherwise
|
|
15
16
|
*/
|
|
16
|
-
export async function checkFileTranslations(apiKey, baseUrl, data, locales, timeoutDuration, resolveOutputPath, downloadStatus) {
|
|
17
|
+
export async function checkFileTranslations(projectId, apiKey, baseUrl, data, locales, timeoutDuration, resolveOutputPath, downloadStatus) {
|
|
17
18
|
const startTime = Date.now();
|
|
18
19
|
console.log();
|
|
19
20
|
const spinner = await createOraSpinner();
|
|
@@ -21,7 +22,7 @@ export async function checkFileTranslations(apiKey, baseUrl, data, locales, time
|
|
|
21
22
|
// Initialize the query data
|
|
22
23
|
const fileQueryData = prepareFileQueryData(data, locales);
|
|
23
24
|
// Do first check immediately
|
|
24
|
-
const initialCheck = await checkTranslationDeployment(baseUrl, apiKey, fileQueryData, downloadStatus, spinner, resolveOutputPath);
|
|
25
|
+
const initialCheck = await checkTranslationDeployment(baseUrl, projectId, apiKey, fileQueryData, downloadStatus, spinner, resolveOutputPath);
|
|
25
26
|
if (initialCheck) {
|
|
26
27
|
spinner.succeed(chalk.green('Files translated!'));
|
|
27
28
|
return true;
|
|
@@ -33,7 +34,7 @@ export async function checkFileTranslations(apiKey, baseUrl, data, locales, time
|
|
|
33
34
|
// Start the interval aligned with the original request time
|
|
34
35
|
setTimeout(() => {
|
|
35
36
|
intervalCheck = setInterval(async () => {
|
|
36
|
-
const isDeployed = await checkTranslationDeployment(baseUrl, apiKey, fileQueryData, downloadStatus, spinner, resolveOutputPath);
|
|
37
|
+
const isDeployed = await checkTranslationDeployment(baseUrl, projectId, apiKey, fileQueryData, downloadStatus, spinner, resolveOutputPath);
|
|
37
38
|
const elapsed = Date.now() - startTime;
|
|
38
39
|
if (isDeployed || elapsed >= timeoutDuration * 1000) {
|
|
39
40
|
clearInterval(intervalCheck);
|
|
@@ -156,7 +157,7 @@ function generateStatusSuffixText(downloadStatus, fileQueryData) {
|
|
|
156
157
|
/**
|
|
157
158
|
* Checks translation status and downloads ready files
|
|
158
159
|
*/
|
|
159
|
-
async function checkTranslationDeployment(baseUrl, apiKey, fileQueryData, downloadStatus, spinner, resolveOutputPath) {
|
|
160
|
+
async function checkTranslationDeployment(baseUrl, projectId, apiKey, fileQueryData, downloadStatus, spinner, resolveOutputPath) {
|
|
160
161
|
try {
|
|
161
162
|
// Only query for files that haven't been downloaded yet
|
|
162
163
|
const currentQueryData = fileQueryData.filter((item) => !downloadStatus.downloaded.has(`${item.fileName}:${item.locale}`) &&
|
|
@@ -169,7 +170,7 @@ async function checkTranslationDeployment(baseUrl, apiKey, fileQueryData, downlo
|
|
|
169
170
|
method: 'POST',
|
|
170
171
|
headers: {
|
|
171
172
|
'Content-Type': 'application/json',
|
|
172
|
-
...(
|
|
173
|
+
...getAuthHeaders(projectId, apiKey),
|
|
173
174
|
},
|
|
174
175
|
body: JSON.stringify({ files: currentQueryData }),
|
|
175
176
|
});
|
|
@@ -193,7 +194,7 @@ async function checkTranslationDeployment(baseUrl, apiKey, fileQueryData, downlo
|
|
|
193
194
|
});
|
|
194
195
|
// Use batch download if there are multiple files
|
|
195
196
|
if (batchFiles.length > 1) {
|
|
196
|
-
const batchResult = await downloadFileBatch(baseUrl, apiKey, batchFiles.map(({ translationId, outputPath }) => ({
|
|
197
|
+
const batchResult = await downloadFileBatch(baseUrl, projectId, apiKey, batchFiles.map(({ translationId, outputPath }) => ({
|
|
197
198
|
translationId,
|
|
198
199
|
outputPath,
|
|
199
200
|
})));
|
|
@@ -211,7 +212,7 @@ async function checkTranslationDeployment(baseUrl, apiKey, fileQueryData, downlo
|
|
|
211
212
|
else if (batchFiles.length === 1) {
|
|
212
213
|
// For a single file, use the original downloadFile method
|
|
213
214
|
const file = batchFiles[0];
|
|
214
|
-
const result = await downloadFile(baseUrl, apiKey, file.translationId, file.outputPath);
|
|
215
|
+
const result = await downloadFile(baseUrl, projectId, apiKey, file.translationId, file.outputPath);
|
|
215
216
|
if (result) {
|
|
216
217
|
downloadStatus.downloaded.add(file.fileLocale);
|
|
217
218
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function downloadFile(baseUrl: string, apiKey: string, translationId: string, outputPath: string, maxRetries?: number, retryDelay?: number): Promise<boolean>;
|
|
1
|
+
export declare function downloadFile(baseUrl: string, projectId: string, apiKey: string, translationId: string, outputPath: string, maxRetries?: number, retryDelay?: number): Promise<boolean>;
|
package/dist/api/downloadFile.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { logError } from '../console/logging.js';
|
|
4
|
+
import { getAuthHeaders } from '../utils/headers.js';
|
|
4
5
|
// Helper function to download a file
|
|
5
|
-
export async function downloadFile(baseUrl, apiKey, translationId, outputPath, maxRetries = 3, retryDelay = 1000) {
|
|
6
|
+
export async function downloadFile(baseUrl, projectId, apiKey, translationId, outputPath, maxRetries = 3, retryDelay = 1000) {
|
|
6
7
|
let retries = 0;
|
|
7
8
|
while (retries <= maxRetries) {
|
|
8
9
|
try {
|
|
9
10
|
const downloadResponse = await fetch(`${baseUrl}/v1/project/translations/files/${translationId}/download`, {
|
|
10
11
|
method: 'GET',
|
|
11
12
|
headers: {
|
|
12
|
-
...(
|
|
13
|
+
...getAuthHeaders(projectId, apiKey),
|
|
13
14
|
},
|
|
14
15
|
});
|
|
15
16
|
if (downloadResponse.ok) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @param retryDelay - Delay between retries in milliseconds
|
|
8
8
|
* @returns Object containing successful and failed file IDs
|
|
9
9
|
*/
|
|
10
|
-
export declare function downloadFileBatch(baseUrl: string, apiKey: string, files: Array<{
|
|
10
|
+
export declare function downloadFileBatch(baseUrl: string, projectId: string, apiKey: string, files: Array<{
|
|
11
11
|
translationId: string;
|
|
12
12
|
outputPath: string;
|
|
13
13
|
}>, maxRetries?: number, retryDelay?: number): Promise<{
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { logError, logWarning } from '../console/logging.js';
|
|
4
|
+
import { getAuthHeaders } from '../utils/headers.js';
|
|
4
5
|
/**
|
|
5
6
|
* Downloads multiple translation files in a single batch request
|
|
6
7
|
* @param baseUrl - The base URL for the General Translation API
|
|
@@ -10,7 +11,7 @@ import { logError, logWarning } from '../console/logging.js';
|
|
|
10
11
|
* @param retryDelay - Delay between retries in milliseconds
|
|
11
12
|
* @returns Object containing successful and failed file IDs
|
|
12
13
|
*/
|
|
13
|
-
export async function downloadFileBatch(baseUrl, apiKey, files, maxRetries = 3, retryDelay = 1000) {
|
|
14
|
+
export async function downloadFileBatch(baseUrl, projectId, apiKey, files, maxRetries = 3, retryDelay = 1000) {
|
|
14
15
|
let retries = 0;
|
|
15
16
|
const fileIds = files.map((file) => file.translationId);
|
|
16
17
|
const result = { successful: [], failed: [] };
|
|
@@ -22,7 +23,7 @@ export async function downloadFileBatch(baseUrl, apiKey, files, maxRetries = 3,
|
|
|
22
23
|
method: 'POST',
|
|
23
24
|
headers: {
|
|
24
25
|
'Content-Type': 'application/json',
|
|
25
|
-
...(
|
|
26
|
+
...getAuthHeaders(projectId, apiKey),
|
|
26
27
|
},
|
|
27
28
|
body: JSON.stringify({ fileIds }),
|
|
28
29
|
});
|
|
@@ -7,4 +7,4 @@ import { RetrievedTranslations } from '../types/api.js';
|
|
|
7
7
|
* @param translationsDir - The directory to save the translations to
|
|
8
8
|
* @param fileType - The file type to save the translations as (file extension)
|
|
9
9
|
*/
|
|
10
|
-
export declare function fetchTranslations(baseUrl: string, apiKey: string, versionId: string): Promise<RetrievedTranslations>;
|
|
10
|
+
export declare function fetchTranslations(baseUrl: string, projectId: string, apiKey: string, versionId: string): Promise<RetrievedTranslations>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { logError } from '../console/logging.js';
|
|
3
|
+
import { getAuthHeaders } from '../utils/headers.js';
|
|
3
4
|
/**
|
|
4
5
|
* Fetches translations from the API and saves them to a local directory
|
|
5
6
|
* @param baseUrl - The base URL for the API
|
|
@@ -8,13 +9,13 @@ import { logError } from '../console/logging.js';
|
|
|
8
9
|
* @param translationsDir - The directory to save the translations to
|
|
9
10
|
* @param fileType - The file type to save the translations as (file extension)
|
|
10
11
|
*/
|
|
11
|
-
export async function fetchTranslations(baseUrl, apiKey, versionId) {
|
|
12
|
+
export async function fetchTranslations(baseUrl, projectId, apiKey, versionId) {
|
|
12
13
|
// First fetch the translations from the API
|
|
13
14
|
const response = await fetch(`${baseUrl}/v1/project/translations/info/${encodeURIComponent(versionId)}`, {
|
|
14
15
|
method: 'GET',
|
|
15
16
|
headers: {
|
|
16
17
|
'Content-Type': 'application/json',
|
|
17
|
-
...(
|
|
18
|
+
...getAuthHeaders(projectId, apiKey),
|
|
18
19
|
},
|
|
19
20
|
});
|
|
20
21
|
if (response.ok) {
|
package/dist/api/sendFiles.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { createSpinner, logMessage, logSuccess } from '../console/logging.js';
|
|
3
|
+
import { getAuthHeaders } from '../utils/headers.js';
|
|
3
4
|
/**
|
|
4
5
|
* Sends multiple files for translation to the API
|
|
5
6
|
* @param files - Array of file objects to translate
|
|
@@ -7,7 +8,6 @@ import { createSpinner, logMessage, logSuccess } from '../console/logging.js';
|
|
|
7
8
|
* @returns The translated content or version ID
|
|
8
9
|
*/
|
|
9
10
|
export async function sendFiles(files, options) {
|
|
10
|
-
const { apiKey } = options;
|
|
11
11
|
logMessage(chalk.cyan('Files to translate:') +
|
|
12
12
|
'\n' +
|
|
13
13
|
files.map((file) => ` - ${chalk.bold(file.fileName)}`).join('\n'));
|
|
@@ -35,7 +35,7 @@ export async function sendFiles(files, options) {
|
|
|
35
35
|
const response = await fetch(`${options.baseUrl}/v1/project/translations/files/upload`, {
|
|
36
36
|
method: 'POST',
|
|
37
37
|
headers: {
|
|
38
|
-
...(
|
|
38
|
+
...getAuthHeaders(options.projectId, options.apiKey),
|
|
39
39
|
},
|
|
40
40
|
body: formData,
|
|
41
41
|
});
|
package/dist/api/sendUpdates.js
CHANGED
|
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { createSpinner, logSuccess, logWarning } from '../console/logging.js';
|
|
3
3
|
import updateConfig from '../fs/config/updateConfig.js';
|
|
4
4
|
import { isUsingLocalTranslations } from '../config/utils.js';
|
|
5
|
+
import { getAuthHeaders } from '../utils/headers.js';
|
|
5
6
|
/**
|
|
6
7
|
* Sends updates to the API
|
|
7
8
|
* @param updates - The updates to send
|
|
@@ -35,7 +36,7 @@ export async function sendUpdates(updates, options, library) {
|
|
|
35
36
|
method: 'POST',
|
|
36
37
|
headers: {
|
|
37
38
|
'Content-Type': 'application/json',
|
|
38
|
-
...(
|
|
39
|
+
...getAuthHeaders(options.projectId, options.apiKey),
|
|
39
40
|
},
|
|
40
41
|
body: JSON.stringify(body),
|
|
41
42
|
});
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
* @param timeoutDuration - The timeout duration for the wait
|
|
9
9
|
* @returns True if all translations are deployed, false otherwise
|
|
10
10
|
*/
|
|
11
|
-
export declare const waitForUpdates: (apiKey: string, baseUrl: string, versionId: string, startTime: number, timeoutDuration: number) => Promise<boolean>;
|
|
11
|
+
export declare const waitForUpdates: (projectId: string, apiKey: string, baseUrl: string, versionId: string, startTime: number, timeoutDuration: number) => Promise<boolean>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { createOraSpinner, logErrorAndExit, } from '../console/logging.js';
|
|
3
3
|
import { getLocaleProperties } from 'generaltranslation';
|
|
4
|
+
import { getAuthHeaders } from '../utils/headers.js';
|
|
4
5
|
/**
|
|
5
6
|
* Waits for translations to be deployed to the General Translation API
|
|
6
7
|
* @param apiKey - The API key for the General Translation API
|
|
@@ -11,7 +12,7 @@ import { getLocaleProperties } from 'generaltranslation';
|
|
|
11
12
|
* @param timeoutDuration - The timeout duration for the wait
|
|
12
13
|
* @returns True if all translations are deployed, false otherwise
|
|
13
14
|
*/
|
|
14
|
-
export const waitForUpdates = async (apiKey, baseUrl, versionId, startTime, timeoutDuration) => {
|
|
15
|
+
export const waitForUpdates = async (projectId, apiKey, baseUrl, versionId, startTime, timeoutDuration) => {
|
|
15
16
|
console.log();
|
|
16
17
|
const spinner = await createOraSpinner();
|
|
17
18
|
spinner.start('Waiting for translation...');
|
|
@@ -21,7 +22,7 @@ export const waitForUpdates = async (apiKey, baseUrl, versionId, startTime, time
|
|
|
21
22
|
method: 'GET',
|
|
22
23
|
headers: {
|
|
23
24
|
'Content-Type': 'application/json',
|
|
24
|
-
...(
|
|
25
|
+
...getAuthHeaders(projectId, apiKey),
|
|
25
26
|
},
|
|
26
27
|
});
|
|
27
28
|
if (response.ok) {
|
package/dist/cli/react.js
CHANGED
|
@@ -16,6 +16,8 @@ import updateConfig from '../fs/config/updateConfig.js';
|
|
|
16
16
|
import { validateConfigExists } from '../config/validateSettings.js';
|
|
17
17
|
import { validateProject } from '../translation/validate.js';
|
|
18
18
|
import { intro } from '@clack/prompts';
|
|
19
|
+
import localizeStaticUrls from '../utils/localizeStaticUrls.js';
|
|
20
|
+
import flattenJsonFiles from '../utils/flattenJsonFiles.js';
|
|
19
21
|
const DEFAULT_TIMEOUT = 600;
|
|
20
22
|
const pkg = 'gt-react';
|
|
21
23
|
export class ReactCLI extends BaseCLI {
|
|
@@ -75,6 +77,9 @@ export class ReactCLI extends BaseCLI {
|
|
|
75
77
|
.option('--ignore-errors', 'Ignore errors encountered while scanning for <T> tags', false)
|
|
76
78
|
.option('--dry-run', 'Dry run, does not send updates to General Translation API', false)
|
|
77
79
|
.option('--timeout <seconds>', 'Timeout in seconds for waiting for updates to be deployed to the CDN', DEFAULT_TIMEOUT.toString())
|
|
80
|
+
.option('--experimental-localize-static-urls', 'Triggering this will run a script after the cli tool that localizes all urls in content files. Currently only supported for md and mdx files.', false)
|
|
81
|
+
.option('--experimental-hide-default-locale', 'When localizing static locales, hide the default locale from the path', false)
|
|
82
|
+
.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)
|
|
78
83
|
.action(async (options) => {
|
|
79
84
|
displayHeader('Translating project...');
|
|
80
85
|
await this.handleTranslate(options);
|
|
@@ -262,7 +267,7 @@ export class ReactCLI extends BaseCLI {
|
|
|
262
267
|
await super.handleGenericTranslate(options);
|
|
263
268
|
// If the base class's handleTranslate completes successfully, continue with ReactCLI-specific code
|
|
264
269
|
}
|
|
265
|
-
catch
|
|
270
|
+
catch {
|
|
266
271
|
// Continue with ReactCLI-specific code even if base handleTranslate failed
|
|
267
272
|
}
|
|
268
273
|
if (!settings.stageTranslations) {
|
|
@@ -280,6 +285,14 @@ export class ReactCLI extends BaseCLI {
|
|
|
280
285
|
}
|
|
281
286
|
await translate(options, settings._versionId);
|
|
282
287
|
}
|
|
288
|
+
// Localize static urls (/docs -> /[locale]/docs)
|
|
289
|
+
if (options.experimentalLocalizeStaticUrls) {
|
|
290
|
+
await localizeStaticUrls(options);
|
|
291
|
+
}
|
|
292
|
+
// Flatten json files into a single file
|
|
293
|
+
if (options.experimentalFlattenJsonFiles) {
|
|
294
|
+
await flattenJsonFiles(options);
|
|
295
|
+
}
|
|
283
296
|
}
|
|
284
297
|
async handleValidate(initOptions) {
|
|
285
298
|
validateConfigExists();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { displayProjectId, warnApiKeyInConfig } from '../console/logging.js';
|
|
2
|
-
import loadConfig from '../fs/config/loadConfig.js';
|
|
1
|
+
import { displayProjectId, logErrorAndExit, warnApiKeyInConfig, } from '../console/logging.js';
|
|
2
|
+
import { loadConfig } from '../fs/config/loadConfig.js';
|
|
3
3
|
import { defaultBaseUrl, libraryDefaultLocale, } from 'generaltranslation/internal';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import { createOrUpdateConfig } from '../fs/config/setupConfig.js';
|
|
@@ -8,6 +8,8 @@ import { validateSettings } from './validateSettings.js';
|
|
|
8
8
|
import { GT_DASHBOARD_URL } from '../utils/constants.js';
|
|
9
9
|
import { resolveProjectId } from '../fs/utils.js';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import { resolveConfig } from './resolveConfig.js';
|
|
11
13
|
export const DEFAULT_SRC_PATTERNS = [
|
|
12
14
|
'src/**/*.{js,jsx,ts,tsx}',
|
|
13
15
|
'app/**/*.{js,jsx,ts,tsx}',
|
|
@@ -29,23 +31,33 @@ export async function generateSettings(options, cwd = process.cwd()) {
|
|
|
29
31
|
if (options.config) {
|
|
30
32
|
gtConfig = loadConfig(options.config);
|
|
31
33
|
}
|
|
32
|
-
else if (fs.existsSync(path.join(cwd, 'gt.config.json'))) {
|
|
33
|
-
options.config = path.join(cwd, 'gt.config.json');
|
|
34
|
-
gtConfig = loadConfig(options.config);
|
|
35
|
-
}
|
|
36
|
-
else if (fs.existsSync(path.join(cwd, 'src/gt.config.json'))) {
|
|
37
|
-
options.config = path.join(cwd, 'src/gt.config.json');
|
|
38
|
-
gtConfig = loadConfig(options.config);
|
|
39
|
-
}
|
|
40
34
|
else {
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
const config = resolveConfig(cwd);
|
|
36
|
+
if (config) {
|
|
37
|
+
gtConfig = config.config;
|
|
38
|
+
options.config = config.path;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
gtConfig = {};
|
|
42
|
+
}
|
|
43
43
|
}
|
|
44
44
|
// Warn if apiKey is present in gt.config.json
|
|
45
45
|
if (gtConfig.apiKey) {
|
|
46
46
|
warnApiKeyInConfig(options.config);
|
|
47
47
|
process.exit(1);
|
|
48
48
|
}
|
|
49
|
+
const projectIdEnv = resolveProjectId();
|
|
50
|
+
// Resolve mismatched projectIds
|
|
51
|
+
if (gtConfig.projectId &&
|
|
52
|
+
options.projectId &&
|
|
53
|
+
gtConfig.projectId !== options.projectId) {
|
|
54
|
+
logErrorAndExit(`Project ID mismatch between ${chalk.green(gtConfig.projectId)} and ${chalk.green(options.projectId)}! Please use the same projectId in all configs.`);
|
|
55
|
+
}
|
|
56
|
+
else if (gtConfig.projectId &&
|
|
57
|
+
projectIdEnv &&
|
|
58
|
+
gtConfig.projectId !== projectIdEnv) {
|
|
59
|
+
logErrorAndExit(`Project ID mismatch between ${chalk.green(gtConfig.projectId)} and ${chalk.green(projectIdEnv)}! Please use the same projectId in all configs.`);
|
|
60
|
+
}
|
|
49
61
|
// merge options
|
|
50
62
|
const mergedOptions = { ...gtConfig, ...options };
|
|
51
63
|
// merge locales
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { loadConfig } from '../fs/config/loadConfig.js';
|
|
4
|
+
export function resolveConfig(cwd) {
|
|
5
|
+
const configFilepath = 'gt.config.json';
|
|
6
|
+
if (fs.existsSync(path.join(cwd, configFilepath))) {
|
|
7
|
+
return {
|
|
8
|
+
path: path.join(cwd, configFilepath),
|
|
9
|
+
config: loadConfig(path.join(cwd, configFilepath)),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
if (fs.existsSync(path.join(cwd, 'src/gt.config.json'))) {
|
|
13
|
+
return {
|
|
14
|
+
path: path.join(cwd, 'src/gt.config.json'),
|
|
15
|
+
config: loadConfig(path.join(cwd, 'src/gt.config.json')),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
@@ -11,3 +11,7 @@ import { TranslateOptions } from '../../cli/base.js';
|
|
|
11
11
|
* @returns Promise that resolves when translation is complete
|
|
12
12
|
*/
|
|
13
13
|
export declare function translateFiles(filePaths: ResolvedFiles, placeholderPaths: ResolvedFiles, transformPaths: TransformFiles, dataFormat: DataFormat | undefined, options: Settings & TranslateOptions): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a mapping between source files and their translated counterparts for each locale
|
|
16
|
+
*/
|
|
17
|
+
export declare function createFileMapping(filePaths: ResolvedFiles, placeholderPaths: ResolvedFiles, transformPaths: TransformFiles, locales: string[]): Record<string, Record<string, string>>;
|
|
@@ -10,6 +10,7 @@ import chalk from 'chalk';
|
|
|
10
10
|
import { downloadFile } from '../../api/downloadFile.js';
|
|
11
11
|
import { downloadFileBatch } from '../../api/downloadFileBatch.js';
|
|
12
12
|
import { SUPPORTED_FILE_EXTENSIONS } from './supportedFiles.js';
|
|
13
|
+
import sanitizeFileContent from '../../utils/sanitizeFileContent.js';
|
|
13
14
|
const SUPPORTED_DATA_FORMATS = ['JSX', 'ICU', 'I18NEXT'];
|
|
14
15
|
/**
|
|
15
16
|
* Sends multiple files to the API for translation
|
|
@@ -49,9 +50,10 @@ export async function translateFiles(filePaths, placeholderPaths, transformPaths
|
|
|
49
50
|
if (filePaths[fileType]) {
|
|
50
51
|
const files = filePaths[fileType].map((filePath) => {
|
|
51
52
|
const content = readFile(filePath);
|
|
53
|
+
const sanitizedContent = sanitizeFileContent(content);
|
|
52
54
|
const relativePath = getRelative(filePath);
|
|
53
55
|
return {
|
|
54
|
-
content,
|
|
56
|
+
content: sanitizedContent,
|
|
55
57
|
fileName: relativePath,
|
|
56
58
|
fileExtension: fileType.toUpperCase(),
|
|
57
59
|
dataFormat,
|
|
@@ -98,7 +100,7 @@ export async function translateFiles(filePaths, placeholderPaths, transformPaths
|
|
|
98
100
|
// Process any translations that were already completed and returned with the initial response
|
|
99
101
|
const downloadStatus = await processInitialTranslations(translations, fileMapping, options);
|
|
100
102
|
// Check for remaining translations
|
|
101
|
-
await checkFileTranslations(options.apiKey, options.baseUrl, data, locales, 600, (sourcePath, locale) => fileMapping[locale][sourcePath], downloadStatus // Pass the already downloaded files to avoid duplicate requests
|
|
103
|
+
await checkFileTranslations(options.projectId, options.apiKey, options.baseUrl, data, locales, 600, (sourcePath, locale) => fileMapping[locale][sourcePath], downloadStatus // Pass the already downloaded files to avoid duplicate requests
|
|
102
104
|
);
|
|
103
105
|
}
|
|
104
106
|
catch (error) {
|
|
@@ -108,7 +110,7 @@ export async function translateFiles(filePaths, placeholderPaths, transformPaths
|
|
|
108
110
|
/**
|
|
109
111
|
* Creates a mapping between source files and their translated counterparts for each locale
|
|
110
112
|
*/
|
|
111
|
-
function createFileMapping(filePaths, placeholderPaths, transformPaths, locales) {
|
|
113
|
+
export function createFileMapping(filePaths, placeholderPaths, transformPaths, locales) {
|
|
112
114
|
const fileMapping = {};
|
|
113
115
|
for (const locale of locales) {
|
|
114
116
|
const translatedPaths = resolveLocaleFiles(placeholderPaths, locale);
|
|
@@ -180,7 +182,7 @@ async function processInitialTranslations(translations = [], fileMapping, option
|
|
|
180
182
|
}
|
|
181
183
|
// Use batch download if there are multiple files
|
|
182
184
|
if (batchFiles.length > 1) {
|
|
183
|
-
const batchResult = await downloadFileBatch(options.baseUrl, options.apiKey, batchFiles.map(({ translationId, outputPath }) => ({
|
|
185
|
+
const batchResult = await downloadFileBatch(options.baseUrl, options.projectId, options.apiKey, batchFiles.map(({ translationId, outputPath }) => ({
|
|
184
186
|
translationId,
|
|
185
187
|
outputPath,
|
|
186
188
|
})));
|
|
@@ -198,7 +200,7 @@ async function processInitialTranslations(translations = [], fileMapping, option
|
|
|
198
200
|
else if (batchFiles.length === 1) {
|
|
199
201
|
// For a single file, use the original downloadFile method
|
|
200
202
|
const file = batchFiles[0];
|
|
201
|
-
const result = await downloadFile(options.baseUrl, options.apiKey, file.translationId, file.outputPath);
|
|
203
|
+
const result = await downloadFile(options.baseUrl, options.projectId, options.apiKey, file.translationId, file.outputPath);
|
|
202
204
|
if (result) {
|
|
203
205
|
downloadStatus.downloaded.add(file.fileLocale);
|
|
204
206
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare function loadConfig(filepath: string): Record<string, any>;
|
|
@@ -92,29 +92,30 @@ function expandGlobPatterns(cwd, includePatterns, excludePatterns, locale, trans
|
|
|
92
92
|
resolvedPaths.push(...matches);
|
|
93
93
|
// For each match, create a version with [locale] in the correct positions
|
|
94
94
|
matches.forEach((match) => {
|
|
95
|
-
// Convert to
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
// Convert to absolute path to make replacement easier
|
|
96
|
+
const absolutePath = path.resolve(cwd, match);
|
|
97
|
+
const patternPath = path.resolve(cwd, pattern);
|
|
98
|
+
let originalAbsolutePath = absolutePath;
|
|
99
99
|
if (localePositions.length > 0) {
|
|
100
|
-
//
|
|
101
|
-
// This is a simplified approach - we'll replace all instances of the locale
|
|
100
|
+
// Replace all instances of [locale]
|
|
102
101
|
// but only in path segments where we expect it based on the original pattern
|
|
103
|
-
const pathParts =
|
|
104
|
-
const patternParts =
|
|
102
|
+
const pathParts = absolutePath.split(path.sep);
|
|
103
|
+
const patternParts = patternPath.split(path.sep);
|
|
105
104
|
for (let i = 0; i < pathParts.length; i++) {
|
|
106
105
|
if (i < patternParts.length) {
|
|
107
106
|
if (patternParts[i].includes(localeTag)) {
|
|
108
107
|
// This segment should have the locale replaced
|
|
109
|
-
|
|
108
|
+
// Create regex from pattern to match the actual path structure
|
|
109
|
+
const regexPattern = patternParts[i].replace(/\[locale\]/g, `(${locale.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`);
|
|
110
|
+
const regex = new RegExp(regexPattern);
|
|
111
|
+
pathParts[i] = pathParts[i].replace(regex, patternParts[i].replace(/\[locale\]/g, localeTag));
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
|
-
|
|
115
|
+
originalAbsolutePath = pathParts.join(path.sep);
|
|
114
116
|
}
|
|
115
117
|
// Convert back to absolute path
|
|
116
|
-
|
|
117
|
-
placeholderPaths.push(originalPath);
|
|
118
|
+
placeholderPaths.push(originalAbsolutePath);
|
|
118
119
|
});
|
|
119
120
|
}
|
|
120
121
|
return { resolvedPaths, placeholderPaths };
|
|
@@ -15,6 +15,7 @@ export async function createOrUpdateConfig(configFilepath, options) {
|
|
|
15
15
|
...(options.defaultLocale && { defaultLocale: options.defaultLocale }),
|
|
16
16
|
...(options.files && { files: options.files }),
|
|
17
17
|
...(options.framework && { framework: options.framework }),
|
|
18
|
+
...(options.baseUrl && { baseUrl: options.baseUrl }),
|
|
18
19
|
};
|
|
19
20
|
try {
|
|
20
21
|
// if file exists
|
|
@@ -6,11 +6,11 @@ export async function translate(settings, versionId) {
|
|
|
6
6
|
// timeout was validated earlier
|
|
7
7
|
const startTime = Date.now();
|
|
8
8
|
const timeout = parseInt(settings.timeout) * 1000;
|
|
9
|
-
const result = await waitForUpdates(settings.apiKey, settings.baseUrl, versionId, startTime, timeout);
|
|
9
|
+
const result = await waitForUpdates(settings.projectId, settings.apiKey, settings.baseUrl, versionId, startTime, timeout);
|
|
10
10
|
if (!result) {
|
|
11
11
|
process.exit(1);
|
|
12
12
|
}
|
|
13
|
-
const translations = await fetchTranslations(settings.baseUrl, settings.apiKey, versionId);
|
|
13
|
+
const translations = await fetchTranslations(settings.baseUrl, settings.projectId, settings.apiKey, versionId);
|
|
14
14
|
// Save translations to local directory if files.gt.output is provided
|
|
15
15
|
if (settings.files && isUsingLocalTranslations(settings)) {
|
|
16
16
|
await saveTranslations(translations, settings.files.placeholderPaths, 'JSX');
|
package/dist/types/index.d.ts
CHANGED
|
@@ -28,6 +28,9 @@ export type Options = {
|
|
|
28
28
|
dryRun: boolean;
|
|
29
29
|
timeout: string;
|
|
30
30
|
stageTranslations?: boolean;
|
|
31
|
+
experimentalLocalizeStaticUrls?: boolean;
|
|
32
|
+
experimentalHideDefaultLocale?: boolean;
|
|
33
|
+
experimentalFlattenJsonFiles?: boolean;
|
|
31
34
|
};
|
|
32
35
|
export type WrapOptions = {
|
|
33
36
|
src?: string[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createFileMapping } from '../formats/files/translate.js';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
export default async function flattenJsonFiles(settings) {
|
|
4
|
+
if (!settings.files ||
|
|
5
|
+
(Object.keys(settings.files.placeholderPaths).length === 1 &&
|
|
6
|
+
settings.files.placeholderPaths.gt)) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const { resolvedPaths: sourceFiles } = settings.files;
|
|
10
|
+
const fileMapping = createFileMapping(sourceFiles, settings.files.placeholderPaths, settings.files.transformPaths, settings.locales);
|
|
11
|
+
await Promise.all(Object.values(fileMapping).map(async (filesMap) => {
|
|
12
|
+
const targetFiles = Object.values(filesMap).filter((path) => path.endsWith('.json'));
|
|
13
|
+
await Promise.all(targetFiles.map(async (file) => {
|
|
14
|
+
// Read each json file
|
|
15
|
+
const json = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
16
|
+
// Flatten the json
|
|
17
|
+
const flattenedJson = flattenJson(json);
|
|
18
|
+
// Write the flattened json to the target file
|
|
19
|
+
await fs.promises.writeFile(file, JSON.stringify(flattenedJson, null, 2));
|
|
20
|
+
return flattenedJson;
|
|
21
|
+
}));
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
function flattenJson(json, prefix = '') {
|
|
25
|
+
const result = {};
|
|
26
|
+
for (const [key, value] of Object.entries(json)) {
|
|
27
|
+
const newKey = prefix ? `${prefix}.${key}` : key;
|
|
28
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
29
|
+
Object.assign(result, flattenJson(value, newKey));
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
result[newKey] = value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getAuthHeaders(projectId: string, apiKey: string): Record<string, string>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function getAuthHeaders(projectId, apiKey) {
|
|
2
|
+
const authHeaders = {
|
|
3
|
+
'x-gt-project-id': projectId,
|
|
4
|
+
};
|
|
5
|
+
if (apiKey) {
|
|
6
|
+
if (apiKey.startsWith('gtx-internal-')) {
|
|
7
|
+
authHeaders['x-gt-internal-api-key'] = apiKey;
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
authHeaders['x-gt-api-key'] = apiKey;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return authHeaders;
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Options, Settings } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Localizes static urls in content files.
|
|
4
|
+
* Currently only supported for md and mdx files. (/docs/ -> /[locale]/docs/)
|
|
5
|
+
* @param settings - The settings object containing the project configuration.
|
|
6
|
+
* @returns void
|
|
7
|
+
*
|
|
8
|
+
* @TODO This is an experimental feature, and only works in very specific cases. This needs to be improved before
|
|
9
|
+
* it can be enabled by default.
|
|
10
|
+
*
|
|
11
|
+
* Before this becomes a non-experimental feature, we need to:
|
|
12
|
+
* - Support more file types
|
|
13
|
+
* - Support more complex paths
|
|
14
|
+
*/
|
|
15
|
+
export default function localizeStaticUrls(settings: Settings & Options): Promise<void>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { createFileMapping } from '../formats/files/translate.js';
|
|
3
|
+
/**
|
|
4
|
+
* Localizes static urls in content files.
|
|
5
|
+
* Currently only supported for md and mdx files. (/docs/ -> /[locale]/docs/)
|
|
6
|
+
* @param settings - The settings object containing the project configuration.
|
|
7
|
+
* @returns void
|
|
8
|
+
*
|
|
9
|
+
* @TODO This is an experimental feature, and only works in very specific cases. This needs to be improved before
|
|
10
|
+
* it can be enabled by default.
|
|
11
|
+
*
|
|
12
|
+
* Before this becomes a non-experimental feature, we need to:
|
|
13
|
+
* - Support more file types
|
|
14
|
+
* - Support more complex paths
|
|
15
|
+
*/
|
|
16
|
+
export default async function localizeStaticUrls(settings) {
|
|
17
|
+
if (!settings.files ||
|
|
18
|
+
(Object.keys(settings.files.placeholderPaths).length === 1 &&
|
|
19
|
+
settings.files.placeholderPaths.gt)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const { resolvedPaths: sourceFiles } = settings.files;
|
|
23
|
+
const fileMapping = createFileMapping(sourceFiles, settings.files.placeholderPaths, settings.files.transformPaths, settings.locales);
|
|
24
|
+
// Process all file types at once with a single call
|
|
25
|
+
await Promise.all(Object.entries(fileMapping).map(async ([locale, filesMap]) => {
|
|
26
|
+
// Get all files that are md or mdx
|
|
27
|
+
const targetFiles = Object.values(filesMap).filter((path) => path.endsWith('.md') || path.endsWith('.mdx'));
|
|
28
|
+
// Replace the placeholder path with the target path
|
|
29
|
+
await Promise.all(targetFiles.map(async (filePath) => {
|
|
30
|
+
// Get file content
|
|
31
|
+
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
32
|
+
// Localize the file
|
|
33
|
+
const localizedFile = localizeStaticUrlsForFile(fileContent, settings.defaultLocale, locale, settings.experimentalHideDefaultLocale || false);
|
|
34
|
+
// Write the localized file to the target path
|
|
35
|
+
await fs.promises.writeFile(filePath, localizedFile);
|
|
36
|
+
}));
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
// Assumption: we will be seeing localized paths in the source files: (docs/en/ -> docs/ja/)
|
|
40
|
+
function localizeStaticUrlsForFile(file, defaultLocale, targetLocale, hideDefaultLocale) {
|
|
41
|
+
// 1. Search for all instances of:
|
|
42
|
+
let regex;
|
|
43
|
+
if (hideDefaultLocale) {
|
|
44
|
+
// Match complete markdown links: `](/docs/...)` or `](/docs)`
|
|
45
|
+
regex = new RegExp(`\\]\\(/docs(?:/([^)]*))?\\)`, 'g');
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Match complete markdown links with default locale: `](/docs/${defaultLocale}/...)` or `](/docs/${defaultLocale})`
|
|
49
|
+
regex = new RegExp(`\\]\\(/docs/${defaultLocale}(?:/([^)]*))?\\)`, 'g');
|
|
50
|
+
}
|
|
51
|
+
const matches = file.match(regex);
|
|
52
|
+
if (!matches) {
|
|
53
|
+
return file;
|
|
54
|
+
}
|
|
55
|
+
// 2. Replace the default locale with the target locale in all matched instances
|
|
56
|
+
const localizedFile = file.replace(regex, (match, pathContent) => {
|
|
57
|
+
if (hideDefaultLocale) {
|
|
58
|
+
// For hideDefaultLocale, check if path already has target locale
|
|
59
|
+
if (pathContent) {
|
|
60
|
+
if (pathContent.startsWith(`${targetLocale}/`) ||
|
|
61
|
+
pathContent === targetLocale) {
|
|
62
|
+
return match; // Already localized
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Add target locale to the path
|
|
66
|
+
if (!pathContent || pathContent === '') {
|
|
67
|
+
return `](/docs/${targetLocale})`;
|
|
68
|
+
}
|
|
69
|
+
return `](/docs/${targetLocale}/${pathContent})`;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// For non-hideDefaultLocale, replace defaultLocale with targetLocale
|
|
73
|
+
// pathContent contains everything after the default locale (no leading slash if present)
|
|
74
|
+
return `](/docs/${targetLocale}${pathContent ? '/' + pathContent : ''})`;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return localizedFile;
|
|
78
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Processes content to escape curl commands within tick marks and existing escape strings
|
|
3
|
+
* @param content - The content to process
|
|
4
|
+
* @returns the processed content with escaped curl commands
|
|
5
|
+
*/
|
|
6
|
+
export default function sanitizeFileContent(content: string): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Processes content to escape curl commands within tick marks and existing escape strings
|
|
3
|
+
* @param content - The content to process
|
|
4
|
+
* @returns the processed content with escaped curl commands
|
|
5
|
+
*/
|
|
6
|
+
export default function sanitizeFileContent(content) {
|
|
7
|
+
const ESCAPE_STRING = '_GT_INTERNAL_ESCAPE';
|
|
8
|
+
const allTickMarkRegex = /`([^`]*)`/g;
|
|
9
|
+
let processedContent = content;
|
|
10
|
+
// First, escape any existing tick marks followed by _GT_INTERNAL_ESCAPE
|
|
11
|
+
// This protects pre-existing escapes
|
|
12
|
+
processedContent = processedContent.replace(new RegExp('`' + ESCAPE_STRING, 'g'), '`' + ESCAPE_STRING + ESCAPE_STRING);
|
|
13
|
+
// Then find ALL tick mark pairs and process them individually
|
|
14
|
+
// This approach is more reliable than negative lookahead with modified content
|
|
15
|
+
processedContent = processedContent.replace(allTickMarkRegex, (match, innerContent) => {
|
|
16
|
+
// Skip if this already starts with our escape string (protected or already processed)
|
|
17
|
+
if (innerContent.startsWith(ESCAPE_STRING)) {
|
|
18
|
+
return match;
|
|
19
|
+
}
|
|
20
|
+
// Check if the content contains a curl command
|
|
21
|
+
if (/\bcurl\b/i.test(innerContent)) {
|
|
22
|
+
// Insert escape string after opening tick
|
|
23
|
+
return '`' + ESCAPE_STRING + innerContent + '`';
|
|
24
|
+
}
|
|
25
|
+
// Return original match if no curl command found
|
|
26
|
+
return match;
|
|
27
|
+
});
|
|
28
|
+
return processedContent;
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gtx-cli",
|
|
3
|
-
"version": "1.2.30-alpha.
|
|
3
|
+
"version": "1.2.30-alpha.34",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": "dist/main.js",
|
|
6
6
|
"files": [
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"esbuild": "^0.25.4",
|
|
88
88
|
"fast-glob": "^3.3.3",
|
|
89
89
|
"form-data": "^4.0.2",
|
|
90
|
-
"generaltranslation": "^7.0.0-alpha.
|
|
90
|
+
"generaltranslation": "^7.0.0-alpha.34",
|
|
91
91
|
"open": "^10.1.1",
|
|
92
92
|
"ora": "^8.2.0",
|
|
93
93
|
"resolve": "^1.22.10",
|