gtx-cli 2.5.13 → 2.5.14
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,13 @@
|
|
|
1
1
|
# gtx-cli
|
|
2
2
|
|
|
3
|
+
## 2.5.14
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#837](https://github.com/generaltranslation/gt/pull/837) [`0772b57`](https://github.com/generaltranslation/gt/commit/0772b5714f1cfe8af5f5edcdf6bcb28125a1536f) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - Making experimentalAddHeaderAnchorIds independent of experimentalLocalizeStaticUrls and fetching anchor IDs from source files when present
|
|
8
|
+
|
|
9
|
+
- [#835](https://github.com/generaltranslation/gt/pull/835) [`79225fb`](https://github.com/generaltranslation/gt/commit/79225fb3bbea3bb7a453cc237c619b67dd0dd3da) Thanks [@brian-lou](https://github.com/brian-lou)! - When using --force translate, also force files to re-download
|
|
10
|
+
|
|
3
11
|
## 2.5.13
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -14,7 +14,8 @@ export async function handleTranslate(options, settings, fileVersionData, jobDat
|
|
|
14
14
|
const { resolvedPaths, placeholderPaths, transformPaths } = settings.files;
|
|
15
15
|
const fileMapping = createFileMapping(resolvedPaths, placeholderPaths, transformPaths, settings.locales, settings.defaultLocale);
|
|
16
16
|
// Check for remaining translations
|
|
17
|
-
await downloadTranslations(fileVersionData, jobData, branchData, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale]?.[sourcePath] ?? null, settings, options.force, options.forceDownload
|
|
17
|
+
await downloadTranslations(fileVersionData, jobData, branchData, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale]?.[sourcePath] ?? null, settings, options.force, options.forceDownload || options.force // if force is true should also force download
|
|
18
|
+
);
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
// Downloads translations that were originally staged
|
|
@@ -31,7 +32,7 @@ export async function handleDownload(options, settings) {
|
|
|
31
32
|
const stagedVersionData = await getStagedVersions(settings.configDirectory);
|
|
32
33
|
// Check for remaining translations
|
|
33
34
|
await downloadTranslations(stagedVersionData, undefined, undefined, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale][sourcePath] ?? null, settings, false, // force is not applicable for downloading staged translations
|
|
34
|
-
options.forceDownload);
|
|
35
|
+
options.force || options.forceDownload);
|
|
35
36
|
}
|
|
36
37
|
export async function postProcessTranslations(settings, includeFiles) {
|
|
37
38
|
// Localize static urls (/docs -> /[locale]/docs) and preserve anchor IDs for non-default locales
|
|
@@ -41,8 +42,12 @@ export async function postProcessTranslations(settings, includeFiles) {
|
|
|
41
42
|
if (nonDefaultLocales.length > 0) {
|
|
42
43
|
await localizeStaticUrls(settings, nonDefaultLocales, includeFiles);
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
}
|
|
46
|
+
const shouldProcessAnchorIds = settings.options?.experimentalLocalizeStaticUrls ||
|
|
47
|
+
settings.options?.experimentalAddHeaderAnchorIds;
|
|
48
|
+
// Add explicit anchor IDs to translated MDX/MD files to preserve navigation
|
|
49
|
+
// Uses inline {#id} format by default, or div wrapping if experimentalAddHeaderAnchorIds is 'mintlify'
|
|
50
|
+
if (shouldProcessAnchorIds) {
|
|
46
51
|
await processAnchorIds(settings, includeFiles);
|
|
47
52
|
}
|
|
48
53
|
// Localize static imports (import Snippet from /snippets/file.mdx -> import Snippet from /snippets/[locale]/file.mdx)
|
package/dist/types/index.d.ts
CHANGED
|
@@ -24,7 +24,11 @@ export type Options = {
|
|
|
24
24
|
experimentalHideDefaultLocale?: boolean;
|
|
25
25
|
experimentalFlattenJsonFiles?: boolean;
|
|
26
26
|
experimentalLocalizeStaticImports?: boolean;
|
|
27
|
-
experimentalAddHeaderAnchorIds?: 'mintlify';
|
|
27
|
+
experimentalAddHeaderAnchorIds?: 'mintlify' | 'default';
|
|
28
|
+
docsImportRewrites?: Array<{
|
|
29
|
+
match: string;
|
|
30
|
+
replace: string;
|
|
31
|
+
}>;
|
|
28
32
|
};
|
|
29
33
|
export type TranslateFlags = {
|
|
30
34
|
config?: string;
|
|
@@ -49,9 +53,13 @@ export type TranslateFlags = {
|
|
|
49
53
|
experimentalHideDefaultLocale?: boolean;
|
|
50
54
|
experimentalFlattenJsonFiles?: boolean;
|
|
51
55
|
experimentalLocalizeStaticImports?: boolean;
|
|
52
|
-
experimentalAddHeaderAnchorIds?: 'mintlify';
|
|
56
|
+
experimentalAddHeaderAnchorIds?: 'mintlify' | 'default';
|
|
53
57
|
excludeStaticUrls?: string[];
|
|
54
58
|
excludeStaticImports?: string[];
|
|
59
|
+
docsImportRewrites?: Array<{
|
|
60
|
+
match: string;
|
|
61
|
+
replace: string;
|
|
62
|
+
}>;
|
|
55
63
|
};
|
|
56
64
|
export type WrapOptions = {
|
|
57
65
|
src?: string[];
|
|
@@ -160,10 +168,14 @@ export type AdditionalOptions = {
|
|
|
160
168
|
clearLocaleDirsExclude?: string[];
|
|
161
169
|
experimentalLocalizeStaticImports?: boolean;
|
|
162
170
|
experimentalLocalizeStaticUrls?: boolean;
|
|
163
|
-
experimentalAddHeaderAnchorIds?: 'mintlify';
|
|
171
|
+
experimentalAddHeaderAnchorIds?: 'mintlify' | 'default';
|
|
164
172
|
experimentalHideDefaultLocale?: boolean;
|
|
165
173
|
experimentalFlattenJsonFiles?: boolean;
|
|
166
174
|
baseDomain?: string;
|
|
175
|
+
docsImportRewrites?: Array<{
|
|
176
|
+
match: string;
|
|
177
|
+
replace: string;
|
|
178
|
+
}>;
|
|
167
179
|
};
|
|
168
180
|
export type SharedStaticAssetsConfig = {
|
|
169
181
|
include: string | string[];
|
|
@@ -30,6 +30,16 @@ function extractHeadingText(heading) {
|
|
|
30
30
|
});
|
|
31
31
|
return text;
|
|
32
32
|
}
|
|
33
|
+
function parseHeadingContent(text) {
|
|
34
|
+
// Support both {#id} and escaped \{#id\} forms
|
|
35
|
+
const anchorMatch = text.match(/(\\\{#([^}]+)\\\}|\{#([^}]+)\})\s*$/);
|
|
36
|
+
if (!anchorMatch) {
|
|
37
|
+
return { cleanedText: text };
|
|
38
|
+
}
|
|
39
|
+
const explicitId = anchorMatch[2] || anchorMatch[3];
|
|
40
|
+
const cleanedText = text.replace(anchorMatch[0], '').trimEnd();
|
|
41
|
+
return { cleanedText, explicitId };
|
|
42
|
+
}
|
|
33
43
|
/**
|
|
34
44
|
* Checks if a heading is already wrapped in a div with id
|
|
35
45
|
*/
|
|
@@ -57,15 +67,34 @@ export function extractHeadingInfo(mdxContent) {
|
|
|
57
67
|
}
|
|
58
68
|
catch (error) {
|
|
59
69
|
console.warn(`Failed to parse MDX content: ${error instanceof Error ? error.message : String(error)}`);
|
|
60
|
-
|
|
70
|
+
// Fallback: simple regex-based extraction to keep IDs usable
|
|
71
|
+
const fallbackHeadings = [];
|
|
72
|
+
const headingRegex = /^(#{1,6})\s+(.*)$/gm;
|
|
73
|
+
let position = 0;
|
|
74
|
+
let match;
|
|
75
|
+
while ((match = headingRegex.exec(mdxContent)) !== null) {
|
|
76
|
+
const hashes = match[1];
|
|
77
|
+
const rawText = match[2];
|
|
78
|
+
const { cleanedText, explicitId } = parseHeadingContent(rawText);
|
|
79
|
+
if (cleanedText || explicitId) {
|
|
80
|
+
fallbackHeadings.push({
|
|
81
|
+
text: cleanedText,
|
|
82
|
+
level: hashes.length,
|
|
83
|
+
slug: explicitId ?? generateSlug(cleanedText),
|
|
84
|
+
position: position++,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return fallbackHeadings;
|
|
61
89
|
}
|
|
62
90
|
let position = 0;
|
|
63
91
|
visit(processedAst, 'heading', (heading) => {
|
|
64
92
|
const headingText = extractHeadingText(heading);
|
|
65
|
-
|
|
66
|
-
|
|
93
|
+
const { cleanedText, explicitId } = parseHeadingContent(headingText);
|
|
94
|
+
if (cleanedText || explicitId) {
|
|
95
|
+
const slug = explicitId ?? generateSlug(cleanedText);
|
|
67
96
|
headings.push({
|
|
68
|
-
text:
|
|
97
|
+
text: cleanedText,
|
|
69
98
|
level: heading.depth,
|
|
70
99
|
slug,
|
|
71
100
|
position: position++,
|
|
@@ -114,13 +143,16 @@ export function addExplicitAnchorIds(translatedContent, sourceHeadingMap, settin
|
|
|
114
143
|
addedIds: [],
|
|
115
144
|
};
|
|
116
145
|
}
|
|
146
|
+
const translatedIsMdx = translatedPath
|
|
147
|
+
? translatedPath.toLowerCase().endsWith('.mdx')
|
|
148
|
+
: true; // default to mdx-style escaping when unknown
|
|
117
149
|
// Apply IDs to translated content
|
|
118
150
|
let content;
|
|
119
151
|
if (useDivWrapping) {
|
|
120
152
|
content = applyDivWrappedIds(translatedContent, translatedHeadings, idMappings);
|
|
121
153
|
}
|
|
122
154
|
else {
|
|
123
|
-
content = applyInlineIds(translatedContent, idMappings);
|
|
155
|
+
content = applyInlineIds(translatedContent, idMappings, translatedIsMdx);
|
|
124
156
|
}
|
|
125
157
|
return {
|
|
126
158
|
content,
|
|
@@ -131,7 +163,17 @@ export function addExplicitAnchorIds(translatedContent, sourceHeadingMap, settin
|
|
|
131
163
|
/**
|
|
132
164
|
* Adds inline {#id} syntax to headings (standard markdown approach)
|
|
133
165
|
*/
|
|
134
|
-
function applyInlineIds(translatedContent, idMappings) {
|
|
166
|
+
function applyInlineIds(translatedContent, idMappings, escapeAnchors) {
|
|
167
|
+
const escapeInlineAnchors = (content) => {
|
|
168
|
+
if (!escapeAnchors)
|
|
169
|
+
return content;
|
|
170
|
+
return content.replace(/\{#([A-Za-z0-9-_]+)\}/g, (match, id, offset, str) => {
|
|
171
|
+
if (offset > 0 && str[offset - 1] === '\\') {
|
|
172
|
+
return match;
|
|
173
|
+
}
|
|
174
|
+
return `\\{#${id}\\}`;
|
|
175
|
+
});
|
|
176
|
+
};
|
|
135
177
|
// Parse the translated content
|
|
136
178
|
let processedAst;
|
|
137
179
|
try {
|
|
@@ -144,7 +186,7 @@ function applyInlineIds(translatedContent, idMappings) {
|
|
|
144
186
|
}
|
|
145
187
|
catch (error) {
|
|
146
188
|
console.warn(`Failed to parse translated MDX content: ${error instanceof Error ? error.message : String(error)}`);
|
|
147
|
-
return translatedContent;
|
|
189
|
+
return applyInlineIdsStringFallback(translatedContent, idMappings, escapeAnchors);
|
|
148
190
|
}
|
|
149
191
|
// Apply IDs to headings based on position
|
|
150
192
|
let headingIndex = 0;
|
|
@@ -154,19 +196,33 @@ function applyInlineIds(translatedContent, idMappings) {
|
|
|
154
196
|
if (id) {
|
|
155
197
|
// Skip if heading already has explicit ID
|
|
156
198
|
if (hasExplicitId(heading, processedAst)) {
|
|
199
|
+
if (escapeAnchors) {
|
|
200
|
+
// Normalize existing inline IDs to escaped form
|
|
201
|
+
const lastChild = heading.children[heading.children.length - 1];
|
|
202
|
+
if (lastChild?.type === 'text') {
|
|
203
|
+
const match = lastChild.value.match(/\{#([^}]+)\}\s*$/);
|
|
204
|
+
const alreadyEscaped = lastChild.value.match(/\\\{#[^}]+\\\}\s*$/);
|
|
205
|
+
if (match && !alreadyEscaped) {
|
|
206
|
+
const anchorId = match[1];
|
|
207
|
+
const base = lastChild.value.replace(/\s*\{#[^}]+\}\s*$/, '');
|
|
208
|
+
lastChild.value = `${base} \\{#${anchorId}\\}`;
|
|
209
|
+
actuallyModifiedContent = true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
157
213
|
headingIndex++;
|
|
158
214
|
return;
|
|
159
215
|
}
|
|
160
216
|
// Add the ID to the heading
|
|
161
217
|
const lastChild = heading.children[heading.children.length - 1];
|
|
162
218
|
if (lastChild?.type === 'text') {
|
|
163
|
-
lastChild.value += ` \\{#${id}\\}`;
|
|
219
|
+
lastChild.value += escapeAnchors ? ` \\{#${id}\\}` : ` {#${id}}`;
|
|
164
220
|
}
|
|
165
221
|
else {
|
|
166
222
|
// If last child is not text, add a new text node
|
|
167
223
|
heading.children.push({
|
|
168
224
|
type: 'text',
|
|
169
|
-
value: ` \\{#${id}\\}`,
|
|
225
|
+
value: escapeAnchors ? ` \\{#${id}\\}` : ` {#${id}}`,
|
|
170
226
|
});
|
|
171
227
|
}
|
|
172
228
|
actuallyModifiedContent = true;
|
|
@@ -175,7 +231,8 @@ function applyInlineIds(translatedContent, idMappings) {
|
|
|
175
231
|
});
|
|
176
232
|
// If we didn't modify any headings, return original content
|
|
177
233
|
if (!actuallyModifiedContent) {
|
|
178
|
-
|
|
234
|
+
const escaped = escapeInlineAnchors(translatedContent);
|
|
235
|
+
return escaped;
|
|
179
236
|
}
|
|
180
237
|
// Convert the modified AST back to MDX string
|
|
181
238
|
try {
|
|
@@ -208,6 +265,31 @@ function applyInlineIds(translatedContent, idMappings) {
|
|
|
208
265
|
return translatedContent;
|
|
209
266
|
}
|
|
210
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Fallback string-based inline ID application when AST parsing fails
|
|
270
|
+
*/
|
|
271
|
+
function applyInlineIdsStringFallback(translatedContent, idMappings, escapeAnchors) {
|
|
272
|
+
let headingIndex = 0;
|
|
273
|
+
return translatedContent.replace(/^(#{1,6}\s+)(.*)$/gm, (match, prefix, text) => {
|
|
274
|
+
const id = idMappings.get(headingIndex++);
|
|
275
|
+
if (!id) {
|
|
276
|
+
return match;
|
|
277
|
+
}
|
|
278
|
+
const hasEscaped = /\\\{#[^}]+\\\}\s*$/.test(text);
|
|
279
|
+
const hasUnescaped = /\{#[^}]+\}\s*$/.test(text);
|
|
280
|
+
if (hasEscaped) {
|
|
281
|
+
return match;
|
|
282
|
+
}
|
|
283
|
+
if (hasUnescaped) {
|
|
284
|
+
if (!escapeAnchors) {
|
|
285
|
+
return match;
|
|
286
|
+
}
|
|
287
|
+
return `${prefix}${text.replace(/\{#([^}]+)\}\s*$/, '\\\\{#$1\\\\}')}`;
|
|
288
|
+
}
|
|
289
|
+
const suffix = escapeAnchors ? ` \\{#${id}\\}` : ` {#${id}}`;
|
|
290
|
+
return `${prefix}${text}${suffix}`;
|
|
291
|
+
});
|
|
292
|
+
}
|
|
211
293
|
/**
|
|
212
294
|
* Wraps headings in divs with IDs (Mintlify approach)
|
|
213
295
|
*/
|
|
@@ -52,7 +52,7 @@ export default async function localizeStaticImports(settings, includeFiles) {
|
|
|
52
52
|
const fileContent = await fs.promises.readFile(filePath, 'utf8');
|
|
53
53
|
// Localize the file using default locale
|
|
54
54
|
const localizedFile = localizeStaticImportsForFile(fileContent, settings.defaultLocale, settings.defaultLocale, // Process as default locale
|
|
55
|
-
settings.options?.docsHideDefaultLocaleImport || false, settings.options?.docsImportPattern, settings.options?.excludeStaticImports, filePath);
|
|
55
|
+
settings.options?.docsHideDefaultLocaleImport || false, settings.options?.docsImportPattern, settings.options?.excludeStaticImports, filePath, settings.options);
|
|
56
56
|
// Write the localized file back to the same path
|
|
57
57
|
await fs.promises.writeFile(filePath, localizedFile);
|
|
58
58
|
}));
|
|
@@ -73,7 +73,7 @@ export default async function localizeStaticImports(settings, includeFiles) {
|
|
|
73
73
|
// Get file content
|
|
74
74
|
const fileContent = await fs.promises.readFile(filePath, 'utf8');
|
|
75
75
|
// Localize the file
|
|
76
|
-
const localizedFile = localizeStaticImportsForFile(fileContent, settings.defaultLocale, locale, settings.options?.docsHideDefaultLocaleImport || false, settings.options?.docsImportPattern, settings.options?.excludeStaticImports, filePath);
|
|
76
|
+
const localizedFile = localizeStaticImportsForFile(fileContent, settings.defaultLocale, locale, settings.options?.docsHideDefaultLocaleImport || false, settings.options?.docsImportPattern, settings.options?.excludeStaticImports, filePath, settings.options);
|
|
77
77
|
// Write the localized file to the target path
|
|
78
78
|
await fs.promises.writeFile(filePath, localizedFile);
|
|
79
79
|
}));
|
|
@@ -190,8 +190,33 @@ function transformNonDefaultLocaleImportPath(fullPath, patternHead, targetLocale
|
|
|
190
190
|
/**
|
|
191
191
|
* Main import path transformation function that delegates to specific scenarios
|
|
192
192
|
*/
|
|
193
|
-
function transformImportPath(fullPath, patternHead, targetLocale, defaultLocale, hideDefaultLocale, currentFilePath, projectRoot = process.cwd() // fallback if not provided
|
|
194
|
-
) {
|
|
193
|
+
function transformImportPath(fullPath, patternHead, targetLocale, defaultLocale, hideDefaultLocale, currentFilePath, projectRoot = process.cwd(), // fallback if not provided
|
|
194
|
+
rewrites) {
|
|
195
|
+
// Apply explicit rewrites first (e.g., Docusaurus @site/docs -> @site/i18n/[locale]/...)
|
|
196
|
+
if (rewrites && rewrites.length > 0) {
|
|
197
|
+
const localeMap = {
|
|
198
|
+
'[locale]': targetLocale,
|
|
199
|
+
'[defaultLocale]': defaultLocale,
|
|
200
|
+
};
|
|
201
|
+
for (const { match, replace } of rewrites) {
|
|
202
|
+
const resolvedMatch = match.replace(/\[locale\]|\[defaultLocale\]/g, (token) => localeMap[token] || token);
|
|
203
|
+
if (fullPath.startsWith(resolvedMatch)) {
|
|
204
|
+
const remainder = fullPath.slice(resolvedMatch.length);
|
|
205
|
+
const resolvedReplace = replace.replace(/\[locale\]|\[defaultLocale\]/g, (token) => localeMap[token] || token);
|
|
206
|
+
let newPath;
|
|
207
|
+
if (resolvedReplace.endsWith('/')) {
|
|
208
|
+
newPath = `${resolvedReplace}${remainder.replace(/^\//, '')}`;
|
|
209
|
+
}
|
|
210
|
+
else if (remainder.startsWith('/')) {
|
|
211
|
+
newPath = `${resolvedReplace}${remainder}`;
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
newPath = `${resolvedReplace}/${remainder}`;
|
|
215
|
+
}
|
|
216
|
+
return newPath;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
195
220
|
let newPath;
|
|
196
221
|
if (targetLocale === defaultLocale) {
|
|
197
222
|
newPath = transformDefaultLocaleImportPath(fullPath, patternHead, defaultLocale, hideDefaultLocale);
|
|
@@ -224,7 +249,7 @@ function transformImportPath(fullPath, patternHead, targetLocale, defaultLocale,
|
|
|
224
249
|
/**
|
|
225
250
|
* AST-based transformation for MDX files using remark-mdx
|
|
226
251
|
*/
|
|
227
|
-
function transformMdxImports(mdxContent, defaultLocale, targetLocale, hideDefaultLocale, pattern = '/[locale]', exclude = [], currentFilePath) {
|
|
252
|
+
function transformMdxImports(mdxContent, defaultLocale, targetLocale, hideDefaultLocale, pattern = '/[locale]', exclude = [], currentFilePath, options) {
|
|
228
253
|
const transformedImports = [];
|
|
229
254
|
// Don't auto-prefix relative patterns that start with . or ..
|
|
230
255
|
if (!pattern.startsWith('/') && !pattern.startsWith('.')) {
|
|
@@ -310,7 +335,7 @@ function transformMdxImports(mdxContent, defaultLocale, targetLocale, hideDefaul
|
|
|
310
335
|
continue;
|
|
311
336
|
const fullPath = line.slice(pathStart, pathEnd);
|
|
312
337
|
// Transform the import path
|
|
313
|
-
const newPath = transformImportPath(fullPath, patternHead, targetLocale, defaultLocale, hideDefaultLocale, currentFilePath);
|
|
338
|
+
const newPath = transformImportPath(fullPath, patternHead, targetLocale, defaultLocale, hideDefaultLocale, currentFilePath, process.cwd(), options?.docsImportRewrites);
|
|
314
339
|
if (!newPath) {
|
|
315
340
|
continue; // No transformation needed
|
|
316
341
|
}
|
|
@@ -337,12 +362,8 @@ function transformMdxImports(mdxContent, defaultLocale, targetLocale, hideDefaul
|
|
|
337
362
|
* AST-based transformation for MDX files only
|
|
338
363
|
*/
|
|
339
364
|
function localizeStaticImportsForFile(file, defaultLocale, targetLocale, hideDefaultLocale, pattern = '/[locale]', // eg /docs/[locale] or /[locale]
|
|
340
|
-
exclude = [], currentFilePath) {
|
|
341
|
-
// Skip .md files entirely - they cannot have imports
|
|
342
|
-
if (currentFilePath && currentFilePath.endsWith('.md')) {
|
|
343
|
-
return file;
|
|
344
|
-
}
|
|
365
|
+
exclude = [], currentFilePath, options) {
|
|
345
366
|
// For MDX files, use AST-based transformation
|
|
346
|
-
const result = transformMdxImports(file, defaultLocale, targetLocale, hideDefaultLocale, pattern, exclude, currentFilePath);
|
|
367
|
+
const result = transformMdxImports(file, defaultLocale, targetLocale, hideDefaultLocale, pattern, exclude, currentFilePath, options);
|
|
347
368
|
return result.content;
|
|
348
369
|
}
|