gt 2.19.0 → 2.20.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 (84) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/api/collectUserEditDiffs.js +23 -23
  3. package/dist/api/collectUserEditDiffs.js.map +1 -1
  4. package/dist/api/downloadFileBatch.js +7 -4
  5. package/dist/api/downloadFileBatch.js.map +1 -1
  6. package/dist/api/saveLocalEdits.js +2 -2
  7. package/dist/api/saveLocalEdits.js.map +1 -1
  8. package/dist/cli/base.d.ts +2 -0
  9. package/dist/cli/base.js +45 -2
  10. package/dist/cli/base.js.map +1 -1
  11. package/dist/cli/commands/api.d.ts +17 -0
  12. package/dist/cli/commands/api.js +153 -0
  13. package/dist/cli/commands/api.js.map +1 -0
  14. package/dist/cli/commands/upload.js +15 -5
  15. package/dist/cli/commands/upload.js.map +1 -1
  16. package/dist/cli/commands/utils/validation.d.ts +4 -0
  17. package/dist/cli/commands/utils/validation.js +12 -5
  18. package/dist/cli/commands/utils/validation.js.map +1 -1
  19. package/dist/config/generateSettings.js +7 -0
  20. package/dist/config/generateSettings.js.map +1 -1
  21. package/dist/console/index.d.ts +1 -0
  22. package/dist/console/index.js +2 -1
  23. package/dist/console/index.js.map +1 -1
  24. package/dist/formats/files/aggregateFiles.js +14 -6
  25. package/dist/formats/files/aggregateFiles.js.map +1 -1
  26. package/dist/fs/fileContent.d.ts +65 -0
  27. package/dist/fs/fileContent.js +171 -0
  28. package/dist/fs/fileContent.js.map +1 -0
  29. package/dist/generated/version.d.ts +1 -1
  30. package/dist/generated/version.js +1 -1
  31. package/dist/generated/version.js.map +1 -1
  32. package/dist/state/recentDownloads.d.ts +3 -2
  33. package/dist/state/recentDownloads.js.map +1 -1
  34. package/dist/translation/reviewSetupWarning.js +3 -3
  35. package/dist/translation/reviewSetupWarning.js.map +1 -1
  36. package/dist/utils/api.d.ts +194 -0
  37. package/dist/utils/api.js +268 -0
  38. package/dist/utils/api.js.map +1 -0
  39. package/dist/utils/persistPostprocessHashes.d.ts +1 -7
  40. package/dist/utils/persistPostprocessHashes.js +15 -1
  41. package/dist/utils/persistPostprocessHashes.js.map +1 -1
  42. package/dist/workflows/download.js +4 -4
  43. package/dist/workflows/download.js.map +1 -1
  44. package/dist/workflows/enqueue.js +4 -4
  45. package/dist/workflows/enqueue.js.map +1 -1
  46. package/dist/workflows/publish.js +2 -2
  47. package/dist/workflows/publish.js.map +1 -1
  48. package/dist/workflows/setupProject.js +4 -4
  49. package/dist/workflows/setupProject.js.map +1 -1
  50. package/dist/workflows/stage.js +7 -7
  51. package/dist/workflows/stage.js.map +1 -1
  52. package/dist/workflows/steps/BranchStep.d.ts +2 -2
  53. package/dist/workflows/steps/BranchStep.js +9 -9
  54. package/dist/workflows/steps/BranchStep.js.map +1 -1
  55. package/dist/workflows/steps/DownloadStep.d.ts +3 -3
  56. package/dist/workflows/steps/DownloadStep.js +3 -3
  57. package/dist/workflows/steps/DownloadStep.js.map +1 -1
  58. package/dist/workflows/steps/EnqueueStep.d.ts +5 -3
  59. package/dist/workflows/steps/EnqueueStep.js.map +1 -1
  60. package/dist/workflows/steps/PollJobsStep.d.ts +3 -3
  61. package/dist/workflows/steps/PollJobsStep.js +6 -6
  62. package/dist/workflows/steps/PollJobsStep.js.map +1 -1
  63. package/dist/workflows/steps/PublishStep.d.ts +3 -3
  64. package/dist/workflows/steps/PublishStep.js +3 -3
  65. package/dist/workflows/steps/PublishStep.js.map +1 -1
  66. package/dist/workflows/steps/SetupStep.d.ts +6 -4
  67. package/dist/workflows/steps/SetupStep.js.map +1 -1
  68. package/dist/workflows/steps/TagStep.d.ts +3 -3
  69. package/dist/workflows/steps/TagStep.js +3 -3
  70. package/dist/workflows/steps/TagStep.js.map +1 -1
  71. package/dist/workflows/steps/UploadSourcesStep.d.ts +3 -3
  72. package/dist/workflows/steps/UploadSourcesStep.js +8 -6
  73. package/dist/workflows/steps/UploadSourcesStep.js.map +1 -1
  74. package/dist/workflows/steps/UploadTranslationsStep.d.ts +3 -3
  75. package/dist/workflows/steps/UploadTranslationsStep.js +24 -6
  76. package/dist/workflows/steps/UploadTranslationsStep.js.map +1 -1
  77. package/dist/workflows/upload.js +4 -4
  78. package/dist/workflows/upload.js.map +1 -1
  79. package/dist/workflows/utils/filterFilesForEnqueue.d.ts +2 -2
  80. package/dist/workflows/utils/filterFilesForEnqueue.js.map +1 -1
  81. package/dist/workflows/utils/queryCompletedTranslations.d.ts +3 -3
  82. package/dist/workflows/utils/queryCompletedTranslations.js +2 -2
  83. package/dist/workflows/utils/queryCompletedTranslations.js.map +1 -1
  84. package/package.json +5 -5
@@ -0,0 +1,171 @@
1
+ import fs from "node:fs";
2
+ import { isBinaryFileFormat } from "generaltranslation/types";
3
+ //#region src/fs/fileContent.ts
4
+ /**
5
+ * Formats whose bytes on disk may carry an encoding other than UTF-8, so reads
6
+ * decode by byte order mark and writes restore it. Older Xcode wrote both of
7
+ * Apple's localization formats as UTF-16.
8
+ */
9
+ const BYTE_ORDER_MARK_FORMATS = new Set(["DOT_STRINGS", "DOT_STRINGSDICT"]);
10
+ /**
11
+ * Ingress. Reads a file as the content the pipeline carries for its format:
12
+ * base64 for binary formats, otherwise UTF-8 text decoded from whatever
13
+ * encoding the file happens to be stored in.
14
+ *
15
+ * A file that cannot be read throws rather than reading as empty, which the
16
+ * pipeline cannot tell apart from a file that is genuinely empty and would
17
+ * upload as a deletion of every string in it.
18
+ * @param {string} filePath - The path to the file to read.
19
+ * @param {FileFormat} fileFormat - The format the file is configured as.
20
+ * @returns {string} - The content.
21
+ */
22
+ function readFileContent(filePath, fileFormat) {
23
+ if (isBinaryFileFormat(fileFormat)) return fs.readFileSync(filePath).toString("base64");
24
+ if (BYTE_ORDER_MARK_FORMATS.has(fileFormat)) return decodeFileText(fs.readFileSync(filePath)).text;
25
+ return fs.readFileSync(filePath, "utf8");
26
+ }
27
+ /**
28
+ * Egress. Renders translated content for disk in the encoding `sourcePath` is
29
+ * stored in, so a repository of UTF-16 files stays UTF-16 rather than showing
30
+ * every one of them as rewritten.
31
+ *
32
+ * The source file is the record of that encoding, which is why it is re-read
33
+ * here rather than carried from ingress: the download that writes this file is
34
+ * usually a different process run from the upload that read it, so there is no
35
+ * value to carry. Nothing has to be persisted in the lockfile or on the server.
36
+ *
37
+ * A source moved or deleted since it was uploaded leaves the translation
38
+ * already on disk as the only remaining record, so `outputPath` is read next.
39
+ * Defaulting straight to UTF-8 would rewrite every UTF-16 file and, for
40
+ * `.stringsdict`, strand a UTF-16 XML declaration over unmarked UTF-8 bytes —
41
+ * the one combination Foundation rejects.
42
+ * @param {string} content - The translated content, as UTF-8 text.
43
+ * @param {FileFormat} fileFormat - The format being written.
44
+ * @param {string} sourcePath - The source file this translation came from.
45
+ * @param {string} [outputPath] - Where the translation is about to be written.
46
+ * @returns The value to hand to `writeFile`.
47
+ */
48
+ function encodeFileContent(content, fileFormat, sourcePath, outputPath) {
49
+ if (!fileFormat || !BYTE_ORDER_MARK_FORMATS.has(fileFormat)) return content;
50
+ const encoding = readFileEncoding(sourcePath) ?? (outputPath ? readFileEncoding(outputPath) : void 0) ?? "utf8";
51
+ return encoding === "utf8" ? content : encodeFileText(content, encoding);
52
+ }
53
+ /**
54
+ * Reads a file's byte order mark without reading the rest of it. Undefined
55
+ * separates "no such file" from a file that is genuinely unmarked UTF-8.
56
+ */
57
+ function readFileEncoding(filePath) {
58
+ if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) return;
59
+ const handle = fs.openSync(filePath, "r");
60
+ try {
61
+ const mark = Buffer.alloc(LONGEST_BYTE_ORDER_MARK);
62
+ const read = fs.readSync(handle, mark, 0, mark.length, 0);
63
+ return detectFileEncoding(mark.subarray(0, read));
64
+ } finally {
65
+ fs.closeSync(handle);
66
+ }
67
+ }
68
+ const BYTE_ORDER_MARKS = [
69
+ ["utf32le", [
70
+ 255,
71
+ 254,
72
+ 0,
73
+ 0
74
+ ]],
75
+ ["utf32be", [
76
+ 0,
77
+ 0,
78
+ 254,
79
+ 255
80
+ ]],
81
+ ["utf16le", [255, 254]],
82
+ ["utf16be", [254, 255]],
83
+ ["utf8-bom", [
84
+ 239,
85
+ 187,
86
+ 191
87
+ ]]
88
+ ];
89
+ const BYTE_ORDER_MARK_BY_ENCODING = new Map(BYTE_ORDER_MARKS.map(([encoding, mark]) => [encoding, Buffer.from(mark)]));
90
+ /** Bytes that must be in hand before a byte order mark can be ruled out. */
91
+ const LONGEST_BYTE_ORDER_MARK = Math.max(...BYTE_ORDER_MARKS.map(([, mark]) => mark.length));
92
+ /** Reads the byte order mark, defaulting to UTF-8 when there is none. */
93
+ function detectFileEncoding(bytes) {
94
+ for (const [encoding, mark] of BYTE_ORDER_MARKS) if (bytes.length >= mark.length && mark.every((b, i) => bytes[i] === b)) return encoding;
95
+ return "utf8";
96
+ }
97
+ /**
98
+ * Decodes a `.strings` or `.stringsdict` file to text, dropping the byte order
99
+ * mark. The encoding is returned so the writer can restore both on the way out.
100
+ */
101
+ function decodeFileText(bytes) {
102
+ const encoding = detectFileEncoding(bytes);
103
+ const body = bytes.subarray(BYTE_ORDER_MARK_BY_ENCODING.get(encoding)?.length ?? 0);
104
+ switch (encoding) {
105
+ case "utf16le": return {
106
+ text: decodeUtf16(body, true),
107
+ encoding
108
+ };
109
+ case "utf16be": return {
110
+ text: decodeUtf16(body, false),
111
+ encoding
112
+ };
113
+ case "utf32le": return {
114
+ text: decodeUtf32(body, true),
115
+ encoding
116
+ };
117
+ case "utf32be": return {
118
+ text: decodeUtf32(body, false),
119
+ encoding
120
+ };
121
+ default: return {
122
+ text: body.toString("utf8"),
123
+ encoding
124
+ };
125
+ }
126
+ }
127
+ /** Re-encodes text into the given layout, byte order mark included. */
128
+ function encodeFileText(text, encoding) {
129
+ const mark = BYTE_ORDER_MARK_BY_ENCODING.get(encoding);
130
+ const body = (() => {
131
+ switch (encoding) {
132
+ case "utf16le": return Buffer.from(text, "utf16le");
133
+ case "utf16be": return Buffer.from(text, "utf16le").swap16();
134
+ case "utf32le": return encodeUtf32(text, true);
135
+ case "utf32be": return encodeUtf32(text, false);
136
+ default: return Buffer.from(text, "utf8");
137
+ }
138
+ })();
139
+ return mark ? Buffer.concat([mark, body]) : body;
140
+ }
141
+ function decodeUtf16(body, littleEndian) {
142
+ if (body.length % 2 !== 0) throw new Error("it has a UTF-16 byte order mark but an odd number of bytes. Re-save it as UTF-8, or as complete UTF-16");
143
+ if (littleEndian) return body.toString("utf16le");
144
+ return Buffer.from(body).swap16().toString("utf16le");
145
+ }
146
+ const CODE_POINTS_PER_CALL = 4096;
147
+ function decodeUtf32(body, littleEndian) {
148
+ if (body.length % 4 !== 0) throw new Error("it has a UTF-32 byte order mark but its length is not a multiple of four bytes. Re-save it as UTF-8, or as complete UTF-32");
149
+ const codePoints = Array.from({ length: body.length / 4 }, (_, i) => littleEndian ? body.readUInt32LE(i * 4) : body.readUInt32BE(i * 4));
150
+ let text = "";
151
+ for (let i = 0; i < codePoints.length; i += CODE_POINTS_PER_CALL) try {
152
+ text += String.fromCodePoint(...codePoints.slice(i, i + CODE_POINTS_PER_CALL));
153
+ } catch {
154
+ throw new Error("it has a UTF-32 byte order mark but contains a value that is not a Unicode code point");
155
+ }
156
+ return text;
157
+ }
158
+ function encodeUtf32(text, littleEndian) {
159
+ const characters = Array.from(text);
160
+ const body = Buffer.alloc(characters.length * 4);
161
+ characters.forEach((character, index) => {
162
+ const codePoint = character.codePointAt(0) ?? 0;
163
+ if (littleEndian) body.writeUInt32LE(codePoint, index * 4);
164
+ else body.writeUInt32BE(codePoint, index * 4);
165
+ });
166
+ return body;
167
+ }
168
+ //#endregion
169
+ export { decodeFileText, detectFileEncoding, encodeFileContent, encodeFileText, readFileContent };
170
+
171
+ //# sourceMappingURL=fileContent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileContent.js","names":[],"sources":["../../src/fs/fileContent.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { isBinaryFileFormat, type FileFormat } from 'generaltranslation/types';\n\n/**\n * How file content crosses the disk boundary.\n *\n * This is the only module in the CLI that knows a file may be stored in\n * something other than UTF-8. `readFileContent` is the single ingress point and\n * `encodeFileContent` the single egress point; in between, content is a UTF-8\n * string and nothing else has to reason about encodings.\n */\n\n/**\n * On-disk byte layouts Foundation accepts for `.strings` and `.stringsdict`.\n *\n * A byte order mark is the only signal. Foundation does not sniff UTF-16 from\n * interleaved NUL bytes, so an unmarked file is UTF-8 even when its bytes are\n * plainly UTF-16 — `plutil` rejects those files, and guessing would translate\n * content Foundation itself cannot read.\n */\nexport type FileEncoding =\n | 'utf8'\n | 'utf8-bom'\n | 'utf16le'\n | 'utf16be'\n | 'utf32le'\n | 'utf32be';\n\n/**\n * Formats whose bytes on disk may carry an encoding other than UTF-8, so reads\n * decode by byte order mark and writes restore it. Older Xcode wrote both of\n * Apple's localization formats as UTF-16.\n */\nconst BYTE_ORDER_MARK_FORMATS: ReadonlySet<FileFormat> = new Set<FileFormat>([\n 'DOT_STRINGS',\n 'DOT_STRINGSDICT',\n]);\n\n/**\n * Ingress. Reads a file as the content the pipeline carries for its format:\n * base64 for binary formats, otherwise UTF-8 text decoded from whatever\n * encoding the file happens to be stored in.\n *\n * A file that cannot be read throws rather than reading as empty, which the\n * pipeline cannot tell apart from a file that is genuinely empty and would\n * upload as a deletion of every string in it.\n * @param {string} filePath - The path to the file to read.\n * @param {FileFormat} fileFormat - The format the file is configured as.\n * @returns {string} - The content.\n */\nexport function readFileContent(\n filePath: string,\n fileFormat: FileFormat\n): string {\n if (isBinaryFileFormat(fileFormat)) {\n return fs.readFileSync(filePath).toString('base64');\n }\n if (BYTE_ORDER_MARK_FORMATS.has(fileFormat)) {\n return decodeFileText(fs.readFileSync(filePath)).text;\n }\n return fs.readFileSync(filePath, 'utf8');\n}\n\n/**\n * Egress. Renders translated content for disk in the encoding `sourcePath` is\n * stored in, so a repository of UTF-16 files stays UTF-16 rather than showing\n * every one of them as rewritten.\n *\n * The source file is the record of that encoding, which is why it is re-read\n * here rather than carried from ingress: the download that writes this file is\n * usually a different process run from the upload that read it, so there is no\n * value to carry. Nothing has to be persisted in the lockfile or on the server.\n *\n * A source moved or deleted since it was uploaded leaves the translation\n * already on disk as the only remaining record, so `outputPath` is read next.\n * Defaulting straight to UTF-8 would rewrite every UTF-16 file and, for\n * `.stringsdict`, strand a UTF-16 XML declaration over unmarked UTF-8 bytes —\n * the one combination Foundation rejects.\n * @param {string} content - The translated content, as UTF-8 text.\n * @param {FileFormat} fileFormat - The format being written.\n * @param {string} sourcePath - The source file this translation came from.\n * @param {string} [outputPath] - Where the translation is about to be written.\n * @returns The value to hand to `writeFile`.\n */\nexport function encodeFileContent(\n content: string,\n fileFormat: FileFormat | undefined,\n sourcePath: string,\n outputPath?: string\n): string | Buffer {\n if (!fileFormat || !BYTE_ORDER_MARK_FORMATS.has(fileFormat)) return content;\n const encoding =\n readFileEncoding(sourcePath) ??\n (outputPath ? readFileEncoding(outputPath) : undefined) ??\n 'utf8';\n return encoding === 'utf8' ? content : encodeFileText(content, encoding);\n}\n\n/**\n * Reads a file's byte order mark without reading the rest of it. Undefined\n * separates \"no such file\" from a file that is genuinely unmarked UTF-8.\n */\nfunction readFileEncoding(filePath: string): FileEncoding | undefined {\n if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {\n return undefined;\n }\n const handle = fs.openSync(filePath, 'r');\n try {\n const mark = Buffer.alloc(LONGEST_BYTE_ORDER_MARK);\n const read = fs.readSync(handle, mark, 0, mark.length, 0);\n return detectFileEncoding(mark.subarray(0, read));\n } finally {\n fs.closeSync(handle);\n }\n}\n\nconst BYTE_ORDER_MARKS = [\n // UTF-32LE is checked before UTF-16LE because its mark begins with the same\n // two bytes; the reverse order silently decodes UTF-32 as NUL-padded UTF-16.\n ['utf32le', [0xff, 0xfe, 0x00, 0x00]],\n ['utf32be', [0x00, 0x00, 0xfe, 0xff]],\n ['utf16le', [0xff, 0xfe]],\n ['utf16be', [0xfe, 0xff]],\n ['utf8-bom', [0xef, 0xbb, 0xbf]],\n] as const satisfies ReadonlyArray<readonly [FileEncoding, number[]]>;\n\nconst BYTE_ORDER_MARK_BY_ENCODING = new Map<FileEncoding, Buffer>(\n BYTE_ORDER_MARKS.map(([encoding, mark]) => [encoding, Buffer.from(mark)])\n);\n\n/** Bytes that must be in hand before a byte order mark can be ruled out. */\nconst LONGEST_BYTE_ORDER_MARK = Math.max(\n ...BYTE_ORDER_MARKS.map(([, mark]) => mark.length)\n);\n\n/** Reads the byte order mark, defaulting to UTF-8 when there is none. */\nexport function detectFileEncoding(bytes: Buffer): FileEncoding {\n for (const [encoding, mark] of BYTE_ORDER_MARKS) {\n if (bytes.length >= mark.length && mark.every((b, i) => bytes[i] === b)) {\n return encoding;\n }\n }\n return 'utf8';\n}\n\n/**\n * Decodes a `.strings` or `.stringsdict` file to text, dropping the byte order\n * mark. The encoding is returned so the writer can restore both on the way out.\n */\nexport function decodeFileText(bytes: Buffer): {\n text: string;\n encoding: FileEncoding;\n} {\n const encoding = detectFileEncoding(bytes);\n const body = bytes.subarray(\n BYTE_ORDER_MARK_BY_ENCODING.get(encoding)?.length ?? 0\n );\n\n switch (encoding) {\n case 'utf16le':\n return { text: decodeUtf16(body, true), encoding };\n case 'utf16be':\n return { text: decodeUtf16(body, false), encoding };\n case 'utf32le':\n return { text: decodeUtf32(body, true), encoding };\n case 'utf32be':\n return { text: decodeUtf32(body, false), encoding };\n default:\n return { text: body.toString('utf8'), encoding };\n }\n}\n\n/** Re-encodes text into the given layout, byte order mark included. */\nexport function encodeFileText(text: string, encoding: FileEncoding): Buffer {\n const mark = BYTE_ORDER_MARK_BY_ENCODING.get(encoding);\n const body = (() => {\n switch (encoding) {\n case 'utf16le':\n return Buffer.from(text, 'utf16le');\n case 'utf16be':\n return Buffer.from(text, 'utf16le').swap16();\n case 'utf32le':\n return encodeUtf32(text, true);\n case 'utf32be':\n return encodeUtf32(text, false);\n default:\n return Buffer.from(text, 'utf8');\n }\n })();\n return mark ? Buffer.concat([mark, body]) : body;\n}\n\nfunction decodeUtf16(body: Buffer, littleEndian: boolean): string {\n if (body.length % 2 !== 0) {\n throw new Error(\n 'it has a UTF-16 byte order mark but an odd number of bytes. Re-save it as UTF-8, or as complete UTF-16'\n );\n }\n if (littleEndian) return body.toString('utf16le');\n // swap16 reorders in place, so copy rather than mutating the caller's bytes.\n return Buffer.from(body).swap16().toString('utf16le');\n}\n\n// String.fromCodePoint takes code points as arguments, so a whole file spread\n// in one call can overflow the stack.\nconst CODE_POINTS_PER_CALL = 4096;\n\nfunction decodeUtf32(body: Buffer, littleEndian: boolean): string {\n if (body.length % 4 !== 0) {\n throw new Error(\n 'it has a UTF-32 byte order mark but its length is not a multiple of four bytes. Re-save it as UTF-8, or as complete UTF-32'\n );\n }\n const codePoints = Array.from({ length: body.length / 4 }, (_, i) =>\n littleEndian ? body.readUInt32LE(i * 4) : body.readUInt32BE(i * 4)\n );\n let text = '';\n for (let i = 0; i < codePoints.length; i += CODE_POINTS_PER_CALL) {\n try {\n text += String.fromCodePoint(\n ...codePoints.slice(i, i + CODE_POINTS_PER_CALL)\n );\n } catch {\n throw new Error(\n 'it has a UTF-32 byte order mark but contains a value that is not a Unicode code point'\n );\n }\n }\n return text;\n}\n\nfunction encodeUtf32(text: string, littleEndian: boolean): Buffer {\n // Iterating a string yields whole code points, so surrogate pairs stay intact.\n const characters = Array.from(text);\n const body = Buffer.alloc(characters.length * 4);\n characters.forEach((character, index) => {\n const codePoint = character.codePointAt(0) ?? 0;\n if (littleEndian) body.writeUInt32LE(codePoint, index * 4);\n else body.writeUInt32BE(codePoint, index * 4);\n });\n return body;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- export declare const PACKAGE_VERSION = "2.19.0";
1
+ export declare const PACKAGE_VERSION = "2.20.0";
@@ -1,5 +1,5 @@
1
1
  //#region src/generated/version.ts
2
- const PACKAGE_VERSION = "2.19.0";
2
+ const PACKAGE_VERSION = "2.20.0";
3
3
  //#endregion
4
4
  export { PACKAGE_VERSION };
5
5
 
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","names":[],"sources":["../../src/generated/version.ts"],"sourcesContent":["// This file is auto-generated. Do not edit manually.\nexport const PACKAGE_VERSION = '2.19.0';\n"],"mappings":";AACA,MAAa,kBAAkB"}
1
+ {"version":3,"file":"version.js","names":[],"sources":["../../src/generated/version.ts"],"sourcesContent":["// This file is auto-generated. Do not edit manually.\nexport const PACKAGE_VERSION = '2.20.0';\n"],"mappings":";AACA,MAAa,kBAAkB"}
@@ -1,8 +1,10 @@
1
- type DownloadMeta = {
1
+ import type { FileFormat } from 'generaltranslation/types';
2
+ export type DownloadMeta = {
2
3
  branchId: string;
3
4
  fileId: string;
4
5
  versionId: string;
5
6
  locale: string;
7
+ fileFormat: FileFormat;
6
8
  inputPath?: string;
7
9
  };
8
10
  export declare function recordDownloaded(filePath: string, meta?: DownloadMeta): void;
@@ -16,4 +18,3 @@ export declare function getDownloaded(): Set<string>;
16
18
  export declare function getNeedsPostprocessing(): Set<string>;
17
19
  export declare function getDownloadedMeta(): Map<string, DownloadMeta>;
18
20
  export declare function clearDownloaded(): void;
19
- export {};
@@ -1 +1 @@
1
- {"version":3,"file":"recentDownloads.js","names":[],"sources":["../../src/state/recentDownloads.ts"],"sourcesContent":["type DownloadMeta = {\n branchId: string;\n fileId: string;\n versionId: string;\n locale: string;\n inputPath?: string;\n};\n\nconst recent = new Set<string>();\nconst recentMeta = new Map<string, DownloadMeta>();\nconst remerged = new Set<string>();\n\nexport function recordDownloaded(filePath: string, meta?: DownloadMeta) {\n recent.add(filePath);\n if (meta) {\n recentMeta.set(filePath, meta);\n }\n}\n\n/**\n * Track a file that was re-merged with the source\n * so that postprocessing still runs on it.\n */\nexport function recordRemerged(filePath: string) {\n remerged.add(filePath);\n}\n\nexport function getDownloaded(): Set<string> {\n return recent;\n}\n\n/** Files that need postprocessing: downloaded OR re-merged */\nexport function getNeedsPostprocessing(): Set<string> {\n return new Set([...recent, ...remerged]);\n}\n\nexport function getDownloadedMeta(): Map<string, DownloadMeta> {\n return recentMeta;\n}\n\nexport function clearDownloaded() {\n recent.clear();\n recentMeta.clear();\n remerged.clear();\n}\n"],"mappings":";AAQA,MAAM,yBAAS,IAAI,KAAa;AAChC,MAAM,6BAAa,IAAI,KAA2B;AAClD,MAAM,2BAAW,IAAI,KAAa;AAElC,SAAgB,iBAAiB,UAAkB,MAAqB;AACtE,QAAO,IAAI,SAAS;AACpB,KAAI,KACF,YAAW,IAAI,UAAU,KAAK;;;;;;AAQlC,SAAgB,eAAe,UAAkB;AAC/C,UAAS,IAAI,SAAS;;AAGxB,SAAgB,gBAA6B;AAC3C,QAAO;;;AAIT,SAAgB,yBAAsC;AACpD,QAAO,IAAI,IAAI,CAAC,GAAG,QAAQ,GAAG,SAAS,CAAC;;AAG1C,SAAgB,oBAA+C;AAC7D,QAAO;;AAGT,SAAgB,kBAAkB;AAChC,QAAO,OAAO;AACd,YAAW,OAAO;AAClB,UAAS,OAAO"}
1
+ {"version":3,"file":"recentDownloads.js","names":[],"sources":["../../src/state/recentDownloads.ts"],"sourcesContent":["import type { FileFormat } from 'generaltranslation/types';\n\nexport type DownloadMeta = {\n branchId: string;\n fileId: string;\n versionId: string;\n locale: string;\n // The format the file was written as, so it can be read back the same way.\n fileFormat: FileFormat;\n inputPath?: string;\n};\n\nconst recent = new Set<string>();\nconst recentMeta = new Map<string, DownloadMeta>();\nconst remerged = new Set<string>();\n\nexport function recordDownloaded(filePath: string, meta?: DownloadMeta) {\n recent.add(filePath);\n if (meta) {\n recentMeta.set(filePath, meta);\n }\n}\n\n/**\n * Track a file that was re-merged with the source\n * so that postprocessing still runs on it.\n */\nexport function recordRemerged(filePath: string) {\n remerged.add(filePath);\n}\n\nexport function getDownloaded(): Set<string> {\n return recent;\n}\n\n/** Files that need postprocessing: downloaded OR re-merged */\nexport function getNeedsPostprocessing(): Set<string> {\n return new Set([...recent, ...remerged]);\n}\n\nexport function getDownloadedMeta(): Map<string, DownloadMeta> {\n return recentMeta;\n}\n\nexport function clearDownloaded() {\n recent.clear();\n recentMeta.clear();\n remerged.clear();\n}\n"],"mappings":";AAYA,MAAM,yBAAS,IAAI,KAAa;AAChC,MAAM,6BAAa,IAAI,KAA2B;AAClD,MAAM,2BAAW,IAAI,KAAa;AAElC,SAAgB,iBAAiB,UAAkB,MAAqB;AACtE,QAAO,IAAI,SAAS;AACpB,KAAI,KACF,YAAW,IAAI,UAAU,KAAK;;;;;;AAQlC,SAAgB,eAAe,UAAkB;AAC/C,UAAS,IAAI,SAAS;;AAGxB,SAAgB,gBAA6B;AAC3C,QAAO;;;AAIT,SAAgB,yBAAsC;AACpD,QAAO,IAAI,IAAI,CAAC,GAAG,QAAQ,GAAG,SAAS,CAAC;;AAG1C,SAAgB,oBAA+C;AAC7D,QAAO;;AAGT,SAAgB,kBAAkB;AAChC,QAAO,OAAO;AACd,YAAW,OAAO;AAClB,UAAS,OAAO"}
@@ -1,5 +1,5 @@
1
1
  import { logger } from "../console/logger.js";
2
- import { gt } from "../utils/gt.js";
2
+ import { api } from "../utils/api.js";
3
3
  import chalk from "chalk";
4
4
  import path from "node:path";
5
5
  //#region src/translation/reviewSetupWarning.ts
@@ -20,12 +20,12 @@ const PROJECT_INFO_TIMEOUT_MS = 1e4;
20
20
  async function warnManualReviewSetup(settings, files) {
21
21
  const requiresReviewPaths = settings.files?.requiresReviewPaths;
22
22
  if (!files.some((file) => {
23
- if (file.fileFormat === "GTJSON") return Object.values(file.formatMetadata ?? {}).some((metadata) => metadata?.requires_review === true);
23
+ if (file.fileFormat === "GTJSON") return Object.values(file.formatMetadata ?? {}).some((metadata) => typeof metadata === "object" && metadata !== null && "requires_review" in metadata && metadata.requires_review === true);
24
24
  return requiresReviewPaths?.has(path.resolve(process.cwd(), file.fileName));
25
25
  })) return;
26
26
  let autoApprove;
27
27
  try {
28
- autoApprove = (await gt.getProjectInfo({ timeout: PROJECT_INFO_TIMEOUT_MS })).autoApprove;
28
+ autoApprove = (await api.getProjectInfo(PROJECT_INFO_TIMEOUT_MS)).autoApprove;
29
29
  } catch {}
30
30
  if (autoApprove === false) return;
31
31
  const settingsUrl = settings.projectId ? `${settings.dashboardUrl}/project/${settings.projectId}/settings` : settings.dashboardUrl;
@@ -1 +1 @@
1
- {"version":3,"file":"reviewSetupWarning.js","names":[],"sources":["../../src/translation/reviewSetupWarning.ts"],"sourcesContent":["import path from 'node:path';\nimport chalk from 'chalk';\nimport type { FileToUpload } from 'generaltranslation/types';\nimport { logger } from '../console/logger.js';\nimport { gt } from '../utils/gt.js';\nimport { Settings } from '../types/index.js';\n\nconst PROJECT_INFO_TIMEOUT_MS = 10_000;\n\n/**\n * Warns when an upload contains review-gated content but the project\n * auto-approves new translations, pointing the user at the dashboard project\n * settings page. The CLI cannot change the auto-approve setting itself —\n * review setup happens in the dashboard.\n *\n * The project's auto-approve setting is fetched lazily (only when the upload\n * actually contains review-gated content):\n * - false: manual review is already the project workflow — no warning\n * - true: requiresReview config has no gating effect — warn\n * - unavailable (older API, dry runs without credentials): warn with\n * conditional wording, since auto-approve is the platform default\n */\nexport async function warnManualReviewSetup(\n settings: Settings,\n files: FileToUpload[]\n): Promise<void> {\n const requiresReviewPaths = settings.files?.requiresReviewPaths;\n\n const hasReviewGatedFile = files.some((file) => {\n if (file.fileFormat === 'GTJSON') {\n // Uploaded GTJSON metadata carries the platform's snake_case key\n return Object.values(file.formatMetadata ?? {}).some(\n (metadata) =>\n (metadata as { requires_review?: boolean })?.requires_review === true\n );\n }\n return requiresReviewPaths?.has(path.resolve(process.cwd(), file.fileName));\n });\n if (!hasReviewGatedFile) return;\n\n let autoApprove: boolean | undefined;\n try {\n autoApprove = (\n await gt.getProjectInfo({ timeout: PROJECT_INFO_TIMEOUT_MS })\n ).autoApprove;\n } catch {\n // Setting unavailable — fall through to the conditional wording\n }\n if (autoApprove === false) return;\n\n const settingsUrl = settings.projectId\n ? `${settings.dashboardUrl}/project/${settings.projectId}/settings`\n : settings.dashboardUrl;\n const autoApproveClause =\n autoApprove === true\n ? 'but this project approves new translations automatically.'\n : 'but new translations are approved automatically unless auto-approval is turned off for this project.';\n // \"Auto approve translations\" matches the dashboard setting title exactly.\n const message =\n `Some of your content requires review (requiresReview), ${autoApproveClause} ` +\n `To review translations before they are used, disable \"Auto approve translations\" in your project settings:`;\n logger.warn(\n chalk.yellow(\n [...wrapWords(message, 70), chalk.cyan(settingsUrl)].join('\\n')\n )\n );\n}\n\n/**\n * Greedy word wrap so warning lines render at a consistent width instead of\n * one terminal-width line followed by stubby fragments.\n */\nfunction wrapWords(text: string, width: number): string[] {\n const lines: string[] = [];\n let line = '';\n for (const word of text.split(' ')) {\n if (line && line.length + 1 + word.length > width) {\n lines.push(line);\n line = word;\n } else {\n line = line ? `${line} ${word}` : word;\n }\n }\n if (line) lines.push(line);\n return lines;\n}\n"],"mappings":";;;;;AAOA,MAAM,0BAA0B;;;;;;;;;;;;;;AAehC,eAAsB,sBACpB,UACA,OACe;CACf,MAAM,sBAAsB,SAAS,OAAO;AAY5C,KAAI,CAVuB,MAAM,MAAM,SAAS;AAC9C,MAAI,KAAK,eAAe,SAEtB,QAAO,OAAO,OAAO,KAAK,kBAAkB,EAAE,CAAC,CAAC,MAC7C,aACE,UAA4C,oBAAoB,KACpE;AAEH,SAAO,qBAAqB,IAAI,KAAK,QAAQ,QAAQ,KAAK,EAAE,KAAK,SAAS,CAAC;GAEtD,CAAE;CAEzB,IAAI;AACJ,KAAI;AACF,iBACE,MAAM,GAAG,eAAe,EAAE,SAAS,yBAAyB,CAAC,EAC7D;SACI;AAGR,KAAI,gBAAgB,MAAO;CAE3B,MAAM,cAAc,SAAS,YACzB,GAAG,SAAS,aAAa,WAAW,SAAS,UAAU,aACvD,SAAS;CAMb,MAAM,UACJ,0DALA,gBAAgB,OACZ,8DACA,uGAGwE;AAE9E,QAAO,KACL,MAAM,OACJ,CAAC,GAAG,UAAU,SAAS,GAAG,EAAE,MAAM,KAAK,YAAY,CAAC,CAAC,KAAK,KAAK,CAChE,CACF;;;;;;AAOH,SAAS,UAAU,MAAc,OAAyB;CACxD,MAAM,QAAkB,EAAE;CAC1B,IAAI,OAAO;AACX,MAAK,MAAM,QAAQ,KAAK,MAAM,IAAI,CAChC,KAAI,QAAQ,KAAK,SAAS,IAAI,KAAK,SAAS,OAAO;AACjD,QAAM,KAAK,KAAK;AAChB,SAAO;OAEP,QAAO,OAAO,GAAG,KAAK,GAAG,SAAS;AAGtC,KAAI,KAAM,OAAM,KAAK,KAAK;AAC1B,QAAO"}
1
+ {"version":3,"file":"reviewSetupWarning.js","names":[],"sources":["../../src/translation/reviewSetupWarning.ts"],"sourcesContent":["import path from 'node:path';\nimport chalk from 'chalk';\nimport type { FileToUpload } from 'generaltranslation/types';\nimport { logger } from '../console/logger.js';\nimport { api } from '../utils/api.js';\nimport { Settings } from '../types/index.js';\n\nconst PROJECT_INFO_TIMEOUT_MS = 10_000;\n\n/**\n * Warns when an upload contains review-gated content but the project\n * auto-approves new translations, pointing the user at the dashboard project\n * settings page. The CLI cannot change the auto-approve setting itself —\n * review setup happens in the dashboard.\n *\n * The project's auto-approve setting is fetched lazily (only when the upload\n * actually contains review-gated content):\n * - false: manual review is already the project workflow — no warning\n * - true: requiresReview config has no gating effect — warn\n * - unavailable (older API, dry runs without credentials): warn with\n * conditional wording, since auto-approve is the platform default\n */\nexport async function warnManualReviewSetup(\n settings: Settings,\n files: FileToUpload[]\n): Promise<void> {\n const requiresReviewPaths = settings.files?.requiresReviewPaths;\n\n const hasReviewGatedFile = files.some((file) => {\n if (file.fileFormat === 'GTJSON') {\n // Uploaded GTJSON metadata carries the platform's snake_case key\n return Object.values(file.formatMetadata ?? {}).some(\n (metadata) =>\n typeof metadata === 'object' &&\n metadata !== null &&\n 'requires_review' in metadata &&\n metadata.requires_review === true\n );\n }\n return requiresReviewPaths?.has(path.resolve(process.cwd(), file.fileName));\n });\n if (!hasReviewGatedFile) return;\n\n let autoApprove: boolean | undefined;\n try {\n autoApprove = (await api.getProjectInfo(PROJECT_INFO_TIMEOUT_MS))\n .autoApprove;\n } catch {\n // Setting unavailable — fall through to the conditional wording\n }\n if (autoApprove === false) return;\n\n const settingsUrl = settings.projectId\n ? `${settings.dashboardUrl}/project/${settings.projectId}/settings`\n : settings.dashboardUrl;\n const autoApproveClause =\n autoApprove === true\n ? 'but this project approves new translations automatically.'\n : 'but new translations are approved automatically unless auto-approval is turned off for this project.';\n // \"Auto approve translations\" matches the dashboard setting title exactly.\n const message =\n `Some of your content requires review (requiresReview), ${autoApproveClause} ` +\n `To review translations before they are used, disable \"Auto approve translations\" in your project settings:`;\n logger.warn(\n chalk.yellow(\n [...wrapWords(message, 70), chalk.cyan(settingsUrl)].join('\\n')\n )\n );\n}\n\n/**\n * Greedy word wrap so warning lines render at a consistent width instead of\n * one terminal-width line followed by stubby fragments.\n */\nfunction wrapWords(text: string, width: number): string[] {\n const lines: string[] = [];\n let line = '';\n for (const word of text.split(' ')) {\n if (line && line.length + 1 + word.length > width) {\n lines.push(line);\n line = word;\n } else {\n line = line ? `${line} ${word}` : word;\n }\n }\n if (line) lines.push(line);\n return lines;\n}\n"],"mappings":";;;;;AAOA,MAAM,0BAA0B;;;;;;;;;;;;;;AAehC,eAAsB,sBACpB,UACA,OACe;CACf,MAAM,sBAAsB,SAAS,OAAO;AAe5C,KAAI,CAbuB,MAAM,MAAM,SAAS;AAC9C,MAAI,KAAK,eAAe,SAEtB,QAAO,OAAO,OAAO,KAAK,kBAAkB,EAAE,CAAC,CAAC,MAC7C,aACC,OAAO,aAAa,YACpB,aAAa,QACb,qBAAqB,YACrB,SAAS,oBAAoB,KAChC;AAEH,SAAO,qBAAqB,IAAI,KAAK,QAAQ,QAAQ,KAAK,EAAE,KAAK,SAAS,CAAC;GAEtD,CAAE;CAEzB,IAAI;AACJ,KAAI;AACF,iBAAe,MAAM,IAAI,eAAe,wBAAwB,EAC7D;SACG;AAGR,KAAI,gBAAgB,MAAO;CAE3B,MAAM,cAAc,SAAS,YACzB,GAAG,SAAS,aAAa,WAAW,SAAS,UAAU,aACvD,SAAS;CAMb,MAAM,UACJ,0DALA,gBAAgB,OACZ,8DACA,uGAGwE;AAE9E,QAAO,KACL,MAAM,OACJ,CAAC,GAAG,UAAU,SAAS,GAAG,EAAE,MAAM,KAAK,YAAY,CAAC,CAAC,KAAK,KAAK,CAChE,CACF;;;;;;AAOH,SAAS,UAAU,MAAc,OAAyB;CACxD,MAAM,QAAkB,EAAE;CAC1B,IAAI,OAAO;AACX,MAAK,MAAM,QAAQ,KAAK,MAAM,IAAI,CAChC,KAAI,QAAQ,KAAK,SAAS,IAAI,KAAK,SAAS,OAAO;AACjD,QAAM,KAAK,KAAK;AAChB,SAAO;OAEP,QAAO,OAAO,GAAG,KAAK,GAAG,SAAS;AAGtC,KAAI,KAAM,OAAM,KAAK,KAAK;AAC1B,QAAO"}
@@ -0,0 +1,194 @@
1
+ import type { CustomMapping } from '@generaltranslation/format/types';
2
+ import { type ApiClientConfig, type AwaitJobsOptions, type CreateBranchData, type CreateProjectData, type CreateTagData, type DownloadFilesData, type EnqueueFileTranslationsData, type GenerateProjectContextData, type GetBranchInfoData, type GetFileInfoData, type ProcessFileMovesData, type PublishFilesData, type SubmitUserEditDiffsData, type UploadSourceFilesData, type UploadTranslationsData } from 'generaltranslation/api';
3
+ export declare function configureApiClient(config: ApiClientConfig & {
4
+ customMapping?: CustomMapping;
5
+ }): void;
6
+ export declare const api: {
7
+ queryBranchData(body: GetBranchInfoData["body"]): Promise<{
8
+ branches: Array<import("@generaltranslation/api").Branch>;
9
+ defaultBranch: import("@generaltranslation/api").Branch & ({
10
+ [key: string]: unknown;
11
+ } | null);
12
+ }>;
13
+ createBranch(body: CreateBranchData["body"]): Promise<{
14
+ branch: import("@generaltranslation/api").Branch;
15
+ }>;
16
+ queryFileData(body: GetFileInfoData["body"]): Promise<{
17
+ translatedFiles: {
18
+ locale: string;
19
+ branchId: string;
20
+ fileId: string;
21
+ versionId: string;
22
+ fileFormat: import("@generaltranslation/api").FileFormat;
23
+ dataFormat: string | null;
24
+ createdAt: string;
25
+ updatedAt: string;
26
+ approvedAt: string | null;
27
+ publishedAt: string | null;
28
+ completedAt: string | null;
29
+ }[];
30
+ sourceFiles: {
31
+ sourceLocale: string;
32
+ locales: string[];
33
+ branchId: string;
34
+ fileId: string;
35
+ versionId: string;
36
+ fileName: string;
37
+ fileFormat: import("@generaltranslation/api").FileFormat;
38
+ dataFormat: string | null;
39
+ createdAt: string;
40
+ updatedAt: string;
41
+ publishedAt: string | null;
42
+ }[];
43
+ }>;
44
+ checkJobStatus(jobIds: string[]): Promise<({
45
+ status: "queued";
46
+ jobId: string;
47
+ } | {
48
+ status: "processing";
49
+ jobId: string;
50
+ } | {
51
+ status: "failed";
52
+ jobId: string;
53
+ error: {
54
+ message: string | null;
55
+ };
56
+ } | {
57
+ status: "completed";
58
+ jobId: string;
59
+ } | {
60
+ status: "unknown";
61
+ jobId: string;
62
+ })[]>;
63
+ getProjectInfo(timeoutMs: number): Promise<{
64
+ id: string;
65
+ name: string;
66
+ orgId: string;
67
+ defaultLocale: string | null;
68
+ currentLocales: Array<string>;
69
+ autoApprove: boolean;
70
+ }>;
71
+ resolveAliasLocale(locale: string): string;
72
+ downloadFileBatch(files: DownloadFilesData["body"]): Promise<{
73
+ files: {
74
+ data: string;
75
+ locale?: string;
76
+ id: string;
77
+ branchId: string;
78
+ fileId: string;
79
+ versionId: string;
80
+ fileName?: string;
81
+ metadata: import("@generaltranslation/api").JsonObject;
82
+ fileFormat: import("@generaltranslation/api").FileFormat;
83
+ }[];
84
+ count: number;
85
+ pending: {
86
+ branchId: string;
87
+ fileId: string;
88
+ locale: string;
89
+ versionId: string;
90
+ }[];
91
+ }>;
92
+ publishFiles(files: PublishFilesData["body"]["files"]): Promise<{
93
+ results: Array<{
94
+ fileId: string;
95
+ versionId: string;
96
+ locale?: string;
97
+ branchId: string;
98
+ success: boolean;
99
+ error?: string;
100
+ }>;
101
+ }>;
102
+ submitUserEditDiffs(body: SubmitUserEditDiffsData["body"]): Promise<{
103
+ filesProcessed: number;
104
+ entriesReceived: number;
105
+ message: string;
106
+ }[]>;
107
+ createTag(body: CreateTagData["body"]): Promise<{
108
+ tag: {
109
+ id: string;
110
+ tagId: string;
111
+ message: string | null;
112
+ createdAt: string;
113
+ updatedAt: string;
114
+ };
115
+ }>;
116
+ createProject(body: CreateProjectData["body"]): Promise<{
117
+ project: {
118
+ id: string;
119
+ name: string;
120
+ orgId: string;
121
+ defaultLocale: string;
122
+ };
123
+ }>;
124
+ getOrphanedFiles(branchId: string, fileIds: string[]): Promise<{
125
+ orphanedFiles: Array<{
126
+ fileId: string;
127
+ versionId: string;
128
+ fileName: string;
129
+ }>;
130
+ }>;
131
+ processFileMoves(moves: ProcessFileMovesData["body"]["moves"], options: Pick<ProcessFileMovesData["body"], "branchId">): Promise<{
132
+ results: {
133
+ oldFileId: string;
134
+ newFileId: string;
135
+ success: boolean;
136
+ newSourceFileId?: string;
137
+ clonedTranslationsCount?: number;
138
+ error?: string;
139
+ }[];
140
+ summary: {
141
+ total: number;
142
+ succeeded: number;
143
+ failed: number;
144
+ };
145
+ }>;
146
+ setupProject(files: GenerateProjectContextData["body"]["files"], options?: Omit<GenerateProjectContextData["body"], "files">): Promise<{
147
+ status: "completed";
148
+ } | {
149
+ setupJobId: string;
150
+ status: "queued";
151
+ }>;
152
+ awaitJobs(jobIds: readonly string[], options?: AwaitJobsOptions): Promise<import("@generaltranslation/api").AwaitJobsResult>;
153
+ enqueueFiles(files: EnqueueFileTranslationsData["body"]["files"], options: {
154
+ sourceLocale?: string;
155
+ targetLocales: string[];
156
+ modelProvider?: string;
157
+ force?: boolean;
158
+ }): Promise<{
159
+ jobData: {
160
+ [k: string]: any;
161
+ };
162
+ locales: string[];
163
+ message: string;
164
+ }>;
165
+ uploadSourceFiles(files: Array<{
166
+ source: UploadSourceFilesData["body"]["data"][number]["source"];
167
+ }>, options: {
168
+ sourceLocale: string;
169
+ }): Promise<{
170
+ uploadedFiles: {
171
+ branchId?: string;
172
+ fileId: string;
173
+ versionId: string;
174
+ fileName: string;
175
+ fileFormat: import("@generaltranslation/api").FileFormat;
176
+ dataFormat?: string;
177
+ locale?: string;
178
+ }[];
179
+ }>;
180
+ uploadTranslations(files: UploadTranslationsData["body"]["data"], options: {
181
+ sourceLocale: string;
182
+ }): Promise<{
183
+ uploadedFiles: {
184
+ branchId?: string;
185
+ fileId: string;
186
+ versionId: string;
187
+ fileName: string;
188
+ fileFormat: import("@generaltranslation/api").FileFormat;
189
+ dataFormat?: string;
190
+ locale?: string;
191
+ }[];
192
+ }>;
193
+ };
194
+ export type ApiClient = typeof api;