gtx-cli 2.6.4 → 2.6.5
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/commands/translate.js +8 -0
- package/dist/cli/flags.js +1 -0
- package/dist/config/generateSettings.js +2 -0
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/utils/localizeRelativeAssets.d.ts +8 -0
- package/dist/utils/localizeRelativeAssets.js +165 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# gtx-cli
|
|
2
2
|
|
|
3
|
+
## 2.6.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#971](https://github.com/generaltranslation/gt/pull/971) [`821b0f0`](https://github.com/generaltranslation/gt/commit/821b0f05bab247e4d4c91cabc3ccfd45d672d25f) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - Adding `experimentalLocalizeRelativeAssets` config option to handle asset paths
|
|
8
|
+
|
|
3
9
|
## 2.6.4
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -4,6 +4,7 @@ import { getStagedVersions } from '../../fs/config/updateVersions.js';
|
|
|
4
4
|
import copyFile from '../../fs/copyFile.js';
|
|
5
5
|
import flattenJsonFiles from '../../utils/flattenJsonFiles.js';
|
|
6
6
|
import localizeStaticUrls from '../../utils/localizeStaticUrls.js';
|
|
7
|
+
import localizeRelativeAssets from '../../utils/localizeRelativeAssets.js';
|
|
7
8
|
import processAnchorIds from '../../utils/processAnchorIds.js';
|
|
8
9
|
import processOpenApi from '../../utils/processOpenApi.js';
|
|
9
10
|
import { noFilesError, noVersionIdError } from '../../console/index.js';
|
|
@@ -48,6 +49,13 @@ export async function postProcessTranslations(settings, includeFiles) {
|
|
|
48
49
|
await localizeStaticUrls(settings, nonDefaultLocales, includeFiles);
|
|
49
50
|
}
|
|
50
51
|
}
|
|
52
|
+
// Rewrite relative asset URLs in translated md/mdx files
|
|
53
|
+
if (settings.options?.experimentalLocalizeRelativeAssets) {
|
|
54
|
+
const nonDefaultLocales = settings.locales.filter((locale) => locale !== settings.defaultLocale);
|
|
55
|
+
if (nonDefaultLocales.length > 0) {
|
|
56
|
+
await localizeRelativeAssets(settings, nonDefaultLocales, includeFiles);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
51
59
|
const shouldProcessAnchorIds = settings.options?.experimentalLocalizeStaticUrls ||
|
|
52
60
|
settings.options?.experimentalAddHeaderAnchorIds;
|
|
53
61
|
// Add explicit anchor IDs to translated MDX/MD files to preserve navigation
|
package/dist/cli/flags.js
CHANGED
|
@@ -29,6 +29,7 @@ export function attachTranslateFlags(command) {
|
|
|
29
29
|
.option('--experimental-hide-default-locale', 'When localizing static locales, hide the default locale from the path', false)
|
|
30
30
|
.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)
|
|
31
31
|
.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)
|
|
32
|
+
.option('--experimental-localize-relative-assets', 'Triggering this will rewrite relative image asset URLs in translated md/mdx files to valid paths.', false)
|
|
32
33
|
.option('--force', 'Force a retranslation, invalidating all existing cached translations if they exist.', false)
|
|
33
34
|
.option('--force-download', 'Force download and overwrite local files, bypassing gt-lock.json checks.', false)
|
|
34
35
|
.option('--experimental-clear-locale-dirs', 'Clear locale directories before downloading new translations', false)
|
|
@@ -127,6 +127,8 @@ export async function generateSettings(flags, cwd = process.cwd()) {
|
|
|
127
127
|
flags.experimentalLocalizeStaticImports,
|
|
128
128
|
experimentalLocalizeStaticUrls: gtConfig.options?.experimentalLocalizeStaticUrls ||
|
|
129
129
|
flags.experimentalLocalizeStaticUrls,
|
|
130
|
+
experimentalLocalizeRelativeAssets: gtConfig.options?.experimentalLocalizeRelativeAssets ||
|
|
131
|
+
flags.experimentalLocalizeRelativeAssets,
|
|
130
132
|
experimentalHideDefaultLocale: gtConfig.options?.experimentalHideDefaultLocale ||
|
|
131
133
|
flags.experimentalHideDefaultLocale,
|
|
132
134
|
experimentalFlattenJsonFiles: gtConfig.options?.experimentalFlattenJsonFiles ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "2.6.
|
|
1
|
+
export declare const PACKAGE_VERSION = "2.6.5";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// This file is auto-generated. Do not edit manually.
|
|
2
|
-
export const PACKAGE_VERSION = '2.6.
|
|
2
|
+
export const PACKAGE_VERSION = '2.6.5';
|
package/dist/types/index.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export type Options = {
|
|
|
24
24
|
experimentalHideDefaultLocale?: boolean;
|
|
25
25
|
experimentalFlattenJsonFiles?: boolean;
|
|
26
26
|
experimentalLocalizeStaticImports?: boolean;
|
|
27
|
+
experimentalLocalizeRelativeAssets?: boolean;
|
|
27
28
|
experimentalAddHeaderAnchorIds?: 'mintlify' | 'default';
|
|
28
29
|
docsImportRewrites?: Array<{
|
|
29
30
|
match: string;
|
|
@@ -63,6 +64,7 @@ export type TranslateFlags = SharedFlags & {
|
|
|
63
64
|
experimentalHideDefaultLocale?: boolean;
|
|
64
65
|
experimentalFlattenJsonFiles?: boolean;
|
|
65
66
|
experimentalLocalizeStaticImports?: boolean;
|
|
67
|
+
experimentalLocalizeRelativeAssets?: boolean;
|
|
66
68
|
experimentalAddHeaderAnchorIds?: 'mintlify' | 'default';
|
|
67
69
|
excludeStaticUrls?: string[];
|
|
68
70
|
excludeStaticImports?: string[];
|
|
@@ -198,6 +200,7 @@ export type AdditionalOptions = {
|
|
|
198
200
|
clearLocaleDirsExclude?: string[];
|
|
199
201
|
experimentalLocalizeStaticImports?: boolean;
|
|
200
202
|
experimentalLocalizeStaticUrls?: boolean;
|
|
203
|
+
experimentalLocalizeRelativeAssets?: boolean;
|
|
201
204
|
experimentalAddHeaderAnchorIds?: 'mintlify' | 'default';
|
|
202
205
|
experimentalHideDefaultLocale?: boolean;
|
|
203
206
|
experimentalFlattenJsonFiles?: boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Settings } from '../types/index.js';
|
|
2
|
+
type RewriteResult = {
|
|
3
|
+
content: string;
|
|
4
|
+
hasChanges: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare function localizeRelativeAssetsForContent(content: string, sourcePath: string, targetPath: string, cwd: string): RewriteResult;
|
|
7
|
+
export default function localizeRelativeAssets(settings: Settings, targetLocales?: string[], includeFiles?: Set<string>): Promise<void>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { unified } from 'unified';
|
|
4
|
+
import remarkParse from 'remark-parse';
|
|
5
|
+
import remarkMdx from 'remark-mdx';
|
|
6
|
+
import remarkFrontmatter from 'remark-frontmatter';
|
|
7
|
+
import remarkStringify from 'remark-stringify';
|
|
8
|
+
import { visit } from 'unist-util-visit';
|
|
9
|
+
import escapeHtmlInTextNodes from 'gt-remark';
|
|
10
|
+
import { createFileMapping } from '../formats/files/fileMapping.js';
|
|
11
|
+
function stripQueryAndHash(url) {
|
|
12
|
+
const match = url.match(/^[^?#]+/);
|
|
13
|
+
const base = match ? match[0] : url;
|
|
14
|
+
const suffix = url.slice(base.length);
|
|
15
|
+
return { base, suffix };
|
|
16
|
+
}
|
|
17
|
+
function isSkippableUrl(url) {
|
|
18
|
+
if (!url)
|
|
19
|
+
return true;
|
|
20
|
+
if (url.startsWith('/'))
|
|
21
|
+
return true;
|
|
22
|
+
if (/^(https?:)?\/\//i.test(url))
|
|
23
|
+
return true;
|
|
24
|
+
if (url.startsWith('data:'))
|
|
25
|
+
return true;
|
|
26
|
+
if (url.startsWith('#'))
|
|
27
|
+
return true;
|
|
28
|
+
if (url.startsWith('mailto:'))
|
|
29
|
+
return true;
|
|
30
|
+
if (url.startsWith('tel:'))
|
|
31
|
+
return true;
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
function toPosix(p) {
|
|
35
|
+
return p.replace(/\\/g, '/');
|
|
36
|
+
}
|
|
37
|
+
function isSubPath(child, parent) {
|
|
38
|
+
const rel = path.relative(parent, child);
|
|
39
|
+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
40
|
+
}
|
|
41
|
+
export function localizeRelativeAssetsForContent(content, sourcePath, targetPath, cwd) {
|
|
42
|
+
let changed = false;
|
|
43
|
+
let ast;
|
|
44
|
+
try {
|
|
45
|
+
const processor = unified()
|
|
46
|
+
.use(remarkParse)
|
|
47
|
+
.use(remarkFrontmatter, ['yaml', 'toml'])
|
|
48
|
+
.use(remarkMdx);
|
|
49
|
+
ast = processor.runSync(processor.parse(content));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return { content, hasChanges: false };
|
|
53
|
+
}
|
|
54
|
+
const sourceDir = path.dirname(sourcePath);
|
|
55
|
+
const targetDir = path.dirname(targetPath);
|
|
56
|
+
const maybeRewrite = (url) => {
|
|
57
|
+
if (isSkippableUrl(url))
|
|
58
|
+
return null;
|
|
59
|
+
const { base, suffix } = stripQueryAndHash(url);
|
|
60
|
+
if (isSkippableUrl(base))
|
|
61
|
+
return null;
|
|
62
|
+
const targetResolved = path.resolve(targetDir, base);
|
|
63
|
+
if (fs.existsSync(targetResolved)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const sourceResolved = path.resolve(sourceDir, base);
|
|
67
|
+
if (!fs.existsSync(sourceResolved)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
let newPath;
|
|
71
|
+
if (isSubPath(sourceResolved, cwd)) {
|
|
72
|
+
newPath = '/' + toPosix(path.relative(cwd, sourceResolved));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const rel = toPosix(path.relative(targetDir, sourceResolved));
|
|
76
|
+
newPath = rel || toPosix(path.basename(sourceResolved));
|
|
77
|
+
}
|
|
78
|
+
if (newPath === base)
|
|
79
|
+
return null;
|
|
80
|
+
changed = true;
|
|
81
|
+
return newPath + suffix;
|
|
82
|
+
};
|
|
83
|
+
visit(ast, (node) => {
|
|
84
|
+
if (node.type === 'image' && typeof node.url === 'string') {
|
|
85
|
+
const newUrl = maybeRewrite(node.url);
|
|
86
|
+
if (newUrl)
|
|
87
|
+
node.url = newUrl;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if ((node.type === 'mdxJsxFlowElement' ||
|
|
91
|
+
node.type === 'mdxJsxTextElement') &&
|
|
92
|
+
node.name === 'img' &&
|
|
93
|
+
Array.isArray(node.attributes)) {
|
|
94
|
+
for (const attr of node.attributes) {
|
|
95
|
+
if (attr &&
|
|
96
|
+
attr.type === 'mdxJsxAttribute' &&
|
|
97
|
+
attr.name === 'src' &&
|
|
98
|
+
typeof attr.value === 'string') {
|
|
99
|
+
const newUrl = maybeRewrite(attr.value);
|
|
100
|
+
if (newUrl)
|
|
101
|
+
attr.value = newUrl;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
try {
|
|
107
|
+
const s = unified()
|
|
108
|
+
.use(remarkFrontmatter, ['yaml', 'toml'])
|
|
109
|
+
.use(remarkMdx)
|
|
110
|
+
.use(escapeHtmlInTextNodes)
|
|
111
|
+
.use(remarkStringify, {
|
|
112
|
+
handlers: {
|
|
113
|
+
text(node) {
|
|
114
|
+
return node.value;
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
const outTree = s.runSync(ast);
|
|
119
|
+
let out = s.stringify(outTree);
|
|
120
|
+
if (out.endsWith('\n') && !content.endsWith('\n'))
|
|
121
|
+
out = out.slice(0, -1);
|
|
122
|
+
if (content.startsWith('\n') && !out.startsWith('\n'))
|
|
123
|
+
out = '\n' + out;
|
|
124
|
+
return { content: out, hasChanges: changed };
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return { content, hasChanges: false };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export default async function localizeRelativeAssets(settings, targetLocales, includeFiles) {
|
|
131
|
+
if (!settings.files ||
|
|
132
|
+
(Object.keys(settings.files.placeholderPaths).length === 1 &&
|
|
133
|
+
settings.files.placeholderPaths.gt)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const { resolvedPaths: sourceFiles } = settings.files;
|
|
137
|
+
const locales = targetLocales || settings.locales;
|
|
138
|
+
const fileMapping = createFileMapping(sourceFiles, settings.files.placeholderPaths, settings.files.transformPaths, settings.locales, settings.defaultLocale);
|
|
139
|
+
const cwd = process.cwd();
|
|
140
|
+
const processPromises = Object.entries(fileMapping)
|
|
141
|
+
.filter(([locale]) => locales.includes(locale))
|
|
142
|
+
.map(async ([locale, filesMap]) => {
|
|
143
|
+
const reverseMap = new Map();
|
|
144
|
+
for (const [sourcePath, targetPath] of Object.entries(filesMap)) {
|
|
145
|
+
reverseMap.set(targetPath, sourcePath);
|
|
146
|
+
}
|
|
147
|
+
const targetFiles = Object.values(filesMap).filter((p) => (p.endsWith('.md') || p.endsWith('.mdx')) &&
|
|
148
|
+
(!includeFiles || includeFiles.has(p)));
|
|
149
|
+
await Promise.all(targetFiles.map(async (targetPath) => {
|
|
150
|
+
if (!fs.existsSync(targetPath))
|
|
151
|
+
return;
|
|
152
|
+
const sourcePath = reverseMap.get(targetPath);
|
|
153
|
+
if (!sourcePath)
|
|
154
|
+
return;
|
|
155
|
+
if (!fs.existsSync(sourcePath))
|
|
156
|
+
return;
|
|
157
|
+
const content = await fs.promises.readFile(targetPath, 'utf8');
|
|
158
|
+
const result = localizeRelativeAssetsForContent(content, sourcePath, targetPath, cwd);
|
|
159
|
+
if (result.hasChanges) {
|
|
160
|
+
await fs.promises.writeFile(targetPath, result.content);
|
|
161
|
+
}
|
|
162
|
+
}));
|
|
163
|
+
});
|
|
164
|
+
await Promise.all(processPromises);
|
|
165
|
+
}
|