gtx-cli 2.1.6 → 2.1.7
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
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# gtx-cli
|
|
2
2
|
|
|
3
|
+
## 2.1.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#579](https://github.com/generaltranslation/gt/pull/579) [`a485533`](https://github.com/generaltranslation/gt/commit/a4855336dfe5242cfdb24fd2e981f86b0bffdf05) Thanks [@SamEggert](https://github.com/SamEggert)! - fix localize static urls, add baseDomain functionality
|
|
8
|
+
|
|
3
9
|
## 2.1.6
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -3,7 +3,6 @@ import { createFileMapping } from '../../formats/files/fileMapping.js';
|
|
|
3
3
|
import { logError } from '../../console/logging.js';
|
|
4
4
|
import { getStagedVersions } from '../../fs/config/updateVersions.js';
|
|
5
5
|
import copyFile from '../../fs/copyFile.js';
|
|
6
|
-
import localizeStaticImports from '../../utils/localizeStaticImports.js';
|
|
7
6
|
import flattenJsonFiles from '../../utils/flattenJsonFiles.js';
|
|
8
7
|
import localizeStaticUrls from '../../utils/localizeStaticUrls.js';
|
|
9
8
|
import { noFilesError, noVersionIdError } from '../../console/index.js';
|
|
@@ -35,13 +34,13 @@ export async function handleDownload(options, settings) {
|
|
|
35
34
|
await checkFileTranslations(stagedVersionData, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale][sourcePath] ?? null, settings);
|
|
36
35
|
}
|
|
37
36
|
export async function postProcessTranslations(settings) {
|
|
38
|
-
// Localize static urls (/docs -> /[locale]/docs)
|
|
37
|
+
// Localize static urls (/docs -> /[locale]/docs) for non-default locales only
|
|
38
|
+
// Default locale is processed earlier in the flow in base.ts
|
|
39
39
|
if (settings.options?.experimentalLocalizeStaticUrls) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
await localizeStaticImports(settings);
|
|
40
|
+
const nonDefaultLocales = settings.locales.filter((locale) => locale !== settings.defaultLocale);
|
|
41
|
+
if (nonDefaultLocales.length > 0) {
|
|
42
|
+
await localizeStaticUrls(settings, nonDefaultLocales);
|
|
43
|
+
}
|
|
45
44
|
}
|
|
46
45
|
// Flatten json files into a single file
|
|
47
46
|
if (settings.options?.experimentalFlattenJsonFiles) {
|
|
@@ -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 =
|
|
107
|
+
mergedOptions.publish = (gtConfig.publish || options.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
|
package/dist/types/index.d.ts
CHANGED
|
@@ -141,6 +141,7 @@ export type AdditionalOptions = {
|
|
|
141
141
|
experimentalLocalizeStaticUrls?: boolean;
|
|
142
142
|
experimentalHideDefaultLocale?: boolean;
|
|
143
143
|
experimentalFlattenJsonFiles?: boolean;
|
|
144
|
+
baseDomain?: string;
|
|
144
145
|
};
|
|
145
146
|
export type JsonSchema = {
|
|
146
147
|
preset?: 'mintlify';
|
|
@@ -12,4 +12,8 @@ import { Settings } from '../types/index.js';
|
|
|
12
12
|
* - Support more file types
|
|
13
13
|
* - Support more complex paths
|
|
14
14
|
*/
|
|
15
|
-
export default function localizeStaticUrls(settings: Settings): Promise<void>;
|
|
15
|
+
export default function localizeStaticUrls(settings: Settings, targetLocales?: string[]): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Main URL transformation function that delegates to specific scenarios
|
|
18
|
+
*/
|
|
19
|
+
export declare function transformUrlPath(originalUrl: string, patternHead: string, targetLocale: string, defaultLocale: string, hideDefaultLocale: boolean): string | null;
|
|
@@ -21,19 +21,24 @@ const { isMatch } = micromatch;
|
|
|
21
21
|
* - Support more file types
|
|
22
22
|
* - Support more complex paths
|
|
23
23
|
*/
|
|
24
|
-
export default async function localizeStaticUrls(settings) {
|
|
24
|
+
export default async function localizeStaticUrls(settings, targetLocales) {
|
|
25
25
|
if (!settings.files ||
|
|
26
26
|
(Object.keys(settings.files.placeholderPaths).length === 1 &&
|
|
27
27
|
settings.files.placeholderPaths.gt)) {
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
30
|
const { resolvedPaths: sourceFiles } = settings.files;
|
|
31
|
-
|
|
31
|
+
// Use filtered locales if provided, otherwise use all locales
|
|
32
|
+
const locales = targetLocales || settings.locales;
|
|
33
|
+
const fileMapping = createFileMapping(sourceFiles, settings.files.placeholderPaths, settings.files.transformPaths, settings.locales, // Always use all locales for mapping, filter later
|
|
34
|
+
settings.defaultLocale);
|
|
32
35
|
// Process all file types at once with a single call
|
|
33
36
|
const processPromises = [];
|
|
34
37
|
// First, process default locale files (from source files)
|
|
35
38
|
// This is needed because they might not be in the fileMapping if they're not being translated
|
|
36
|
-
if
|
|
39
|
+
// Only process default locale if it's in the target locales filter
|
|
40
|
+
if (!fileMapping[settings.defaultLocale] &&
|
|
41
|
+
locales.includes(settings.defaultLocale)) {
|
|
37
42
|
const defaultLocaleFiles = [];
|
|
38
43
|
// Collect all .md and .mdx files from sourceFiles
|
|
39
44
|
if (sourceFiles.md) {
|
|
@@ -47,16 +52,20 @@ export default async function localizeStaticUrls(settings) {
|
|
|
47
52
|
// Get file content
|
|
48
53
|
const fileContent = await fs.promises.readFile(filePath, 'utf8');
|
|
49
54
|
// Localize the file using default locale
|
|
50
|
-
const
|
|
51
|
-
settings.options?.experimentalHideDefaultLocale || false, settings.options?.docsUrlPattern, settings.options?.excludeStaticUrls);
|
|
52
|
-
//
|
|
53
|
-
|
|
55
|
+
const result = localizeStaticUrlsForFile(fileContent, settings.defaultLocale, settings.defaultLocale, // Process as default locale
|
|
56
|
+
settings.options?.experimentalHideDefaultLocale || false, settings.options?.docsUrlPattern, settings.options?.excludeStaticUrls, settings.options?.baseDomain);
|
|
57
|
+
// Only write the file if there were changes
|
|
58
|
+
if (result.hasChanges) {
|
|
59
|
+
await fs.promises.writeFile(filePath, result.content);
|
|
60
|
+
}
|
|
54
61
|
}));
|
|
55
62
|
processPromises.push(defaultPromise);
|
|
56
63
|
}
|
|
57
64
|
}
|
|
58
65
|
// Then process all other locales from fileMapping
|
|
59
|
-
const mappingPromises = Object.entries(fileMapping)
|
|
66
|
+
const mappingPromises = Object.entries(fileMapping)
|
|
67
|
+
.filter(([locale, filesMap]) => locales.includes(locale)) // Filter by target locales
|
|
68
|
+
.map(async ([locale, filesMap]) => {
|
|
60
69
|
// Get all files that are md or mdx
|
|
61
70
|
const targetFiles = Object.values(filesMap).filter((path) => path.endsWith('.md') || path.endsWith('.mdx'));
|
|
62
71
|
// Replace the placeholder path with the target path
|
|
@@ -64,9 +73,11 @@ export default async function localizeStaticUrls(settings) {
|
|
|
64
73
|
// Get file content
|
|
65
74
|
const fileContent = await fs.promises.readFile(filePath, 'utf8');
|
|
66
75
|
// Localize the file (handles both URLs and hrefs in single AST pass)
|
|
67
|
-
const
|
|
68
|
-
//
|
|
69
|
-
|
|
76
|
+
const result = localizeStaticUrlsForFile(fileContent, settings.defaultLocale, locale, settings.options?.experimentalHideDefaultLocale || false, settings.options?.docsUrlPattern, settings.options?.excludeStaticUrls, settings.options?.baseDomain);
|
|
77
|
+
// Only write the file if there were changes
|
|
78
|
+
if (result.hasChanges) {
|
|
79
|
+
await fs.promises.writeFile(filePath, result.content);
|
|
80
|
+
}
|
|
70
81
|
}));
|
|
71
82
|
});
|
|
72
83
|
processPromises.push(...mappingPromises);
|
|
@@ -75,21 +86,28 @@ export default async function localizeStaticUrls(settings) {
|
|
|
75
86
|
/**
|
|
76
87
|
* Determines if a URL should be processed based on pattern matching
|
|
77
88
|
*/
|
|
78
|
-
function shouldProcessUrl(originalUrl, patternHead, targetLocale, defaultLocale) {
|
|
79
|
-
// Skip absolute URLs (http://, https://, //, etc.)
|
|
80
|
-
if (originalUrl.includes(':')) {
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
89
|
+
function shouldProcessUrl(originalUrl, patternHead, targetLocale, defaultLocale, baseDomain) {
|
|
83
90
|
const patternWithoutSlash = patternHead.replace(/\/$/, '');
|
|
91
|
+
// Handle absolute URLs with baseDomain
|
|
92
|
+
let urlToCheck = originalUrl;
|
|
93
|
+
if (baseDomain && originalUrl.startsWith(baseDomain)) {
|
|
94
|
+
urlToCheck = originalUrl.substring(baseDomain.length);
|
|
95
|
+
}
|
|
84
96
|
if (targetLocale === defaultLocale) {
|
|
85
97
|
// For default locale processing, check if URL contains the pattern
|
|
86
|
-
return
|
|
98
|
+
return urlToCheck.includes(patternWithoutSlash);
|
|
87
99
|
}
|
|
88
100
|
else {
|
|
89
101
|
// For non-default locales, check if URL starts with pattern
|
|
90
|
-
return
|
|
102
|
+
return urlToCheck.startsWith(patternWithoutSlash);
|
|
91
103
|
}
|
|
92
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Determines if a URL should be processed based on the base domain
|
|
107
|
+
*/
|
|
108
|
+
function shouldProcessAbsoluteUrl(originalUrl, baseDomain) {
|
|
109
|
+
return originalUrl.startsWith(baseDomain);
|
|
110
|
+
}
|
|
93
111
|
/**
|
|
94
112
|
* Checks if a URL should be excluded based on exclusion patterns
|
|
95
113
|
*/
|
|
@@ -98,102 +116,79 @@ function isUrlExcluded(originalUrl, exclude, defaultLocale) {
|
|
|
98
116
|
return excludePatterns.some((pattern) => isMatch(originalUrl, pattern));
|
|
99
117
|
}
|
|
100
118
|
/**
|
|
101
|
-
*
|
|
102
|
-
*/
|
|
103
|
-
function transformDefaultLocaleUrl(originalUrl, patternHead, defaultLocale, hideDefaultLocale) {
|
|
104
|
-
if (hideDefaultLocale) {
|
|
105
|
-
// Remove locale from URLs that have it: '/docs/en/file' -> '/docs/file'
|
|
106
|
-
if (originalUrl.includes(`/${defaultLocale}/`)) {
|
|
107
|
-
return originalUrl.replace(`/${defaultLocale}/`, '/');
|
|
108
|
-
}
|
|
109
|
-
else if (originalUrl.endsWith(`/${defaultLocale}`)) {
|
|
110
|
-
return originalUrl.replace(`/${defaultLocale}`, '');
|
|
111
|
-
}
|
|
112
|
-
return null; // URL doesn't have default locale
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
// Add locale to URLs that don't have it: '/docs/file' -> '/docs/en/file'
|
|
116
|
-
if (originalUrl.includes(`/${defaultLocale}/`) ||
|
|
117
|
-
originalUrl.endsWith(`/${defaultLocale}`)) {
|
|
118
|
-
return null; // Already has default locale
|
|
119
|
-
}
|
|
120
|
-
if (originalUrl.startsWith(patternHead)) {
|
|
121
|
-
const pathAfterHead = originalUrl.slice(patternHead.length);
|
|
122
|
-
if (pathAfterHead) {
|
|
123
|
-
return `${patternHead}${defaultLocale}/${pathAfterHead}`;
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
return `${patternHead.replace(/\/$/, '')}/${defaultLocale}`;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return null; // URL doesn't match pattern
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Transforms URL for non-default locale processing with hideDefaultLocale=true
|
|
119
|
+
* Main URL transformation function that delegates to specific scenarios
|
|
134
120
|
*/
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
121
|
+
export function transformUrlPath(originalUrl, patternHead, targetLocale, defaultLocale, hideDefaultLocale) {
|
|
122
|
+
const originalPathArray = originalUrl
|
|
123
|
+
.split('/')
|
|
124
|
+
.filter((path) => path !== '');
|
|
125
|
+
const patternHeadArray = patternHead.split('/').filter((path) => path !== '');
|
|
126
|
+
// check if the pattern head matches the original path
|
|
127
|
+
if (!checkIfPathMatchesPattern(originalPathArray, patternHeadArray)) {
|
|
139
128
|
return null;
|
|
140
129
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (originalUrl.startsWith(`${expectedPathWithDefaultLocale}/`) ||
|
|
144
|
-
originalUrl === expectedPathWithDefaultLocale) {
|
|
145
|
-
return originalUrl.replace(`${patternHead}${defaultLocale}`, `${patternHead}${targetLocale}`);
|
|
146
|
-
}
|
|
147
|
-
// Handle exact pattern match
|
|
148
|
-
if (originalUrl === patternHead.replace(/\/$/, '')) {
|
|
149
|
-
return `${patternHead.replace(/\/$/, '')}/${targetLocale}`;
|
|
130
|
+
if (patternHeadArray.length > originalPathArray.length) {
|
|
131
|
+
return null; // Pattern is longer than the URL path
|
|
150
132
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// Replace existing default locale with target locale
|
|
165
|
-
return originalUrl.replace(`${patternHead}${defaultLocale}`, `${patternHead}${targetLocale}`);
|
|
166
|
-
}
|
|
167
|
-
else if (originalUrl.startsWith(patternHead)) {
|
|
168
|
-
// Add target locale to URL that doesn't have any locale
|
|
169
|
-
const pathAfterHead = originalUrl.slice(patternHead.length);
|
|
170
|
-
if (pathAfterHead) {
|
|
171
|
-
return `${patternHead}${targetLocale}/${pathAfterHead}`;
|
|
133
|
+
let result = null;
|
|
134
|
+
if (targetLocale === defaultLocale) {
|
|
135
|
+
if (hideDefaultLocale) {
|
|
136
|
+
// check if default locale is already present
|
|
137
|
+
if (originalPathArray?.[patternHeadArray.length] !== defaultLocale) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
// remove default locale
|
|
141
|
+
const newPathArray = [
|
|
142
|
+
...originalPathArray.slice(0, patternHeadArray.length),
|
|
143
|
+
...originalPathArray.slice(patternHeadArray.length + 1),
|
|
144
|
+
];
|
|
145
|
+
result = newPathArray.join('/');
|
|
172
146
|
}
|
|
173
147
|
else {
|
|
174
|
-
|
|
148
|
+
// check if default locale is already present
|
|
149
|
+
if (originalPathArray?.[patternHeadArray.length] === defaultLocale) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
// insert default locale
|
|
153
|
+
const newPathArray = [
|
|
154
|
+
...originalPathArray.slice(0, patternHeadArray.length),
|
|
155
|
+
defaultLocale,
|
|
156
|
+
...originalPathArray.slice(patternHeadArray.length),
|
|
157
|
+
];
|
|
158
|
+
result = newPathArray.join('/');
|
|
175
159
|
}
|
|
176
160
|
}
|
|
177
|
-
return null; // URL doesn't match pattern
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Main URL transformation function that delegates to specific scenarios
|
|
181
|
-
*/
|
|
182
|
-
function transformUrlPath(originalUrl, patternHead, targetLocale, defaultLocale, hideDefaultLocale) {
|
|
183
|
-
if (targetLocale === defaultLocale) {
|
|
184
|
-
return transformDefaultLocaleUrl(originalUrl, patternHead, defaultLocale, hideDefaultLocale);
|
|
185
|
-
}
|
|
186
161
|
else if (hideDefaultLocale) {
|
|
187
|
-
|
|
162
|
+
const newPathArray = [
|
|
163
|
+
...originalPathArray.slice(0, patternHeadArray.length),
|
|
164
|
+
targetLocale,
|
|
165
|
+
...originalPathArray.slice(patternHeadArray.length),
|
|
166
|
+
];
|
|
167
|
+
result = newPathArray.join('/');
|
|
188
168
|
}
|
|
189
169
|
else {
|
|
190
|
-
|
|
170
|
+
// check default locale
|
|
171
|
+
if (originalPathArray?.[patternHeadArray.length] !== defaultLocale) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
// replace default locale with target locale
|
|
175
|
+
const newPathArray = [...originalPathArray];
|
|
176
|
+
newPathArray[patternHeadArray.length] = targetLocale;
|
|
177
|
+
result = newPathArray.join('/');
|
|
191
178
|
}
|
|
179
|
+
// check for leading and trailing slashes
|
|
180
|
+
if (originalUrl.startsWith('/')) {
|
|
181
|
+
result = '/' + result;
|
|
182
|
+
}
|
|
183
|
+
if (originalUrl.endsWith('/')) {
|
|
184
|
+
result = result + '/';
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
192
187
|
}
|
|
193
188
|
/**
|
|
194
189
|
* AST-based transformation for MDX files using remark-mdx
|
|
195
190
|
*/
|
|
196
|
-
function transformMdxUrls(mdxContent, defaultLocale, targetLocale, hideDefaultLocale, pattern = '/[locale]', exclude = []) {
|
|
191
|
+
function transformMdxUrls(mdxContent, defaultLocale, targetLocale, hideDefaultLocale, pattern = '/[locale]', exclude = [], baseDomain) {
|
|
197
192
|
const transformedUrls = [];
|
|
198
193
|
if (!pattern.startsWith('/')) {
|
|
199
194
|
pattern = '/' + pattern;
|
|
@@ -240,13 +235,32 @@ function transformMdxUrls(mdxContent, defaultLocale, targetLocale, hideDefaultLo
|
|
|
240
235
|
return {
|
|
241
236
|
content: mdxContent,
|
|
242
237
|
hasChanges: false,
|
|
243
|
-
transformedUrls
|
|
238
|
+
transformedUrls,
|
|
244
239
|
};
|
|
245
240
|
}
|
|
246
241
|
// Helper function to transform URL based on pattern
|
|
247
242
|
const transformUrl = (originalUrl, linkType) => {
|
|
248
243
|
// Check if URL should be processed
|
|
249
|
-
if (!shouldProcessUrl(originalUrl, patternHead, targetLocale, defaultLocale)) {
|
|
244
|
+
if (!shouldProcessUrl(originalUrl, patternHead, targetLocale, defaultLocale, baseDomain)) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
// Skip absolute URLs (http://, https://, //, etc.)
|
|
248
|
+
if (baseDomain && shouldProcessAbsoluteUrl(originalUrl, baseDomain)) {
|
|
249
|
+
// Get everything after the base domain
|
|
250
|
+
const afterDomain = originalUrl.substring(baseDomain.length);
|
|
251
|
+
const transformedPath = transformUrlPath(afterDomain, patternHead, targetLocale, defaultLocale, hideDefaultLocale);
|
|
252
|
+
if (!transformedPath) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
transformedUrls.push({
|
|
256
|
+
originalPath: originalUrl,
|
|
257
|
+
newPath: transformedPath,
|
|
258
|
+
type: linkType,
|
|
259
|
+
});
|
|
260
|
+
return transformedPath ? baseDomain + transformedPath : null;
|
|
261
|
+
}
|
|
262
|
+
// Exclude colon-prefixed URLs (http://, https://, //, etc.)
|
|
263
|
+
if (originalUrl.split('?')[0].includes(':')) {
|
|
250
264
|
return null;
|
|
251
265
|
}
|
|
252
266
|
// Transform the URL based on locale and configuration
|
|
@@ -381,8 +395,23 @@ function transformMdxUrls(mdxContent, defaultLocale, targetLocale, hideDefaultLo
|
|
|
381
395
|
}
|
|
382
396
|
// AST-based transformation for MDX files using remark
|
|
383
397
|
function localizeStaticUrlsForFile(file, defaultLocale, targetLocale, hideDefaultLocale, pattern = '/[locale]', // eg /docs/[locale] or /[locale]
|
|
384
|
-
exclude = []) {
|
|
398
|
+
exclude = [], baseDomain) {
|
|
385
399
|
// Use AST-based transformation for MDX files
|
|
386
|
-
|
|
387
|
-
|
|
400
|
+
return transformMdxUrls(file, defaultLocale, targetLocale, hideDefaultLocale, pattern, exclude, baseDomain || '');
|
|
401
|
+
}
|
|
402
|
+
function cleanPath(path) {
|
|
403
|
+
let cleanedPath = path.startsWith('/') ? path.slice(1) : path;
|
|
404
|
+
cleanedPath = cleanedPath.endsWith('/')
|
|
405
|
+
? cleanedPath.slice(0, -1)
|
|
406
|
+
: cleanedPath;
|
|
407
|
+
return cleanedPath;
|
|
408
|
+
}
|
|
409
|
+
function checkIfPathMatchesPattern(originalUrlArray, patternHeadArray) {
|
|
410
|
+
// check if the pattern head matches the original path
|
|
411
|
+
for (let i = 0; i < patternHeadArray.length; i++) {
|
|
412
|
+
if (patternHeadArray[i] !== originalUrlArray?.[i]) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return true;
|
|
388
417
|
}
|