gtx-cli 2.0.9 → 2.0.10
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/cli/base.d.ts +1 -0
- package/dist/cli/base.js +11 -0
- package/dist/cli/react.js +1 -0
- package/dist/config/generateSettings.js +11 -0
- package/dist/config/optionPresets.js +16 -0
- package/dist/formats/json/utils.js +1 -1
- package/dist/fs/copyFile.d.ts +8 -0
- package/dist/fs/copyFile.js +39 -0
- package/dist/types/index.d.ts +10 -5
- package/dist/utils/localizeStaticImports.d.ts +15 -0
- package/dist/utils/localizeStaticImports.js +93 -0
- package/dist/utils/localizeStaticUrls.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# gtx-cli
|
|
2
2
|
|
|
3
|
+
## 2.0.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#499](https://github.com/generaltranslation/gt/pull/499) [`0793ef7`](https://github.com/generaltranslation/gt/commit/0793ef7f0d5b391805d072ff0c251fe43fa58b29) Thanks [@ErnestM1234](https://github.com/ErnestM1234)! - feat: add localization for imports
|
|
8
|
+
|
|
3
9
|
## 2.0.9
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/dist/cli/base.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type TranslateOptions = {
|
|
|
10
10
|
experimentalLocalizeStaticUrls?: boolean;
|
|
11
11
|
experimentalHideDefaultLocale?: boolean;
|
|
12
12
|
experimentalFlattenJsonFiles?: boolean;
|
|
13
|
+
experimentalLocalizeStaticImports?: boolean;
|
|
13
14
|
};
|
|
14
15
|
export type LoginOptions = {
|
|
15
16
|
keyType?: 'development' | 'production';
|
package/dist/cli/base.js
CHANGED
|
@@ -16,6 +16,8 @@ import { retrieveCredentials, setCredentials } from '../utils/credentials.js';
|
|
|
16
16
|
import { areCredentialsSet } from '../utils/credentials.js';
|
|
17
17
|
import localizeStaticUrls from '../utils/localizeStaticUrls.js';
|
|
18
18
|
import flattenJsonFiles from '../utils/flattenJsonFiles.js';
|
|
19
|
+
import localizeStaticImports from '../utils/localizeStaticImports.js';
|
|
20
|
+
import copyFile from '../fs/copyFile.js';
|
|
19
21
|
export class BaseCLI {
|
|
20
22
|
library;
|
|
21
23
|
additionalModules;
|
|
@@ -54,6 +56,7 @@ export class BaseCLI {
|
|
|
54
56
|
.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)
|
|
55
57
|
.option('--experimental-hide-default-locale', 'When localizing static locales, hide the default locale from the path', false)
|
|
56
58
|
.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)
|
|
59
|
+
.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)
|
|
57
60
|
.action(async (initOptions) => {
|
|
58
61
|
displayHeader('Starting translation...');
|
|
59
62
|
const settings = await generateSettings(initOptions);
|
|
@@ -181,10 +184,18 @@ See the docs for more information: https://generaltranslation.com/docs/react/tut
|
|
|
181
184
|
if (settings.experimentalLocalizeStaticUrls) {
|
|
182
185
|
await localizeStaticUrls(settings);
|
|
183
186
|
}
|
|
187
|
+
// Localize static imports (/docs -> /[locale]/docs)
|
|
188
|
+
if (settings.experimentalLocalizeStaticImports) {
|
|
189
|
+
await localizeStaticImports(settings);
|
|
190
|
+
}
|
|
184
191
|
// Flatten json files into a single file
|
|
185
192
|
if (settings.experimentalFlattenJsonFiles) {
|
|
186
193
|
await flattenJsonFiles(settings);
|
|
187
194
|
}
|
|
195
|
+
// Copy files to the target locale
|
|
196
|
+
if (settings.options?.copyFiles) {
|
|
197
|
+
await copyFile(settings);
|
|
198
|
+
}
|
|
188
199
|
}
|
|
189
200
|
async handleSetupReactCommand(options) {
|
|
190
201
|
await handleSetupReactCommand(options);
|
package/dist/cli/react.js
CHANGED
|
@@ -78,6 +78,7 @@ export class ReactCLI extends BaseCLI {
|
|
|
78
78
|
.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)
|
|
79
79
|
.option('--experimental-hide-default-locale', 'When localizing static locales, hide the default locale from the path', false)
|
|
80
80
|
.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)
|
|
81
|
+
.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)
|
|
81
82
|
.action(async (options) => {
|
|
82
83
|
displayHeader('Translating project...');
|
|
83
84
|
await this.handleTranslate(options);
|
|
@@ -64,6 +64,17 @@ export async function generateSettings(options, cwd = process.cwd()) {
|
|
|
64
64
|
!options.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 (options.options?.docsImportPattern &&
|
|
68
|
+
!options.options?.docsImportPattern.includes('[locale]')) {
|
|
69
|
+
logErrorAndExit('Failed to localize static imports: Import pattern must include "[locale]" to denote the location of the locale');
|
|
70
|
+
}
|
|
71
|
+
if (options.options?.copyFiles) {
|
|
72
|
+
for (const file of options.options.copyFiles) {
|
|
73
|
+
if (!file.includes('[locale]')) {
|
|
74
|
+
logErrorAndExit('Failed to copy files: File path must include "[locale]" to denote the location of the locale');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
67
78
|
// merge options
|
|
68
79
|
const mergedOptions = { ...gtConfig, ...options };
|
|
69
80
|
// merge locales
|
|
@@ -21,6 +21,22 @@ export function generatePreset(preset) {
|
|
|
21
21
|
},
|
|
22
22
|
},
|
|
23
23
|
},
|
|
24
|
+
// Enable this when support multiple language objects in array
|
|
25
|
+
// '$.redirects': {
|
|
26
|
+
// type: 'array',
|
|
27
|
+
// key: '$.language',
|
|
28
|
+
// include: [],
|
|
29
|
+
// transform: {
|
|
30
|
+
// '$.source': {
|
|
31
|
+
// match: '^/{locale}/(.*)$',
|
|
32
|
+
// replace: '/{locale}/$1',
|
|
33
|
+
// },
|
|
34
|
+
// '$.destination': {
|
|
35
|
+
// match: '^/{locale}/(.*)$',
|
|
36
|
+
// replace: '/{locale}/$1',
|
|
37
|
+
// },
|
|
38
|
+
// },
|
|
39
|
+
// },
|
|
24
40
|
},
|
|
25
41
|
};
|
|
26
42
|
default:
|
|
@@ -29,7 +29,7 @@ export function findMatchingItemArray(locale, sourceObjectOptions, sourceObjectP
|
|
|
29
29
|
exit(1);
|
|
30
30
|
}
|
|
31
31
|
// Validate the key is the identifying locale property
|
|
32
|
-
if (!keyCandidates
|
|
32
|
+
if (!keyCandidates.length ||
|
|
33
33
|
identifyingLocaleProperty !== keyCandidates[0].value) {
|
|
34
34
|
continue;
|
|
35
35
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Settings } from '../types/index.js';
|
|
2
|
+
import { TranslateOptions } from '../cli/base.js';
|
|
3
|
+
/**
|
|
4
|
+
* Copy a file to target locale without translation
|
|
5
|
+
*
|
|
6
|
+
* This is a naive approach, does not allow for wild cards
|
|
7
|
+
*/
|
|
8
|
+
export default function copyFile(settings: Settings & TranslateOptions): Promise<void>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { logError } from '../console/logging.js';
|
|
4
|
+
/**
|
|
5
|
+
* Copy a file to target locale without translation
|
|
6
|
+
*
|
|
7
|
+
* This is a naive approach, does not allow for wild cards
|
|
8
|
+
*/
|
|
9
|
+
export default async function copyFile(settings) {
|
|
10
|
+
if (!settings.options?.copyFiles || settings.options.copyFiles.length === 0) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
// Construct a map of source paths to target paths
|
|
14
|
+
const copyFiles = settings.options.copyFiles.reduce((paths, filePathTemplate) => {
|
|
15
|
+
const sourcePath = path.join(process.cwd(), filePathTemplate.replace('[locale]', settings.defaultLocale));
|
|
16
|
+
if (!fs.existsSync(sourcePath)) {
|
|
17
|
+
logError(`Failed to copy files: File path does not exist: ${sourcePath}`);
|
|
18
|
+
return paths;
|
|
19
|
+
}
|
|
20
|
+
paths[sourcePath] = [];
|
|
21
|
+
for (const locale of settings.locales) {
|
|
22
|
+
if (locale === settings.defaultLocale)
|
|
23
|
+
continue;
|
|
24
|
+
const targetPath = path.join(process.cwd(), filePathTemplate.replace('[locale]', locale));
|
|
25
|
+
paths[sourcePath].push(targetPath);
|
|
26
|
+
}
|
|
27
|
+
return paths;
|
|
28
|
+
}, {});
|
|
29
|
+
// Copy each file to the target locale
|
|
30
|
+
for (const sourcePath in copyFiles) {
|
|
31
|
+
for (const targetPath of copyFiles[sourcePath]) {
|
|
32
|
+
// Ensure the target directory exists
|
|
33
|
+
const targetDir = path.dirname(targetPath);
|
|
34
|
+
await fs.promises.mkdir(targetDir, { recursive: true });
|
|
35
|
+
// Copy the file
|
|
36
|
+
await fs.promises.copyFile(sourcePath, targetPath);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export type Options = {
|
|
|
20
20
|
experimentalLocalizeStaticUrls?: boolean;
|
|
21
21
|
experimentalHideDefaultLocale?: boolean;
|
|
22
22
|
experimentalFlattenJsonFiles?: boolean;
|
|
23
|
+
experimentalLocalizeStaticImports?: boolean;
|
|
23
24
|
};
|
|
24
25
|
export type WrapOptions = {
|
|
25
26
|
src?: string[];
|
|
@@ -99,6 +100,9 @@ export type AdditionalOptions = {
|
|
|
99
100
|
[fileGlob: string]: JsonSchema;
|
|
100
101
|
};
|
|
101
102
|
docsUrlPattern?: string;
|
|
103
|
+
docsImportPattern?: string;
|
|
104
|
+
docsHideDefaultLocaleImport?: boolean;
|
|
105
|
+
copyFiles?: string[];
|
|
102
106
|
};
|
|
103
107
|
export type JsonSchema = {
|
|
104
108
|
preset?: 'mintlify';
|
|
@@ -112,10 +116,11 @@ export type SourceObjectOptions = {
|
|
|
112
116
|
include: string[];
|
|
113
117
|
key?: string;
|
|
114
118
|
localeProperty?: string;
|
|
115
|
-
transform?:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
transform?: TransformOptions;
|
|
120
|
+
};
|
|
121
|
+
export type TransformOptions = {
|
|
122
|
+
[transformPath: string]: {
|
|
123
|
+
match?: string;
|
|
124
|
+
replace: string;
|
|
120
125
|
};
|
|
121
126
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Options, Settings } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Localizes static imports 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 localizeStaticImports(settings: Omit<Settings & Options, 'ignoreErrors' | 'suppressWarnings' | 'timeout'>): Promise<void>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { createFileMapping } from '../formats/files/translate.js';
|
|
3
|
+
import { logError } from '../console/logging.js';
|
|
4
|
+
/**
|
|
5
|
+
* Localizes static imports in content files.
|
|
6
|
+
* Currently only supported for md and mdx files. (/docs/ -> /[locale]/docs/)
|
|
7
|
+
* @param settings - The settings object containing the project configuration.
|
|
8
|
+
* @returns void
|
|
9
|
+
*
|
|
10
|
+
* @TODO This is an experimental feature, and only works in very specific cases. This needs to be improved before
|
|
11
|
+
* it can be enabled by default.
|
|
12
|
+
*
|
|
13
|
+
* Before this becomes a non-experimental feature, we need to:
|
|
14
|
+
* - Support more file types
|
|
15
|
+
* - Support more complex paths
|
|
16
|
+
*/
|
|
17
|
+
export default async function localizeStaticImports(settings) {
|
|
18
|
+
if (!settings.files ||
|
|
19
|
+
(Object.keys(settings.files.placeholderPaths).length === 1 &&
|
|
20
|
+
settings.files.placeholderPaths.gt)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const { resolvedPaths: sourceFiles } = settings.files;
|
|
24
|
+
const fileMapping = createFileMapping(sourceFiles, settings.files.placeholderPaths, settings.files.transformPaths, settings.locales);
|
|
25
|
+
// Process all file types at once with a single call
|
|
26
|
+
await Promise.all(Object.entries(fileMapping).map(async ([locale, filesMap]) => {
|
|
27
|
+
// Get all files that are md or mdx
|
|
28
|
+
const targetFiles = Object.values(filesMap).filter((path) => path.endsWith('.md') || path.endsWith('.mdx'));
|
|
29
|
+
// Replace the placeholder path with the target path
|
|
30
|
+
await Promise.all(targetFiles.map(async (filePath) => {
|
|
31
|
+
// Get file content
|
|
32
|
+
const fileContent = await fs.promises.readFile(filePath, 'utf8');
|
|
33
|
+
// Localize the file
|
|
34
|
+
const localizedFile = localizeStaticImportsForFile(fileContent, settings.defaultLocale, locale, settings.options?.docsHideDefaultLocaleImport || false, settings.options?.docsImportPattern);
|
|
35
|
+
// Write the localized file to the target path
|
|
36
|
+
await fs.promises.writeFile(filePath, localizedFile);
|
|
37
|
+
}));
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
// Naive find and replace, in the future, construct an AST
|
|
41
|
+
function localizeStaticImportsForFile(file, defaultLocale, targetLocale, hideDefaultLocale, pattern = '/[locale]' // eg /docs/[locale] or /[locale]
|
|
42
|
+
) {
|
|
43
|
+
if (!pattern.startsWith('/')) {
|
|
44
|
+
pattern = '/' + pattern;
|
|
45
|
+
}
|
|
46
|
+
// 1. Search for all instances of:
|
|
47
|
+
const patternHead = pattern.split('[locale]')[0];
|
|
48
|
+
let regex;
|
|
49
|
+
if (hideDefaultLocale) {
|
|
50
|
+
const trimmedPatternHead = patternHead.endsWith('/')
|
|
51
|
+
? patternHead.slice(0, -1)
|
|
52
|
+
: patternHead;
|
|
53
|
+
// Match complete markdown links: `import { Foo } from '@/docs/[locale]/foo.md'`
|
|
54
|
+
regex = new RegExp(`import\\s+(.*?)\\s+from\\s+(["'])${trimmedPatternHead}(.*?)\\2`, 'g');
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Match complete markdown links with default locale: `import { Foo } from '@/docs/${defaultLocale}/foo.md'`
|
|
58
|
+
regex = new RegExp(`import\\s+(.*?)\\s+from\\s+(["'])${patternHead}${defaultLocale}(.*?)\\2`, 'g');
|
|
59
|
+
}
|
|
60
|
+
const matches = file.match(regex);
|
|
61
|
+
if (!matches) {
|
|
62
|
+
return file;
|
|
63
|
+
}
|
|
64
|
+
// 2. Replace the default locale with the target locale in all matched instances
|
|
65
|
+
const localizedFile = file.replace(regex, (match, bindings, quoteType, pathContent) => {
|
|
66
|
+
// get the quote type
|
|
67
|
+
quoteType = match.match(/["']/)?.[0] || '"';
|
|
68
|
+
if (!quoteType) {
|
|
69
|
+
logError(`Failed to localize static imports: Import pattern must include quotes in ${pattern}`);
|
|
70
|
+
return match;
|
|
71
|
+
}
|
|
72
|
+
if (hideDefaultLocale) {
|
|
73
|
+
// For hideDefaultLocale, check if path already has target locale
|
|
74
|
+
if (pathContent) {
|
|
75
|
+
if (pathContent.startsWith(`${targetLocale}/`) ||
|
|
76
|
+
pathContent === targetLocale) {
|
|
77
|
+
return match; // Already localized
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Add target locale to the path
|
|
81
|
+
if (!pathContent || pathContent === '') {
|
|
82
|
+
return `import ${bindings} from ${quoteType}${patternHead}${targetLocale}${quoteType}`;
|
|
83
|
+
}
|
|
84
|
+
return `import ${bindings} from ${quoteType}${patternHead}${targetLocale}${pathContent}${quoteType}`;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// For non-hideDefaultLocale, replace defaultLocale with targetLocale
|
|
88
|
+
// pathContent contains everything after the default locale (no leading slash if present)
|
|
89
|
+
return `import ${bindings} from ${quoteType}${patternHead}${targetLocale}${pathContent}${quoteType}`;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
return localizedFile;
|
|
93
|
+
}
|
|
@@ -28,7 +28,7 @@ export default async function localizeStaticUrls(settings) {
|
|
|
28
28
|
// Replace the placeholder path with the target path
|
|
29
29
|
await Promise.all(targetFiles.map(async (filePath) => {
|
|
30
30
|
// Get file content
|
|
31
|
-
const fileContent = fs.
|
|
31
|
+
const fileContent = await fs.promises.readFile(filePath, 'utf8');
|
|
32
32
|
// Localize the file
|
|
33
33
|
const localizedFile = localizeStaticUrlsForFile(fileContent, settings.defaultLocale, locale, settings.experimentalHideDefaultLocale || false, settings.options?.docsUrlPattern);
|
|
34
34
|
// Write the localized file to the target path
|