gt 2.16.3 → 2.17.0-iris.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 +36 -0
- package/LICENSE.md +18 -102
- package/dist/api/downloadFileBatch.d.ts +6 -3
- package/dist/api/downloadFileBatch.js +10 -7
- package/dist/api/downloadFileBatch.js.map +1 -1
- package/dist/cli/base.d.ts +24 -2
- package/dist/cli/base.js +67 -20
- package/dist/cli/base.js.map +1 -1
- package/dist/cli/commands/download.js +9 -4
- package/dist/cli/commands/download.js.map +1 -1
- package/dist/cli/commands/enqueue.js +3 -2
- package/dist/cli/commands/enqueue.js.map +1 -1
- package/dist/cli/commands/setupProject.js +3 -3
- package/dist/cli/commands/setupProject.js.map +1 -1
- package/dist/cli/commands/stage.d.ts +2 -0
- package/dist/cli/commands/stage.js +6 -4
- package/dist/cli/commands/stage.js.map +1 -1
- package/dist/cli/commands/translate.d.ts +2 -1
- package/dist/cli/commands/translate.js +2 -1
- package/dist/cli/commands/translate.js.map +1 -1
- package/dist/cli/flags.d.ts +2 -2
- package/dist/cli/flags.js +6 -6
- package/dist/cli/flags.js.map +1 -1
- package/dist/cli/inline.d.ts +5 -1
- package/dist/cli/inline.js +15 -8
- package/dist/cli/inline.js.map +1 -1
- package/dist/cli/react.js +1 -1
- package/dist/cli/vue.d.ts +17 -0
- package/dist/cli/vue.js +42 -0
- package/dist/cli/vue.js.map +1 -0
- package/dist/console/logging.d.ts +2 -1
- package/dist/console/logging.js +4 -2
- package/dist/console/logging.js.map +1 -1
- package/dist/formats/files/collectFiles.d.ts +4 -0
- package/dist/formats/files/collectFiles.js +16 -4
- package/dist/formats/files/collectFiles.js.map +1 -1
- package/dist/fs/config/setupConfig.d.ts +3 -0
- package/dist/fs/config/setupConfig.js +2 -0
- package/dist/fs/config/setupConfig.js.map +1 -1
- package/dist/fs/determineFramework/index.d.ts +9 -1
- package/dist/fs/determineFramework/index.js +15 -3
- package/dist/fs/determineFramework/index.js.map +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/generated/version.js.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/next/parse/wrapContent.js +1 -1
- package/dist/react/jsx/utils/jsxParsing/removeNullChildrenFields.js.map +1 -1
- package/dist/react/jsx/utils/parseString.js +1 -1
- package/dist/react/jsx/utils/stringParsing/derivation/handleDerivation.d.ts +0 -1
- package/dist/react/jsx/utils/stringParsing/derivation/handleDerivation.js.map +1 -1
- package/dist/react/jsx/utils/stringParsing/derivation/isDeriveCall.js +1 -1
- package/dist/react/parse/createInlineUpdates.js +1 -1
- package/dist/react/parse/wrapContent.js +1 -1
- package/dist/setup/wizard.js +1 -1
- package/dist/translation/extractInline.d.ts +12 -0
- package/dist/translation/extractInline.js +37 -0
- package/dist/translation/extractInline.js.map +1 -0
- package/dist/translation/parse.js +3 -2
- package/dist/translation/parse.js.map +1 -1
- package/dist/translation/validate.d.ts +1 -1
- package/dist/translation/validate.js +7 -3
- package/dist/translation/validate.js.map +1 -1
- package/dist/types/libraries.d.ts +4 -1
- package/dist/types/libraries.js +7 -1
- package/dist/types/libraries.js.map +1 -1
- package/dist/types/parsing.d.ts +5 -0
- package/dist/types/parsing.js.map +1 -1
- package/dist/workflows/download.d.ts +3 -1
- package/dist/workflows/download.js +3 -3
- package/dist/workflows/download.js.map +1 -1
- package/dist/workflows/enqueue.d.ts +3 -1
- package/dist/workflows/enqueue.js +2 -2
- package/dist/workflows/enqueue.js.map +1 -1
- package/dist/workflows/setupProject.d.ts +2 -1
- package/dist/workflows/setupProject.js +2 -2
- package/dist/workflows/setupProject.js.map +1 -1
- package/dist/workflows/stage.d.ts +3 -1
- package/dist/workflows/stage.js +2 -2
- package/dist/workflows/stage.js.map +1 -1
- package/dist/workflows/steps/DownloadStep.d.ts +3 -1
- package/dist/workflows/steps/DownloadStep.js +3 -2
- package/dist/workflows/steps/DownloadStep.js.map +1 -1
- package/dist/workflows/steps/PollJobsStep.d.ts +3 -1
- package/dist/workflows/steps/PollJobsStep.js +4 -2
- package/dist/workflows/steps/PollJobsStep.js.map +1 -1
- package/package.json +10 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# gtx-cli
|
|
2
2
|
|
|
3
|
+
## 2.17.0-iris.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b05b470: Add Vue source extraction through the standalone `@generaltranslation/vue-extractor` package.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 5d8b78a: Migrate the package license from FSL-1.1-ALv2 to MIT.
|
|
12
|
+
- Updated dependencies [697093a]
|
|
13
|
+
- Updated dependencies [f2204b9]
|
|
14
|
+
- Updated dependencies [b05b470]
|
|
15
|
+
- Updated dependencies [5d8b78a]
|
|
16
|
+
- @generaltranslation/vue-extractor@0.1.0-iris.0
|
|
17
|
+
- @generaltranslation/format@0.1.5-iris.0
|
|
18
|
+
- generaltranslation@9.1.3-iris.0
|
|
19
|
+
- @generaltranslation/icu@0.1.2-iris.0
|
|
20
|
+
- @generaltranslation/python-extractor@0.2.36-iris.0
|
|
21
|
+
- @generaltranslation/supported-locales@2.1.16-iris.0
|
|
22
|
+
- gt-remark@1.0.12-iris.0
|
|
23
|
+
|
|
24
|
+
## 2.16.3-iris.0
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- 01ba884: Exercise Iris prerelease publishing for the CLI.
|
|
29
|
+
|
|
30
|
+
## 2.16.4
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- Updated dependencies [[`b00b93e`](https://github.com/generaltranslation/gt/commit/b00b93eb3b830b8528ef3dbd5f503ff76d1b338a)]:
|
|
35
|
+
- generaltranslation@9.1.2
|
|
36
|
+
- @generaltranslation/python-extractor@0.2.35
|
|
37
|
+
- @generaltranslation/supported-locales@2.1.15
|
|
38
|
+
|
|
3
39
|
## 2.16.3
|
|
4
40
|
|
|
5
41
|
### Patch Changes
|
package/LICENSE.md
CHANGED
|
@@ -1,105 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
## Abbreviation
|
|
4
|
-
|
|
5
|
-
FSL-1.1-ALv2
|
|
6
|
-
|
|
7
|
-
## Notice
|
|
1
|
+
MIT License
|
|
8
2
|
|
|
9
3
|
Copyright 2025 General Translation, Inc.
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
-
|
|
30
|
-
### Permitted Purpose
|
|
31
|
-
|
|
32
|
-
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
-
means making the Software available to others in a commercial product or
|
|
34
|
-
service that:
|
|
35
|
-
|
|
36
|
-
1. substitutes for the Software;
|
|
37
|
-
|
|
38
|
-
2. substitutes for any other product or service we offer using the Software
|
|
39
|
-
that exists as of the date we make the Software available; or
|
|
40
|
-
|
|
41
|
-
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
-
|
|
43
|
-
Permitted Purposes specifically include using the Software:
|
|
44
|
-
|
|
45
|
-
1. for your internal use and access;
|
|
46
|
-
|
|
47
|
-
2. for non-commercial education;
|
|
48
|
-
|
|
49
|
-
3. for non-commercial research; and
|
|
50
|
-
|
|
51
|
-
4. in connection with professional services that you provide to a licensee
|
|
52
|
-
using the Software in accordance with these Terms and Conditions.
|
|
53
|
-
|
|
54
|
-
### Patents
|
|
55
|
-
|
|
56
|
-
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
-
patents, the license grant above includes a license under our patents. If you
|
|
58
|
-
make a claim against any party that the Software infringes or contributes to
|
|
59
|
-
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
-
immediately.
|
|
61
|
-
|
|
62
|
-
### Redistribution
|
|
63
|
-
|
|
64
|
-
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
-
the Software.
|
|
66
|
-
|
|
67
|
-
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
-
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
-
remove any copyright notices provided in or with the Software.
|
|
70
|
-
|
|
71
|
-
### Disclaimer
|
|
72
|
-
|
|
73
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
-
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
-
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
-
|
|
77
|
-
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
-
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
-
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
-
|
|
81
|
-
### Trademarks
|
|
82
|
-
|
|
83
|
-
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
-
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
-
trademarks, trade names, service marks or product names.
|
|
86
|
-
|
|
87
|
-
## Grant of Future License
|
|
88
|
-
|
|
89
|
-
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
-
the Apache License, Version 2.0 that is effective on the second anniversary of
|
|
91
|
-
the date we make the Software available. On or after that date, you may use the
|
|
92
|
-
Software under the Apache License, Version 2.0, in which case the following
|
|
93
|
-
will apply:
|
|
94
|
-
|
|
95
|
-
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
96
|
-
this file except in compliance with the License.
|
|
97
|
-
|
|
98
|
-
You may obtain a copy of the License at
|
|
99
|
-
|
|
100
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
101
|
-
|
|
102
|
-
Unless required by applicable law or agreed to in writing, software distributed
|
|
103
|
-
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
|
-
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
|
-
specific language governing permissions and limitations under the License.
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Settings } from '../types/index.js';
|
|
2
2
|
import type { FileStatusTracker } from '../workflows/steps/PollJobsStep.js';
|
|
3
|
+
import { type InlineLibrary } from '../types/libraries.js';
|
|
3
4
|
export type BatchedFiles = {
|
|
4
5
|
branchId: string;
|
|
5
6
|
fileId: string;
|
|
@@ -15,9 +16,11 @@ export type DownloadFileBatchResult = {
|
|
|
15
16
|
};
|
|
16
17
|
/**
|
|
17
18
|
* Downloads multiple translation files in a single batch request
|
|
19
|
+
* @param fileTracker - Current translation status keyed by file identity
|
|
18
20
|
* @param files - Array of files to download with their output paths
|
|
19
|
-
* @param
|
|
20
|
-
* @param
|
|
21
|
+
* @param options - Resolved CLI settings used for merge and lockfile behavior
|
|
22
|
+
* @param forceDownload - Whether to bypass an up-to-date local translation
|
|
23
|
+
* @param inlineLibrary - Inline runtime used to label the shared GTJSON file
|
|
21
24
|
* @returns Object containing successful and failed file IDs
|
|
22
25
|
*/
|
|
23
|
-
export declare function downloadFileBatch(fileTracker: FileStatusTracker, files: BatchedFiles, options: Settings, forceDownload?: boolean): Promise<DownloadFileBatchResult>;
|
|
26
|
+
export declare function downloadFileBatch(fileTracker: FileStatusTracker, files: BatchedFiles, options: Settings, forceDownload?: boolean, inlineLibrary?: InlineLibrary): Promise<DownloadFileBatchResult>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logger } from "../console/logger.js";
|
|
2
2
|
import { gt } from "../utils/gt.js";
|
|
3
|
+
import { getInlineElementsLabel } from "../types/libraries.js";
|
|
3
4
|
import { getRelative } from "../fs/findFilepath.js";
|
|
4
5
|
import { SUPPORTED_FILE_EXTENSIONS } from "../formats/files/supportedFiles.js";
|
|
5
6
|
import { hasNonIdentityFileFormatTransformForType } from "../formats/files/transformFormat.js";
|
|
@@ -24,12 +25,12 @@ import stringify from "fast-json-stable-stringify";
|
|
|
24
25
|
* the served content so partially-served files don't read as fully
|
|
25
26
|
* translated — for both fresh downloads and already-downloaded skips.
|
|
26
27
|
*/
|
|
27
|
-
function reportWithheldGtJsonComponents(fileFormat, servedContent, componentCount, locale) {
|
|
28
|
+
function reportWithheldGtJsonComponents(fileFormat, servedContent, componentCount, locale, inlineLibrary) {
|
|
28
29
|
if (fileFormat !== "GTJSON" || componentCount == null) return;
|
|
29
30
|
const received = countGtJsonEntries(servedContent);
|
|
30
31
|
if (received == null) return;
|
|
31
32
|
const withheld = componentCount - received;
|
|
32
|
-
if (withheld > 0) recordWarning("pending_review",
|
|
33
|
+
if (withheld > 0) recordWarning("pending_review", `<${getInlineElementsLabel(inlineLibrary)}>`, `${withheld} component translation(s) for locale ${locale} require review and are not approved yet`);
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
36
|
* Counts entries in downloaded GTJSON output, tolerating both the flat
|
|
@@ -90,12 +91,14 @@ function shouldSkipSourceFormatMerge(inputPath, options) {
|
|
|
90
91
|
}
|
|
91
92
|
/**
|
|
92
93
|
* Downloads multiple translation files in a single batch request
|
|
94
|
+
* @param fileTracker - Current translation status keyed by file identity
|
|
93
95
|
* @param files - Array of files to download with their output paths
|
|
94
|
-
* @param
|
|
95
|
-
* @param
|
|
96
|
+
* @param options - Resolved CLI settings used for merge and lockfile behavior
|
|
97
|
+
* @param forceDownload - Whether to bypass an up-to-date local translation
|
|
98
|
+
* @param inlineLibrary - Inline runtime used to label the shared GTJSON file
|
|
96
99
|
* @returns Object containing successful and failed file IDs
|
|
97
100
|
*/
|
|
98
|
-
async function downloadFileBatch(fileTracker, files, options, forceDownload = false) {
|
|
101
|
+
async function downloadFileBatch(fileTracker, files, options, forceDownload = false, inlineLibrary) {
|
|
99
102
|
const { data: downloadedVersions, entryMap, originalV1 } = readLockfile(options);
|
|
100
103
|
let didUpdateDownloadedLock = false;
|
|
101
104
|
const requestedFileMap = new Map(files.map((file) => [`${file.branchId}:${file.fileId}:${file.versionId}:${file.locale}`, file]));
|
|
@@ -177,7 +180,7 @@ async function downloadFileBatch(fileTracker, files, options, forceDownload = fa
|
|
|
177
180
|
} catch (error) {
|
|
178
181
|
logger.warn(`Failed to re-merge existing translation for ${outputPath} (${locale}): ${error}`);
|
|
179
182
|
}
|
|
180
|
-
reportWithheldGtJsonComponents(file.fileFormat, file.data, fileProperties.componentCount, locale);
|
|
183
|
+
reportWithheldGtJsonComponents(file.fileFormat, file.data, fileProperties.componentCount, locale, inlineLibrary);
|
|
181
184
|
result.skipped.push(requestedFile);
|
|
182
185
|
continue;
|
|
183
186
|
}
|
|
@@ -195,7 +198,7 @@ async function downloadFileBatch(fileTracker, files, options, forceDownload = fa
|
|
|
195
198
|
locale,
|
|
196
199
|
inputPath
|
|
197
200
|
});
|
|
198
|
-
reportWithheldGtJsonComponents(file.fileFormat, data, fileProperties.componentCount, locale);
|
|
201
|
+
reportWithheldGtJsonComponents(file.fileFormat, data, fileProperties.componentCount, locale, inlineLibrary);
|
|
199
202
|
result.successful.push(requestedFile);
|
|
200
203
|
if (fileId && versionId && locale) {
|
|
201
204
|
const entry = findOrCreateEntry(entryMap, downloadedVersions.entries, fileId, versionId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"downloadFileBatch.js","names":["fs","path"],"sources":["../../src/api/downloadFileBatch.ts"],"sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { isBinaryFileFormat } from 'generaltranslation/types';\nimport { logger } from '../console/logger.js';\nimport { gt } from '../utils/gt.js';\nimport { Settings } from '../types/index.js';\nimport { validateJsonSchema } from '../formats/json/utils.js';\nimport { validateYamlSchema } from '../formats/yaml/utils.js';\nimport { mergeJson } from '../formats/json/mergeJson.js';\nimport { extractJson } from '../formats/json/extractJson.js';\nimport mergeYaml from '../formats/yaml/mergeYaml.js';\nimport { extractYaml } from '../formats/yaml/extractYaml.js';\nimport {\n resolveMintlifyRefs,\n shouldResolveRefs,\n} from '../utils/resolveMintlifyRefs.js';\nimport {\n readLockfile,\n writeLockfile,\n findOrCreateEntry,\n} from '../fs/config/downloadedVersions.js';\nimport { recordDownloaded, recordRemerged } from '../state/recentDownloads.js';\nimport { recordWarning } from '../state/translateWarnings.js';\nimport stringify from 'fast-json-stable-stringify';\nimport type { FileStatusTracker } from '../workflows/steps/PollJobsStep.js';\nimport { SUPPORTED_FILE_EXTENSIONS } from '../formats/files/supportedFiles.js';\nimport { hasNonIdentityFileFormatTransformForType } from '../formats/files/transformFormat.js';\nimport { getRelative } from '../fs/findFilepath.js';\n\n/**\n * The platform withholds unapproved review-gated GTJSON components from\n * served output. Reports the delta between the source component count and\n * the served content so partially-served files don't read as fully\n * translated — for both fresh downloads and already-downloaded skips.\n */\nfunction reportWithheldGtJsonComponents(\n fileFormat: string | undefined,\n servedContent: string,\n componentCount: number | undefined,\n locale: string\n): void {\n if (fileFormat !== 'GTJSON' || componentCount == null) return;\n const received = countGtJsonEntries(servedContent);\n if (received == null) return;\n const withheld = componentCount - received;\n if (withheld > 0) {\n recordWarning(\n 'pending_review',\n '<React Elements>',\n `${withheld} component translation(s) for locale ${locale} require review and are not approved yet`\n );\n }\n}\n\n/**\n * Counts entries in downloaded GTJSON output, tolerating both the flat\n * public shape and a wrapped { type, data } shape.\n */\nfunction countGtJsonEntries(content: string): number | undefined {\n try {\n const parsed = JSON.parse(content);\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return undefined;\n }\n const entries =\n parsed.type === 'GTJSON' &&\n parsed.data &&\n typeof parsed.data === 'object' &&\n !Array.isArray(parsed.data)\n ? parsed.data\n : parsed;\n return Object.keys(entries).length;\n } catch {\n return undefined;\n }\n}\n\nfunction sortJsonString(data: string): string {\n const sortedData = stringify(JSON.parse(data));\n return JSON.stringify(JSON.parse(sortedData), null, 2);\n}\n\n/**\n * Merges translated content with the current source file for schema-based formats.\n */\nfunction mergeWithSource(\n translatedContent: string,\n locale: string,\n inputPath: string,\n options: Settings\n): string {\n if (shouldSkipSourceFormatMerge(inputPath, options)) {\n return translatedContent;\n }\n if (!options.options) return translatedContent;\n\n const jsonSchema = options.options.jsonSchema\n ? validateJsonSchema(options.options, inputPath)\n : null;\n const yamlSchema =\n !jsonSchema && options.options.yamlSchema\n ? validateYamlSchema(options.options, inputPath)\n : null;\n\n if (!jsonSchema && !yamlSchema) return translatedContent;\n\n const sourceContent = fs.readFileSync(inputPath, 'utf8');\n if (!sourceContent) return translatedContent;\n\n if (jsonSchema) {\n // Resolve $ref before merging if configured\n let resolvedSourceContent = sourceContent;\n if (shouldResolveRefs(inputPath, options.options)) {\n try {\n const json = JSON.parse(sourceContent);\n const { resolved } = resolveMintlifyRefs(json, inputPath);\n resolvedSourceContent = JSON.stringify(resolved, null, 2);\n } catch {\n // Fall through with original content\n }\n }\n return mergeJson(\n resolvedSourceContent,\n inputPath,\n options.options,\n [{ translatedContent, targetLocale: locale }],\n options.defaultLocale,\n options.locales\n )[0];\n } else {\n return mergeYaml(\n sourceContent,\n inputPath,\n options.options,\n [{ translatedContent, targetLocale: locale }],\n options.defaultLocale\n )[0];\n }\n}\n\n/**\n * Determines whether a source file should be skipped for schema re-merging.\n * @param inputPath - The path of the source file\n * @param options - The settings for the project\n * @returns True if the source file should be skipped for schema re-merging, false otherwise\n */\nfunction shouldSkipSourceFormatMerge(\n inputPath: string,\n options: Settings\n): boolean {\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n if (!hasNonIdentityFileFormatTransformForType(options, fileType)) continue;\n\n const transformedSourcePaths = options.files.resolvedPaths[fileType] || [];\n if (\n transformedSourcePaths.some(\n (sourcePath) => getRelative(sourcePath) === inputPath\n )\n ) {\n return true;\n }\n }\n\n return false;\n}\n\nexport type BatchedFiles = {\n branchId: string;\n fileId: string;\n versionId: string;\n locale: string;\n outputPath: string;\n inputPath: string;\n}[];\n\nexport type DownloadFileBatchResult = {\n successful: BatchedFiles;\n failed: BatchedFiles;\n skipped: BatchedFiles;\n};\n/**\n * Downloads multiple translation files in a single batch request\n * @param files - Array of files to download with their output paths\n * @param maxRetries - Maximum number of retry attempts\n * @param retryDelay - Delay between retries in milliseconds\n * @returns Object containing successful and failed file IDs\n */\nexport async function downloadFileBatch(\n fileTracker: FileStatusTracker,\n files: BatchedFiles,\n options: Settings,\n forceDownload: boolean = false\n): Promise<DownloadFileBatchResult> {\n // Local record of what version was last downloaded for each fileName:locale\n const {\n data: downloadedVersions,\n entryMap,\n originalV1,\n } = readLockfile(options);\n let didUpdateDownloadedLock = false;\n\n // Create a map of requested file keys to the file object\n const requestedFileMap = new Map(\n files.map((file) => [\n `${file.branchId}:${file.fileId}:${file.versionId}:${file.locale}`,\n file,\n ])\n );\n const result: DownloadFileBatchResult = {\n successful: [],\n failed: [],\n skipped: [],\n };\n\n // Create a map of translationId to outputPath for easier lookup\n const outputPathMap = new Map(\n files.map((file) => [\n `${file.branchId}:${file.fileId}:${file.versionId}:${file.locale}`,\n file.outputPath,\n ])\n );\n\n try {\n // Download the files\n const responseData = await gt.downloadFileBatch(\n files.map((file) => ({\n fileId: file.fileId,\n branchId: file.branchId,\n versionId: file.versionId,\n locale: file.locale,\n }))\n );\n const downloadedFiles = responseData.files || [];\n\n // Process each file in the response\n for (const file of downloadedFiles) {\n const fileKey = `${file.branchId}:${file.fileId}:${file.versionId}:${file.locale}`;\n const requestedFile = requestedFileMap.get(fileKey);\n if (!requestedFile) {\n continue;\n }\n try {\n const outputPath = outputPathMap.get(fileKey);\n const fileProperties = fileTracker.completed.get(fileKey);\n\n if (!outputPath || !fileProperties) {\n logger.warn(`No input/output path found for file: ${fileKey}`);\n recordWarning(\n 'failed_download',\n fileKey,\n 'No input/output path found'\n );\n result.failed.push(requestedFile);\n continue;\n }\n\n const {\n fileId,\n versionId,\n locale,\n branchId,\n fileName: inputPath,\n } = fileProperties;\n\n // Ensure the directory exists\n const dir = path.dirname(outputPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n // If a local translation already exists for the same source version, skip overwrite\n const existingEntry = entryMap.get(fileId);\n const downloadedTranslation =\n existingEntry?.versionId === versionId\n ? existingEntry.translations[locale]\n : undefined;\n const fileExists = fs.existsSync(outputPath);\n\n // Binary formats (e.g. LOTTIE zip bundles) carry base64 content that must\n // not go through the text merge/sort path — decode straight to bytes and\n // write. Skip only when an unchanged local translation already exists.\n if (isBinaryFileFormat(file.fileFormat)) {\n if (!forceDownload && fileExists && downloadedTranslation) {\n result.skipped.push(requestedFile);\n continue;\n }\n await fs.promises.writeFile(\n outputPath,\n Buffer.from(file.data, 'base64')\n );\n recordDownloaded(outputPath, {\n branchId,\n fileId,\n versionId,\n locale,\n inputPath,\n });\n result.successful.push(requestedFile);\n if (fileId && versionId && locale) {\n const entry = findOrCreateEntry(\n entryMap,\n downloadedVersions.entries,\n fileId,\n versionId\n );\n entry.fileName = inputPath;\n entry.translations[locale] = {\n updatedAt: new Date().toISOString(),\n // Store a relative path (matches the text path) so gt-lock.json\n // stays portable and the skip-on-re-download check matches.\n fileName: getRelative(outputPath),\n };\n // A staged entry tracks the whole file, not one locale. Keep it\n // staged until every configured locale has downloaded, so a partial\n // batch (locales still translating) doesn't abandon the rest.\n if (entry.staged) {\n entry.staged = !options.locales.every(\n (l) => entry.translations[l]\n );\n }\n didUpdateDownloadedLock = true;\n }\n continue;\n }\n\n // Composite schema files merge translations into the source file itself,\n // so outputPath always exists and the lock can't tell whether derived\n // split outputs (e.g. {locale}/docs.json) are still on disk. Always\n // merge fresh API data so derived files are regenerated every run;\n // local edits to translated output are preserved via `gt save-local`.\n const isInPlaceComposite = options.options?.jsonSchema\n ? !!validateJsonSchema(options.options, inputPath)?.composite\n : false;\n\n if (\n !forceDownload &&\n fileExists &&\n downloadedTranslation &&\n !isInPlaceComposite\n ) {\n // For schema-based files, re-merge with current source in case\n // non-translatable fields changed (skip the API download, not the merge)\n try {\n let existingContent = fs.readFileSync(outputPath, 'utf8');\n // Resolve $ref before extraction if configured\n if (shouldResolveRefs(outputPath, options.options)) {\n try {\n const json = JSON.parse(existingContent);\n const { resolved } = resolveMintlifyRefs(json, outputPath);\n existingContent = JSON.stringify(resolved, null, 2);\n } catch {\n // Fall through with original content\n }\n }\n const jsonExtracted = options.options?.jsonSchema\n ? extractJson(\n existingContent,\n inputPath,\n options.options,\n locale,\n options.defaultLocale\n )\n : null;\n const extracted =\n jsonExtracted ??\n (options.options?.yamlSchema\n ? extractYaml(existingContent, inputPath, options.options)\n : null);\n if (extracted) {\n const remerged = mergeWithSource(\n extracted,\n locale,\n inputPath,\n options\n );\n let remergedData = remerged;\n if (outputPath.endsWith('.json')) {\n try {\n remergedData = sortJsonString(remergedData);\n } catch {\n // Fall through with unsorted content\n }\n }\n if (remergedData !== existingContent) {\n await fs.promises.writeFile(outputPath, remergedData);\n }\n // Track for postprocessing (e.g. openapi path localization)\n // even when the API download was skipped\n recordRemerged(outputPath);\n }\n } catch (error) {\n // If re-merge fails, still count as skipped — not worth failing\n // the download, but surface it so missing output is diagnosable\n logger.warn(\n `Failed to re-merge existing translation for ${outputPath} (${locale}): ${error}`\n );\n }\n // Even when the local copy is current, the served content may\n // still be missing review-withheld components — keep reporting\n reportWithheldGtJsonComponents(\n file.fileFormat,\n file.data,\n fileProperties.componentCount,\n locale\n );\n result.skipped.push(requestedFile);\n continue;\n }\n let data = mergeWithSource(file.data, locale, inputPath, options);\n\n // Stable sort JSON keys for deterministic output\n if (file.fileFormat === 'GTJSON' || outputPath.endsWith('.json')) {\n try {\n data = sortJsonString(data);\n } catch (error) {\n logger.warn(`Failed to sort JSON file: ${file.id}: ` + error);\n }\n }\n\n // Write the file to disk\n await fs.promises.writeFile(outputPath, data);\n // Track as downloaded with metadata for downstream postprocessing\n recordDownloaded(outputPath, {\n branchId,\n fileId,\n versionId,\n locale,\n inputPath,\n });\n\n reportWithheldGtJsonComponents(\n file.fileFormat,\n data,\n fileProperties.componentCount,\n locale\n );\n\n result.successful.push(requestedFile);\n if (fileId && versionId && locale) {\n const entry = findOrCreateEntry(\n entryMap,\n downloadedVersions.entries,\n fileId,\n versionId\n );\n entry.fileName = inputPath;\n entry.translations[locale] = {\n updatedAt: new Date().toISOString(),\n fileName: getRelative(outputPath),\n };\n // A staged entry tracks the whole file, not one locale. Keep it\n // staged until every configured locale has downloaded, so a partial\n // batch (locales still translating) doesn't abandon the rest.\n if (entry.staged) {\n entry.staged = !options.locales.every((l) => entry.translations[l]);\n }\n didUpdateDownloadedLock = true;\n }\n } catch (error) {\n logger.error(`Error saving file ${fileKey}: ` + error);\n recordWarning(\n 'failed_download',\n fileKey,\n `Error saving file: ${error}`\n );\n result.failed.push(requestedFile);\n }\n }\n\n // Add any files that weren't in the response to the failed list\n const downloadedFileKeys = new Set(\n downloadedFiles.map(\n (file) =>\n `${file.branchId}:${file.fileId}:${file.versionId}:${file.locale}`\n )\n );\n for (const [fileKey, requestedFile] of requestedFileMap.entries()) {\n if (!downloadedFileKeys.has(fileKey)) {\n result.failed.push(requestedFile);\n }\n }\n\n // Persist any updates to the downloaded map at the end of a successful cycle\n if (didUpdateDownloadedLock) {\n writeLockfile(downloadedVersions, originalV1);\n didUpdateDownloadedLock = false;\n }\n return result;\n } catch (error) {\n logger.error(\n `An unexpected error occurred while downloading files: ` + error\n );\n }\n\n // Mark all files as failed if we get here\n result.failed = [...requestedFileMap.values()];\n if (didUpdateDownloadedLock) {\n writeLockfile(downloadedVersions, originalV1);\n }\n return result;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"downloadFileBatch.js","names":["fs","path"],"sources":["../../src/api/downloadFileBatch.ts"],"sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { isBinaryFileFormat } from 'generaltranslation/types';\nimport { logger } from '../console/logger.js';\nimport { gt } from '../utils/gt.js';\nimport { Settings } from '../types/index.js';\nimport { validateJsonSchema } from '../formats/json/utils.js';\nimport { validateYamlSchema } from '../formats/yaml/utils.js';\nimport { mergeJson } from '../formats/json/mergeJson.js';\nimport { extractJson } from '../formats/json/extractJson.js';\nimport mergeYaml from '../formats/yaml/mergeYaml.js';\nimport { extractYaml } from '../formats/yaml/extractYaml.js';\nimport {\n resolveMintlifyRefs,\n shouldResolveRefs,\n} from '../utils/resolveMintlifyRefs.js';\nimport {\n readLockfile,\n writeLockfile,\n findOrCreateEntry,\n} from '../fs/config/downloadedVersions.js';\nimport { recordDownloaded, recordRemerged } from '../state/recentDownloads.js';\nimport { recordWarning } from '../state/translateWarnings.js';\nimport stringify from 'fast-json-stable-stringify';\nimport type { FileStatusTracker } from '../workflows/steps/PollJobsStep.js';\nimport { SUPPORTED_FILE_EXTENSIONS } from '../formats/files/supportedFiles.js';\nimport { hasNonIdentityFileFormatTransformForType } from '../formats/files/transformFormat.js';\nimport { getRelative } from '../fs/findFilepath.js';\nimport {\n getInlineElementsLabel,\n type InlineLibrary,\n} from '../types/libraries.js';\n\n/**\n * The platform withholds unapproved review-gated GTJSON components from\n * served output. Reports the delta between the source component count and\n * the served content so partially-served files don't read as fully\n * translated — for both fresh downloads and already-downloaded skips.\n */\nfunction reportWithheldGtJsonComponents(\n fileFormat: string | undefined,\n servedContent: string,\n componentCount: number | undefined,\n locale: string,\n inlineLibrary?: InlineLibrary\n): void {\n if (fileFormat !== 'GTJSON' || componentCount == null) return;\n const received = countGtJsonEntries(servedContent);\n if (received == null) return;\n const withheld = componentCount - received;\n if (withheld > 0) {\n recordWarning(\n 'pending_review',\n `<${getInlineElementsLabel(inlineLibrary)}>`,\n `${withheld} component translation(s) for locale ${locale} require review and are not approved yet`\n );\n }\n}\n\n/**\n * Counts entries in downloaded GTJSON output, tolerating both the flat\n * public shape and a wrapped { type, data } shape.\n */\nfunction countGtJsonEntries(content: string): number | undefined {\n try {\n const parsed = JSON.parse(content);\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return undefined;\n }\n const entries =\n parsed.type === 'GTJSON' &&\n parsed.data &&\n typeof parsed.data === 'object' &&\n !Array.isArray(parsed.data)\n ? parsed.data\n : parsed;\n return Object.keys(entries).length;\n } catch {\n return undefined;\n }\n}\n\nfunction sortJsonString(data: string): string {\n const sortedData = stringify(JSON.parse(data));\n return JSON.stringify(JSON.parse(sortedData), null, 2);\n}\n\n/**\n * Merges translated content with the current source file for schema-based formats.\n */\nfunction mergeWithSource(\n translatedContent: string,\n locale: string,\n inputPath: string,\n options: Settings\n): string {\n if (shouldSkipSourceFormatMerge(inputPath, options)) {\n return translatedContent;\n }\n if (!options.options) return translatedContent;\n\n const jsonSchema = options.options.jsonSchema\n ? validateJsonSchema(options.options, inputPath)\n : null;\n const yamlSchema =\n !jsonSchema && options.options.yamlSchema\n ? validateYamlSchema(options.options, inputPath)\n : null;\n\n if (!jsonSchema && !yamlSchema) return translatedContent;\n\n const sourceContent = fs.readFileSync(inputPath, 'utf8');\n if (!sourceContent) return translatedContent;\n\n if (jsonSchema) {\n // Resolve $ref before merging if configured\n let resolvedSourceContent = sourceContent;\n if (shouldResolveRefs(inputPath, options.options)) {\n try {\n const json = JSON.parse(sourceContent);\n const { resolved } = resolveMintlifyRefs(json, inputPath);\n resolvedSourceContent = JSON.stringify(resolved, null, 2);\n } catch {\n // Fall through with original content\n }\n }\n return mergeJson(\n resolvedSourceContent,\n inputPath,\n options.options,\n [{ translatedContent, targetLocale: locale }],\n options.defaultLocale,\n options.locales\n )[0];\n } else {\n return mergeYaml(\n sourceContent,\n inputPath,\n options.options,\n [{ translatedContent, targetLocale: locale }],\n options.defaultLocale\n )[0];\n }\n}\n\n/**\n * Determines whether a source file should be skipped for schema re-merging.\n * @param inputPath - The path of the source file\n * @param options - The settings for the project\n * @returns True if the source file should be skipped for schema re-merging, false otherwise\n */\nfunction shouldSkipSourceFormatMerge(\n inputPath: string,\n options: Settings\n): boolean {\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n if (!hasNonIdentityFileFormatTransformForType(options, fileType)) continue;\n\n const transformedSourcePaths = options.files.resolvedPaths[fileType] || [];\n if (\n transformedSourcePaths.some(\n (sourcePath) => getRelative(sourcePath) === inputPath\n )\n ) {\n return true;\n }\n }\n\n return false;\n}\n\nexport type BatchedFiles = {\n branchId: string;\n fileId: string;\n versionId: string;\n locale: string;\n outputPath: string;\n inputPath: string;\n}[];\n\nexport type DownloadFileBatchResult = {\n successful: BatchedFiles;\n failed: BatchedFiles;\n skipped: BatchedFiles;\n};\n/**\n * Downloads multiple translation files in a single batch request\n * @param fileTracker - Current translation status keyed by file identity\n * @param files - Array of files to download with their output paths\n * @param options - Resolved CLI settings used for merge and lockfile behavior\n * @param forceDownload - Whether to bypass an up-to-date local translation\n * @param inlineLibrary - Inline runtime used to label the shared GTJSON file\n * @returns Object containing successful and failed file IDs\n */\nexport async function downloadFileBatch(\n fileTracker: FileStatusTracker,\n files: BatchedFiles,\n options: Settings,\n forceDownload: boolean = false,\n inlineLibrary?: InlineLibrary\n): Promise<DownloadFileBatchResult> {\n // Local record of what version was last downloaded for each fileName:locale\n const {\n data: downloadedVersions,\n entryMap,\n originalV1,\n } = readLockfile(options);\n let didUpdateDownloadedLock = false;\n\n // Create a map of requested file keys to the file object\n const requestedFileMap = new Map(\n files.map((file) => [\n `${file.branchId}:${file.fileId}:${file.versionId}:${file.locale}`,\n file,\n ])\n );\n const result: DownloadFileBatchResult = {\n successful: [],\n failed: [],\n skipped: [],\n };\n\n // Create a map of translationId to outputPath for easier lookup\n const outputPathMap = new Map(\n files.map((file) => [\n `${file.branchId}:${file.fileId}:${file.versionId}:${file.locale}`,\n file.outputPath,\n ])\n );\n\n try {\n // Download the files\n const responseData = await gt.downloadFileBatch(\n files.map((file) => ({\n fileId: file.fileId,\n branchId: file.branchId,\n versionId: file.versionId,\n locale: file.locale,\n }))\n );\n const downloadedFiles = responseData.files || [];\n\n // Process each file in the response\n for (const file of downloadedFiles) {\n const fileKey = `${file.branchId}:${file.fileId}:${file.versionId}:${file.locale}`;\n const requestedFile = requestedFileMap.get(fileKey);\n if (!requestedFile) {\n continue;\n }\n try {\n const outputPath = outputPathMap.get(fileKey);\n const fileProperties = fileTracker.completed.get(fileKey);\n\n if (!outputPath || !fileProperties) {\n logger.warn(`No input/output path found for file: ${fileKey}`);\n recordWarning(\n 'failed_download',\n fileKey,\n 'No input/output path found'\n );\n result.failed.push(requestedFile);\n continue;\n }\n\n const {\n fileId,\n versionId,\n locale,\n branchId,\n fileName: inputPath,\n } = fileProperties;\n\n // Ensure the directory exists\n const dir = path.dirname(outputPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n // If a local translation already exists for the same source version, skip overwrite\n const existingEntry = entryMap.get(fileId);\n const downloadedTranslation =\n existingEntry?.versionId === versionId\n ? existingEntry.translations[locale]\n : undefined;\n const fileExists = fs.existsSync(outputPath);\n\n // Binary formats (e.g. LOTTIE zip bundles) carry base64 content that must\n // not go through the text merge/sort path — decode straight to bytes and\n // write. Skip only when an unchanged local translation already exists.\n if (isBinaryFileFormat(file.fileFormat)) {\n if (!forceDownload && fileExists && downloadedTranslation) {\n result.skipped.push(requestedFile);\n continue;\n }\n await fs.promises.writeFile(\n outputPath,\n Buffer.from(file.data, 'base64')\n );\n recordDownloaded(outputPath, {\n branchId,\n fileId,\n versionId,\n locale,\n inputPath,\n });\n result.successful.push(requestedFile);\n if (fileId && versionId && locale) {\n const entry = findOrCreateEntry(\n entryMap,\n downloadedVersions.entries,\n fileId,\n versionId\n );\n entry.fileName = inputPath;\n entry.translations[locale] = {\n updatedAt: new Date().toISOString(),\n // Store a relative path (matches the text path) so gt-lock.json\n // stays portable and the skip-on-re-download check matches.\n fileName: getRelative(outputPath),\n };\n // A staged entry tracks the whole file, not one locale. Keep it\n // staged until every configured locale has downloaded, so a partial\n // batch (locales still translating) doesn't abandon the rest.\n if (entry.staged) {\n entry.staged = !options.locales.every(\n (l) => entry.translations[l]\n );\n }\n didUpdateDownloadedLock = true;\n }\n continue;\n }\n\n // Composite schema files merge translations into the source file itself,\n // so outputPath always exists and the lock can't tell whether derived\n // split outputs (e.g. {locale}/docs.json) are still on disk. Always\n // merge fresh API data so derived files are regenerated every run;\n // local edits to translated output are preserved via `gt save-local`.\n const isInPlaceComposite = options.options?.jsonSchema\n ? !!validateJsonSchema(options.options, inputPath)?.composite\n : false;\n\n if (\n !forceDownload &&\n fileExists &&\n downloadedTranslation &&\n !isInPlaceComposite\n ) {\n // For schema-based files, re-merge with current source in case\n // non-translatable fields changed (skip the API download, not the merge)\n try {\n let existingContent = fs.readFileSync(outputPath, 'utf8');\n // Resolve $ref before extraction if configured\n if (shouldResolveRefs(outputPath, options.options)) {\n try {\n const json = JSON.parse(existingContent);\n const { resolved } = resolveMintlifyRefs(json, outputPath);\n existingContent = JSON.stringify(resolved, null, 2);\n } catch {\n // Fall through with original content\n }\n }\n const jsonExtracted = options.options?.jsonSchema\n ? extractJson(\n existingContent,\n inputPath,\n options.options,\n locale,\n options.defaultLocale\n )\n : null;\n const extracted =\n jsonExtracted ??\n (options.options?.yamlSchema\n ? extractYaml(existingContent, inputPath, options.options)\n : null);\n if (extracted) {\n const remerged = mergeWithSource(\n extracted,\n locale,\n inputPath,\n options\n );\n let remergedData = remerged;\n if (outputPath.endsWith('.json')) {\n try {\n remergedData = sortJsonString(remergedData);\n } catch {\n // Fall through with unsorted content\n }\n }\n if (remergedData !== existingContent) {\n await fs.promises.writeFile(outputPath, remergedData);\n }\n // Track for postprocessing (e.g. openapi path localization)\n // even when the API download was skipped\n recordRemerged(outputPath);\n }\n } catch (error) {\n // If re-merge fails, still count as skipped — not worth failing\n // the download, but surface it so missing output is diagnosable\n logger.warn(\n `Failed to re-merge existing translation for ${outputPath} (${locale}): ${error}`\n );\n }\n // Even when the local copy is current, the served content may\n // still be missing review-withheld components — keep reporting\n reportWithheldGtJsonComponents(\n file.fileFormat,\n file.data,\n fileProperties.componentCount,\n locale,\n inlineLibrary\n );\n result.skipped.push(requestedFile);\n continue;\n }\n let data = mergeWithSource(file.data, locale, inputPath, options);\n\n // Stable sort JSON keys for deterministic output\n if (file.fileFormat === 'GTJSON' || outputPath.endsWith('.json')) {\n try {\n data = sortJsonString(data);\n } catch (error) {\n logger.warn(`Failed to sort JSON file: ${file.id}: ` + error);\n }\n }\n\n // Write the file to disk\n await fs.promises.writeFile(outputPath, data);\n // Track as downloaded with metadata for downstream postprocessing\n recordDownloaded(outputPath, {\n branchId,\n fileId,\n versionId,\n locale,\n inputPath,\n });\n\n reportWithheldGtJsonComponents(\n file.fileFormat,\n data,\n fileProperties.componentCount,\n locale,\n inlineLibrary\n );\n\n result.successful.push(requestedFile);\n if (fileId && versionId && locale) {\n const entry = findOrCreateEntry(\n entryMap,\n downloadedVersions.entries,\n fileId,\n versionId\n );\n entry.fileName = inputPath;\n entry.translations[locale] = {\n updatedAt: new Date().toISOString(),\n fileName: getRelative(outputPath),\n };\n // A staged entry tracks the whole file, not one locale. Keep it\n // staged until every configured locale has downloaded, so a partial\n // batch (locales still translating) doesn't abandon the rest.\n if (entry.staged) {\n entry.staged = !options.locales.every((l) => entry.translations[l]);\n }\n didUpdateDownloadedLock = true;\n }\n } catch (error) {\n logger.error(`Error saving file ${fileKey}: ` + error);\n recordWarning(\n 'failed_download',\n fileKey,\n `Error saving file: ${error}`\n );\n result.failed.push(requestedFile);\n }\n }\n\n // Add any files that weren't in the response to the failed list\n const downloadedFileKeys = new Set(\n downloadedFiles.map(\n (file) =>\n `${file.branchId}:${file.fileId}:${file.versionId}:${file.locale}`\n )\n );\n for (const [fileKey, requestedFile] of requestedFileMap.entries()) {\n if (!downloadedFileKeys.has(fileKey)) {\n result.failed.push(requestedFile);\n }\n }\n\n // Persist any updates to the downloaded map at the end of a successful cycle\n if (didUpdateDownloadedLock) {\n writeLockfile(downloadedVersions, originalV1);\n didUpdateDownloadedLock = false;\n }\n return result;\n } catch (error) {\n logger.error(\n `An unexpected error occurred while downloading files: ` + error\n );\n }\n\n // Mark all files as failed if we get here\n result.failed = [...requestedFileMap.values()];\n if (didUpdateDownloadedLock) {\n writeLockfile(downloadedVersions, originalV1);\n }\n return result;\n}\n"],"mappings":"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"}
|
package/dist/cli/base.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { Settings, SupportedLibraries, TranslateFlags } from '../types/index.js';
|
|
2
|
+
import { Settings, SupportedLibraries, SetupOptions, TranslateFlags } from '../types/index.js';
|
|
3
3
|
export type UploadOptions = {
|
|
4
4
|
config?: string;
|
|
5
5
|
apiKey?: string;
|
|
@@ -16,6 +16,16 @@ export type GitSetupOptions = {
|
|
|
16
16
|
omitConfigIds?: boolean;
|
|
17
17
|
driverCommand?: string;
|
|
18
18
|
};
|
|
19
|
+
type LocalTranslationGuidanceOptions = {
|
|
20
|
+
generatedLoader: boolean;
|
|
21
|
+
runtimeSetup: InlineRuntimeSetup;
|
|
22
|
+
translationsDir: string;
|
|
23
|
+
};
|
|
24
|
+
type InlineRuntimeSetup = {
|
|
25
|
+
hasOtherInlineRuntime: boolean;
|
|
26
|
+
hasVueRuntime: boolean;
|
|
27
|
+
ranReactSetup: boolean;
|
|
28
|
+
};
|
|
19
29
|
export declare class BaseCLI {
|
|
20
30
|
protected library: SupportedLibraries;
|
|
21
31
|
protected additionalModules: SupportedLibraries[];
|
|
@@ -60,7 +70,19 @@ export declare class BaseCLI {
|
|
|
60
70
|
protected setupLoginCommand(): void;
|
|
61
71
|
protected setupInitCommand(): void;
|
|
62
72
|
protected setupConfigureCommand(): void;
|
|
73
|
+
protected handleConfigureCommand(options?: SetupOptions): Promise<void>;
|
|
63
74
|
protected handleUploadCommand(settings: Settings & UploadOptions): Promise<void>;
|
|
64
|
-
|
|
75
|
+
/** Describes installed runtimes without changing historical package lookup. */
|
|
76
|
+
protected getInlineRuntimeSetup(packageJson: Record<string, unknown>, ranReactSetup?: boolean): InlineRuntimeSetup;
|
|
77
|
+
/** Returns whether any inline runtime is installed for setup. */
|
|
78
|
+
protected isInlineRuntimeInstalled(packageJson: Record<string, unknown>): boolean;
|
|
79
|
+
/** Returns whether every installed runtime can consume CDN translations. */
|
|
80
|
+
protected supportsCDNStorage(runtimeSetup: InlineRuntimeSetup): boolean;
|
|
81
|
+
/** Returns whether setup should generate a local runtime loader. */
|
|
82
|
+
protected shouldGenerateLocalTranslationLoader(isVite: boolean, runtimeSetup: InlineRuntimeSetup): boolean;
|
|
83
|
+
/** Returns framework guidance after selecting local translation storage. */
|
|
84
|
+
protected getLocalTranslationGuidance({ generatedLoader, runtimeSetup, translationsDir, }: LocalTranslationGuidanceOptions): string | undefined;
|
|
85
|
+
protected handleInitCommand(ranReactSetup: boolean, useDefaults?: boolean, isVite?: boolean, options?: SetupOptions): Promise<void>;
|
|
65
86
|
protected handleLoginCommand(options: LoginOptions): Promise<void>;
|
|
66
87
|
}
|
|
88
|
+
export {};
|
package/dist/cli/base.js
CHANGED
|
@@ -2,10 +2,10 @@ import { logger } from "../console/logger.js";
|
|
|
2
2
|
import { isPackageInstalled, searchForPackageJson } from "../utils/packageJson.js";
|
|
3
3
|
import { DEFAULT_TRANSLATIONS_DIR, DEFAULT_VITE_TRANSLATIONS_DIR } from "../utils/constants.js";
|
|
4
4
|
import { parseGlobPatterns } from "../console/promptParsing.js";
|
|
5
|
+
import { INLINE_LIBRARIES } from "../types/libraries.js";
|
|
5
6
|
import { displayHeader, exitSync, logErrorAndExit, promptConfirm, promptGlobPatterns, promptMultiSelect, promptSelect, promptText } from "../console/logging.js";
|
|
6
7
|
import findFilepath from "../fs/findFilepath.js";
|
|
7
8
|
import { lottieTranslateError } from "../console/index.js";
|
|
8
|
-
import { INLINE_LIBRARIES } from "../types/libraries.js";
|
|
9
9
|
import { loadConfig } from "../fs/config/loadConfig.js";
|
|
10
10
|
import { FILE_EXT_TO_EXT_LABEL } from "../formats/files/supportedFiles.js";
|
|
11
11
|
import { generateSettings } from "../config/generateSettings.js";
|
|
@@ -41,6 +41,7 @@ import chalk from "chalk";
|
|
|
41
41
|
import path from "node:path";
|
|
42
42
|
import fs from "node:fs";
|
|
43
43
|
import { createDiagnosticMessage } from "generaltranslation/internal";
|
|
44
|
+
import { manifestDirectlyDeclaresGTVue } from "@generaltranslation/vue-extractor/integration";
|
|
44
45
|
//#region src/cli/base.ts
|
|
45
46
|
const ID_COMPATIBILITY_WARNING_COMMANDS = new Set([
|
|
46
47
|
"download",
|
|
@@ -233,7 +234,7 @@ var BaseCLI = class {
|
|
|
233
234
|
if (!settings.stageTranslations) {
|
|
234
235
|
if (settings.files?.resolvedPaths.lottie?.length) return logErrorAndExit(lottieTranslateError);
|
|
235
236
|
const results = await handleStage(initOptions, settings, this.library, false);
|
|
236
|
-
if (results) await handleTranslate(initOptions, settings, results.fileVersionData, results.jobData, results.branchData, results.publishMap);
|
|
237
|
+
if (results) await handleTranslate(initOptions, settings, results.fileVersionData, results.jobData, results.branchData, results.publishMap, results.inlineLibrary);
|
|
237
238
|
} else await handleDownload(initOptions, settings, this.library);
|
|
238
239
|
const include = getNeedsPostprocessing();
|
|
239
240
|
if (include.size > 0) await postProcessTranslations(settings, include);
|
|
@@ -328,28 +329,68 @@ var BaseCLI = class {
|
|
|
328
329
|
});
|
|
329
330
|
}
|
|
330
331
|
setupConfigureCommand() {
|
|
331
|
-
this.program.command("configure").description("Configure your project for General Translation. This will create a gt.config.json file in your codebase.").action(
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
332
|
+
this.program.command("configure").description("Configure your project for General Translation. This will create a gt.config.json file in your codebase.").action(() => this.handleConfigureCommand());
|
|
333
|
+
}
|
|
334
|
+
async handleConfigureCommand(options) {
|
|
335
|
+
await exitIfUnsupportedSetupTarget();
|
|
336
|
+
displayHeader("Configuring project...");
|
|
337
|
+
logger.info("Welcome! This tool will help you configure your gt.config.json file. See the docs: https://generaltranslation.com/docs/cli/reference/config for more information.");
|
|
338
|
+
const framework = await detectFramework();
|
|
339
|
+
await this.handleInitCommand(false, false, framework.name === "vite", options);
|
|
340
|
+
logger.endCommand("Done! Make sure you have an API key and project ID to use General Translation. Get them on the dashboard: https://generaltranslation.com/dashboard");
|
|
339
341
|
}
|
|
340
342
|
async handleUploadCommand(settings) {
|
|
341
343
|
if (!settings.files) return;
|
|
342
344
|
await upload(settings);
|
|
343
345
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
+
/** Describes installed runtimes without changing historical package lookup. */
|
|
347
|
+
getInlineRuntimeSetup(packageJson, ranReactSetup = false) {
|
|
348
|
+
return {
|
|
349
|
+
hasOtherInlineRuntime: INLINE_LIBRARIES.some((lib) => lib !== "gt-vue" && isPackageInstalled(lib, packageJson)),
|
|
350
|
+
hasVueRuntime: manifestDirectlyDeclaresGTVue(packageJson),
|
|
351
|
+
ranReactSetup
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
/** Returns whether any inline runtime is installed for setup. */
|
|
355
|
+
isInlineRuntimeInstalled(packageJson) {
|
|
356
|
+
const { hasOtherInlineRuntime, hasVueRuntime } = this.getInlineRuntimeSetup(packageJson);
|
|
357
|
+
return hasOtherInlineRuntime || hasVueRuntime;
|
|
358
|
+
}
|
|
359
|
+
/** Returns whether every installed runtime can consume CDN translations. */
|
|
360
|
+
supportsCDNStorage(runtimeSetup) {
|
|
361
|
+
return !runtimeSetup.hasVueRuntime;
|
|
362
|
+
}
|
|
363
|
+
/** Returns whether setup should generate a local runtime loader. */
|
|
364
|
+
shouldGenerateLocalTranslationLoader(isVite, runtimeSetup) {
|
|
365
|
+
if (isVite) return false;
|
|
366
|
+
if (!runtimeSetup.hasVueRuntime) return true;
|
|
367
|
+
return runtimeSetup.hasOtherInlineRuntime || runtimeSetup.ranReactSetup;
|
|
368
|
+
}
|
|
369
|
+
/** Returns framework guidance after selecting local translation storage. */
|
|
370
|
+
getLocalTranslationGuidance({ generatedLoader, runtimeSetup, translationsDir }) {
|
|
371
|
+
const guidance = [];
|
|
372
|
+
if (generatedLoader) guidance.push(`Created ${chalk.cyan("loadTranslations.js")} file for local translations.
|
|
373
|
+
Make sure to add this function to your app configuration.
|
|
374
|
+
See https://generaltranslation.com/en/docs/next/guides/local-tx`);
|
|
375
|
+
if (runtimeSetup.hasVueRuntime) guidance.push(`GT will write local translation files to ${translationsDir}.
|
|
376
|
+
Configure createGT({ loadTranslations }) to load files from that directory.
|
|
377
|
+
See https://www.npmjs.com/package/gt-vue`);
|
|
378
|
+
return guidance.length > 0 ? guidance.join("\n") : void 0;
|
|
379
|
+
}
|
|
380
|
+
async handleInitCommand(ranReactSetup, useDefaults = false, isVite = false, options) {
|
|
381
|
+
const configFilepath = options?.config || (!isVite && fs.existsSync("src/gt.config.json") ? "src/gt.config.json" : "gt.config.json");
|
|
346
382
|
const { defaultLocale, locales } = await getDesiredLocales(loadConfig(configFilepath));
|
|
347
383
|
const packageJson = await searchForPackageJson();
|
|
348
|
-
const
|
|
349
|
-
|
|
384
|
+
const runtimeSetup = packageJson ? this.getInlineRuntimeSetup(packageJson, ranReactSetup) : {
|
|
385
|
+
hasOtherInlineRuntime: false,
|
|
386
|
+
hasVueRuntime: false,
|
|
387
|
+
ranReactSetup
|
|
388
|
+
};
|
|
389
|
+
const isUsingGT = runtimeSetup.ranReactSetup || runtimeSetup.hasOtherInlineRuntime || runtimeSetup.hasVueRuntime;
|
|
350
390
|
const usingCDN = await (async () => {
|
|
351
391
|
if (!isUsingGT) return false;
|
|
352
392
|
if (useDefaults) return false;
|
|
393
|
+
if (!this.supportsCDNStorage(runtimeSetup)) return false;
|
|
353
394
|
return await promptSelect({
|
|
354
395
|
message: `Would you like to save translation files locally or use the General Translation CDN to store them?`,
|
|
355
396
|
options: [{
|
|
@@ -367,11 +408,15 @@ var BaseCLI = class {
|
|
|
367
408
|
message: "What is the path to the directory where you would like to store your translation files?",
|
|
368
409
|
defaultValue: defaultTranslationsDir
|
|
369
410
|
}) : null)?.trim() || defaultTranslationsDir;
|
|
370
|
-
if (isUsingGT && !usingCDN
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
411
|
+
if (isUsingGT && !usingCDN) {
|
|
412
|
+
const generatedLoader = this.shouldGenerateLocalTranslationLoader(isVite, runtimeSetup);
|
|
413
|
+
if (generatedLoader) await createLoadTranslationsFile(process.cwd(), finalTranslationsDir, locales);
|
|
414
|
+
const guidance = this.getLocalTranslationGuidance({
|
|
415
|
+
generatedLoader,
|
|
416
|
+
runtimeSetup,
|
|
417
|
+
translationsDir: finalTranslationsDir
|
|
418
|
+
});
|
|
419
|
+
if (guidance) logger.message(guidance);
|
|
375
420
|
}
|
|
376
421
|
const message = !isUsingGT ? "What is the format of your language resource files? Select as many as applicable.\nAdditionally, you can translate any other files you have in your project." : `Do you have any additional files in this project to translate? For example, Markdown files for docs. ${chalk.dim("(To continue without selecting press Enter)")}`;
|
|
377
422
|
const fileExtensions = useDefaults && isUsingGT ? [] : await promptMultiSelect({
|
|
@@ -417,9 +462,11 @@ See https://generaltranslation.com/en/docs/next/guides/local-tx`);
|
|
|
417
462
|
await createOrUpdateConfig(configFilepath, {
|
|
418
463
|
defaultLocale,
|
|
419
464
|
locales,
|
|
465
|
+
src: options?.src,
|
|
420
466
|
files: Object.keys(files).length > 0 ? files : void 0,
|
|
421
467
|
framework: isVite ? "vite" : void 0,
|
|
422
|
-
publish: isUsingGT && usingCDN
|
|
468
|
+
publish: isUsingGT && usingCDN,
|
|
469
|
+
clearPublish: runtimeSetup.hasVueRuntime && !usingCDN
|
|
423
470
|
});
|
|
424
471
|
logger.success(`Edit ${chalk.cyan(configFilepath)} to customize your translation setup. Docs: https://generaltranslation.com/docs/cli/reference/config`);
|
|
425
472
|
if (ranReactSetup && isVite) await setupViteSPA({
|