gt 2.20.4 → 2.21.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 (41) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/api/collectUserEditDiffs.d.ts +2 -0
  3. package/dist/api/collectUserEditDiffs.js +31 -8
  4. package/dist/api/collectUserEditDiffs.js.map +1 -1
  5. package/dist/api/downloadFileBatch.js +13 -6
  6. package/dist/api/downloadFileBatch.js.map +1 -1
  7. package/dist/cli/commands/upload.js +30 -11
  8. package/dist/cli/commands/upload.js.map +1 -1
  9. package/dist/console/index.d.ts +4 -0
  10. package/dist/console/index.js +2 -1
  11. package/dist/console/index.js.map +1 -1
  12. package/dist/formats/files/aggregateFiles.js +39 -2
  13. package/dist/formats/files/aggregateFiles.js.map +1 -1
  14. package/dist/formats/files/localeContent.d.ts +18 -0
  15. package/dist/formats/files/localeContent.js +34 -0
  16. package/dist/formats/files/localeContent.js.map +1 -0
  17. package/dist/formats/files/supportedFiles.d.ts +2 -1
  18. package/dist/formats/files/supportedFiles.js +4 -2
  19. package/dist/formats/files/supportedFiles.js.map +1 -1
  20. package/dist/formats/files/transformFormat.d.ts +2 -0
  21. package/dist/formats/files/transformFormat.js +6 -3
  22. package/dist/formats/files/transformFormat.js.map +1 -1
  23. package/dist/formats/xcstrings/mergeXcstrings.d.ts +13 -0
  24. package/dist/formats/xcstrings/mergeXcstrings.js +71 -0
  25. package/dist/formats/xcstrings/mergeXcstrings.js.map +1 -0
  26. package/dist/formats/xcstrings/parseXcstrings.d.ts +43 -0
  27. package/dist/formats/xcstrings/parseXcstrings.js +123 -0
  28. package/dist/formats/xcstrings/parseXcstrings.js.map +1 -0
  29. package/dist/fs/config/parseFilesConfig.d.ts +2 -2
  30. package/dist/fs/config/parseFilesConfig.js +17 -3
  31. package/dist/fs/config/parseFilesConfig.js.map +1 -1
  32. package/dist/generated/version.d.ts +1 -1
  33. package/dist/generated/version.js +1 -1
  34. package/dist/generated/version.js.map +1 -1
  35. package/dist/state/recentDownloads.d.ts +1 -1
  36. package/dist/state/recentDownloads.js +3 -1
  37. package/dist/state/recentDownloads.js.map +1 -1
  38. package/dist/utils/persistPostprocessHashes.d.ts +1 -1
  39. package/dist/utils/persistPostprocessHashes.js +18 -14
  40. package/dist/utils/persistPostprocessHashes.js.map +1 -1
  41. package/package.json +7 -7
@@ -0,0 +1,123 @@
1
+ //#region src/formats/xcstrings/parseXcstrings.ts
2
+ function isPlainObject(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ function invalid(path, expected) {
6
+ return /* @__PURE__ */ new Error(`Invalid .xcstrings content: ${path} must be ${expected}`);
7
+ }
8
+ const RESERVED_KEY_NAME = "__proto__";
9
+ function assertSafeKey(name, path) {
10
+ if (name === RESERVED_KEY_NAME) throw new Error(`Invalid .xcstrings content: ${path} uses the reserved name "${name}"`);
11
+ }
12
+ /**
13
+ * Parses a raw .xcstrings document and validates the structure the slicer
14
+ * traverses. The `strings` and `localizations` containers are re-keyed onto
15
+ * prototype-less records so lookups by catalog-chosen names cannot resolve to
16
+ * inherited properties. Throws on invalid content.
17
+ */
18
+ function parseXcstringsCatalog(content) {
19
+ let parsed;
20
+ try {
21
+ parsed = JSON.parse(content);
22
+ } catch {
23
+ throw new Error("Invalid .xcstrings content: not valid JSON");
24
+ }
25
+ if (!isPlainObject(parsed)) throw invalid("document root", "an object");
26
+ if (typeof parsed.sourceLanguage !== "string" || !parsed.sourceLanguage) throw invalid("sourceLanguage", "a non-empty string");
27
+ assertSafeKey(parsed.sourceLanguage, "sourceLanguage");
28
+ if (!isPlainObject(parsed.strings)) throw invalid("strings", "an object");
29
+ const strings = Object.create(null);
30
+ for (const [key, entry] of Object.entries(parsed.strings)) {
31
+ const path = `strings[${JSON.stringify(key)}]`;
32
+ assertSafeKey(key, path);
33
+ if (!isPlainObject(entry)) throw invalid(path, "an object");
34
+ if (entry.localizations === void 0) {
35
+ strings[key] = entry;
36
+ continue;
37
+ }
38
+ if (!isPlainObject(entry.localizations)) throw invalid(`${path}.localizations`, "an object");
39
+ const localizations = Object.create(null);
40
+ for (const [locale, localization] of Object.entries(entry.localizations)) {
41
+ assertSafeKey(locale, `${path}.localizations[${JSON.stringify(locale)}]`);
42
+ localizations[locale] = localization;
43
+ }
44
+ strings[key] = {
45
+ ...entry,
46
+ localizations
47
+ };
48
+ }
49
+ return {
50
+ ...parsed,
51
+ strings
52
+ };
53
+ }
54
+ /**
55
+ * PINNED SERIALIZATION — DO NOT CHANGE.
56
+ *
57
+ * The source slice is hashed into versionId (see aggregateFiles), so any
58
+ * change to these bytes re-versions every customer .xcstrings file and
59
+ * re-triggers translation fleet-wide. The byte-exact tests on this format are
60
+ * the contract.
61
+ */
62
+ function serializeXcstringsSlice(catalog) {
63
+ return JSON.stringify(catalog, null, 2) + "\n";
64
+ }
65
+ /**
66
+ * Clones a validated catalog keeping, per entry, only `localizations[locale]`.
67
+ * An entry without that localization is dropped, or kept without a
68
+ * `localizations` key when `keepEntriesWithoutLocale` is set: an entry whose
69
+ * localizations hold only other locales then slices to the same shape as one
70
+ * with none, so translations added to the catalog leave the source slice,
71
+ * and with it versionId, unchanged. Nodes are cloned, never rebuilt, so
72
+ * unknown fields and key order survive at every level.
73
+ */
74
+ function sliceCatalog(catalog, locale, keepEntriesWithoutLocale) {
75
+ const strings = Object.create(null);
76
+ for (const [key, entry] of Object.entries(catalog.strings)) {
77
+ if (entry.localizations === void 0 || !Object.hasOwn(entry.localizations, locale)) {
78
+ if (!keepEntriesWithoutLocale) continue;
79
+ const { localizations: _otherLocales, ...withoutLocalizations } = entry;
80
+ strings[key] = withoutLocalizations;
81
+ continue;
82
+ }
83
+ const localizations = Object.create(null);
84
+ localizations[locale] = entry.localizations[locale];
85
+ strings[key] = {
86
+ ...entry,
87
+ localizations
88
+ };
89
+ }
90
+ return {
91
+ ...catalog,
92
+ strings
93
+ };
94
+ }
95
+ /**
96
+ * Produces the source-language slice of a validated catalog: a single-locale
97
+ * catalog holding, per entry, only the source-language localization. Entries
98
+ * without a source localization (the key itself is the source) keep their
99
+ * other fields and carry no `localizations`.
100
+ */
101
+ function sliceSourceCatalog(catalog) {
102
+ return sliceCatalog(catalog, catalog.sourceLanguage, true);
103
+ }
104
+ /**
105
+ * Produces one locale's translation slice: the entries that carry
106
+ * `localizations[locale]`, each holding only that localization. Returns
107
+ * undefined when the catalog carries no translation for the locale.
108
+ */
109
+ function sliceTranslationCatalog(catalog, locale) {
110
+ const slice = sliceCatalog(catalog, locale, false);
111
+ return Object.keys(slice.strings).length > 0 ? slice : void 0;
112
+ }
113
+ /**
114
+ * Parses, slices to the catalog's source language, and serializes with the
115
+ * pinned byte layout. Throws on invalid content.
116
+ */
117
+ function parseXcstrings(content) {
118
+ return serializeXcstringsSlice(sliceSourceCatalog(parseXcstringsCatalog(content)));
119
+ }
120
+ //#endregion
121
+ export { parseXcstrings, parseXcstringsCatalog, serializeXcstringsSlice, sliceSourceCatalog, sliceTranslationCatalog };
122
+
123
+ //# sourceMappingURL=parseXcstrings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseXcstrings.js","names":[],"sources":["../../../src/formats/xcstrings/parseXcstrings.ts"],"sourcesContent":["// Slicing for Apple .xcstrings catalogs. One on-disk catalog holds every\n// locale; the upload carries the source-language slice as the source document\n// and one single-locale slice per translated locale. Slices clone nodes and\n// keep only the wanted locale key, so unknown fields survive verbatim at every\n// level and a later download-merge can fold per-locale translations back into\n// the same on-disk catalog.\n\nexport type XcstringsEntry = {\n localizations?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\nexport type XcstringsCatalog = {\n sourceLanguage: string;\n strings: Record<string, XcstringsEntry>;\n [key: string]: unknown;\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction invalid(path: string, expected: string): Error {\n return new Error(`Invalid .xcstrings content: ${path} must be ${expected}`);\n}\n\n// Catalogs are untrusted input. Every container keyed by catalog-chosen names\n// (string keys, locales) is built without a prototype, so `constructor` and\n// `prototype` are ordinary properties and stay uploadable, matching the\n// server's validator. `__proto__` is still rejected: assigning it on a plain\n// object reassigns the prototype instead of creating a property.\nconst RESERVED_KEY_NAME = '__proto__';\n\nfunction assertSafeKey(name: string, path: string): void {\n if (name === RESERVED_KEY_NAME) {\n throw new Error(\n `Invalid .xcstrings content: ${path} uses the reserved name \"${name}\"`\n );\n }\n}\n\n/**\n * Parses a raw .xcstrings document and validates the structure the slicer\n * traverses. The `strings` and `localizations` containers are re-keyed onto\n * prototype-less records so lookups by catalog-chosen names cannot resolve to\n * inherited properties. Throws on invalid content.\n */\nexport function parseXcstringsCatalog(content: string): XcstringsCatalog {\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch {\n throw new Error('Invalid .xcstrings content: not valid JSON');\n }\n if (!isPlainObject(parsed)) throw invalid('document root', 'an object');\n if (typeof parsed.sourceLanguage !== 'string' || !parsed.sourceLanguage) {\n throw invalid('sourceLanguage', 'a non-empty string');\n }\n assertSafeKey(parsed.sourceLanguage, 'sourceLanguage');\n if (!isPlainObject(parsed.strings)) throw invalid('strings', 'an object');\n const strings: Record<string, XcstringsEntry> = Object.create(null);\n for (const [key, entry] of Object.entries(parsed.strings)) {\n const path = `strings[${JSON.stringify(key)}]`;\n assertSafeKey(key, path);\n if (!isPlainObject(entry)) throw invalid(path, 'an object');\n if (entry.localizations === undefined) {\n strings[key] = entry;\n continue;\n }\n if (!isPlainObject(entry.localizations)) {\n throw invalid(`${path}.localizations`, 'an object');\n }\n const localizations: Record<string, unknown> = Object.create(null);\n for (const [locale, localization] of Object.entries(entry.localizations)) {\n assertSafeKey(locale, `${path}.localizations[${JSON.stringify(locale)}]`);\n localizations[locale] = localization;\n }\n strings[key] = { ...entry, localizations };\n }\n return { ...parsed, strings } as XcstringsCatalog;\n}\n\n/**\n * PINNED SERIALIZATION — DO NOT CHANGE.\n *\n * The source slice is hashed into versionId (see aggregateFiles), so any\n * change to these bytes re-versions every customer .xcstrings file and\n * re-triggers translation fleet-wide. The byte-exact tests on this format are\n * the contract.\n */\nexport function serializeXcstringsSlice(catalog: XcstringsCatalog): string {\n return JSON.stringify(catalog, null, 2) + '\\n';\n}\n\n/**\n * Clones a validated catalog keeping, per entry, only `localizations[locale]`.\n * An entry without that localization is dropped, or kept without a\n * `localizations` key when `keepEntriesWithoutLocale` is set: an entry whose\n * localizations hold only other locales then slices to the same shape as one\n * with none, so translations added to the catalog leave the source slice,\n * and with it versionId, unchanged. Nodes are cloned, never rebuilt, so\n * unknown fields and key order survive at every level.\n */\nfunction sliceCatalog(\n catalog: XcstringsCatalog,\n locale: string,\n keepEntriesWithoutLocale: boolean\n): XcstringsCatalog {\n const strings: Record<string, XcstringsEntry> = Object.create(null);\n for (const [key, entry] of Object.entries(catalog.strings)) {\n if (\n entry.localizations === undefined ||\n !Object.hasOwn(entry.localizations, locale)\n ) {\n if (!keepEntriesWithoutLocale) continue;\n const { localizations: _otherLocales, ...withoutLocalizations } = entry;\n strings[key] = withoutLocalizations;\n continue;\n }\n const localizations: Record<string, unknown> = Object.create(null);\n localizations[locale] = entry.localizations[locale];\n strings[key] = { ...entry, localizations };\n }\n return { ...catalog, strings };\n}\n\n/**\n * Produces the source-language slice of a validated catalog: a single-locale\n * catalog holding, per entry, only the source-language localization. Entries\n * without a source localization (the key itself is the source) keep their\n * other fields and carry no `localizations`.\n */\nexport function sliceSourceCatalog(\n catalog: XcstringsCatalog\n): XcstringsCatalog {\n return sliceCatalog(catalog, catalog.sourceLanguage, true);\n}\n\n/**\n * Produces one locale's translation slice: the entries that carry\n * `localizations[locale]`, each holding only that localization. Returns\n * undefined when the catalog carries no translation for the locale.\n */\nexport function sliceTranslationCatalog(\n catalog: XcstringsCatalog,\n locale: string\n): XcstringsCatalog | undefined {\n const slice = sliceCatalog(catalog, locale, false);\n return Object.keys(slice.strings).length > 0 ? slice : undefined;\n}\n\n/**\n * Parses, slices to the catalog's source language, and serializes with the\n * pinned byte layout. Throws on invalid content.\n */\nexport function parseXcstrings(content: string): string {\n const catalog = parseXcstringsCatalog(content);\n return serializeXcstringsSlice(sliceSourceCatalog(catalog));\n}\n"],"mappings":";AAkBA,SAAS,cAAc,OAAkD;AACvE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;AAG7E,SAAS,QAAQ,MAAc,UAAyB;AACtD,wBAAO,IAAI,MAAM,+BAA+B,KAAK,WAAW,WAAW;;AAQ7E,MAAM,oBAAoB;AAE1B,SAAS,cAAc,MAAc,MAAoB;AACvD,KAAI,SAAS,kBACX,OAAM,IAAI,MACR,+BAA+B,KAAK,2BAA2B,KAAK,GACrE;;;;;;;;AAUL,SAAgB,sBAAsB,SAAmC;CACvE,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,QAAQ;SACtB;AACN,QAAM,IAAI,MAAM,6CAA6C;;AAE/D,KAAI,CAAC,cAAc,OAAO,CAAE,OAAM,QAAQ,iBAAiB,YAAY;AACvE,KAAI,OAAO,OAAO,mBAAmB,YAAY,CAAC,OAAO,eACvD,OAAM,QAAQ,kBAAkB,qBAAqB;AAEvD,eAAc,OAAO,gBAAgB,iBAAiB;AACtD,KAAI,CAAC,cAAc,OAAO,QAAQ,CAAE,OAAM,QAAQ,WAAW,YAAY;CACzE,MAAM,UAA0C,OAAO,OAAO,KAAK;AACnE,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,QAAQ,EAAE;EACzD,MAAM,OAAO,WAAW,KAAK,UAAU,IAAI,CAAC;AAC5C,gBAAc,KAAK,KAAK;AACxB,MAAI,CAAC,cAAc,MAAM,CAAE,OAAM,QAAQ,MAAM,YAAY;AAC3D,MAAI,MAAM,kBAAkB,KAAA,GAAW;AACrC,WAAQ,OAAO;AACf;;AAEF,MAAI,CAAC,cAAc,MAAM,cAAc,CACrC,OAAM,QAAQ,GAAG,KAAK,iBAAiB,YAAY;EAErD,MAAM,gBAAyC,OAAO,OAAO,KAAK;AAClE,OAAK,MAAM,CAAC,QAAQ,iBAAiB,OAAO,QAAQ,MAAM,cAAc,EAAE;AACxE,iBAAc,QAAQ,GAAG,KAAK,iBAAiB,KAAK,UAAU,OAAO,CAAC,GAAG;AACzE,iBAAc,UAAU;;AAE1B,UAAQ,OAAO;GAAE,GAAG;GAAO;GAAe;;AAE5C,QAAO;EAAE,GAAG;EAAQ;EAAS;;;;;;;;;;AAW/B,SAAgB,wBAAwB,SAAmC;AACzE,QAAO,KAAK,UAAU,SAAS,MAAM,EAAE,GAAG;;;;;;;;;;;AAY5C,SAAS,aACP,SACA,QACA,0BACkB;CAClB,MAAM,UAA0C,OAAO,OAAO,KAAK;AACnE,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AAC1D,MACE,MAAM,kBAAkB,KAAA,KACxB,CAAC,OAAO,OAAO,MAAM,eAAe,OAAO,EAC3C;AACA,OAAI,CAAC,yBAA0B;GAC/B,MAAM,EAAE,eAAe,eAAe,GAAG,yBAAyB;AAClE,WAAQ,OAAO;AACf;;EAEF,MAAM,gBAAyC,OAAO,OAAO,KAAK;AAClE,gBAAc,UAAU,MAAM,cAAc;AAC5C,UAAQ,OAAO;GAAE,GAAG;GAAO;GAAe;;AAE5C,QAAO;EAAE,GAAG;EAAS;EAAS;;;;;;;;AAShC,SAAgB,mBACd,SACkB;AAClB,QAAO,aAAa,SAAS,QAAQ,gBAAgB,KAAK;;;;;;;AAQ5D,SAAgB,wBACd,SACA,QAC8B;CAC9B,MAAM,QAAQ,aAAa,SAAS,QAAQ,MAAM;AAClD,QAAO,OAAO,KAAK,MAAM,QAAQ,CAAC,SAAS,IAAI,QAAQ,KAAA;;;;;;AAOzD,SAAgB,eAAe,SAAyB;AAEtD,QAAO,wBAAwB,mBADf,sBAAsB,QACmB,CAAC,CAAC"}
@@ -1,4 +1,4 @@
1
- import { FilesOptions, IncludePattern, ResolvedFiles, Settings, TransformOption } from '../../types/index.js';
1
+ import { FilesOptions, IncludePattern, ResolvedFiles, Settings, SupportedFileExtension, TransformOption } from '../../types/index.js';
2
2
  /**
3
3
  * Resolves the files from the files object
4
4
  * Replaces [locale] with the actual locale in the files
@@ -26,7 +26,7 @@ export declare function normalizeIncludePatterns(patterns: IncludePattern[]): {
26
26
  * @returns The resolved files
27
27
  */
28
28
  export declare function resolveFiles(files: FilesOptions, locale: string, locales: string[], cwd: string, compositePatterns?: string[], requiresReviewDefault?: boolean): Settings['files'];
29
- export declare function expandGlobPatterns(cwd: string, includePatterns: string[], excludePatterns: string[], locale: string, locales: string[], transformPatterns?: TransformOption | string | TransformOption[], compositePatterns?: string[]): {
29
+ export declare function expandGlobPatterns(cwd: string, includePatterns: string[], excludePatterns: string[], locale: string, locales: string[], transformPatterns?: TransformOption | string | TransformOption[], compositePatterns?: string[], fileType?: SupportedFileExtension): {
30
30
  resolvedPaths: string[];
31
31
  placeholderPaths: string[];
32
32
  };
@@ -65,13 +65,14 @@ function resolveFiles(files, locale, locales, cwd, compositePatterns, requiresRe
65
65
  const parsingFlags = {};
66
66
  if (files.gt?.output) placeholderResult.gt = path.resolve(cwd, files.gt.output);
67
67
  for (const fileType of SUPPORTED_FILE_EXTENSIONS) {
68
+ if (fileType === "xcstrings") validateXcstringsInPlace(files.xcstrings);
68
69
  const transform = files[fileType]?.transform;
69
70
  if (transform && (typeof transform === "string" || typeof transform === "object" || Array.isArray(transform))) transformPaths[fileType] = transform;
70
71
  const transformFormat = resolveTransformationFormat(fileType, files[fileType]?.transformationFormat);
71
72
  if (transformFormat) transformFormats[fileType] = transformFormat;
72
73
  if (files[fileType]?.include) {
73
74
  const { paths, publishPatterns, unpublishPatterns } = normalizeIncludePatterns(files[fileType].include);
74
- const filePaths = expandGlobPatterns(cwd, paths, files[fileType]?.exclude || [], locale, locales, transformPaths[fileType] || void 0, compositePatterns);
75
+ const filePaths = expandGlobPatterns(cwd, paths, files[fileType]?.exclude || [], locale, locales, transformPaths[fileType] || void 0, compositePatterns, fileType);
75
76
  resolvedPaths[fileType] = filePaths.resolvedPaths;
76
77
  placeholderResult[fileType] = filePaths.placeholderPaths;
77
78
  classifyPublishPaths(filePaths.resolvedPaths, publishPatterns, unpublishPatterns, cwd, locale, publishPaths, unpublishPaths);
@@ -103,11 +104,11 @@ function resolveFiles(files, locale, locales, cwd, compositePatterns, requiresRe
103
104
  })()
104
105
  };
105
106
  }
106
- function expandGlobPatterns(cwd, includePatterns, excludePatterns, locale, locales, transformPatterns, compositePatterns) {
107
+ function expandGlobPatterns(cwd, includePatterns, excludePatterns, locale, locales, transformPatterns, compositePatterns, fileType) {
107
108
  const resolvedPaths = [];
108
109
  const placeholderPaths = [];
109
110
  for (const pattern of includePatterns) {
110
- if (!pattern.includes("[locale]") && !transformPatterns && !compositePatterns?.includes(pattern)) logger.warn(chalk.yellow(`Pattern "${pattern}" does not include [locale], so the CLI tool may incorrectly save translated files.`));
111
+ if (!pattern.includes("[locale]") && !transformPatterns && !compositePatterns?.includes(pattern) && fileType !== "xcstrings") logger.warn(chalk.yellow(`Pattern "${pattern}" does not include [locale], so the CLI tool may incorrectly save translated files.`));
111
112
  const localePositions = [];
112
113
  let searchIndex = 0;
113
114
  const localeTag = "[locale]";
@@ -179,6 +180,19 @@ function classifyPublishPaths(resolvedPaths, publishPatterns, unpublishPatterns,
179
180
  }
180
181
  }
181
182
  /**
183
+ * An .xcstrings catalog holds every locale in one file and is updated in
184
+ * place: each locale's download is merged into the source catalog. A path
185
+ * transform or a `[locale]` placeholder would map each locale to a separate
186
+ * output, and every write would start from the source catalog and discard the
187
+ * locales written before it.
188
+ */
189
+ function validateXcstringsInPlace(config) {
190
+ if (!config) return;
191
+ if (config.transform) logErrorAndExit("files.xcstrings.transform is not supported. An .xcstrings catalog holds every locale in one file and is updated in place, so remove the transform.");
192
+ const localePatterns = normalizeIncludePatterns(config.include ?? []).paths.filter((pattern) => pattern.includes("[locale]"));
193
+ if (localePatterns.length > 0) logErrorAndExit(`files.xcstrings.include must not contain [locale]: ${localePatterns.map((pattern) => `"${pattern}"`).join(", ")}. An .xcstrings catalog holds every locale in one file and is updated in place, so point the pattern at the catalog itself.`);
194
+ }
195
+ /**
182
196
  * Validates a file-type requiresReview config value. Only a boolean or an
183
197
  * object of include/exclude glob string arrays is accepted — notably not
184
198
  * string "true"/"false", so a misquoted boolean fails loudly instead of
@@ -1 +1 @@
1
- {"version":3,"file":"parseFilesConfig.js","names":[],"sources":["../../../src/fs/config/parseFilesConfig.ts"],"sourcesContent":["import path from 'node:path';\nimport {\n FilesOptions,\n IncludePattern,\n RequiresReviewConfig,\n ResolvedFiles,\n Settings,\n TransformFormats,\n TransformFiles,\n TransformOption,\n} from '../../types/index.js';\nimport { logErrorAndExit } from '../../console/logging.js';\nimport fg from 'fast-glob';\nimport { SUPPORTED_FILE_EXTENSIONS } from '../../formats/files/supportedFiles.js';\nimport { logger } from '../../console/logger.js';\nimport chalk from 'chalk';\nimport micromatch from 'micromatch';\nimport { ParseFlagsByFileType } from '../../types/parsing.js';\nimport {\n BASE_PARSING_FLAGS_DEFAULT,\n GT_PARSING_FLAGS_DEFAULT,\n} from '../../config/defaults.js';\nimport { resolveTransformationFormat } from '../../formats/files/transformFormat.js';\nimport { localeForFilePath } from '../../formats/files/localePath.js';\n\n/**\n * Resolves the files from the files object\n * Replaces [locale] with the actual locale in the files\n *\n * @param files - The files object\n * @param locale - The locale to replace [locale] with\n * @returns The resolved files\n */\nexport function resolveLocaleFiles(\n files: ResolvedFiles,\n locale: string\n): ResolvedFiles {\n const result: ResolvedFiles = {};\n\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n const replacement = localeForFilePath(fileType, locale);\n result[fileType] = files[fileType]?.map((filepath) =>\n filepath.replace(/\\[locale\\]/g, replacement)\n );\n }\n\n // Replace [locale] with locale in all paths\n result.gt = files.gt?.replace(/\\[locale\\]/g, locale);\n\n return result;\n}\n/**\n * Normalizes include patterns into plain path strings and tracks which\n * patterns have explicit publish flags.\n */\nexport function normalizeIncludePatterns(patterns: IncludePattern[]): {\n paths: string[];\n publishPatterns: string[];\n unpublishPatterns: string[];\n} {\n const paths: string[] = [];\n const publishPatterns: string[] = [];\n const unpublishPatterns: string[] = [];\n\n for (const pattern of patterns) {\n if (typeof pattern === 'string') {\n paths.push(pattern);\n } else {\n paths.push(pattern.pattern);\n if (pattern.publish === true) {\n publishPatterns.push(pattern.pattern);\n } else if (pattern.publish === false) {\n unpublishPatterns.push(pattern.pattern);\n }\n }\n }\n\n return { paths, publishPatterns, unpublishPatterns };\n}\n\n/**\n * Resolves the files from the files object.\n * Performs glob pattern expansion on the files.\n * Replaces [locale] with the actual locale in the files.\n *\n * @param files - The files object\n * @returns The resolved files\n */\nexport function resolveFiles(\n files: FilesOptions,\n locale: string,\n locales: string[],\n cwd: string,\n compositePatterns?: string[],\n requiresReviewDefault: boolean = false\n): Settings['files'] {\n // Initialize result object with empty arrays for each file type\n const resolvedPaths: ResolvedFiles = {};\n const placeholderResult: ResolvedFiles = {};\n const transformPaths: TransformFiles = {};\n // Output format transforms are tracked separately from path transforms.\n const transformFormats: TransformFormats = {};\n const publishPaths = new Set<string>();\n const unpublishPaths = new Set<string>();\n const requiresReviewPaths = new Set<string>();\n const parsingFlags: ParseFlagsByFileType = {};\n\n // Process GT files\n if (files.gt?.output) {\n placeholderResult.gt = path.resolve(cwd, files.gt.output);\n }\n\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n // ==== TRANSFORMS ==== //\n const transform = files[fileType]?.transform;\n if (\n transform &&\n (typeof transform === 'string' ||\n typeof transform === 'object' ||\n Array.isArray(transform))\n ) {\n transformPaths[fileType] = transform;\n }\n // Validate source -> output format transforms during settings generation.\n const transformFormat = resolveTransformationFormat(\n fileType,\n files[fileType]?.transformationFormat\n );\n if (transformFormat) {\n transformFormats[fileType] = transformFormat;\n }\n // ==== PLACEHOLDERS ==== //\n if (files[fileType]?.include) {\n const { paths, publishPatterns, unpublishPatterns } =\n normalizeIncludePatterns(files[fileType].include);\n\n const filePaths = expandGlobPatterns(\n cwd,\n paths,\n files[fileType]?.exclude || [],\n locale,\n locales,\n transformPaths[fileType] || undefined,\n compositePatterns\n );\n resolvedPaths[fileType] = filePaths.resolvedPaths;\n placeholderResult[fileType] = filePaths.placeholderPaths;\n\n // Classify resolved paths into publish/unpublish sets\n classifyPublishPaths(\n filePaths.resolvedPaths,\n publishPatterns,\n unpublishPatterns,\n cwd,\n locale,\n publishPaths,\n unpublishPaths\n );\n\n // Classify resolved paths by effective requiresReview policy\n classifyRequiresReviewPaths(\n filePaths.resolvedPaths,\n validateRequiresReviewConfig(files[fileType]?.requiresReview, fileType),\n requiresReviewDefault,\n cwd,\n locale,\n requiresReviewPaths\n );\n }\n // ==== OTHER ==== //\n if (files[fileType]?.parsingFlags) {\n parsingFlags[fileType] = {\n ...BASE_PARSING_FLAGS_DEFAULT,\n ...files[fileType].parsingFlags,\n };\n }\n }\n\n return {\n resolvedPaths,\n placeholderPaths: placeholderResult,\n transformPaths: transformPaths,\n transformFormats,\n publishPaths,\n unpublishPaths,\n requiresReviewPaths,\n parsingFlags,\n gtJson: (() => {\n const rawGtFlags = (files.gt?.parsingFlags || {}) as Record<\n string,\n unknown\n >;\n return {\n publish: files.gt?.publish,\n parsingFlags: {\n ...GT_PARSING_FLAGS_DEFAULT,\n ...rawGtFlags,\n },\n };\n })(),\n };\n}\n\n// Helper function to expand glob patterns\nexport function expandGlobPatterns(\n cwd: string,\n includePatterns: string[],\n excludePatterns: string[],\n locale: string,\n locales: string[],\n transformPatterns?: TransformOption | string | TransformOption[],\n compositePatterns?: string[]\n): {\n resolvedPaths: string[];\n placeholderPaths: string[];\n} {\n // Expand glob patterns to include all matching files\n const resolvedPaths: string[] = [];\n const placeholderPaths: string[] = [];\n\n // Process include patterns\n for (const pattern of includePatterns) {\n // Track positions where [locale] appears in the original pattern\n // It must be included in the pattern, otherwise the CLI tool will not be able to find the correct output path\n // Warn if it's not included\n // Ignore if is composite pattern\n if (\n !pattern.includes('[locale]') &&\n !transformPatterns &&\n !compositePatterns?.includes(pattern)\n ) {\n logger.warn(\n chalk.yellow(\n `Pattern \"${pattern}\" does not include [locale], so the CLI tool may incorrectly save translated files.`\n )\n );\n }\n const localePositions: number[] = [];\n let searchIndex = 0;\n const localeTag = '[locale]';\n\n while (true) {\n const foundIndex = pattern.indexOf(localeTag, searchIndex);\n if (foundIndex === -1) break;\n localePositions.push(foundIndex);\n searchIndex = foundIndex + localeTag.length;\n }\n\n const expandedPattern = pattern.replace(/\\[locale\\]/g, locale);\n\n // Resolve the absolute pattern path\n const absolutePattern = path.resolve(cwd, expandedPattern);\n\n // Prepare exclude patterns with locale replaced\n const expandedExcludePatterns = Array.from(\n new Set(\n excludePatterns.flatMap((p) =>\n locales.map((targetLocale) =>\n path.resolve(\n cwd,\n p\n .replace(/\\[locale\\]/g, locale)\n .replace(/\\[locales\\]/g, targetLocale)\n )\n )\n )\n )\n );\n\n // Use fast-glob to find all matching files, excluding the patterns\n const matches = fg.sync(absolutePattern, {\n absolute: true,\n ignore: expandedExcludePatterns,\n });\n\n resolvedPaths.push(...matches);\n\n // For each match, create a version with [locale] in the correct positions\n matches.forEach((match) => {\n const absolutePath = path.resolve(cwd, match);\n const patternPath = path.resolve(cwd, pattern);\n let originalAbsolutePath = absolutePath;\n\n if (localePositions.length > 0) {\n const placeholderPath = buildPlaceholderPathFromPattern(\n patternPath,\n absolutePath,\n localeTag\n );\n originalAbsolutePath = placeholderPath;\n }\n\n placeholderPaths.push(originalAbsolutePath);\n });\n }\n\n return { resolvedPaths, placeholderPaths };\n}\n\nfunction buildPlaceholderPathFromPattern(\n patternPath: string,\n absolutePath: string,\n localeTag: string\n): string {\n if (!patternPath.includes(localeTag)) {\n return absolutePath;\n }\n\n const posixPattern = toPosixPath(patternPath);\n const posixPath = toPosixPath(absolutePath);\n\n const baseRegex = micromatch.makeRe(posixPattern, {\n literalBrackets: true,\n });\n const localeRegexSource = baseRegex.source.replace(\n /\\\\\\[locale\\\\\\]/g,\n '([^/]+)'\n );\n const flags = baseRegex.flags.includes('d')\n ? baseRegex.flags\n : `${baseRegex.flags}d`;\n const matcher = new RegExp(localeRegexSource, flags);\n const match = matcher.exec(posixPath);\n\n const matchWithIndices = match as RegExpExecArray & {\n indices?: Array<[number, number]>;\n };\n\n if (!match || !matchWithIndices.indices) {\n return absolutePath;\n }\n\n let placeholderPosixPath = posixPath;\n const indices = matchWithIndices.indices;\n\n for (let i = indices.length - 1; i >= 1; i--) {\n const [start, end] = indices[i];\n if (start === -1 || end === -1) continue;\n placeholderPosixPath =\n placeholderPosixPath.slice(0, start) +\n localeTag +\n placeholderPosixPath.slice(end);\n }\n\n return path.normalize(placeholderPosixPath);\n}\n\nfunction toPosixPath(value: string): string {\n return value.split(path.sep).join(path.posix.sep);\n}\n\n/**\n * Classifies resolved file paths into publish/unpublish sets by matching\n * them against the given glob patterns. Uses POSIX paths for micromatch\n * compatibility but stores platform-native paths in the output sets.\n */\nfunction classifyPublishPaths(\n resolvedPaths: string[],\n publishPatterns: string[],\n unpublishPatterns: string[],\n cwd: string,\n locale: string,\n publishPaths: Set<string>,\n unpublishPaths: Set<string>\n): void {\n if (publishPatterns.length === 0 && unpublishPatterns.length === 0) return;\n\n const posixPaths = resolvedPaths.map(toPosixPath);\n const toAbsoluteGlob = (p: string) =>\n toPosixPath(path.resolve(cwd, p.replace(/\\[locale\\]/g, locale)));\n\n for (const pattern of publishPatterns) {\n const matched = new Set(micromatch(posixPaths, toAbsoluteGlob(pattern)));\n for (let i = 0; i < posixPaths.length; i++) {\n if (matched.has(posixPaths[i])) {\n publishPaths.add(resolvedPaths[i]);\n }\n }\n }\n\n for (const pattern of unpublishPatterns) {\n const matched = new Set(micromatch(posixPaths, toAbsoluteGlob(pattern)));\n for (let i = 0; i < posixPaths.length; i++) {\n if (matched.has(posixPaths[i])) {\n unpublishPaths.add(resolvedPaths[i]);\n }\n }\n }\n}\n\n/**\n * Validates a file-type requiresReview config value. Only a boolean or an\n * object of include/exclude glob string arrays is accepted — notably not\n * string \"true\"/\"false\", so a misquoted boolean fails loudly instead of\n * silently changing review policy (and with it, version identity).\n */\nfunction validateRequiresReviewConfig(\n config: unknown,\n fileType: string\n): RequiresReviewConfig | undefined {\n if (config === undefined || typeof config === 'boolean') {\n return config;\n }\n const isStringArray = (value: unknown): value is string[] =>\n Array.isArray(value) && value.every((item) => typeof item === 'string');\n if (\n config !== null &&\n typeof config === 'object' &&\n !Array.isArray(config) &&\n Object.keys(config).every((key) => key === 'include' || key === 'exclude')\n ) {\n const { include, exclude } = config as Record<string, unknown>;\n if (\n (include === undefined || isStringArray(include)) &&\n (exclude === undefined || isStringArray(exclude))\n ) {\n return config as RequiresReviewConfig;\n }\n }\n return logErrorAndExit(\n `files.${fileType}.requiresReview must be a boolean or an object of glob string arrays: { include?: string[], exclude?: string[] }`\n );\n}\n\n/**\n * Classifies resolved file paths by effective requiresReview policy and adds\n * paths whose policy is true to requiresReviewPaths. Precedence: file-type\n * include/exclude globs (exclude wins) > file-type boolean > top-level default.\n */\nfunction classifyRequiresReviewPaths(\n resolvedPaths: string[],\n config: RequiresReviewConfig | undefined,\n requiresReviewDefault: boolean,\n cwd: string,\n locale: string,\n requiresReviewPaths: Set<string>\n): void {\n if (typeof config === 'boolean' || config === undefined) {\n if (config ?? requiresReviewDefault) {\n for (const resolvedPath of resolvedPaths) {\n requiresReviewPaths.add(resolvedPath);\n }\n }\n return;\n }\n\n const posixPaths = resolvedPaths.map(toPosixPath);\n const toAbsoluteGlob = (p: string) =>\n toPosixPath(path.resolve(cwd, p.replace(/\\[locale\\]/g, locale)));\n const matchAny = (patterns: string[]) => {\n const matched = new Set<string>();\n for (const pattern of patterns) {\n for (const match of micromatch(posixPaths, toAbsoluteGlob(pattern))) {\n matched.add(match);\n }\n }\n return matched;\n };\n\n const included = matchAny(config.include ?? []);\n const excluded = matchAny(config.exclude ?? []);\n\n for (let i = 0; i < posixPaths.length; i++) {\n const requiresReview = excluded.has(posixPaths[i])\n ? false\n : included.has(posixPaths[i]) || requiresReviewDefault;\n if (requiresReview) {\n requiresReviewPaths.add(resolvedPaths[i]);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"parseFilesConfig.js","names":[],"sources":["../../../src/fs/config/parseFilesConfig.ts"],"sourcesContent":["import path from 'node:path';\nimport {\n FilesOptions,\n IncludePattern,\n RequiresReviewConfig,\n ResolvedFiles,\n Settings,\n SupportedFileExtension,\n TransformFormats,\n TransformFiles,\n TransformOption,\n} from '../../types/index.js';\nimport { logErrorAndExit } from '../../console/logging.js';\nimport fg from 'fast-glob';\nimport { SUPPORTED_FILE_EXTENSIONS } from '../../formats/files/supportedFiles.js';\nimport { logger } from '../../console/logger.js';\nimport chalk from 'chalk';\nimport micromatch from 'micromatch';\nimport { ParseFlagsByFileType } from '../../types/parsing.js';\nimport {\n BASE_PARSING_FLAGS_DEFAULT,\n GT_PARSING_FLAGS_DEFAULT,\n} from '../../config/defaults.js';\nimport { resolveTransformationFormat } from '../../formats/files/transformFormat.js';\nimport { localeForFilePath } from '../../formats/files/localePath.js';\n\n/**\n * Resolves the files from the files object\n * Replaces [locale] with the actual locale in the files\n *\n * @param files - The files object\n * @param locale - The locale to replace [locale] with\n * @returns The resolved files\n */\nexport function resolveLocaleFiles(\n files: ResolvedFiles,\n locale: string\n): ResolvedFiles {\n const result: ResolvedFiles = {};\n\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n const replacement = localeForFilePath(fileType, locale);\n result[fileType] = files[fileType]?.map((filepath) =>\n filepath.replace(/\\[locale\\]/g, replacement)\n );\n }\n\n // Replace [locale] with locale in all paths\n result.gt = files.gt?.replace(/\\[locale\\]/g, locale);\n\n return result;\n}\n/**\n * Normalizes include patterns into plain path strings and tracks which\n * patterns have explicit publish flags.\n */\nexport function normalizeIncludePatterns(patterns: IncludePattern[]): {\n paths: string[];\n publishPatterns: string[];\n unpublishPatterns: string[];\n} {\n const paths: string[] = [];\n const publishPatterns: string[] = [];\n const unpublishPatterns: string[] = [];\n\n for (const pattern of patterns) {\n if (typeof pattern === 'string') {\n paths.push(pattern);\n } else {\n paths.push(pattern.pattern);\n if (pattern.publish === true) {\n publishPatterns.push(pattern.pattern);\n } else if (pattern.publish === false) {\n unpublishPatterns.push(pattern.pattern);\n }\n }\n }\n\n return { paths, publishPatterns, unpublishPatterns };\n}\n\n/**\n * Resolves the files from the files object.\n * Performs glob pattern expansion on the files.\n * Replaces [locale] with the actual locale in the files.\n *\n * @param files - The files object\n * @returns The resolved files\n */\nexport function resolveFiles(\n files: FilesOptions,\n locale: string,\n locales: string[],\n cwd: string,\n compositePatterns?: string[],\n requiresReviewDefault: boolean = false\n): Settings['files'] {\n // Initialize result object with empty arrays for each file type\n const resolvedPaths: ResolvedFiles = {};\n const placeholderResult: ResolvedFiles = {};\n const transformPaths: TransformFiles = {};\n // Output format transforms are tracked separately from path transforms.\n const transformFormats: TransformFormats = {};\n const publishPaths = new Set<string>();\n const unpublishPaths = new Set<string>();\n const requiresReviewPaths = new Set<string>();\n const parsingFlags: ParseFlagsByFileType = {};\n\n // Process GT files\n if (files.gt?.output) {\n placeholderResult.gt = path.resolve(cwd, files.gt.output);\n }\n\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n if (fileType === 'xcstrings') validateXcstringsInPlace(files.xcstrings);\n // ==== TRANSFORMS ==== //\n const transform = files[fileType]?.transform;\n if (\n transform &&\n (typeof transform === 'string' ||\n typeof transform === 'object' ||\n Array.isArray(transform))\n ) {\n transformPaths[fileType] = transform;\n }\n // Validate source -> output format transforms during settings generation.\n const transformFormat = resolveTransformationFormat(\n fileType,\n files[fileType]?.transformationFormat\n );\n if (transformFormat) {\n transformFormats[fileType] = transformFormat;\n }\n // ==== PLACEHOLDERS ==== //\n if (files[fileType]?.include) {\n const { paths, publishPatterns, unpublishPatterns } =\n normalizeIncludePatterns(files[fileType].include);\n\n const filePaths = expandGlobPatterns(\n cwd,\n paths,\n files[fileType]?.exclude || [],\n locale,\n locales,\n transformPaths[fileType] || undefined,\n compositePatterns,\n fileType\n );\n resolvedPaths[fileType] = filePaths.resolvedPaths;\n placeholderResult[fileType] = filePaths.placeholderPaths;\n\n // Classify resolved paths into publish/unpublish sets\n classifyPublishPaths(\n filePaths.resolvedPaths,\n publishPatterns,\n unpublishPatterns,\n cwd,\n locale,\n publishPaths,\n unpublishPaths\n );\n\n // Classify resolved paths by effective requiresReview policy\n classifyRequiresReviewPaths(\n filePaths.resolvedPaths,\n validateRequiresReviewConfig(files[fileType]?.requiresReview, fileType),\n requiresReviewDefault,\n cwd,\n locale,\n requiresReviewPaths\n );\n }\n // ==== OTHER ==== //\n if (files[fileType]?.parsingFlags) {\n parsingFlags[fileType] = {\n ...BASE_PARSING_FLAGS_DEFAULT,\n ...files[fileType].parsingFlags,\n };\n }\n }\n\n return {\n resolvedPaths,\n placeholderPaths: placeholderResult,\n transformPaths: transformPaths,\n transformFormats,\n publishPaths,\n unpublishPaths,\n requiresReviewPaths,\n parsingFlags,\n gtJson: (() => {\n const rawGtFlags = (files.gt?.parsingFlags || {}) as Record<\n string,\n unknown\n >;\n return {\n publish: files.gt?.publish,\n parsingFlags: {\n ...GT_PARSING_FLAGS_DEFAULT,\n ...rawGtFlags,\n },\n };\n })(),\n };\n}\n\n// Helper function to expand glob patterns\nexport function expandGlobPatterns(\n cwd: string,\n includePatterns: string[],\n excludePatterns: string[],\n locale: string,\n locales: string[],\n transformPatterns?: TransformOption | string | TransformOption[],\n compositePatterns?: string[],\n fileType?: SupportedFileExtension\n): {\n resolvedPaths: string[];\n placeholderPaths: string[];\n} {\n // Expand glob patterns to include all matching files\n const resolvedPaths: string[] = [];\n const placeholderPaths: string[] = [];\n\n // Process include patterns\n for (const pattern of includePatterns) {\n // Track positions where [locale] appears in the original pattern\n // It must be included in the pattern, otherwise the CLI tool will not be able to find the correct output path\n // Warn if it's not included\n // Ignore if is composite pattern\n // xcstrings catalogs hold every locale in one shared file, so a pattern\n // without [locale] is the expected layout there, not a misconfiguration\n if (\n !pattern.includes('[locale]') &&\n !transformPatterns &&\n !compositePatterns?.includes(pattern) &&\n fileType !== 'xcstrings'\n ) {\n logger.warn(\n chalk.yellow(\n `Pattern \"${pattern}\" does not include [locale], so the CLI tool may incorrectly save translated files.`\n )\n );\n }\n const localePositions: number[] = [];\n let searchIndex = 0;\n const localeTag = '[locale]';\n\n while (true) {\n const foundIndex = pattern.indexOf(localeTag, searchIndex);\n if (foundIndex === -1) break;\n localePositions.push(foundIndex);\n searchIndex = foundIndex + localeTag.length;\n }\n\n const expandedPattern = pattern.replace(/\\[locale\\]/g, locale);\n\n // Resolve the absolute pattern path\n const absolutePattern = path.resolve(cwd, expandedPattern);\n\n // Prepare exclude patterns with locale replaced\n const expandedExcludePatterns = Array.from(\n new Set(\n excludePatterns.flatMap((p) =>\n locales.map((targetLocale) =>\n path.resolve(\n cwd,\n p\n .replace(/\\[locale\\]/g, locale)\n .replace(/\\[locales\\]/g, targetLocale)\n )\n )\n )\n )\n );\n\n // Use fast-glob to find all matching files, excluding the patterns\n const matches = fg.sync(absolutePattern, {\n absolute: true,\n ignore: expandedExcludePatterns,\n });\n\n resolvedPaths.push(...matches);\n\n // For each match, create a version with [locale] in the correct positions\n matches.forEach((match) => {\n const absolutePath = path.resolve(cwd, match);\n const patternPath = path.resolve(cwd, pattern);\n let originalAbsolutePath = absolutePath;\n\n if (localePositions.length > 0) {\n const placeholderPath = buildPlaceholderPathFromPattern(\n patternPath,\n absolutePath,\n localeTag\n );\n originalAbsolutePath = placeholderPath;\n }\n\n placeholderPaths.push(originalAbsolutePath);\n });\n }\n\n return { resolvedPaths, placeholderPaths };\n}\n\nfunction buildPlaceholderPathFromPattern(\n patternPath: string,\n absolutePath: string,\n localeTag: string\n): string {\n if (!patternPath.includes(localeTag)) {\n return absolutePath;\n }\n\n const posixPattern = toPosixPath(patternPath);\n const posixPath = toPosixPath(absolutePath);\n\n const baseRegex = micromatch.makeRe(posixPattern, {\n literalBrackets: true,\n });\n const localeRegexSource = baseRegex.source.replace(\n /\\\\\\[locale\\\\\\]/g,\n '([^/]+)'\n );\n const flags = baseRegex.flags.includes('d')\n ? baseRegex.flags\n : `${baseRegex.flags}d`;\n const matcher = new RegExp(localeRegexSource, flags);\n const match = matcher.exec(posixPath);\n\n const matchWithIndices = match as RegExpExecArray & {\n indices?: Array<[number, number]>;\n };\n\n if (!match || !matchWithIndices.indices) {\n return absolutePath;\n }\n\n let placeholderPosixPath = posixPath;\n const indices = matchWithIndices.indices;\n\n for (let i = indices.length - 1; i >= 1; i--) {\n const [start, end] = indices[i];\n if (start === -1 || end === -1) continue;\n placeholderPosixPath =\n placeholderPosixPath.slice(0, start) +\n localeTag +\n placeholderPosixPath.slice(end);\n }\n\n return path.normalize(placeholderPosixPath);\n}\n\nfunction toPosixPath(value: string): string {\n return value.split(path.sep).join(path.posix.sep);\n}\n\n/**\n * Classifies resolved file paths into publish/unpublish sets by matching\n * them against the given glob patterns. Uses POSIX paths for micromatch\n * compatibility but stores platform-native paths in the output sets.\n */\nfunction classifyPublishPaths(\n resolvedPaths: string[],\n publishPatterns: string[],\n unpublishPatterns: string[],\n cwd: string,\n locale: string,\n publishPaths: Set<string>,\n unpublishPaths: Set<string>\n): void {\n if (publishPatterns.length === 0 && unpublishPatterns.length === 0) return;\n\n const posixPaths = resolvedPaths.map(toPosixPath);\n const toAbsoluteGlob = (p: string) =>\n toPosixPath(path.resolve(cwd, p.replace(/\\[locale\\]/g, locale)));\n\n for (const pattern of publishPatterns) {\n const matched = new Set(micromatch(posixPaths, toAbsoluteGlob(pattern)));\n for (let i = 0; i < posixPaths.length; i++) {\n if (matched.has(posixPaths[i])) {\n publishPaths.add(resolvedPaths[i]);\n }\n }\n }\n\n for (const pattern of unpublishPatterns) {\n const matched = new Set(micromatch(posixPaths, toAbsoluteGlob(pattern)));\n for (let i = 0; i < posixPaths.length; i++) {\n if (matched.has(posixPaths[i])) {\n unpublishPaths.add(resolvedPaths[i]);\n }\n }\n }\n}\n\n/**\n * An .xcstrings catalog holds every locale in one file and is updated in\n * place: each locale's download is merged into the source catalog. A path\n * transform or a `[locale]` placeholder would map each locale to a separate\n * output, and every write would start from the source catalog and discard the\n * locales written before it.\n */\nfunction validateXcstringsInPlace(config: FilesOptions['xcstrings']): void {\n if (!config) return;\n if (config.transform) {\n logErrorAndExit(\n 'files.xcstrings.transform is not supported. An .xcstrings catalog holds every locale in one file and is updated in place, so remove the transform.'\n );\n }\n const localePatterns = normalizeIncludePatterns(\n config.include ?? []\n ).paths.filter((pattern) => pattern.includes('[locale]'));\n if (localePatterns.length > 0) {\n logErrorAndExit(\n `files.xcstrings.include must not contain [locale]: ${localePatterns\n .map((pattern) => `\"${pattern}\"`)\n .join(\n ', '\n )}. An .xcstrings catalog holds every locale in one file and is updated in place, so point the pattern at the catalog itself.`\n );\n }\n}\n\n/**\n * Validates a file-type requiresReview config value. Only a boolean or an\n * object of include/exclude glob string arrays is accepted — notably not\n * string \"true\"/\"false\", so a misquoted boolean fails loudly instead of\n * silently changing review policy (and with it, version identity).\n */\nfunction validateRequiresReviewConfig(\n config: unknown,\n fileType: string\n): RequiresReviewConfig | undefined {\n if (config === undefined || typeof config === 'boolean') {\n return config;\n }\n const isStringArray = (value: unknown): value is string[] =>\n Array.isArray(value) && value.every((item) => typeof item === 'string');\n if (\n config !== null &&\n typeof config === 'object' &&\n !Array.isArray(config) &&\n Object.keys(config).every((key) => key === 'include' || key === 'exclude')\n ) {\n const { include, exclude } = config as Record<string, unknown>;\n if (\n (include === undefined || isStringArray(include)) &&\n (exclude === undefined || isStringArray(exclude))\n ) {\n return config as RequiresReviewConfig;\n }\n }\n return logErrorAndExit(\n `files.${fileType}.requiresReview must be a boolean or an object of glob string arrays: { include?: string[], exclude?: string[] }`\n );\n}\n\n/**\n * Classifies resolved file paths by effective requiresReview policy and adds\n * paths whose policy is true to requiresReviewPaths. Precedence: file-type\n * include/exclude globs (exclude wins) > file-type boolean > top-level default.\n */\nfunction classifyRequiresReviewPaths(\n resolvedPaths: string[],\n config: RequiresReviewConfig | undefined,\n requiresReviewDefault: boolean,\n cwd: string,\n locale: string,\n requiresReviewPaths: Set<string>\n): void {\n if (typeof config === 'boolean' || config === undefined) {\n if (config ?? requiresReviewDefault) {\n for (const resolvedPath of resolvedPaths) {\n requiresReviewPaths.add(resolvedPath);\n }\n }\n return;\n }\n\n const posixPaths = resolvedPaths.map(toPosixPath);\n const toAbsoluteGlob = (p: string) =>\n toPosixPath(path.resolve(cwd, p.replace(/\\[locale\\]/g, locale)));\n const matchAny = (patterns: string[]) => {\n const matched = new Set<string>();\n for (const pattern of patterns) {\n for (const match of micromatch(posixPaths, toAbsoluteGlob(pattern))) {\n matched.add(match);\n }\n }\n return matched;\n };\n\n const included = matchAny(config.include ?? []);\n const excluded = matchAny(config.exclude ?? []);\n\n for (let i = 0; i < posixPaths.length; i++) {\n const requiresReview = excluded.has(posixPaths[i])\n ? false\n : included.has(posixPaths[i]) || requiresReviewDefault;\n if (requiresReview) {\n requiresReviewPaths.add(resolvedPaths[i]);\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- export declare const PACKAGE_VERSION = "2.20.4";
1
+ export declare const PACKAGE_VERSION = "2.21.0";
@@ -1,5 +1,5 @@
1
1
  //#region src/generated/version.ts
2
- const PACKAGE_VERSION = "2.20.4";
2
+ const PACKAGE_VERSION = "2.21.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.20.4';\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.21.0';\n"],"mappings":";AACA,MAAa,kBAAkB"}
@@ -16,5 +16,5 @@ export declare function recordRemerged(filePath: string): void;
16
16
  export declare function getDownloaded(): Set<string>;
17
17
  /** Files that need postprocessing: downloaded OR re-merged */
18
18
  export declare function getNeedsPostprocessing(): Set<string>;
19
- export declare function getDownloadedMeta(): Map<string, DownloadMeta>;
19
+ export declare function getDownloadedMeta(): Map<string, DownloadMeta[]>;
20
20
  export declare function clearDownloaded(): void;
@@ -4,7 +4,9 @@ const recentMeta = /* @__PURE__ */ new Map();
4
4
  const remerged = /* @__PURE__ */ new Set();
5
5
  function recordDownloaded(filePath, meta) {
6
6
  recent.add(filePath);
7
- if (meta) recentMeta.set(filePath, meta);
7
+ if (!meta) return;
8
+ const metas = recentMeta.get(filePath) ?? [];
9
+ recentMeta.set(filePath, [...metas.filter((existing) => existing.locale !== meta.locale), meta]);
8
10
  }
9
11
  /**
10
12
  * Track a file that was re-merged with the source
@@ -1 +1 @@
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
+ {"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>();\n// A file that holds every locale (a Mintlify composite docs.json) is written once per\n// locale, so a path keeps one entry per locale rather than the last one.\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) return;\n const metas = recentMeta.get(filePath) ?? [];\n recentMeta.set(filePath, [\n ...metas.filter((existing) => existing.locale !== meta.locale),\n 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;AAGhC,MAAM,6BAAa,IAAI,KAA6B;AACpD,MAAM,2BAAW,IAAI,KAAa;AAElC,SAAgB,iBAAiB,UAAkB,MAAqB;AACtE,QAAO,IAAI,SAAS;AACpB,KAAI,CAAC,KAAM;CACX,MAAM,QAAQ,WAAW,IAAI,SAAS,IAAI,EAAE;AAC5C,YAAW,IAAI,UAAU,CACvB,GAAG,MAAM,QAAQ,aAAa,SAAS,WAAW,KAAK,OAAO,EAC9D,KACD,CAAC;;;;;;AAOJ,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,oBAAiD;AAC/D,QAAO;;AAGT,SAAgB,kBAAkB;AAChC,QAAO,OAAO;AACd,YAAW,OAAO;AAClB,UAAS,OAAO"}
@@ -3,4 +3,4 @@ import type { Settings } from '../types/index.js';
3
3
  /**
4
4
  * Persist postprocessed content hashes for recently downloaded files into gt-lock.json.
5
5
  */
6
- export declare function persistPostProcessHashes(settings: Settings, includeFiles: Set<string> | undefined, downloadedMeta: Map<string, DownloadMeta>): void;
6
+ export declare function persistPostProcessHashes(settings: Settings, includeFiles: Set<string> | undefined, downloadedMeta: Map<string, DownloadMeta[]>): void;
@@ -5,6 +5,7 @@ import { fileEncodingSkipReason } from "../console/index.js";
5
5
  import { readFileContent } from "../fs/fileContent.js";
6
6
  import { recordWarning } from "../state/translateWarnings.js";
7
7
  import { findOrCreateEntry, readLockfile, writeLockfile } from "../fs/config/downloadedVersions.js";
8
+ import { emptyLocaleContent, localeContent } from "../formats/files/localeContent.js";
8
9
  import * as fs$1 from "node:fs";
9
10
  //#region src/utils/persistPostprocessHashes.ts
10
11
  /**
@@ -20,12 +21,13 @@ function persistPostProcessHashes(settings, includeFiles, downloadedMeta) {
20
21
  });
21
22
  let lockUpdated = false;
22
23
  for (const filePath of includeFiles) {
23
- const meta = downloadedMeta.get(filePath);
24
- if (!meta) continue;
24
+ const metas = downloadedMeta.get(filePath);
25
+ if (!metas) continue;
25
26
  if (!fs$1.existsSync(filePath)) continue;
26
- let hash;
27
+ const hashes = [];
27
28
  try {
28
- hash = hashStringSync(readFileContent(filePath, meta.fileFormat));
29
+ const content = readFileContent(filePath, metas[0].fileFormat);
30
+ for (const meta of metas) hashes.push([meta, hashStringSync(localeContent(content, meta.fileFormat, meta.locale) ?? emptyLocaleContent(content, meta.fileFormat))]);
29
31
  } catch (error) {
30
32
  const relativePath = getRelative(filePath);
31
33
  const reason = fileEncodingSkipReason(error);
@@ -33,22 +35,24 @@ function persistPostProcessHashes(settings, includeFiles, downloadedMeta) {
33
35
  recordWarning("skipped_file", relativePath, reason);
34
36
  continue;
35
37
  }
36
- const entry = findOrCreateEntry(entryMap, data.entries, meta.fileId, meta.versionId);
37
- const existing = entry.translations[meta.locale] || {};
38
- if (existing.postProcessHash !== hash) {
39
- entry.translations[meta.locale] = {
40
- ...existing,
41
- postProcessHash: hash
42
- };
43
- lockUpdated = true;
38
+ for (const [meta, hash] of hashes) {
39
+ const entry = findOrCreateEntry(entryMap, data.entries, meta.fileId, meta.versionId);
40
+ const existing = entry.translations[meta.locale] || {};
41
+ if (existing.postProcessHash !== hash) {
42
+ entry.translations[meta.locale] = {
43
+ ...existing,
44
+ postProcessHash: hash
45
+ };
46
+ lockUpdated = true;
47
+ }
44
48
  }
45
49
  }
46
50
  if (lockUpdated) writeLockfile(data, originalV1);
47
51
  }
48
52
  function findDownloadedBranchId(includeFiles, downloadedMeta) {
49
53
  for (const filePath of includeFiles) {
50
- const meta = downloadedMeta.get(filePath);
51
- if (meta) return meta.branchId;
54
+ const branchId = downloadedMeta.get(filePath)?.[0]?.branchId;
55
+ if (branchId) return branchId;
52
56
  }
53
57
  }
54
58
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"persistPostprocessHashes.js","names":["fs"],"sources":["../../src/utils/persistPostprocessHashes.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport {\n findOrCreateEntry,\n readLockfile,\n writeLockfile,\n} from '../fs/config/downloadedVersions.js';\nimport { hashStringSync } from './hash.js';\nimport { fileEncodingSkipReason } from '../console/index.js';\nimport { logger } from '../console/logger.js';\nimport { getRelative } from '../fs/findFilepath.js';\nimport { recordWarning } from '../state/translateWarnings.js';\nimport { readFileContent } from '../fs/fileContent.js';\nimport type { DownloadMeta } from '../state/recentDownloads.js';\nimport type { Settings } from '../types/index.js';\n\n/**\n * Persist postprocessed content hashes for recently downloaded files into gt-lock.json.\n */\nexport function persistPostProcessHashes(\n settings: Settings,\n includeFiles: Set<string> | undefined,\n downloadedMeta: Map<string, DownloadMeta>\n): void {\n if (!includeFiles || includeFiles.size === 0 || downloadedMeta.size === 0) {\n return;\n }\n\n const branchId = findDownloadedBranchId(includeFiles, downloadedMeta);\n if (!branchId) return;\n\n const { data, entryMap, originalV1 } = readLockfile({\n ...settings,\n _branchId: branchId,\n });\n let lockUpdated = false;\n\n for (const filePath of includeFiles) {\n const meta = downloadedMeta.get(filePath);\n if (!meta) continue;\n if (!fs.existsSync(filePath)) continue;\n\n // The hash stands for the file's pipeline content, which is what every\n // other producer and consumer of it compares against.\n let hash: string;\n try {\n hash = hashStringSync(readFileContent(filePath, meta.fileFormat));\n } catch (error) {\n // The translation is already written; failing here would lose the whole\n // run's lockfile update over one unreadable file. Skip it and report it\n const relativePath = getRelative(filePath);\n const reason = fileEncodingSkipReason(error);\n logger.warn(`Skipping ${relativePath}: ${reason}`);\n recordWarning('skipped_file', relativePath, reason);\n continue;\n }\n\n const entry = findOrCreateEntry(\n entryMap,\n data.entries,\n meta.fileId,\n meta.versionId\n );\n\n const existing = entry.translations[meta.locale] || {};\n\n if (existing.postProcessHash !== hash) {\n entry.translations[meta.locale] = {\n ...existing,\n postProcessHash: hash,\n };\n lockUpdated = true;\n }\n }\n\n if (lockUpdated) {\n writeLockfile(data, originalV1);\n }\n}\n\nfunction findDownloadedBranchId(\n includeFiles: Set<string>,\n downloadedMeta: Map<string, DownloadMeta>\n): string | undefined {\n for (const filePath of includeFiles) {\n const meta = downloadedMeta.get(filePath);\n if (meta) return meta.branchId;\n }\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;AAkBA,SAAgB,yBACd,UACA,cACA,gBACM;AACN,KAAI,CAAC,gBAAgB,aAAa,SAAS,KAAK,eAAe,SAAS,EACtE;CAGF,MAAM,WAAW,uBAAuB,cAAc,eAAe;AACrE,KAAI,CAAC,SAAU;CAEf,MAAM,EAAE,MAAM,UAAU,eAAe,aAAa;EAClD,GAAG;EACH,WAAW;EACZ,CAAC;CACF,IAAI,cAAc;AAElB,MAAK,MAAM,YAAY,cAAc;EACnC,MAAM,OAAO,eAAe,IAAI,SAAS;AACzC,MAAI,CAAC,KAAM;AACX,MAAI,CAACA,KAAG,WAAW,SAAS,CAAE;EAI9B,IAAI;AACJ,MAAI;AACF,UAAO,eAAe,gBAAgB,UAAU,KAAK,WAAW,CAAC;WAC1D,OAAO;GAGd,MAAM,eAAe,YAAY,SAAS;GAC1C,MAAM,SAAS,uBAAuB,MAAM;AAC5C,UAAO,KAAK,YAAY,aAAa,IAAI,SAAS;AAClD,iBAAc,gBAAgB,cAAc,OAAO;AACnD;;EAGF,MAAM,QAAQ,kBACZ,UACA,KAAK,SACL,KAAK,QACL,KAAK,UACN;EAED,MAAM,WAAW,MAAM,aAAa,KAAK,WAAW,EAAE;AAEtD,MAAI,SAAS,oBAAoB,MAAM;AACrC,SAAM,aAAa,KAAK,UAAU;IAChC,GAAG;IACH,iBAAiB;IAClB;AACD,iBAAc;;;AAIlB,KAAI,YACF,eAAc,MAAM,WAAW;;AAInC,SAAS,uBACP,cACA,gBACoB;AACpB,MAAK,MAAM,YAAY,cAAc;EACnC,MAAM,OAAO,eAAe,IAAI,SAAS;AACzC,MAAI,KAAM,QAAO,KAAK"}
1
+ {"version":3,"file":"persistPostprocessHashes.js","names":["fs"],"sources":["../../src/utils/persistPostprocessHashes.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport {\n findOrCreateEntry,\n readLockfile,\n writeLockfile,\n} from '../fs/config/downloadedVersions.js';\nimport { hashStringSync } from './hash.js';\nimport { fileEncodingSkipReason } from '../console/index.js';\nimport { logger } from '../console/logger.js';\nimport { getRelative } from '../fs/findFilepath.js';\nimport { recordWarning } from '../state/translateWarnings.js';\nimport { readFileContent } from '../fs/fileContent.js';\nimport {\n emptyLocaleContent,\n localeContent,\n} from '../formats/files/localeContent.js';\nimport type { DownloadMeta } from '../state/recentDownloads.js';\nimport type { Settings } from '../types/index.js';\n\n/**\n * Persist postprocessed content hashes for recently downloaded files into gt-lock.json.\n */\nexport function persistPostProcessHashes(\n settings: Settings,\n includeFiles: Set<string> | undefined,\n downloadedMeta: Map<string, DownloadMeta[]>\n): void {\n if (!includeFiles || includeFiles.size === 0 || downloadedMeta.size === 0) {\n return;\n }\n\n const branchId = findDownloadedBranchId(includeFiles, downloadedMeta);\n if (!branchId) return;\n\n const { data, entryMap, originalV1 } = readLockfile({\n ...settings,\n _branchId: branchId,\n });\n let lockUpdated = false;\n\n for (const filePath of includeFiles) {\n const metas = downloadedMeta.get(filePath);\n if (!metas) continue;\n if (!fs.existsSync(filePath)) continue;\n\n // Each hash stands for the locale's share of the file's pipeline content,\n // which is what upload records and user-edit detection compares against.\n const hashes: [DownloadMeta, string][] = [];\n try {\n const content = readFileContent(filePath, metas[0].fileFormat);\n for (const meta of metas) {\n hashes.push([\n meta,\n hashStringSync(\n localeContent(content, meta.fileFormat, meta.locale) ??\n emptyLocaleContent(content, meta.fileFormat)\n ),\n ]);\n }\n } catch (error) {\n // The translation is already written; failing here would lose the whole\n // run's lockfile update over one unreadable file. Skip it and report it\n const relativePath = getRelative(filePath);\n const reason = fileEncodingSkipReason(error);\n logger.warn(`Skipping ${relativePath}: ${reason}`);\n recordWarning('skipped_file', relativePath, reason);\n continue;\n }\n\n for (const [meta, hash] of hashes) {\n const entry = findOrCreateEntry(\n entryMap,\n data.entries,\n meta.fileId,\n meta.versionId\n );\n\n const existing = entry.translations[meta.locale] || {};\n\n if (existing.postProcessHash !== hash) {\n entry.translations[meta.locale] = {\n ...existing,\n postProcessHash: hash,\n };\n lockUpdated = true;\n }\n }\n }\n\n if (lockUpdated) {\n writeLockfile(data, originalV1);\n }\n}\n\nfunction findDownloadedBranchId(\n includeFiles: Set<string>,\n downloadedMeta: Map<string, DownloadMeta[]>\n): string | undefined {\n for (const filePath of includeFiles) {\n const branchId = downloadedMeta.get(filePath)?.[0]?.branchId;\n if (branchId) return branchId;\n }\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;AAsBA,SAAgB,yBACd,UACA,cACA,gBACM;AACN,KAAI,CAAC,gBAAgB,aAAa,SAAS,KAAK,eAAe,SAAS,EACtE;CAGF,MAAM,WAAW,uBAAuB,cAAc,eAAe;AACrE,KAAI,CAAC,SAAU;CAEf,MAAM,EAAE,MAAM,UAAU,eAAe,aAAa;EAClD,GAAG;EACH,WAAW;EACZ,CAAC;CACF,IAAI,cAAc;AAElB,MAAK,MAAM,YAAY,cAAc;EACnC,MAAM,QAAQ,eAAe,IAAI,SAAS;AAC1C,MAAI,CAAC,MAAO;AACZ,MAAI,CAACA,KAAG,WAAW,SAAS,CAAE;EAI9B,MAAM,SAAmC,EAAE;AAC3C,MAAI;GACF,MAAM,UAAU,gBAAgB,UAAU,MAAM,GAAG,WAAW;AAC9D,QAAK,MAAM,QAAQ,MACjB,QAAO,KAAK,CACV,MACA,eACE,cAAc,SAAS,KAAK,YAAY,KAAK,OAAO,IAClD,mBAAmB,SAAS,KAAK,WAAW,CAC/C,CACF,CAAC;WAEG,OAAO;GAGd,MAAM,eAAe,YAAY,SAAS;GAC1C,MAAM,SAAS,uBAAuB,MAAM;AAC5C,UAAO,KAAK,YAAY,aAAa,IAAI,SAAS;AAClD,iBAAc,gBAAgB,cAAc,OAAO;AACnD;;AAGF,OAAK,MAAM,CAAC,MAAM,SAAS,QAAQ;GACjC,MAAM,QAAQ,kBACZ,UACA,KAAK,SACL,KAAK,QACL,KAAK,UACN;GAED,MAAM,WAAW,MAAM,aAAa,KAAK,WAAW,EAAE;AAEtD,OAAI,SAAS,oBAAoB,MAAM;AACrC,UAAM,aAAa,KAAK,UAAU;KAChC,GAAG;KACH,iBAAiB;KAClB;AACD,kBAAc;;;;AAKpB,KAAI,YACF,eAAc,MAAM,WAAW;;AAInC,SAAS,uBACP,cACA,gBACoB;AACpB,MAAK,MAAM,YAAY,cAAc;EACnC,MAAM,WAAW,eAAe,IAAI,SAAS,GAAG,IAAI;AACpD,MAAI,SAAU,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gt",
3
- "version": "2.20.4",
3
+ "version": "2.21.0",
4
4
  "main": "dist/index.js",
5
5
  "bin": "bin/main.js",
6
6
  "files": [
@@ -115,13 +115,13 @@
115
115
  "unified": "^11.0.5",
116
116
  "unist-util-visit": "^5.0.0",
117
117
  "yaml": "^2.8.0",
118
+ "@generaltranslation/icu": "0.1.2",
118
119
  "@generaltranslation/format": "0.1.8",
119
- "@generaltranslation/supported-locales": "2.1.27",
120
- "generaltranslation": "9.2.0",
121
- "@generaltranslation/vue-extractor": "0.1.7",
122
- "gt-remark": "1.0.12",
123
- "@generaltranslation/python-extractor": "0.2.47",
124
- "@generaltranslation/icu": "0.1.2"
120
+ "@generaltranslation/python-extractor": "0.2.48",
121
+ "@generaltranslation/supported-locales": "2.1.28",
122
+ "@generaltranslation/vue-extractor": "0.1.8",
123
+ "generaltranslation": "9.3.0",
124
+ "gt-remark": "1.0.12"
125
125
  },
126
126
  "devDependencies": {
127
127
  "@types/babel__generator": "^7.27.0",