gt 2.6.30-alpha.0 → 2.7.0
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 +20 -0
- package/dist/console/index.d.ts +1 -0
- package/dist/console/index.js +2 -0
- package/dist/formats/files/aggregateFiles.js +8 -25
- package/dist/formats/files/preprocess/mdx.d.ts +6 -0
- package/dist/formats/files/preprocess/mdx.js +14 -0
- package/dist/formats/files/preprocess/mintlify.d.ts +5 -0
- package/dist/formats/files/preprocess/mintlify.js +15 -0
- package/dist/formats/files/preprocessContent.d.ts +8 -0
- package/dist/formats/files/preprocessContent.js +23 -0
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/react/jsx/utils/constants.d.ts +2 -0
- package/dist/react/jsx/utils/constants.js +4 -1
- package/dist/react/jsx/utils/jsxParsing/addGTIdentifierToSyntaxTree.js +4 -1
- package/dist/react/jsx/utils/jsxParsing/parseJsx.js +9 -3
- package/dist/utils/wrapPlainUrls.d.ts +8 -0
- package/dist/utils/wrapPlainUrls.js +72 -0
- package/dist/workflows/download.js +4 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# gtx-cli
|
|
2
2
|
|
|
3
|
+
## 2.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1069](https://github.com/generaltranslation/gt/pull/1069) [`ff38c7c`](https://github.com/generaltranslation/gt/commit/ff38c7c72886882ddb8851fc8173e1ba863d0078) Thanks [@ErnestM1234](https://github.com/ErnestM1234)! - feat: add new gt package
|
|
8
|
+
|
|
9
|
+
## 2.6.31
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#1070](https://github.com/generaltranslation/gt/pull/1070) [`516979d`](https://github.com/generaltranslation/gt/commit/516979d36cd16c4bc9080ea7dc06b7e299200919) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - Handle case where all jobs fail
|
|
14
|
+
|
|
15
|
+
## 2.6.30
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [#1068](https://github.com/generaltranslation/gt/pull/1068) [`94b95ef`](https://github.com/generaltranslation/gt/commit/94b95ef662b81dac51416ecc64f3318339171f0b) Thanks [@ErnestM1234](https://github.com/ErnestM1234)! - fix: runtime calculation for the injection of 'data-' attribute in jsx
|
|
20
|
+
|
|
21
|
+
- [#1066](https://github.com/generaltranslation/gt/pull/1066) [`7b4837f`](https://github.com/generaltranslation/gt/commit/7b4837fe44e387c4de812d9b3f7fc394cb24e49e) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - Wrapping text node URLs for Mintlify MDX to align with their parser
|
|
22
|
+
|
|
3
23
|
## 2.6.29
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/dist/console/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export declare const warnFunctionNotFoundSync: (file: string, functionName: stri
|
|
|
18
18
|
export declare const warnInvalidDeclareVarNameSync: (file: string, value: string, location?: string) => string;
|
|
19
19
|
export declare const warnDuplicateFunctionDefinitionSync: (file: string, functionName: string, location?: string) => string;
|
|
20
20
|
export declare const warnInvalidStaticInitSync: (file: string, functionName: string, location?: string) => string;
|
|
21
|
+
export declare const warnDataAttrOnBranch: (file: string, attrName: string, location?: string) => string;
|
|
21
22
|
export declare const warnRecursiveFunctionCallSync: (file: string, functionName: string, location?: string) => string;
|
|
22
23
|
export declare const warnDeclareStaticNotWrappedSync: (file: string, functionName: string, location?: string) => string;
|
|
23
24
|
export declare const warnDeclareStaticNoResultsSync: (file: string, functionName: string, location?: string) => string;
|
package/dist/console/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BRANCH_COMPONENT } from '../react/jsx/utils/constants.js';
|
|
1
2
|
import { colorizeFilepath, colorizeComponent, colorizeIdString, colorizeContent, colorizeLine, colorizeFunctionName, } from './colors.js';
|
|
2
3
|
import { formatCodeClamp } from './formatting.js';
|
|
3
4
|
const withWillErrorInNextVersion = (message) => `${message} (This will become an error in the next major version of the CLI.)`;
|
|
@@ -28,6 +29,7 @@ export const warnDuplicateFunctionDefinitionSync = (file, functionName, location
|
|
|
28
29
|
export const warnInvalidStaticInitSync = (file, functionName, location) => withLocation(file, withStaticError(`The definition for ${colorizeFunctionName(functionName)} could not be resolved. When using arrow syntax to define a static function, the right hand side or the assignment MUST only contain the arrow function itself and no other expressions.
|
|
29
30
|
Example: ${colorizeContent(`const ${colorizeFunctionName(functionName)} = () => { ... }`)}
|
|
30
31
|
Invalid: ${colorizeContent(`const ${colorizeFunctionName(functionName)} = [() => { ... }][0]`)}`), location);
|
|
32
|
+
export const warnDataAttrOnBranch = (file, attrName, location) => withLocation(file, `${colorizeComponent(`<${BRANCH_COMPONENT}>`)} component ignores attributes prefixed with ${colorizeIdString('"data-"')}. Found ${colorizeIdString(attrName)}. Remove it or use a different attribute name.`, location);
|
|
31
33
|
export const warnRecursiveFunctionCallSync = (file, functionName, location) => withLocation(file, withStaticError(`Recursive function call detected: ${colorizeFunctionName(functionName)}. A static function cannot use recursive calls to construct its result.`), location);
|
|
32
34
|
export const warnDeclareStaticNotWrappedSync = (file, functionName, location) => withLocation(file, withDeclareStaticError(`Could not resolve ${colorizeFunctionName(formatCodeClamp(functionName))}. This call is not wrapped in declareStatic(). Ensure the function is properly wrapped with declareStatic() and does not have circular import dependencies.`), location);
|
|
33
35
|
export const warnDeclareStaticNoResultsSync = (file, functionName, location) => withLocation(file, withDeclareStaticError(`Could not resolve ${colorizeFunctionName(formatCodeClamp(functionName))}. DeclareStatic can only receive function invocations and cannot use undefined values or looped calls to construct its result.`), location);
|
|
@@ -2,14 +2,12 @@ import { logger } from '../../console/logger.js';
|
|
|
2
2
|
import { recordWarning } from '../../state/translateWarnings.js';
|
|
3
3
|
import { getRelative, readFile } from '../../fs/findFilepath.js';
|
|
4
4
|
import { SUPPORTED_FILE_EXTENSIONS } from './supportedFiles.js';
|
|
5
|
-
import sanitizeFileContent from '../../utils/sanitizeFileContent.js';
|
|
6
5
|
import { parseJson } from '../json/parseJson.js';
|
|
7
6
|
import parseYaml from '../yaml/parseYaml.js';
|
|
8
7
|
import YAML from 'yaml';
|
|
9
8
|
import { determineLibrary } from '../../fs/determineFramework.js';
|
|
10
|
-
import { isValidMdx } from '../../utils/validateMdx.js';
|
|
11
9
|
import { hashStringSync } from '../../utils/hash.js';
|
|
12
|
-
import {
|
|
10
|
+
import { preprocessContent } from './preprocessContent.js';
|
|
13
11
|
export const SUPPORTED_DATA_FORMATS = ['JSX', 'ICU', 'I18NEXT'];
|
|
14
12
|
export async function aggregateFiles(settings) {
|
|
15
13
|
// Aggregate all files to translate
|
|
@@ -123,33 +121,18 @@ export async function aggregateFiles(settings) {
|
|
|
123
121
|
.map((filePath) => {
|
|
124
122
|
const content = readFile(filePath);
|
|
125
123
|
const relativePath = getRelative(filePath);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
recordWarning('skipped_file', relativePath, `MDX file is not AST parsable${validation.error ? `: ${validation.error}` : ''}`);
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
let processedContent = content;
|
|
137
|
-
let addedMintlifyTitle = false;
|
|
138
|
-
if (fileType === 'mdx' &&
|
|
139
|
-
settings.options?.mintlify?.inferTitleFromFilename) {
|
|
140
|
-
const result = applyMintlifyTitleFallback(processedContent, relativePath, settings.defaultLocale);
|
|
141
|
-
processedContent = result.content;
|
|
142
|
-
addedMintlifyTitle = result.addedTitle;
|
|
124
|
+
const processed = preprocessContent(content, relativePath, fileType, settings);
|
|
125
|
+
if (typeof processed !== 'string') {
|
|
126
|
+
logger.warn(`Skipping ${relativePath}: ${processed.skip}`);
|
|
127
|
+
recordWarning('skipped_file', relativePath, processed.skip);
|
|
128
|
+
return null;
|
|
143
129
|
}
|
|
144
|
-
const sanitizedContent = sanitizeFileContent(processedContent);
|
|
145
|
-
// Always hash original content for versionId
|
|
146
|
-
const computedVersionId = hashStringSync(content);
|
|
147
130
|
return {
|
|
148
|
-
content:
|
|
131
|
+
content: processed,
|
|
149
132
|
fileName: relativePath,
|
|
150
133
|
fileFormat: fileType.toUpperCase(),
|
|
151
134
|
fileId: hashStringSync(relativePath),
|
|
152
|
-
versionId:
|
|
135
|
+
versionId: hashStringSync(content),
|
|
153
136
|
locale: settings.defaultLocale,
|
|
154
137
|
};
|
|
155
138
|
})
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Settings } from '../../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs MDX-specific preprocessing. Returns a skip reason if the file
|
|
4
|
+
* should be skipped, or null if validation passed.
|
|
5
|
+
*/
|
|
6
|
+
export declare function preprocessMdx(content: string, filePath: string, settings: Settings): string | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { isValidMdx } from '../../../utils/validateMdx.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs MDX-specific preprocessing. Returns a skip reason if the file
|
|
4
|
+
* should be skipped, or null if validation passed.
|
|
5
|
+
*/
|
|
6
|
+
export function preprocessMdx(content, filePath, settings) {
|
|
7
|
+
if (!settings.options?.skipFileValidation?.mdx) {
|
|
8
|
+
const validation = isValidMdx(content, filePath);
|
|
9
|
+
if (!validation.isValid) {
|
|
10
|
+
return `MDX file is not AST parsable${validation.error ? `: ${validation.error}` : ''}`;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { applyMintlifyTitleFallback } from '../../../utils/mintlifyTitleFallback.js';
|
|
2
|
+
import wrapPlainUrls from '../../../utils/wrapPlainUrls.js';
|
|
3
|
+
/**
|
|
4
|
+
* Runs all Mintlify-specific preprocessing on file content.
|
|
5
|
+
*/
|
|
6
|
+
export function preprocessMintlify(content, filePath, fileType, settings) {
|
|
7
|
+
if (fileType !== 'mdx')
|
|
8
|
+
return content;
|
|
9
|
+
let result = content;
|
|
10
|
+
if (settings.options?.mintlify?.inferTitleFromFilename) {
|
|
11
|
+
result = applyMintlifyTitleFallback(result, filePath, settings.defaultLocale).content;
|
|
12
|
+
}
|
|
13
|
+
result = wrapPlainUrls(result);
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Settings } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Preprocesses file content before upload. Returns the processed content,
|
|
4
|
+
* or { skip: reason } if the file should be skipped.
|
|
5
|
+
*/
|
|
6
|
+
export declare function preprocessContent(content: string, filePath: string, fileType: string, settings: Settings): string | {
|
|
7
|
+
skip: string;
|
|
8
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { preprocessMdx } from './preprocess/mdx.js';
|
|
2
|
+
import { preprocessMintlify } from './preprocess/mintlify.js';
|
|
3
|
+
import sanitizeFileContent from '../../utils/sanitizeFileContent.js';
|
|
4
|
+
/**
|
|
5
|
+
* Preprocesses file content before upload. Returns the processed content,
|
|
6
|
+
* or { skip: reason } if the file should be skipped.
|
|
7
|
+
*/
|
|
8
|
+
export function preprocessContent(content, filePath, fileType, settings) {
|
|
9
|
+
let result = content;
|
|
10
|
+
// File-type-specific
|
|
11
|
+
if (fileType === 'mdx') {
|
|
12
|
+
const skipReason = preprocessMdx(result, filePath, settings);
|
|
13
|
+
if (skipReason)
|
|
14
|
+
return { skip: skipReason };
|
|
15
|
+
}
|
|
16
|
+
// Framework-specific
|
|
17
|
+
if (settings.framework === 'mintlify') {
|
|
18
|
+
result = preprocessMintlify(result, filePath, fileType, settings);
|
|
19
|
+
}
|
|
20
|
+
// Universal
|
|
21
|
+
result = sanitizeFileContent(result);
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "2.
|
|
1
|
+
export declare const PACKAGE_VERSION = "2.7.0";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// This file is auto-generated. Do not edit manually.
|
|
2
|
-
export const PACKAGE_VERSION = '2.
|
|
2
|
+
export const PACKAGE_VERSION = '2.7.0';
|
|
@@ -7,7 +7,9 @@ export declare const INLINE_MESSAGE_HOOK = "useMessages";
|
|
|
7
7
|
export declare const INLINE_MESSAGE_HOOK_ASYNC = "getMessages";
|
|
8
8
|
export declare const TRANSLATION_COMPONENT = "T";
|
|
9
9
|
export declare const STATIC_COMPONENT = "Static";
|
|
10
|
+
export declare const BRANCH_COMPONENT = "Branch";
|
|
10
11
|
export declare const GT_TRANSLATION_FUNCS: string[];
|
|
11
12
|
export declare const VARIABLE_COMPONENTS: string[];
|
|
12
13
|
export declare const GT_ATTRIBUTES_WITH_SUGAR: readonly ["$id", "$context", "$maxChars"];
|
|
13
14
|
export declare const GT_ATTRIBUTES: readonly ["id", "context", "maxChars", "$id", "$context", "$maxChars"];
|
|
15
|
+
export declare const DATA_ATTR_PREFIX: "data-";
|
|
@@ -7,6 +7,7 @@ export const INLINE_MESSAGE_HOOK = 'useMessages';
|
|
|
7
7
|
export const INLINE_MESSAGE_HOOK_ASYNC = 'getMessages';
|
|
8
8
|
export const TRANSLATION_COMPONENT = 'T';
|
|
9
9
|
export const STATIC_COMPONENT = 'Static';
|
|
10
|
+
export const BRANCH_COMPONENT = 'Branch';
|
|
10
11
|
// GT translation functions
|
|
11
12
|
export const GT_TRANSLATION_FUNCS = [
|
|
12
13
|
INLINE_TRANSLATION_HOOK,
|
|
@@ -22,7 +23,7 @@ export const GT_TRANSLATION_FUNCS = [
|
|
|
22
23
|
'DateTime',
|
|
23
24
|
'Currency',
|
|
24
25
|
'Num',
|
|
25
|
-
|
|
26
|
+
BRANCH_COMPONENT,
|
|
26
27
|
'Plural',
|
|
27
28
|
];
|
|
28
29
|
// Valid variable components
|
|
@@ -44,3 +45,5 @@ export const GT_ATTRIBUTES = [
|
|
|
44
45
|
'maxChars',
|
|
45
46
|
...GT_ATTRIBUTES_WITH_SUGAR,
|
|
46
47
|
];
|
|
48
|
+
// Data attribute prefix injected by build tools
|
|
49
|
+
export const DATA_ATTR_PREFIX = 'data-';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { HTML_CONTENT_PROPS, } from 'generaltranslation/types';
|
|
2
2
|
import { defaultVariableNames, getVariableName, minifyVariableType, } from '../../../utils/getVariableName.js';
|
|
3
3
|
import { isAcceptedPluralForm } from 'generaltranslation/internal';
|
|
4
|
+
import { DATA_ATTR_PREFIX } from '../constants.js';
|
|
4
5
|
/**
|
|
5
6
|
* Construct the data-_gt prop
|
|
6
7
|
* @param type - The type of the element
|
|
@@ -33,7 +34,9 @@ function constructGTProp(type, props, id) {
|
|
|
33
34
|
}
|
|
34
35
|
else if (type === 'Branch') {
|
|
35
36
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
|
|
36
|
-
const { children, branch, ...
|
|
37
|
+
const { children, branch, ...allBranches } = props;
|
|
38
|
+
// Filter out data-* attributes injected by build tools
|
|
39
|
+
const branches = Object.fromEntries(Object.entries(allBranches).filter(([key]) => !key.startsWith(DATA_ATTR_PREFIX)));
|
|
37
40
|
const resultBranches = Object.entries(branches).reduce((acc, [branchName, branch]) => {
|
|
38
41
|
acc[branchName] = addGTIdentifierToSyntaxTree(branch, id);
|
|
39
42
|
return acc;
|
|
@@ -6,10 +6,10 @@ import * as t from '@babel/types';
|
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import { parse } from '@babel/parser';
|
|
8
8
|
import addGTIdentifierToSyntaxTree from './addGTIdentifierToSyntaxTree.js';
|
|
9
|
-
import { warnHasUnwrappedExpressionSync, warnNestedTComponent, warnFunctionNotFoundSync, warnMissingReturnSync, warnDuplicateFunctionDefinitionSync, warnInvalidStaticInitSync, warnRecursiveFunctionCallSync, } from '../../../../console/index.js';
|
|
9
|
+
import { warnHasUnwrappedExpressionSync, warnNestedTComponent, warnFunctionNotFoundSync, warnMissingReturnSync, warnDuplicateFunctionDefinitionSync, warnInvalidStaticInitSync, warnRecursiveFunctionCallSync, warnDataAttrOnBranch, } from '../../../../console/index.js';
|
|
10
10
|
import { isAcceptedPluralForm } from 'generaltranslation/internal';
|
|
11
11
|
import { isStaticExpression } from '../../evaluateJsx.js';
|
|
12
|
-
import { STATIC_COMPONENT, TRANSLATION_COMPONENT, VARIABLE_COMPONENTS, } from '../constants.js';
|
|
12
|
+
import { DATA_ATTR_PREFIX, STATIC_COMPONENT, TRANSLATION_COMPONENT, VARIABLE_COMPONENTS, } from '../constants.js';
|
|
13
13
|
import { HTML_CONTENT_PROPS } from 'generaltranslation/types';
|
|
14
14
|
import { resolveImportPath } from '../resolveImportPath.js';
|
|
15
15
|
import traverseModule from '@babel/traverse';
|
|
@@ -161,6 +161,10 @@ function buildJSXTree({ node, helperPath, scopeNode, insideT, inStatic, config,
|
|
|
161
161
|
? attr.name.name
|
|
162
162
|
: attr.name.name.name;
|
|
163
163
|
let attrValue = null;
|
|
164
|
+
if (elementIsBranch && attrName.startsWith(DATA_ATTR_PREFIX)) {
|
|
165
|
+
const location = `${attr.loc?.start?.line}:${attr.loc?.start?.column}`;
|
|
166
|
+
output.errors.push(warnDataAttrOnBranch(config.file, attrName, location));
|
|
167
|
+
}
|
|
164
168
|
if (attr.value) {
|
|
165
169
|
if (t.isStringLiteral(attr.value)) {
|
|
166
170
|
attrValue = attr.value.value;
|
|
@@ -171,7 +175,9 @@ function buildJSXTree({ node, helperPath, scopeNode, insideT, inStatic, config,
|
|
|
171
175
|
const isHtmlContentProp = Object.values(HTML_CONTENT_PROPS).includes(attrName);
|
|
172
176
|
// If its a plural or branch prop
|
|
173
177
|
if ((elementIsPlural && isAcceptedPluralForm(attrName)) ||
|
|
174
|
-
(elementIsBranch &&
|
|
178
|
+
(elementIsBranch &&
|
|
179
|
+
attrName !== 'branch' &&
|
|
180
|
+
!attrName.startsWith(DATA_ATTR_PREFIX))) {
|
|
175
181
|
// Make sure that variable strings like {`I have ${count} book`} are invalid!
|
|
176
182
|
if (t.isTemplateLiteral(attr.value.expression) &&
|
|
177
183
|
!isStaticExpression(attr.value.expression, true).isStatic) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps plain URLs in markdown link syntax [url](url) so that
|
|
3
|
+
* translation pipelines preserve the URL separately from surrounding text.
|
|
4
|
+
*
|
|
5
|
+
* Uses remark AST parsing to identify URLs that appear in text nodes only.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
export default function wrapPlainUrls(content: string): string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { unified } from 'unified';
|
|
2
|
+
import remarkParse from 'remark-parse';
|
|
3
|
+
import remarkMdx from 'remark-mdx';
|
|
4
|
+
import remarkFrontmatter from 'remark-frontmatter';
|
|
5
|
+
import { visit } from 'unist-util-visit';
|
|
6
|
+
/**
|
|
7
|
+
* Wraps plain URLs in markdown link syntax [url](url) so that
|
|
8
|
+
* translation pipelines preserve the URL separately from surrounding text.
|
|
9
|
+
*
|
|
10
|
+
* Uses remark AST parsing to identify URLs that appear in text nodes only.
|
|
11
|
+
*
|
|
12
|
+
*/
|
|
13
|
+
export default function wrapPlainUrls(content) {
|
|
14
|
+
const URL_REGEX = /https?:\/\/[^\s<>\[\]]*[^\s<>\[\].,;:!?'"\]}>]/g;
|
|
15
|
+
let ast;
|
|
16
|
+
try {
|
|
17
|
+
const processor = unified()
|
|
18
|
+
.use(remarkParse)
|
|
19
|
+
.use(remarkFrontmatter, ['yaml', 'toml'])
|
|
20
|
+
.use(remarkMdx);
|
|
21
|
+
ast = processor.parse(content);
|
|
22
|
+
ast = processor.runSync(ast);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// If parsing fails, return content unchanged
|
|
26
|
+
return content;
|
|
27
|
+
}
|
|
28
|
+
// Collect all URL replacements from text nodes with their positions
|
|
29
|
+
const replacements = [];
|
|
30
|
+
visit(ast, 'text', (node, _index, parent) => {
|
|
31
|
+
// Skip text nodes inside links — those are already display text for a link
|
|
32
|
+
if (parent && parent.type === 'link')
|
|
33
|
+
return;
|
|
34
|
+
const pos = node.position;
|
|
35
|
+
if (!pos)
|
|
36
|
+
return;
|
|
37
|
+
const value = node.value;
|
|
38
|
+
let match;
|
|
39
|
+
while ((match = URL_REGEX.exec(value)) !== null) {
|
|
40
|
+
let url = match[0];
|
|
41
|
+
const nodeStartOffset = pos.start.offset;
|
|
42
|
+
if (nodeStartOffset === undefined)
|
|
43
|
+
continue;
|
|
44
|
+
// Trim unbalanced trailing ')' so that prose like "(see https://example.com)"
|
|
45
|
+
// doesn't absorb the surrounding paren, while Wikipedia-style URLs with
|
|
46
|
+
// balanced parens (e.g. /wiki/Unix_(operating_system)) are kept intact.
|
|
47
|
+
while (url.endsWith(')')) {
|
|
48
|
+
const open = url.split('(').length - 1;
|
|
49
|
+
const close = url.split(')').length - 1;
|
|
50
|
+
if (close > open) {
|
|
51
|
+
url = url.slice(0, -1);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Calculate the absolute offset in the original content
|
|
58
|
+
const urlStart = nodeStartOffset + match.index;
|
|
59
|
+
const urlEnd = urlStart + url.length;
|
|
60
|
+
replacements.push({ start: urlStart, end: urlEnd, url });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
if (replacements.length === 0)
|
|
64
|
+
return content;
|
|
65
|
+
// Apply replacements in reverse order to preserve positions
|
|
66
|
+
let result = content;
|
|
67
|
+
for (let i = replacements.length - 1; i >= 0; i--) {
|
|
68
|
+
const { start, end, url } = replacements[i];
|
|
69
|
+
result = result.slice(0, start) + `[${url}](${url})` + result.slice(end);
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
@@ -75,6 +75,10 @@ export async function runDownloadWorkflow({ fileVersionData, jobData, branchData
|
|
|
75
75
|
for (const [, value] of pollResult.fileTracker.failed) {
|
|
76
76
|
recordWarning('failed_translation', value.fileName, `Failed to translate for locale ${value.locale}`);
|
|
77
77
|
}
|
|
78
|
+
// If all files failed translation, exit early
|
|
79
|
+
if (pollResult.fileTracker.completed.size === 0) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
78
82
|
}
|
|
79
83
|
// Even if polling timed out, still download whatever completed successfully
|
|
80
84
|
if (!pollResult.success) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gt",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": "dist/main.js",
|
|
6
6
|
"files": [
|
|
@@ -131,7 +131,8 @@
|
|
|
131
131
|
"prettier": "^3.4.2",
|
|
132
132
|
"ts-node": "^10.9.2",
|
|
133
133
|
"tslib": "^2.8.1",
|
|
134
|
-
"typescript": "^5.5.4"
|
|
134
|
+
"typescript": "^5.5.4",
|
|
135
|
+
"vitest": "^2.0.0"
|
|
135
136
|
},
|
|
136
137
|
"scripts": {
|
|
137
138
|
"build": "node scripts/generate-version.js && tsc && rm -rf dist/setup/instructions && cp -r src/setup/instructions dist/setup/instructions",
|