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.
Files changed (88) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE.md +18 -102
  3. package/dist/api/downloadFileBatch.d.ts +6 -3
  4. package/dist/api/downloadFileBatch.js +10 -7
  5. package/dist/api/downloadFileBatch.js.map +1 -1
  6. package/dist/cli/base.d.ts +24 -2
  7. package/dist/cli/base.js +67 -20
  8. package/dist/cli/base.js.map +1 -1
  9. package/dist/cli/commands/download.js +9 -4
  10. package/dist/cli/commands/download.js.map +1 -1
  11. package/dist/cli/commands/enqueue.js +3 -2
  12. package/dist/cli/commands/enqueue.js.map +1 -1
  13. package/dist/cli/commands/setupProject.js +3 -3
  14. package/dist/cli/commands/setupProject.js.map +1 -1
  15. package/dist/cli/commands/stage.d.ts +2 -0
  16. package/dist/cli/commands/stage.js +6 -4
  17. package/dist/cli/commands/stage.js.map +1 -1
  18. package/dist/cli/commands/translate.d.ts +2 -1
  19. package/dist/cli/commands/translate.js +2 -1
  20. package/dist/cli/commands/translate.js.map +1 -1
  21. package/dist/cli/flags.d.ts +2 -2
  22. package/dist/cli/flags.js +6 -6
  23. package/dist/cli/flags.js.map +1 -1
  24. package/dist/cli/inline.d.ts +5 -1
  25. package/dist/cli/inline.js +15 -8
  26. package/dist/cli/inline.js.map +1 -1
  27. package/dist/cli/react.js +1 -1
  28. package/dist/cli/vue.d.ts +17 -0
  29. package/dist/cli/vue.js +42 -0
  30. package/dist/cli/vue.js.map +1 -0
  31. package/dist/console/logging.d.ts +2 -1
  32. package/dist/console/logging.js +4 -2
  33. package/dist/console/logging.js.map +1 -1
  34. package/dist/formats/files/collectFiles.d.ts +4 -0
  35. package/dist/formats/files/collectFiles.js +16 -4
  36. package/dist/formats/files/collectFiles.js.map +1 -1
  37. package/dist/fs/config/setupConfig.d.ts +3 -0
  38. package/dist/fs/config/setupConfig.js +2 -0
  39. package/dist/fs/config/setupConfig.js.map +1 -1
  40. package/dist/fs/determineFramework/index.d.ts +9 -1
  41. package/dist/fs/determineFramework/index.js +15 -3
  42. package/dist/fs/determineFramework/index.js.map +1 -1
  43. package/dist/generated/version.d.ts +1 -1
  44. package/dist/generated/version.js +1 -1
  45. package/dist/generated/version.js.map +1 -1
  46. package/dist/index.js +5 -2
  47. package/dist/index.js.map +1 -1
  48. package/dist/next/parse/wrapContent.js +1 -1
  49. package/dist/react/jsx/utils/jsxParsing/removeNullChildrenFields.js.map +1 -1
  50. package/dist/react/jsx/utils/parseString.js +1 -1
  51. package/dist/react/jsx/utils/stringParsing/derivation/handleDerivation.d.ts +0 -1
  52. package/dist/react/jsx/utils/stringParsing/derivation/handleDerivation.js.map +1 -1
  53. package/dist/react/jsx/utils/stringParsing/derivation/isDeriveCall.js +1 -1
  54. package/dist/react/parse/createInlineUpdates.js +1 -1
  55. package/dist/react/parse/wrapContent.js +1 -1
  56. package/dist/setup/wizard.js +1 -1
  57. package/dist/translation/extractInline.d.ts +12 -0
  58. package/dist/translation/extractInline.js +37 -0
  59. package/dist/translation/extractInline.js.map +1 -0
  60. package/dist/translation/parse.js +3 -2
  61. package/dist/translation/parse.js.map +1 -1
  62. package/dist/translation/validate.d.ts +1 -1
  63. package/dist/translation/validate.js +7 -3
  64. package/dist/translation/validate.js.map +1 -1
  65. package/dist/types/libraries.d.ts +4 -1
  66. package/dist/types/libraries.js +7 -1
  67. package/dist/types/libraries.js.map +1 -1
  68. package/dist/types/parsing.d.ts +5 -0
  69. package/dist/types/parsing.js.map +1 -1
  70. package/dist/workflows/download.d.ts +3 -1
  71. package/dist/workflows/download.js +3 -3
  72. package/dist/workflows/download.js.map +1 -1
  73. package/dist/workflows/enqueue.d.ts +3 -1
  74. package/dist/workflows/enqueue.js +2 -2
  75. package/dist/workflows/enqueue.js.map +1 -1
  76. package/dist/workflows/setupProject.d.ts +2 -1
  77. package/dist/workflows/setupProject.js +2 -2
  78. package/dist/workflows/setupProject.js.map +1 -1
  79. package/dist/workflows/stage.d.ts +3 -1
  80. package/dist/workflows/stage.js +2 -2
  81. package/dist/workflows/stage.js.map +1 -1
  82. package/dist/workflows/steps/DownloadStep.d.ts +3 -1
  83. package/dist/workflows/steps/DownloadStep.js +3 -2
  84. package/dist/workflows/steps/DownloadStep.js.map +1 -1
  85. package/dist/workflows/steps/PollJobsStep.d.ts +3 -1
  86. package/dist/workflows/steps/PollJobsStep.js +4 -2
  87. package/dist/workflows/steps/PollJobsStep.js.map +1 -1
  88. 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
- # Functional Source License, Version 1.1, ALv2 Future License
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
- ## Terms and Conditions
12
-
13
- ### Licensor ("We")
14
-
15
- The party offering the Software under these Terms and Conditions.
16
-
17
- ### The Software
18
-
19
- The "Software" is each version of the software that we make available under
20
- these Terms and Conditions, as indicated by our inclusion of these Terms and
21
- Conditions with the Software.
22
-
23
- ### License Grant
24
-
25
- Subject to your compliance with this License Grant and the Patents,
26
- Redistribution and Trademark clauses below, we hereby grant you the right to
27
- use, copy, modify, create derivative works, publicly perform, publicly display
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 maxRetries - Maximum number of retry attempts
20
- * @param retryDelay - Delay between retries in milliseconds
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", "<React Elements>", `${withheld} component translation(s) for locale ${locale} require review and are not approved yet`);
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 maxRetries - Maximum number of retry attempts
95
- * @param retryDelay - Delay between retries in milliseconds
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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"}
@@ -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
- protected handleInitCommand(ranReactSetup: boolean, useDefaults?: boolean, isVite?: boolean): Promise<void>;
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(async () => {
332
- await exitIfUnsupportedSetupTarget();
333
- displayHeader("Configuring project...");
334
- 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.");
335
- const framework = await detectFramework();
336
- await this.handleInitCommand(false, false, framework.name === "vite");
337
- 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");
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
- async handleInitCommand(ranReactSetup, useDefaults = false, isVite = false) {
345
- const configFilepath = !isVite && fs.existsSync("src/gt.config.json") ? "src/gt.config.json" : "gt.config.json";
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 gtInstalled = !!packageJson && INLINE_LIBRARIES.some((lib) => isPackageInstalled(lib, packageJson));
349
- const isUsingGT = ranReactSetup || gtInstalled;
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 && !isVite) {
371
- await createLoadTranslationsFile(process.cwd(), finalTranslationsDir, locales);
372
- logger.message(`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`);
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({