gt 2.14.57 → 2.14.58

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 (57) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/api/downloadFileBatch.js +29 -0
  3. package/dist/api/downloadFileBatch.js.map +1 -1
  4. package/dist/cli/commands/enqueue.js +2 -0
  5. package/dist/cli/commands/enqueue.js.map +1 -1
  6. package/dist/cli/commands/stage.js +2 -0
  7. package/dist/cli/commands/stage.js.map +1 -1
  8. package/dist/config/generateSettings.js +4 -1
  9. package/dist/config/generateSettings.js.map +1 -1
  10. package/dist/console/displayTranslateSummary.js +2 -0
  11. package/dist/console/displayTranslateSummary.js.map +1 -1
  12. package/dist/console/index.d.ts +1 -0
  13. package/dist/console/index.js +2 -1
  14. package/dist/console/index.js.map +1 -1
  15. package/dist/extraction/postProcess.js +1 -0
  16. package/dist/extraction/postProcess.js.map +1 -1
  17. package/dist/formats/files/aggregateFiles.js +6 -5
  18. package/dist/formats/files/aggregateFiles.js.map +1 -1
  19. package/dist/formats/files/collectFiles.js +7 -1
  20. package/dist/formats/files/collectFiles.js.map +1 -1
  21. package/dist/formats/files/convertToFileTranslationData.js +12 -1
  22. package/dist/formats/files/convertToFileTranslationData.js.map +1 -1
  23. package/dist/fs/config/parseFilesConfig.d.ts +1 -1
  24. package/dist/fs/config/parseFilesConfig.js +41 -1
  25. package/dist/fs/config/parseFilesConfig.js.map +1 -1
  26. package/dist/generated/version.d.ts +1 -1
  27. package/dist/generated/version.js +1 -1
  28. package/dist/generated/version.js.map +1 -1
  29. package/dist/react/jsx/utils/constants.d.ts +2 -2
  30. package/dist/react/jsx/utils/constants.js +3 -1
  31. package/dist/react/jsx/utils/constants.js.map +1 -1
  32. package/dist/react/jsx/utils/jsxParsing/parseTProps.js +14 -1
  33. package/dist/react/jsx/utils/jsxParsing/parseTProps.js.map +1 -1
  34. package/dist/react/jsx/utils/mapAttributeName.d.ts +1 -1
  35. package/dist/react/jsx/utils/mapAttributeName.js +1 -0
  36. package/dist/react/jsx/utils/mapAttributeName.js.map +1 -1
  37. package/dist/react/jsx/utils/stringParsing/processTranslationCall/extractStringEntryMetadata.d.ts +1 -0
  38. package/dist/react/jsx/utils/stringParsing/processTranslationCall/extractStringEntryMetadata.js +4 -2
  39. package/dist/react/jsx/utils/stringParsing/processTranslationCall/extractStringEntryMetadata.js.map +1 -1
  40. package/dist/state/translateWarnings.d.ts +1 -1
  41. package/dist/state/translateWarnings.js.map +1 -1
  42. package/dist/translation/reviewSetupWarning.d.ts +16 -0
  43. package/dist/translation/reviewSetupWarning.js +52 -0
  44. package/dist/translation/reviewSetupWarning.js.map +1 -0
  45. package/dist/types/files.d.ts +2 -0
  46. package/dist/types/index.d.ts +7 -0
  47. package/dist/utils/hash.d.ts +8 -0
  48. package/dist/utils/hash.js +11 -1
  49. package/dist/utils/hash.js.map +1 -1
  50. package/dist/workflows/download.d.ts +2 -0
  51. package/dist/workflows/download.js +2 -1
  52. package/dist/workflows/download.js.map +1 -1
  53. package/dist/workflows/steps/DownloadStep.js +20 -0
  54. package/dist/workflows/steps/DownloadStep.js.map +1 -1
  55. package/dist/workflows/steps/EnqueueStep.js +0 -1
  56. package/dist/workflows/steps/EnqueueStep.js.map +1 -1
  57. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"aggregateFiles.js","names":[],"sources":["../../../src/formats/files/aggregateFiles.ts"],"sourcesContent":["import { logger } from '../../console/logger.js';\nimport { recordWarning } from '../../state/translateWarnings.js';\nimport { getRelative, readFile } from '../../fs/findFilepath.js';\nimport { Settings } from '../../types/index.js';\nimport type { FileFormat, DataFormat, FileToUpload } from '../../types/data.js';\nimport { SUPPORTED_FILE_EXTENSIONS } from './supportedFiles.js';\nimport { parseJson } from '../json/parseJson.js';\nimport {\n resolveMintlifyRefs,\n shouldResolveRefs,\n} from '../../utils/resolveMintlifyRefs.js';\nimport { storeRefMap } from '../../state/mintlifyRefMap.js';\nimport parseYaml from '../yaml/parseYaml.js';\nimport { validateYamlSchema } from '../yaml/utils.js';\nimport { flattenJson } from '../json/flattenJson.js';\nimport type { JSONObject } from '../../types/data/json.js';\nimport YAML from 'yaml';\nimport { determineLibrary } from '../../fs/determineFramework/index.js';\nimport { hashStringSync } from '../../utils/hash.js';\nimport { preprocessContent } from './preprocessContent.js';\nimport {\n parseKeyedMetadata,\n type KeyedMetadata,\n} from '../parseKeyedMetadata.js';\nimport { buildPublishMap } from '../../utils/resolvePublish.js';\nimport { getTransformFormatProperty } from './transformFormat.js';\n\n/**\n * Checks if a file path is a metadata companion file (e.g. foo.metadata.json)\n * AND its corresponding source file (e.g. foo.json) exists in the file list.\n * If both conditions are true, the metadata file should be skipped as a translation source.\n */\nfunction isCompanionMetadataFile(\n filePath: string,\n allFilePaths: string[]\n): boolean {\n const metadataPattern = /\\.metadata\\.(json|yaml|yml)$/;\n if (!metadataPattern.test(filePath)) return false;\n\n // Derive the source file path: foo.metadata.json -> foo.json\n const sourceFilePath = filePath.replace(\n /\\.metadata\\.(json|yaml|yml)$/,\n '.$1'\n );\n return allFilePaths.includes(sourceFilePath);\n}\nexport const SUPPORTED_DATA_FORMATS = ['JSX', 'ICU', 'I18NEXT'];\n\nexport async function aggregateFiles(\n settings: Settings\n): Promise<{ files: FileToUpload[]; publishMap: Map<string, boolean> }> {\n // Aggregate all files to translate\n const files: FileToUpload[] = [];\n if (\n !settings.files ||\n (Object.keys(settings.files.placeholderPaths).length === 1 &&\n settings.files.placeholderPaths.gt)\n ) {\n return { files, publishMap: new Map<string, boolean>() };\n }\n\n const { resolvedPaths: filePaths } = settings.files;\n const skipValidation = settings.options?.skipFileValidation;\n\n // Build publish map upfront from resolved paths.\n const publishMap = buildPublishMap(filePaths, settings);\n\n // Process JSON files\n if (filePaths.json) {\n const { library, additionalModules } = determineLibrary();\n\n // Determine dataFormat for JSONs\n let dataFormat: DataFormat;\n if (library === 'next-intl') {\n dataFormat = 'ICU';\n } else if (library === 'i18next') {\n if (additionalModules.includes('i18next-icu')) {\n dataFormat = 'ICU';\n } else {\n dataFormat = 'I18NEXT';\n }\n } else {\n dataFormat = 'STRING';\n }\n\n const jsonFiles = filePaths.json\n .filter((filePath) => !isCompanionMetadataFile(filePath, filePaths.json!))\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate JSON parseability\n if (!skipValidation?.json) {\n try {\n JSON.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: JSON file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'JSON file is not parsable'\n );\n return null;\n }\n }\n\n // Resolve $ref before parsing if configured\n let contentForParsing = content;\n if (shouldResolveRefs(filePath, settings.options)) {\n try {\n const json = JSON.parse(content);\n const { resolved, refMap } = resolveMintlifyRefs(json, filePath);\n storeRefMap(refMap);\n contentForParsing = JSON.stringify(resolved, null, 2);\n } catch {\n // JSON parse errors are handled below by parseJson\n }\n }\n\n const parsedJson = parseJson(\n contentForParsing,\n filePath,\n settings.options || {},\n settings.defaultLocale\n );\n\n // Detect companion metadata file\n let keyedMetadata: KeyedMetadata | undefined;\n let parsedContent: JSONObject | undefined;\n try {\n parsedContent = JSON.parse(content) as JSONObject;\n } catch {\n // Content not parsable — skip metadata detection\n }\n if (parsedContent) {\n const rawMetadata = parseKeyedMetadata(filePath, parsedContent);\n if (rawMetadata) {\n // Run metadata through the same include/composite schema as the source\n // so key paths align at translation time\n const transformed = parseJson(\n JSON.stringify(rawMetadata),\n filePath,\n settings.options || {},\n settings.defaultLocale,\n false\n );\n const transformedMetadata = JSON.parse(transformed);\n\n // Filter metadata to only keep keys that exist in the transformed source\n // This prevents misaligned entries from wide JSONPath patterns\n const sourceKeys = new Set(Object.keys(JSON.parse(parsedJson)));\n const filtered = Object.fromEntries(\n Object.entries(transformedMetadata).filter(([k]) =>\n sourceKeys.has(k)\n )\n ) as KeyedMetadata;\n\n if (Object.keys(filtered).length > 0) {\n keyedMetadata = filtered;\n } else {\n logger.warn(\n `Companion metadata found for ${relativePath} but no keys aligned with the JSON schema — metadata was not attached`\n );\n }\n }\n }\n\n return {\n fileId: hashStringSync(relativePath),\n versionId: hashStringSync(parsedJson),\n content: parsedJson,\n fileName: relativePath,\n fileFormat: 'JSON' as const,\n ...getTransformFormatProperty(settings, 'json'),\n dataFormat,\n locale: settings.defaultLocale,\n ...(keyedMetadata && {\n formatMetadata: { keyedMetadata },\n }),\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file) return false;\n if (typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(`Skipping ${file.fileName}: JSON file is empty`);\n recordWarning('skipped_file', file.fileName, 'JSON file is empty');\n return false;\n }\n return true;\n });\n files.push(...jsonFiles.filter((file) => file !== null));\n }\n\n // Process YAML files\n if (filePaths.yaml) {\n const yamlFiles = filePaths.yaml\n .filter((filePath) => !isCompanionMetadataFile(filePath, filePaths.yaml!))\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate YAML parseability\n if (!skipValidation?.yaml) {\n try {\n YAML.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: YAML file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'YAML file is not parsable'\n );\n return null;\n }\n }\n\n const { content: parsedYaml, fileFormat } = parseYaml(\n content,\n filePath,\n settings.options || {}\n );\n\n // Detect companion metadata file\n let keyedMetadata: KeyedMetadata | undefined;\n try {\n const parsedYamlContent = YAML.parse(content);\n const rawMetadata = parseKeyedMetadata(filePath, parsedYamlContent);\n if (rawMetadata) {\n const yamlSchema = validateYamlSchema(\n settings.options || {},\n filePath\n );\n if (yamlSchema?.include) {\n // Flatten metadata through the same include schema as the source\n const flattened = flattenJson(rawMetadata, yamlSchema.include);\n // Filter to only keep keys that exist in the transformed source\n const sourceKeys = new Set(Object.keys(JSON.parse(parsedYaml)));\n const filtered = Object.fromEntries(\n Object.entries(flattened).filter(([k]) => sourceKeys.has(k))\n ) as KeyedMetadata;\n if (Object.keys(filtered).length > 0) {\n keyedMetadata = filtered;\n } else {\n logger.warn(\n `Companion metadata found for ${relativePath} but no keys aligned with the YAML schema — metadata was not attached`\n );\n }\n } else {\n keyedMetadata = rawMetadata;\n }\n }\n } catch {\n // Content not parsable as YAML — skip metadata detection\n }\n\n return {\n content: parsedYaml,\n fileName: relativePath,\n fileFormat,\n ...getTransformFormatProperty(settings, 'yaml'),\n fileId: hashStringSync(relativePath),\n versionId: hashStringSync(parsedYaml),\n locale: settings.defaultLocale,\n ...(keyedMetadata && {\n formatMetadata: { keyedMetadata },\n }),\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file || typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(\n `Skipping ${file?.fileName ?? 'unknown'}: YAML file is empty`\n );\n recordWarning(\n 'skipped_file',\n file?.fileName ?? 'unknown',\n 'YAML file is empty'\n );\n return false;\n }\n return true;\n });\n files.push(...yamlFiles.filter((file) => file !== null));\n }\n\n // Process Twilio Content JSON files\n if (filePaths.twilioContentJson) {\n const twilioContentJsonFiles = filePaths.twilioContentJson\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate JSON parseability\n if (!skipValidation?.json) {\n try {\n JSON.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: JSON file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'JSON file is not parsable'\n );\n return null;\n }\n }\n\n const parsedJson = parseJson(\n content,\n filePath,\n settings.options || {},\n settings.defaultLocale\n );\n\n return {\n fileId: hashStringSync(relativePath),\n versionId: hashStringSync(parsedJson),\n content: parsedJson,\n fileName: relativePath,\n fileFormat: 'TWILIO_CONTENT_JSON' as const,\n ...getTransformFormatProperty(settings, 'twilioContentJson'),\n dataFormat: 'STRING' as const,\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file) return false;\n if (typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(`Skipping ${file.fileName}: JSON file is empty`);\n recordWarning('skipped_file', file.fileName, 'JSON file is empty');\n return false;\n }\n return true;\n });\n files.push(...twilioContentJsonFiles.filter((file) => file !== null));\n }\n\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n if (\n fileType === 'json' ||\n fileType === 'yaml' ||\n fileType === 'twilioContentJson'\n )\n continue;\n if (filePaths[fileType]) {\n const parsed = filePaths[fileType]\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n const processed = preprocessContent(\n content,\n relativePath,\n fileType,\n settings\n );\n\n if (typeof processed !== 'string') {\n logger.warn(`Skipping ${relativePath}: ${processed.skip}`);\n recordWarning('skipped_file', relativePath, processed.skip);\n return null;\n }\n\n return {\n content: processed,\n fileName: relativePath,\n fileFormat: fileType.toUpperCase() as FileFormat,\n ...getTransformFormatProperty(settings, fileType),\n fileId: hashStringSync(relativePath),\n versionId: hashStringSync(processed),\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (\n !file ||\n typeof file.content !== 'string' ||\n !file.content.trim()\n ) {\n logger.warn(\n `Skipping ${file?.fileName ?? 'unknown'}: File is empty after sanitization`\n );\n recordWarning(\n 'skipped_file',\n file?.fileName ?? 'unknown',\n 'File is empty after sanitization'\n );\n return false;\n }\n return true;\n });\n files.push(...parsed.filter((file) => file !== null));\n }\n }\n\n // Remove stale entries for files that were skipped during validation\n const validFileIds = new Set(files.map((f) => f.fileId));\n for (const fileId of publishMap.keys()) {\n if (!validFileIds.has(fileId)) {\n publishMap.delete(fileId);\n }\n }\n\n return { files, publishMap };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"aggregateFiles.js","names":[],"sources":["../../../src/formats/files/aggregateFiles.ts"],"sourcesContent":["import { logger } from '../../console/logger.js';\nimport { recordWarning } from '../../state/translateWarnings.js';\nimport { getRelative, readFile } from '../../fs/findFilepath.js';\nimport { Settings } from '../../types/index.js';\nimport type { FileFormat, DataFormat, FileToUpload } from '../../types/data.js';\nimport { SUPPORTED_FILE_EXTENSIONS } from './supportedFiles.js';\nimport { parseJson } from '../json/parseJson.js';\nimport {\n resolveMintlifyRefs,\n shouldResolveRefs,\n} from '../../utils/resolveMintlifyRefs.js';\nimport { storeRefMap } from '../../state/mintlifyRefMap.js';\nimport parseYaml from '../yaml/parseYaml.js';\nimport { validateYamlSchema } from '../yaml/utils.js';\nimport { flattenJson } from '../json/flattenJson.js';\nimport type { JSONObject } from '../../types/data/json.js';\nimport YAML from 'yaml';\nimport { determineLibrary } from '../../fs/determineFramework/index.js';\nimport { hashStringSync, hashVersionId } from '../../utils/hash.js';\nimport { preprocessContent } from './preprocessContent.js';\nimport {\n parseKeyedMetadata,\n type KeyedMetadata,\n} from '../parseKeyedMetadata.js';\nimport { buildPublishMap } from '../../utils/resolvePublish.js';\nimport { getTransformFormatProperty } from './transformFormat.js';\n\n/**\n * Checks if a file path is a metadata companion file (e.g. foo.metadata.json)\n * AND its corresponding source file (e.g. foo.json) exists in the file list.\n * If both conditions are true, the metadata file should be skipped as a translation source.\n */\nfunction isCompanionMetadataFile(\n filePath: string,\n allFilePaths: string[]\n): boolean {\n const metadataPattern = /\\.metadata\\.(json|yaml|yml)$/;\n if (!metadataPattern.test(filePath)) return false;\n\n // Derive the source file path: foo.metadata.json -> foo.json\n const sourceFilePath = filePath.replace(\n /\\.metadata\\.(json|yaml|yml)$/,\n '.$1'\n );\n return allFilePaths.includes(sourceFilePath);\n}\nexport const SUPPORTED_DATA_FORMATS = ['JSX', 'ICU', 'I18NEXT'];\n\nexport async function aggregateFiles(\n settings: Settings\n): Promise<{ files: FileToUpload[]; publishMap: Map<string, boolean> }> {\n // Aggregate all files to translate\n const files: FileToUpload[] = [];\n if (\n !settings.files ||\n (Object.keys(settings.files.placeholderPaths).length === 1 &&\n settings.files.placeholderPaths.gt)\n ) {\n return { files, publishMap: new Map<string, boolean>() };\n }\n\n const { resolvedPaths: filePaths } = settings.files;\n // Tolerate partially-constructed settings from programmatic callers\n const requiresReviewPaths =\n settings.files.requiresReviewPaths ?? new Set<string>();\n const skipValidation = settings.options?.skipFileValidation;\n\n // Build publish map upfront from resolved paths.\n const publishMap = buildPublishMap(filePaths, settings);\n\n // Process JSON files\n if (filePaths.json) {\n const { library, additionalModules } = determineLibrary();\n\n // Determine dataFormat for JSONs\n let dataFormat: DataFormat;\n if (library === 'next-intl') {\n dataFormat = 'ICU';\n } else if (library === 'i18next') {\n if (additionalModules.includes('i18next-icu')) {\n dataFormat = 'ICU';\n } else {\n dataFormat = 'I18NEXT';\n }\n } else {\n dataFormat = 'STRING';\n }\n\n const jsonFiles = filePaths.json\n .filter((filePath) => !isCompanionMetadataFile(filePath, filePaths.json!))\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate JSON parseability\n if (!skipValidation?.json) {\n try {\n JSON.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: JSON file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'JSON file is not parsable'\n );\n return null;\n }\n }\n\n // Resolve $ref before parsing if configured\n let contentForParsing = content;\n if (shouldResolveRefs(filePath, settings.options)) {\n try {\n const json = JSON.parse(content);\n const { resolved, refMap } = resolveMintlifyRefs(json, filePath);\n storeRefMap(refMap);\n contentForParsing = JSON.stringify(resolved, null, 2);\n } catch {\n // JSON parse errors are handled below by parseJson\n }\n }\n\n const parsedJson = parseJson(\n contentForParsing,\n filePath,\n settings.options || {},\n settings.defaultLocale\n );\n\n // Detect companion metadata file\n let keyedMetadata: KeyedMetadata | undefined;\n let parsedContent: JSONObject | undefined;\n try {\n parsedContent = JSON.parse(content) as JSONObject;\n } catch {\n // Content not parsable — skip metadata detection\n }\n if (parsedContent) {\n const rawMetadata = parseKeyedMetadata(filePath, parsedContent);\n if (rawMetadata) {\n // Run metadata through the same include/composite schema as the source\n // so key paths align at translation time\n const transformed = parseJson(\n JSON.stringify(rawMetadata),\n filePath,\n settings.options || {},\n settings.defaultLocale,\n false\n );\n const transformedMetadata = JSON.parse(transformed);\n\n // Filter metadata to only keep keys that exist in the transformed source\n // This prevents misaligned entries from wide JSONPath patterns\n const sourceKeys = new Set(Object.keys(JSON.parse(parsedJson)));\n const filtered = Object.fromEntries(\n Object.entries(transformedMetadata).filter(([k]) =>\n sourceKeys.has(k)\n )\n ) as KeyedMetadata;\n\n if (Object.keys(filtered).length > 0) {\n keyedMetadata = filtered;\n } else {\n logger.warn(\n `Companion metadata found for ${relativePath} but no keys aligned with the JSON schema — metadata was not attached`\n );\n }\n }\n }\n\n return {\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedJson,\n requiresReviewPaths.has(filePath)\n ),\n content: parsedJson,\n fileName: relativePath,\n fileFormat: 'JSON' as const,\n ...getTransformFormatProperty(settings, 'json'),\n dataFormat,\n locale: settings.defaultLocale,\n ...(keyedMetadata && {\n formatMetadata: { keyedMetadata },\n }),\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file) return false;\n if (typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(`Skipping ${file.fileName}: JSON file is empty`);\n recordWarning('skipped_file', file.fileName, 'JSON file is empty');\n return false;\n }\n return true;\n });\n files.push(...jsonFiles.filter((file) => file !== null));\n }\n\n // Process YAML files\n if (filePaths.yaml) {\n const yamlFiles = filePaths.yaml\n .filter((filePath) => !isCompanionMetadataFile(filePath, filePaths.yaml!))\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate YAML parseability\n if (!skipValidation?.yaml) {\n try {\n YAML.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: YAML file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'YAML file is not parsable'\n );\n return null;\n }\n }\n\n const { content: parsedYaml, fileFormat } = parseYaml(\n content,\n filePath,\n settings.options || {}\n );\n\n // Detect companion metadata file\n let keyedMetadata: KeyedMetadata | undefined;\n try {\n const parsedYamlContent = YAML.parse(content);\n const rawMetadata = parseKeyedMetadata(filePath, parsedYamlContent);\n if (rawMetadata) {\n const yamlSchema = validateYamlSchema(\n settings.options || {},\n filePath\n );\n if (yamlSchema?.include) {\n // Flatten metadata through the same include schema as the source\n const flattened = flattenJson(rawMetadata, yamlSchema.include);\n // Filter to only keep keys that exist in the transformed source\n const sourceKeys = new Set(Object.keys(JSON.parse(parsedYaml)));\n const filtered = Object.fromEntries(\n Object.entries(flattened).filter(([k]) => sourceKeys.has(k))\n ) as KeyedMetadata;\n if (Object.keys(filtered).length > 0) {\n keyedMetadata = filtered;\n } else {\n logger.warn(\n `Companion metadata found for ${relativePath} but no keys aligned with the YAML schema — metadata was not attached`\n );\n }\n } else {\n keyedMetadata = rawMetadata;\n }\n }\n } catch {\n // Content not parsable as YAML — skip metadata detection\n }\n\n return {\n content: parsedYaml,\n fileName: relativePath,\n fileFormat,\n ...getTransformFormatProperty(settings, 'yaml'),\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedYaml,\n requiresReviewPaths.has(filePath)\n ),\n locale: settings.defaultLocale,\n ...(keyedMetadata && {\n formatMetadata: { keyedMetadata },\n }),\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file || typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(\n `Skipping ${file?.fileName ?? 'unknown'}: YAML file is empty`\n );\n recordWarning(\n 'skipped_file',\n file?.fileName ?? 'unknown',\n 'YAML file is empty'\n );\n return false;\n }\n return true;\n });\n files.push(...yamlFiles.filter((file) => file !== null));\n }\n\n // Process Twilio Content JSON files\n if (filePaths.twilioContentJson) {\n const twilioContentJsonFiles = filePaths.twilioContentJson\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate JSON parseability\n if (!skipValidation?.json) {\n try {\n JSON.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: JSON file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'JSON file is not parsable'\n );\n return null;\n }\n }\n\n const parsedJson = parseJson(\n content,\n filePath,\n settings.options || {},\n settings.defaultLocale\n );\n\n return {\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedJson,\n requiresReviewPaths.has(filePath)\n ),\n content: parsedJson,\n fileName: relativePath,\n fileFormat: 'TWILIO_CONTENT_JSON' as const,\n ...getTransformFormatProperty(settings, 'twilioContentJson'),\n dataFormat: 'STRING' as const,\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file) return false;\n if (typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(`Skipping ${file.fileName}: JSON file is empty`);\n recordWarning('skipped_file', file.fileName, 'JSON file is empty');\n return false;\n }\n return true;\n });\n files.push(...twilioContentJsonFiles.filter((file) => file !== null));\n }\n\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n if (\n fileType === 'json' ||\n fileType === 'yaml' ||\n fileType === 'twilioContentJson'\n )\n continue;\n if (filePaths[fileType]) {\n const parsed = filePaths[fileType]\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n const processed = preprocessContent(\n content,\n relativePath,\n fileType,\n settings\n );\n\n if (typeof processed !== 'string') {\n logger.warn(`Skipping ${relativePath}: ${processed.skip}`);\n recordWarning('skipped_file', relativePath, processed.skip);\n return null;\n }\n\n return {\n content: processed,\n fileName: relativePath,\n fileFormat: fileType.toUpperCase() as FileFormat,\n ...getTransformFormatProperty(settings, fileType),\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n processed,\n requiresReviewPaths.has(filePath)\n ),\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (\n !file ||\n typeof file.content !== 'string' ||\n !file.content.trim()\n ) {\n logger.warn(\n `Skipping ${file?.fileName ?? 'unknown'}: File is empty after sanitization`\n );\n recordWarning(\n 'skipped_file',\n file?.fileName ?? 'unknown',\n 'File is empty after sanitization'\n );\n return false;\n }\n return true;\n });\n files.push(...parsed.filter((file) => file !== null));\n }\n }\n\n // Remove stale entries for files that were skipped during validation\n const validFileIds = new Set(files.map((f) => f.fileId));\n for (const fileId of publishMap.keys()) {\n if (!validFileIds.has(fileId)) {\n publishMap.delete(fileId);\n }\n }\n\n return { files, publishMap };\n}\n"],"mappings":"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"}
@@ -19,6 +19,9 @@ async function collectFiles(options, settings, library) {
19
19
  for (const update of updates) {
20
20
  const { source, metadata, dataFormat } = update;
21
21
  metadata.dataFormat = dataFormat;
22
+ const effectiveRequiresReview = metadata.requiresReview !== void 0 ? metadata.requiresReview : settings.requiresReview ? true : void 0;
23
+ delete metadata.requiresReview;
24
+ if (effectiveRequiresReview !== void 0) metadata.requires_review = effectiveRequiresReview;
22
25
  const { hash, id } = metadata;
23
26
  if (id) {
24
27
  fileData[id] = source;
@@ -29,13 +32,16 @@ async function collectFiles(options, settings, library) {
29
32
  }
30
33
  }
31
34
  reactComponents = updates.length;
35
+ const sortedKeys = Object.keys(fileData).sort();
36
+ const reviewRequiredKeys = sortedKeys.filter((key) => fileMetadata[key]?.requires_review === true);
37
+ const versionId = reviewRequiredKeys.length ? hashStringSync(JSON.stringify(sortedKeys) + "\0requiresReview\0" + JSON.stringify(reviewRequiredKeys)) : hashStringSync(JSON.stringify(sortedKeys));
32
38
  files.push({
33
39
  fileName: TEMPLATE_FILE_NAME,
34
40
  content: JSON.stringify(fileData),
35
41
  fileFormat: "GTJSON",
36
42
  formatMetadata: fileMetadata,
37
43
  fileId: TEMPLATE_FILE_ID,
38
- versionId: hashStringSync(JSON.stringify(Object.keys(fileData).sort())),
44
+ versionId,
39
45
  locale: settings.defaultLocale
40
46
  });
41
47
  const gtPublishValue = shouldPublishGt(settings);
@@ -1 +1 @@
1
- {"version":3,"file":"collectFiles.js","names":[],"sources":["../../../src/formats/files/collectFiles.ts"],"sourcesContent":["import { logErrorAndExit } from '../../console/logging.js';\nimport {\n Settings,\n SupportedLibraries,\n TranslateFlags,\n} from '../../types/index.js';\nimport { invalidConfigurationError } from '../../console/index.js';\nimport { aggregateFiles } from './aggregateFiles.js';\nimport { aggregateInlineTranslations } from '../../translation/stage.js';\nimport type { JsxChildren } from '@generaltranslation/format/types';\nimport type { FileToUpload } from 'generaltranslation/types';\nimport { hashStringSync } from '../../utils/hash.js';\nimport { TEMPLATE_FILE_NAME, TEMPLATE_FILE_ID } from '../../utils/constants.js';\nimport { isInlineLibrary } from '../../types/libraries.js';\nimport { shouldPublishGt } from '../../utils/resolvePublish.js';\n\nexport async function collectFiles(\n options: TranslateFlags,\n settings: Settings,\n library: SupportedLibraries\n): Promise<{\n files: FileToUpload[];\n reactComponents: number;\n publishMap: Map<string, boolean>;\n}> {\n // Aggregate files\n const { files, publishMap } = await aggregateFiles(settings);\n\n // Parse for React components\n let reactComponents = 0;\n if (isInlineLibrary(library)) {\n const updates = await aggregateInlineTranslations(\n options,\n settings,\n library\n );\n if (updates.length > 0) {\n if (!settings.publish && !settings.files?.placeholderPaths.gt) {\n logErrorAndExit(invalidConfigurationError);\n }\n // Convert updates to a file object\n const fileData: Record<string, JsxChildren> = {};\n const fileMetadata: Record<string, unknown> = {};\n // Convert updates to the proper data format\n for (const update of updates) {\n const { source, metadata, dataFormat } = update;\n metadata.dataFormat = dataFormat; // add the data format to the metadata\n const { hash, id } = metadata;\n if (id) {\n fileData[id] = source;\n fileMetadata[id] = metadata;\n } else if (hash) {\n fileData[hash] = source;\n fileMetadata[hash] = metadata;\n }\n }\n reactComponents = updates.length;\n files.push({\n fileName: TEMPLATE_FILE_NAME,\n content: JSON.stringify(fileData),\n fileFormat: 'GTJSON',\n formatMetadata: fileMetadata,\n fileId: TEMPLATE_FILE_ID,\n versionId: hashStringSync(JSON.stringify(Object.keys(fileData).sort())),\n locale: settings.defaultLocale,\n } satisfies FileToUpload);\n // Only add GT JSON to publishMap if there's an explicit publish config\n const gtPublishValue = shouldPublishGt(settings);\n if (gtPublishValue !== undefined) {\n publishMap.set(TEMPLATE_FILE_ID, gtPublishValue);\n }\n }\n }\n return { files, reactComponents, publishMap };\n}\n"],"mappings":";;;;;;;;;AAgBA,eAAsB,aACpB,SACA,UACA,SAKC;CAED,MAAM,EAAE,OAAO,eAAe,MAAM,eAAe,SAAS;CAG5D,IAAI,kBAAkB;AACtB,KAAI,gBAAgB,QAAQ,EAAE;EAC5B,MAAM,UAAU,MAAM,4BACpB,SACA,UACA,QACD;AACD,MAAI,QAAQ,SAAS,GAAG;AACtB,OAAI,CAAC,SAAS,WAAW,CAAC,SAAS,OAAO,iBAAiB,GACzD,iBAAgB,0BAA0B;GAG5C,MAAM,WAAwC,EAAE;GAChD,MAAM,eAAwC,EAAE;AAEhD,QAAK,MAAM,UAAU,SAAS;IAC5B,MAAM,EAAE,QAAQ,UAAU,eAAe;AACzC,aAAS,aAAa;IACtB,MAAM,EAAE,MAAM,OAAO;AACrB,QAAI,IAAI;AACN,cAAS,MAAM;AACf,kBAAa,MAAM;eACV,MAAM;AACf,cAAS,QAAQ;AACjB,kBAAa,QAAQ;;;AAGzB,qBAAkB,QAAQ;AAC1B,SAAM,KAAK;IACT,UAAU;IACV,SAAS,KAAK,UAAU,SAAS;IACjC,YAAY;IACZ,gBAAgB;IAChB,QAAQ;IACR,WAAW,eAAe,KAAK,UAAU,OAAO,KAAK,SAAS,CAAC,MAAM,CAAC,CAAC;IACvE,QAAQ,SAAS;IAClB,CAAwB;GAEzB,MAAM,iBAAiB,gBAAgB,SAAS;AAChD,OAAI,mBAAmB,KAAA,EACrB,YAAW,IAAI,kBAAkB,eAAe;;;AAItD,QAAO;EAAE;EAAO;EAAiB;EAAY"}
1
+ {"version":3,"file":"collectFiles.js","names":[],"sources":["../../../src/formats/files/collectFiles.ts"],"sourcesContent":["import { logErrorAndExit } from '../../console/logging.js';\nimport {\n Settings,\n SupportedLibraries,\n TranslateFlags,\n} from '../../types/index.js';\nimport { invalidConfigurationError } from '../../console/index.js';\nimport { aggregateFiles } from './aggregateFiles.js';\nimport { aggregateInlineTranslations } from '../../translation/stage.js';\nimport type { JsxChildren } from '@generaltranslation/format/types';\nimport type { FileToUpload } from 'generaltranslation/types';\nimport { hashStringSync } from '../../utils/hash.js';\nimport { TEMPLATE_FILE_NAME, TEMPLATE_FILE_ID } from '../../utils/constants.js';\nimport { isInlineLibrary } from '../../types/libraries.js';\nimport { shouldPublishGt } from '../../utils/resolvePublish.js';\n\nexport async function collectFiles(\n options: TranslateFlags,\n settings: Settings,\n library: SupportedLibraries\n): Promise<{\n files: FileToUpload[];\n reactComponents: number;\n publishMap: Map<string, boolean>;\n}> {\n // Aggregate files\n const { files, publishMap } = await aggregateFiles(settings);\n\n // Parse for React components\n let reactComponents = 0;\n if (isInlineLibrary(library)) {\n const updates = await aggregateInlineTranslations(\n options,\n settings,\n library\n );\n if (updates.length > 0) {\n if (!settings.publish && !settings.files?.placeholderPaths.gt) {\n logErrorAndExit(invalidConfigurationError);\n }\n // Convert updates to a file object\n const fileData: Record<string, JsxChildren> = {};\n const fileMetadata: Record<string, unknown> = {};\n // Convert updates to the proper data format\n for (const update of updates) {\n const { source, metadata, dataFormat } = update;\n metadata.dataFormat = dataFormat; // add the data format to the metadata\n // Materialize effective requiresReview into component metadata:\n // the explicit prop wins; otherwise inherit the top-level config\n // default. Only the explicit prop is part of component hashes — the\n // effective flag here is what the platform filters serving on.\n // Uploaded metadata uses the platform's canonical snake_case key;\n // camelCase requiresReview stays client-internal (props + hashing).\n // An explicit prop (true or false) is always materialized; without\n // one, only a true config default is — a false/absent default means\n // the key is omitted entirely, matching pre-feature metadata.\n const effectiveRequiresReview =\n metadata.requiresReview !== undefined\n ? metadata.requiresReview\n : settings.requiresReview\n ? true\n : undefined;\n delete metadata.requiresReview;\n if (effectiveRequiresReview !== undefined) {\n metadata.requires_review = effectiveRequiresReview;\n }\n const { hash, id } = metadata;\n if (id) {\n fileData[id] = source;\n fileMetadata[id] = metadata;\n } else if (hash) {\n fileData[hash] = source;\n fileMetadata[hash] = metadata;\n }\n }\n reactComponents = updates.length;\n // Version identity includes which components effectively require\n // review, so config-only review changes produce a new source version.\n // When nothing requires review this reduces to the legacy key-only\n // hash, keeping existing projects' version IDs stable.\n const sortedKeys = Object.keys(fileData).sort();\n const reviewRequiredKeys = sortedKeys.filter(\n (key) =>\n (fileMetadata[key] as { requires_review?: boolean })\n ?.requires_review === true\n );\n const versionId = reviewRequiredKeys.length\n ? hashStringSync(\n JSON.stringify(sortedKeys) +\n '\\u0000requiresReview\\u0000' +\n JSON.stringify(reviewRequiredKeys)\n )\n : hashStringSync(JSON.stringify(sortedKeys));\n files.push({\n fileName: TEMPLATE_FILE_NAME,\n content: JSON.stringify(fileData),\n fileFormat: 'GTJSON',\n formatMetadata: fileMetadata,\n fileId: TEMPLATE_FILE_ID,\n versionId,\n locale: settings.defaultLocale,\n } satisfies FileToUpload);\n // Only add GT JSON to publishMap if there's an explicit publish config\n const gtPublishValue = shouldPublishGt(settings);\n if (gtPublishValue !== undefined) {\n publishMap.set(TEMPLATE_FILE_ID, gtPublishValue);\n }\n }\n }\n return { files, reactComponents, publishMap };\n}\n"],"mappings":";;;;;;;;;AAgBA,eAAsB,aACpB,SACA,UACA,SAKC;CAED,MAAM,EAAE,OAAO,eAAe,MAAM,eAAe,SAAS;CAG5D,IAAI,kBAAkB;AACtB,KAAI,gBAAgB,QAAQ,EAAE;EAC5B,MAAM,UAAU,MAAM,4BACpB,SACA,UACA,QACD;AACD,MAAI,QAAQ,SAAS,GAAG;AACtB,OAAI,CAAC,SAAS,WAAW,CAAC,SAAS,OAAO,iBAAiB,GACzD,iBAAgB,0BAA0B;GAG5C,MAAM,WAAwC,EAAE;GAChD,MAAM,eAAwC,EAAE;AAEhD,QAAK,MAAM,UAAU,SAAS;IAC5B,MAAM,EAAE,QAAQ,UAAU,eAAe;AACzC,aAAS,aAAa;IAUtB,MAAM,0BACJ,SAAS,mBAAmB,KAAA,IACxB,SAAS,iBACT,SAAS,iBACP,OACA,KAAA;AACR,WAAO,SAAS;AAChB,QAAI,4BAA4B,KAAA,EAC9B,UAAS,kBAAkB;IAE7B,MAAM,EAAE,MAAM,OAAO;AACrB,QAAI,IAAI;AACN,cAAS,MAAM;AACf,kBAAa,MAAM;eACV,MAAM;AACf,cAAS,QAAQ;AACjB,kBAAa,QAAQ;;;AAGzB,qBAAkB,QAAQ;GAK1B,MAAM,aAAa,OAAO,KAAK,SAAS,CAAC,MAAM;GAC/C,MAAM,qBAAqB,WAAW,QACnC,QACE,aAAa,MACV,oBAAoB,KAC3B;GACD,MAAM,YAAY,mBAAmB,SACjC,eACE,KAAK,UAAU,WAAW,GACxB,uBACA,KAAK,UAAU,mBAAmB,CACrC,GACD,eAAe,KAAK,UAAU,WAAW,CAAC;AAC9C,SAAM,KAAK;IACT,UAAU;IACV,SAAS,KAAK,UAAU,SAAS;IACjC,YAAY;IACZ,gBAAgB;IAChB,QAAQ;IACR;IACA,QAAQ,SAAS;IAClB,CAAwB;GAEzB,MAAM,iBAAiB,gBAAgB,SAAS;AAChD,OAAI,mBAAmB,KAAA,EACrB,YAAW,IAAI,kBAAkB,eAAe;;;AAItD,QAAO;EAAE;EAAO;EAAiB;EAAY"}
@@ -14,9 +14,20 @@
14
14
  function convertToFileTranslationData(files) {
15
15
  return Object.fromEntries(files.map((file) => [file.fileId, {
16
16
  versionId: file.versionId,
17
- fileName: file.fileName
17
+ fileName: file.fileName,
18
+ ...file.fileFormat === "GTJSON" && { componentCount: countGtJsonComponents(file.content) }
18
19
  }]));
19
20
  }
21
+ /**
22
+ * Counts source components in a GTJSON template so the download step can
23
+ * report how many were withheld from the served output pending review.
24
+ */
25
+ function countGtJsonComponents(content) {
26
+ try {
27
+ const parsed = JSON.parse(content);
28
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return Object.keys(parsed).length;
29
+ } catch {}
30
+ }
20
31
  //#endregion
21
32
  export { convertToFileTranslationData };
22
33
 
@@ -1 +1 @@
1
- {"version":3,"file":"convertToFileTranslationData.js","names":[],"sources":["../../../src/formats/files/convertToFileTranslationData.ts"],"sourcesContent":["import { FileToUpload } from 'generaltranslation/types';\nimport { FileTranslationData } from '../../workflows/download.js';\n\n/**\n * Convert files to a file version data object\n * @param files - The files to get the version data for\n * @returns The version data for each file\n *\n * @example\n * const { allFiles } = await collectFiles(options, settings, library);\n * if (allFiles.length > 0) {\n * const fileVersionData = getFileVersionData(allFiles);\n * console.log(fileVersionData);\n * }\n */\nexport function convertToFileTranslationData(\n files: FileToUpload[]\n): FileTranslationData {\n return Object.fromEntries(\n files.map((file) => [\n file.fileId,\n {\n versionId: file.versionId,\n fileName: file.fileName,\n },\n ])\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,SAAgB,6BACd,OACqB;AACrB,QAAO,OAAO,YACZ,MAAM,KAAK,SAAS,CAClB,KAAK,QACL;EACE,WAAW,KAAK;EAChB,UAAU,KAAK;EAChB,CACF,CAAC,CACH"}
1
+ {"version":3,"file":"convertToFileTranslationData.js","names":[],"sources":["../../../src/formats/files/convertToFileTranslationData.ts"],"sourcesContent":["import { FileToUpload } from 'generaltranslation/types';\nimport { FileTranslationData } from '../../workflows/download.js';\n\n/**\n * Convert files to a file version data object\n * @param files - The files to get the version data for\n * @returns The version data for each file\n *\n * @example\n * const { allFiles } = await collectFiles(options, settings, library);\n * if (allFiles.length > 0) {\n * const fileVersionData = getFileVersionData(allFiles);\n * console.log(fileVersionData);\n * }\n */\nexport function convertToFileTranslationData(\n files: FileToUpload[]\n): FileTranslationData {\n return Object.fromEntries(\n files.map((file) => [\n file.fileId,\n {\n versionId: file.versionId,\n fileName: file.fileName,\n ...(file.fileFormat === 'GTJSON' && {\n componentCount: countGtJsonComponents(file.content),\n }),\n },\n ])\n );\n}\n\n/**\n * Counts source components in a GTJSON template so the download step can\n * report how many were withheld from the served output pending review.\n */\nfunction countGtJsonComponents(content: string): number | undefined {\n try {\n const parsed = JSON.parse(content);\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return Object.keys(parsed).length;\n }\n } catch {\n // Not parsable — no count available\n }\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,SAAgB,6BACd,OACqB;AACrB,QAAO,OAAO,YACZ,MAAM,KAAK,SAAS,CAClB,KAAK,QACL;EACE,WAAW,KAAK;EAChB,UAAU,KAAK;EACf,GAAI,KAAK,eAAe,YAAY,EAClC,gBAAgB,sBAAsB,KAAK,QAAQ,EACpD;EACF,CACF,CAAC,CACH;;;;;;AAOH,SAAS,sBAAsB,SAAqC;AAClE,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,QAAQ;AAClC,MAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,OAAO,CAChE,QAAO,OAAO,KAAK,OAAO,CAAC;SAEvB"}
@@ -25,7 +25,7 @@ export declare function normalizeIncludePatterns(patterns: IncludePattern[]): {
25
25
  * @param files - The files object
26
26
  * @returns The resolved files
27
27
  */
28
- export declare function resolveFiles(files: FilesOptions, locale: string, locales: string[], cwd: string, compositePatterns?: string[]): Settings['files'];
28
+ export declare function resolveFiles(files: FilesOptions, locale: string, locales: string[], cwd: string, compositePatterns?: string[], requiresReviewDefault?: boolean): Settings['files'];
29
29
  export declare function expandGlobPatterns(cwd: string, includePatterns: string[], excludePatterns: string[], locale: string, locales: string[], transformPatterns?: TransformOption | string | TransformOption[], compositePatterns?: string[]): {
30
30
  resolvedPaths: string[];
31
31
  placeholderPaths: string[];
@@ -1,4 +1,5 @@
1
1
  import { logger } from "../../console/logger.js";
2
+ import { logErrorAndExit } from "../../console/logging.js";
2
3
  import { SUPPORTED_FILE_EXTENSIONS } from "../../formats/files/supportedFiles.js";
3
4
  import { BASE_PARSING_FLAGS_DEFAULT, GT_PARSING_FLAGS_DEFAULT } from "../../config/defaults.js";
4
5
  import { resolveTransformationFormat } from "../../formats/files/transformFormat.js";
@@ -49,13 +50,14 @@ function normalizeIncludePatterns(patterns) {
49
50
  * @param files - The files object
50
51
  * @returns The resolved files
51
52
  */
52
- function resolveFiles(files, locale, locales, cwd, compositePatterns) {
53
+ function resolveFiles(files, locale, locales, cwd, compositePatterns, requiresReviewDefault = false) {
53
54
  const resolvedPaths = {};
54
55
  const placeholderResult = {};
55
56
  const transformPaths = {};
56
57
  const transformFormats = {};
57
58
  const publishPaths = /* @__PURE__ */ new Set();
58
59
  const unpublishPaths = /* @__PURE__ */ new Set();
60
+ const requiresReviewPaths = /* @__PURE__ */ new Set();
59
61
  const parsingFlags = {};
60
62
  if (files.gt?.output) placeholderResult.gt = path.resolve(cwd, files.gt.output);
61
63
  for (const fileType of SUPPORTED_FILE_EXTENSIONS) {
@@ -69,6 +71,7 @@ function resolveFiles(files, locale, locales, cwd, compositePatterns) {
69
71
  resolvedPaths[fileType] = filePaths.resolvedPaths;
70
72
  placeholderResult[fileType] = filePaths.placeholderPaths;
71
73
  classifyPublishPaths(filePaths.resolvedPaths, publishPatterns, unpublishPatterns, cwd, locale, publishPaths, unpublishPaths);
74
+ classifyRequiresReviewPaths(filePaths.resolvedPaths, validateRequiresReviewConfig(files[fileType]?.requiresReview, fileType), requiresReviewDefault, cwd, locale, requiresReviewPaths);
72
75
  }
73
76
  if (files[fileType]?.parsingFlags) parsingFlags[fileType] = {
74
77
  ...BASE_PARSING_FLAGS_DEFAULT,
@@ -82,6 +85,7 @@ function resolveFiles(files, locale, locales, cwd, compositePatterns) {
82
85
  transformFormats,
83
86
  publishPaths,
84
87
  unpublishPaths,
88
+ requiresReviewPaths,
85
89
  parsingFlags,
86
90
  gtJson: (() => {
87
91
  const rawGtFlags = files.gt?.parsingFlags || {};
@@ -174,6 +178,42 @@ function classifyPublishPaths(resolvedPaths, publishPatterns, unpublishPatterns,
174
178
  for (let i = 0; i < posixPaths.length; i++) if (matched.has(posixPaths[i])) unpublishPaths.add(resolvedPaths[i]);
175
179
  }
176
180
  }
181
+ /**
182
+ * Validates a file-type requiresReview config value. Only a boolean or an
183
+ * object of include/exclude glob string arrays is accepted — notably not
184
+ * string "true"/"false", so a misquoted boolean fails loudly instead of
185
+ * silently changing review policy (and with it, version identity).
186
+ */
187
+ function validateRequiresReviewConfig(config, fileType) {
188
+ if (config === void 0 || typeof config === "boolean") return config;
189
+ const isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
190
+ if (config !== null && typeof config === "object" && !Array.isArray(config) && Object.keys(config).every((key) => key === "include" || key === "exclude")) {
191
+ const { include, exclude } = config;
192
+ if ((include === void 0 || isStringArray(include)) && (exclude === void 0 || isStringArray(exclude))) return config;
193
+ }
194
+ return logErrorAndExit(`files.${fileType}.requiresReview must be a boolean or an object of glob string arrays: { include?: string[], exclude?: string[] }`);
195
+ }
196
+ /**
197
+ * Classifies resolved file paths by effective requiresReview policy and adds
198
+ * paths whose policy is true to requiresReviewPaths. Precedence: file-type
199
+ * include/exclude globs (exclude wins) > file-type boolean > top-level default.
200
+ */
201
+ function classifyRequiresReviewPaths(resolvedPaths, config, requiresReviewDefault, cwd, locale, requiresReviewPaths) {
202
+ if (typeof config === "boolean" || config === void 0) {
203
+ if (config ?? requiresReviewDefault) for (const resolvedPath of resolvedPaths) requiresReviewPaths.add(resolvedPath);
204
+ return;
205
+ }
206
+ const posixPaths = resolvedPaths.map(toPosixPath);
207
+ const toAbsoluteGlob = (p) => toPosixPath(path.resolve(cwd, p.replace(/\[locale\]/g, locale)));
208
+ const matchAny = (patterns) => {
209
+ const matched = /* @__PURE__ */ new Set();
210
+ for (const pattern of patterns) for (const match of micromatch(posixPaths, toAbsoluteGlob(pattern))) matched.add(match);
211
+ return matched;
212
+ };
213
+ const included = matchAny(config.include ?? []);
214
+ const excluded = matchAny(config.exclude ?? []);
215
+ for (let i = 0; i < posixPaths.length; i++) if (excluded.has(posixPaths[i]) ? false : included.has(posixPaths[i]) || requiresReviewDefault) requiresReviewPaths.add(resolvedPaths[i]);
216
+ }
177
217
  //#endregion
178
218
  export { expandGlobPatterns, normalizeIncludePatterns, resolveFiles, resolveLocaleFiles };
179
219
 
@@ -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 ResolvedFiles,\n Settings,\n TransformFormats,\n TransformFiles,\n TransformOption,\n} from '../../types/index.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';\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 result[fileType] = files[fileType]?.map((filepath) =>\n filepath.replace(/\\[locale\\]/g, locale)\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): 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 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 // ==== 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 parsingFlags,\n gtJson: (() => {\n const rawGtFlags = (files.gt?.parsingFlags || {}) as Record<\n string,\n unknown\n >;\n if ('autoDerive' in rawGtFlags && !('autoderive' in rawGtFlags)) {\n rawGtFlags.autoderive = rawGtFlags.autoDerive;\n delete rawGtFlags.autoDerive;\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"],"mappings":";;;;;;;;;;;;;;;;;AA8BA,SAAgB,mBACd,OACA,QACe;CACf,MAAM,SAAwB,EAAE;AAEhC,MAAK,MAAM,YAAY,0BACrB,QAAO,YAAY,MAAM,WAAW,KAAK,aACvC,SAAS,QAAQ,eAAe,OAAO,CACxC;AAIH,QAAO,KAAK,MAAM,IAAI,QAAQ,eAAe,OAAO;AAEpD,QAAO;;;;;;AAMT,SAAgB,yBAAyB,UAIvC;CACA,MAAM,QAAkB,EAAE;CAC1B,MAAM,kBAA4B,EAAE;CACpC,MAAM,oBAA8B,EAAE;AAEtC,MAAK,MAAM,WAAW,SACpB,KAAI,OAAO,YAAY,SACrB,OAAM,KAAK,QAAQ;MACd;AACL,QAAM,KAAK,QAAQ,QAAQ;AAC3B,MAAI,QAAQ,YAAY,KACtB,iBAAgB,KAAK,QAAQ,QAAQ;WAC5B,QAAQ,YAAY,MAC7B,mBAAkB,KAAK,QAAQ,QAAQ;;AAK7C,QAAO;EAAE;EAAO;EAAiB;EAAmB;;;;;;;;;;AAWtD,SAAgB,aACd,OACA,QACA,SACA,KACA,mBACmB;CAEnB,MAAM,gBAA+B,EAAE;CACvC,MAAM,oBAAmC,EAAE;CAC3C,MAAM,iBAAiC,EAAE;CAEzC,MAAM,mBAAqC,EAAE;CAC7C,MAAM,+BAAe,IAAI,KAAa;CACtC,MAAM,iCAAiB,IAAI,KAAa;CACxC,MAAM,eAAqC,EAAE;AAG7C,KAAI,MAAM,IAAI,OACZ,mBAAkB,KAAK,KAAK,QAAQ,KAAK,MAAM,GAAG,OAAO;AAG3D,MAAK,MAAM,YAAY,2BAA2B;EAEhD,MAAM,YAAY,MAAM,WAAW;AACnC,MACE,cACC,OAAO,cAAc,YACpB,OAAO,cAAc,YACrB,MAAM,QAAQ,UAAU,EAE1B,gBAAe,YAAY;EAG7B,MAAM,kBAAkB,4BACtB,UACA,MAAM,WAAW,qBAClB;AACD,MAAI,gBACF,kBAAiB,YAAY;AAG/B,MAAI,MAAM,WAAW,SAAS;GAC5B,MAAM,EAAE,OAAO,iBAAiB,sBAC9B,yBAAyB,MAAM,UAAU,QAAQ;GAEnD,MAAM,YAAY,mBAChB,KACA,OACA,MAAM,WAAW,WAAW,EAAE,EAC9B,QACA,SACA,eAAe,aAAa,KAAA,GAC5B,kBACD;AACD,iBAAc,YAAY,UAAU;AACpC,qBAAkB,YAAY,UAAU;AAGxC,wBACE,UAAU,eACV,iBACA,mBACA,KACA,QACA,cACA,eACD;;AAGH,MAAI,MAAM,WAAW,aACnB,cAAa,YAAY;GACvB,GAAG;GACH,GAAG,MAAM,UAAU;GACpB;;AAIL,QAAO;EACL;EACA,kBAAkB;EACF;EAChB;EACA;EACA;EACA;EACA,eAAe;GACb,MAAM,aAAc,MAAM,IAAI,gBAAgB,EAAE;AAIhD,OAAI,gBAAgB,cAAc,EAAE,gBAAgB,aAAa;AAC/D,eAAW,aAAa,WAAW;AACnC,WAAO,WAAW;;AAEpB,UAAO;IACL,SAAS,MAAM,IAAI;IACnB,cAAc;KACZ,GAAG;KACH,GAAG;KACJ;IACF;MACC;EACL;;AAIH,SAAgB,mBACd,KACA,iBACA,iBACA,QACA,SACA,mBACA,mBAIA;CAEA,MAAM,gBAA0B,EAAE;CAClC,MAAM,mBAA6B,EAAE;AAGrC,MAAK,MAAM,WAAW,iBAAiB;AAKrC,MACE,CAAC,QAAQ,SAAS,WAAW,IAC7B,CAAC,qBACD,CAAC,mBAAmB,SAAS,QAAQ,CAErC,QAAO,KACL,MAAM,OACJ,YAAY,QAAQ,qFACrB,CACF;EAEH,MAAM,kBAA4B,EAAE;EACpC,IAAI,cAAc;EAClB,MAAM,YAAY;AAElB,SAAO,MAAM;GACX,MAAM,aAAa,QAAQ,QAAQ,WAAW,YAAY;AAC1D,OAAI,eAAe,GAAI;AACvB,mBAAgB,KAAK,WAAW;AAChC,iBAAc,aAAa;;EAG7B,MAAM,kBAAkB,QAAQ,QAAQ,eAAe,OAAO;EAG9D,MAAM,kBAAkB,KAAK,QAAQ,KAAK,gBAAgB;EAG1D,MAAM,0BAA0B,MAAM,KACpC,IAAI,IACF,gBAAgB,SAAS,MACvB,QAAQ,KAAK,iBACX,KAAK,QACH,KACA,EACG,QAAQ,eAAe,OAAO,CAC9B,QAAQ,gBAAgB,aAAa,CACzC,CACF,CACF,CACF,CACF;EAGD,MAAM,UAAU,GAAG,KAAK,iBAAiB;GACvC,UAAU;GACV,QAAQ;GACT,CAAC;AAEF,gBAAc,KAAK,GAAG,QAAQ;AAG9B,UAAQ,SAAS,UAAU;GACzB,MAAM,eAAe,KAAK,QAAQ,KAAK,MAAM;GAC7C,MAAM,cAAc,KAAK,QAAQ,KAAK,QAAQ;GAC9C,IAAI,uBAAuB;AAE3B,OAAI,gBAAgB,SAAS,EAM3B,wBALwB,gCACtB,aACA,cACA,UAEoC;AAGxC,oBAAiB,KAAK,qBAAqB;IAC3C;;AAGJ,QAAO;EAAE;EAAe;EAAkB;;AAG5C,SAAS,gCACP,aACA,cACA,WACQ;AACR,KAAI,CAAC,YAAY,SAAS,UAAU,CAClC,QAAO;CAGT,MAAM,eAAe,YAAY,YAAY;CAC7C,MAAM,YAAY,YAAY,aAAa;CAE3C,MAAM,YAAY,WAAW,OAAO,cAAc,EAChD,iBAAiB,MAClB,CAAC;CACF,MAAM,oBAAoB,UAAU,OAAO,QACzC,mBACA,UACD;CACD,MAAM,QAAQ,UAAU,MAAM,SAAS,IAAI,GACvC,UAAU,QACV,GAAG,UAAU,MAAM;CAEvB,MAAM,QAAQ,IADM,OAAO,mBAAmB,MACzB,CAAC,KAAK,UAAU;CAErC,MAAM,mBAAmB;AAIzB,KAAI,CAAC,SAAS,CAAC,iBAAiB,QAC9B,QAAO;CAGT,IAAI,uBAAuB;CAC3B,MAAM,UAAU,iBAAiB;AAEjC,MAAK,IAAI,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;EAC5C,MAAM,CAAC,OAAO,OAAO,QAAQ;AAC7B,MAAI,UAAU,MAAM,QAAQ,GAAI;AAChC,yBACE,qBAAqB,MAAM,GAAG,MAAM,GACpC,YACA,qBAAqB,MAAM,IAAI;;AAGnC,QAAO,KAAK,UAAU,qBAAqB;;AAG7C,SAAS,YAAY,OAAuB;AAC1C,QAAO,MAAM,MAAM,KAAK,IAAI,CAAC,KAAK,KAAK,MAAM,IAAI;;;;;;;AAQnD,SAAS,qBACP,eACA,iBACA,mBACA,KACA,QACA,cACA,gBACM;AACN,KAAI,gBAAgB,WAAW,KAAK,kBAAkB,WAAW,EAAG;CAEpE,MAAM,aAAa,cAAc,IAAI,YAAY;CACjD,MAAM,kBAAkB,MACtB,YAAY,KAAK,QAAQ,KAAK,EAAE,QAAQ,eAAe,OAAO,CAAC,CAAC;AAElE,MAAK,MAAM,WAAW,iBAAiB;EACrC,MAAM,UAAU,IAAI,IAAI,WAAW,YAAY,eAAe,QAAQ,CAAC,CAAC;AACxE,OAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,IACrC,KAAI,QAAQ,IAAI,WAAW,GAAG,CAC5B,cAAa,IAAI,cAAc,GAAG;;AAKxC,MAAK,MAAM,WAAW,mBAAmB;EACvC,MAAM,UAAU,IAAI,IAAI,WAAW,YAAY,eAAe,QAAQ,CAAC,CAAC;AACxE,OAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,IACrC,KAAI,QAAQ,IAAI,WAAW,GAAG,CAC5B,gBAAe,IAAI,cAAc,GAAG"}
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';\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 result[fileType] = files[fileType]?.map((filepath) =>\n filepath.replace(/\\[locale\\]/g, locale)\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 if ('autoDerive' in rawGtFlags && !('autoderive' in rawGtFlags)) {\n rawGtFlags.autoderive = rawGtFlags.autoDerive;\n delete rawGtFlags.autoDerive;\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 +1 @@
1
- export declare const PACKAGE_VERSION = "2.14.57";
1
+ export declare const PACKAGE_VERSION = "2.14.58";
@@ -1,5 +1,5 @@
1
1
  //#region src/generated/version.ts
2
- const PACKAGE_VERSION = "2.14.57";
2
+ const PACKAGE_VERSION = "2.14.58";
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.14.57';\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.14.58';\n"],"mappings":";AACA,MAAa,kBAAkB"}
@@ -31,8 +31,8 @@ export declare const GT_TRANSLATION_FUNCS: string[];
31
31
  export declare const STRING_REGISTRATION_FUNCS: readonly ["msg", "t"];
32
32
  export declare const GT_DERIVE_STRING_FUNCTIONS: string[];
33
33
  export declare const VARIABLE_COMPONENTS: string[];
34
- export declare const GT_ATTRIBUTES_WITH_SUGAR: readonly ["$id", "$context", "$maxChars", "$format"];
35
- export declare const GT_ATTRIBUTES: readonly ["id", "context", "maxChars", "$id", "$context", "$maxChars", "$format"];
34
+ export declare const GT_ATTRIBUTES_WITH_SUGAR: readonly ["$id", "$context", "$maxChars", "$format", "$requiresReview"];
35
+ export declare const GT_ATTRIBUTES: readonly ["id", "context", "maxChars", "requiresReview", "$id", "$context", "$maxChars", "$format", "$requiresReview"];
36
36
  export declare const DATA_ATTR_PREFIX: "data-";
37
37
  /** Branch control props — not translatable content. */
38
38
  export declare const BRANCH_CONTROL_PROPS: Set<string>;
@@ -67,12 +67,14 @@ const GT_ATTRIBUTES_WITH_SUGAR = [
67
67
  "$id",
68
68
  "$context",
69
69
  "$maxChars",
70
- "$format"
70
+ "$format",
71
+ "$requiresReview"
71
72
  ];
72
73
  const GT_ATTRIBUTES = [
73
74
  "id",
74
75
  "context",
75
76
  "maxChars",
77
+ "requiresReview",
76
78
  ...GT_ATTRIBUTES_WITH_SUGAR
77
79
  ];
78
80
  const DATA_ATTR_PREFIX = "data-";
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","names":[],"sources":["../../../../src/react/jsx/utils/constants.ts"],"sourcesContent":["export const DECLARE_VAR_FUNCTION = 'declareVar';\n/**\n * COMPAT: Legacy support for declareStatic function.\n */\nexport const DECLARE_STATIC_FUNCTION = 'declareStatic';\nexport const DERIVE_FUNCTION = 'derive';\nexport const MSG_REGISTRATION_FUNCTION = 'msg';\nexport const T_REGISTRATION_FUNCTION = 't';\nexport const T_GLOBAL_REGISTRATION_FUNCTION = T_REGISTRATION_FUNCTION;\nexport const INLINE_TRANSLATION_HOOK = 'useGT';\nexport const INLINE_TRANSLATION_HOOK_ASYNC = 'getGT';\nexport const INLINE_MESSAGE_HOOK = 'useMessages';\nexport const INLINE_MESSAGE_HOOK_ASYNC = 'getMessages';\nexport const TRANSLATION_COMPONENT = 'T';\n/**\n * COMPAT: Legacy support for Static component.\n */\nexport const STATIC_COMPONENT = 'Static';\nexport const DERIVE_COMPONENT = 'Derive';\nexport const BRANCH_COMPONENT = 'Branch';\nexport const DEFAULT_GT_IMPORT_SOURCE = 'gt-react/browser';\nexport const INTERNAL_TRANSLATION_COMPONENT = 'GtInternalTranslateJsx';\nexport const INTERNAL_VAR_COMPONENT = 'GtInternalVar';\n\n// Variable components\nexport const VAR_COMPONENT = 'Var';\nexport const DATETIME_COMPONENT = 'DateTime';\nexport const RELATIVE_TIME_COMPONENT = 'RelativeTime';\nexport const CURRENCY_COMPONENT = 'Currency';\nexport const NUM_COMPONENT = 'Num';\nexport const PLURAL_COMPONENT = 'Plural';\n\n// GT translation functions\nexport const GT_TRANSLATION_FUNCS = [\n INLINE_TRANSLATION_HOOK,\n INLINE_TRANSLATION_HOOK_ASYNC,\n INLINE_MESSAGE_HOOK,\n INLINE_MESSAGE_HOOK_ASYNC,\n MSG_REGISTRATION_FUNCTION,\n T_REGISTRATION_FUNCTION,\n DECLARE_VAR_FUNCTION,\n DECLARE_STATIC_FUNCTION,\n DERIVE_FUNCTION,\n TRANSLATION_COMPONENT,\n STATIC_COMPONENT,\n DERIVE_COMPONENT,\n VAR_COMPONENT,\n DATETIME_COMPONENT,\n RELATIVE_TIME_COMPONENT,\n CURRENCY_COMPONENT,\n NUM_COMPONENT,\n BRANCH_COMPONENT,\n INTERNAL_TRANSLATION_COMPONENT,\n INTERNAL_VAR_COMPONENT,\n PLURAL_COMPONENT,\n];\n// GT String translation functions\nexport const STRING_REGISTRATION_FUNCS = [\n MSG_REGISTRATION_FUNCTION,\n T_REGISTRATION_FUNCTION,\n] as const;\n\n// Derive functions that are imported from GT\nexport const GT_DERIVE_STRING_FUNCTIONS = [\n DECLARE_STATIC_FUNCTION,\n DERIVE_FUNCTION,\n];\n\n// Valid variable components\nexport const VARIABLE_COMPONENTS = [\n VAR_COMPONENT,\n DATETIME_COMPONENT,\n RELATIVE_TIME_COMPONENT,\n CURRENCY_COMPONENT,\n NUM_COMPONENT,\n STATIC_COMPONENT,\n DERIVE_COMPONENT,\n INTERNAL_VAR_COMPONENT,\n];\n\nexport const GT_ATTRIBUTES_WITH_SUGAR = [\n '$id',\n '$context',\n '$maxChars',\n '$format',\n] as const;\n\nexport const GT_ATTRIBUTES = [\n 'id',\n 'context',\n 'maxChars',\n ...GT_ATTRIBUTES_WITH_SUGAR,\n] as const;\n\n// Data attribute prefix injected by build tools\nexport const DATA_ATTR_PREFIX = 'data-' as const;\n\n/** Branch control props — not translatable content. */\nexport const BRANCH_CONTROL_PROPS = new Set(['branch']);\n\n/** Plural control props — not translatable content. */\nexport const PLURAL_CONTROL_PROPS = new Set(['n', 'locales']);\n\n// demarcation for global t macro\nexport const T_GLOBAL_REGISTRATION_FUNCTION_MARKER =\n '_gt_internal_t_global_registration_marker';\n"],"mappings":";AAAA,MAAa,uBAAuB;;;;AAIpC,MAAa,0BAA0B;AACvC,MAAa,kBAAkB;AAC/B,MAAa,4BAA4B;AACzC,MAAa,0BAA0B;AACvC,MAAa,iCAAA;AACb,MAAa,0BAA0B;AACvC,MAAa,gCAAgC;AAC7C,MAAa,sBAAsB;AACnC,MAAa,4BAA4B;AACzC,MAAa,wBAAwB;;;;AAIrC,MAAa,mBAAmB;AAChC,MAAa,mBAAmB;AAChC,MAAa,mBAAmB;AAChC,MAAa,2BAA2B;AACxC,MAAa,iCAAiC;AAC9C,MAAa,yBAAyB;AAGtC,MAAa,gBAAgB;AAC7B,MAAa,qBAAqB;AAClC,MAAa,0BAA0B;AACvC,MAAa,qBAAqB;AAClC,MAAa,gBAAgB;AAC7B,MAAa,mBAAmB;AAGhC,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;;;CAGA;CACA;CACA;;CAEA;CACA;;CAEA;CACA;CACA;;CAEA;CACA;CACA;CACA;CACD;AAED,MAAa,4BAA4B,CAAA,OAAA,IAGxC;AAGD,MAAa,6BAA6B,CACxC,yBACA,gBACD;AAGD,MAAa,sBAAsB;;CAEjC;CACA;CACA;;CAEA;CACA;CACA;CACD;AAED,MAAa,2BAA2B;CACtC;CACA;CACA;CACA;CACD;AAED,MAAa,gBAAgB;CAC3B;CACA;CACA;CACA,GAAG;CACJ;AAGD,MAAa,mBAAmB;;AAGhC,MAAa,uBAAuB,IAAI,IAAI,CAAC,SAAS,CAAC;;AAGvD,MAAa,uBAAuB,IAAI,IAAI,CAAC,KAAK,UAAU,CAAC;AAG7D,MAAa,wCACX"}
1
+ {"version":3,"file":"constants.js","names":[],"sources":["../../../../src/react/jsx/utils/constants.ts"],"sourcesContent":["export const DECLARE_VAR_FUNCTION = 'declareVar';\n/**\n * COMPAT: Legacy support for declareStatic function.\n */\nexport const DECLARE_STATIC_FUNCTION = 'declareStatic';\nexport const DERIVE_FUNCTION = 'derive';\nexport const MSG_REGISTRATION_FUNCTION = 'msg';\nexport const T_REGISTRATION_FUNCTION = 't';\nexport const T_GLOBAL_REGISTRATION_FUNCTION = T_REGISTRATION_FUNCTION;\nexport const INLINE_TRANSLATION_HOOK = 'useGT';\nexport const INLINE_TRANSLATION_HOOK_ASYNC = 'getGT';\nexport const INLINE_MESSAGE_HOOK = 'useMessages';\nexport const INLINE_MESSAGE_HOOK_ASYNC = 'getMessages';\nexport const TRANSLATION_COMPONENT = 'T';\n/**\n * COMPAT: Legacy support for Static component.\n */\nexport const STATIC_COMPONENT = 'Static';\nexport const DERIVE_COMPONENT = 'Derive';\nexport const BRANCH_COMPONENT = 'Branch';\nexport const DEFAULT_GT_IMPORT_SOURCE = 'gt-react/browser';\nexport const INTERNAL_TRANSLATION_COMPONENT = 'GtInternalTranslateJsx';\nexport const INTERNAL_VAR_COMPONENT = 'GtInternalVar';\n\n// Variable components\nexport const VAR_COMPONENT = 'Var';\nexport const DATETIME_COMPONENT = 'DateTime';\nexport const RELATIVE_TIME_COMPONENT = 'RelativeTime';\nexport const CURRENCY_COMPONENT = 'Currency';\nexport const NUM_COMPONENT = 'Num';\nexport const PLURAL_COMPONENT = 'Plural';\n\n// GT translation functions\nexport const GT_TRANSLATION_FUNCS = [\n INLINE_TRANSLATION_HOOK,\n INLINE_TRANSLATION_HOOK_ASYNC,\n INLINE_MESSAGE_HOOK,\n INLINE_MESSAGE_HOOK_ASYNC,\n MSG_REGISTRATION_FUNCTION,\n T_REGISTRATION_FUNCTION,\n DECLARE_VAR_FUNCTION,\n DECLARE_STATIC_FUNCTION,\n DERIVE_FUNCTION,\n TRANSLATION_COMPONENT,\n STATIC_COMPONENT,\n DERIVE_COMPONENT,\n VAR_COMPONENT,\n DATETIME_COMPONENT,\n RELATIVE_TIME_COMPONENT,\n CURRENCY_COMPONENT,\n NUM_COMPONENT,\n BRANCH_COMPONENT,\n INTERNAL_TRANSLATION_COMPONENT,\n INTERNAL_VAR_COMPONENT,\n PLURAL_COMPONENT,\n];\n// GT String translation functions\nexport const STRING_REGISTRATION_FUNCS = [\n MSG_REGISTRATION_FUNCTION,\n T_REGISTRATION_FUNCTION,\n] as const;\n\n// Derive functions that are imported from GT\nexport const GT_DERIVE_STRING_FUNCTIONS = [\n DECLARE_STATIC_FUNCTION,\n DERIVE_FUNCTION,\n];\n\n// Valid variable components\nexport const VARIABLE_COMPONENTS = [\n VAR_COMPONENT,\n DATETIME_COMPONENT,\n RELATIVE_TIME_COMPONENT,\n CURRENCY_COMPONENT,\n NUM_COMPONENT,\n STATIC_COMPONENT,\n DERIVE_COMPONENT,\n INTERNAL_VAR_COMPONENT,\n];\n\nexport const GT_ATTRIBUTES_WITH_SUGAR = [\n '$id',\n '$context',\n '$maxChars',\n '$format',\n '$requiresReview',\n] as const;\n\nexport const GT_ATTRIBUTES = [\n 'id',\n 'context',\n 'maxChars',\n 'requiresReview',\n ...GT_ATTRIBUTES_WITH_SUGAR,\n] as const;\n\n// Data attribute prefix injected by build tools\nexport const DATA_ATTR_PREFIX = 'data-' as const;\n\n/** Branch control props — not translatable content. */\nexport const BRANCH_CONTROL_PROPS = new Set(['branch']);\n\n/** Plural control props — not translatable content. */\nexport const PLURAL_CONTROL_PROPS = new Set(['n', 'locales']);\n\n// demarcation for global t macro\nexport const T_GLOBAL_REGISTRATION_FUNCTION_MARKER =\n '_gt_internal_t_global_registration_marker';\n"],"mappings":";AAAA,MAAa,uBAAuB;;;;AAIpC,MAAa,0BAA0B;AACvC,MAAa,kBAAkB;AAC/B,MAAa,4BAA4B;AACzC,MAAa,0BAA0B;AACvC,MAAa,iCAAA;AACb,MAAa,0BAA0B;AACvC,MAAa,gCAAgC;AAC7C,MAAa,sBAAsB;AACnC,MAAa,4BAA4B;AACzC,MAAa,wBAAwB;;;;AAIrC,MAAa,mBAAmB;AAChC,MAAa,mBAAmB;AAChC,MAAa,mBAAmB;AAChC,MAAa,2BAA2B;AACxC,MAAa,iCAAiC;AAC9C,MAAa,yBAAyB;AAGtC,MAAa,gBAAgB;AAC7B,MAAa,qBAAqB;AAClC,MAAa,0BAA0B;AACvC,MAAa,qBAAqB;AAClC,MAAa,gBAAgB;AAC7B,MAAa,mBAAmB;AAGhC,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;;;CAGA;CACA;CACA;;CAEA;CACA;;CAEA;CACA;CACA;;CAEA;CACA;CACA;CACA;CACD;AAED,MAAa,4BAA4B,CAAA,OAAA,IAGxC;AAGD,MAAa,6BAA6B,CACxC,yBACA,gBACD;AAGD,MAAa,sBAAsB;;CAEjC;CACA;CACA;;CAEA;CACA;CACA;CACD;AAED,MAAa,2BAA2B;CACtC;CACA;CACA;CACA;CACA;CACD;AAED,MAAa,gBAAgB;CAC3B;CACA;CACA;CACA;CACA,GAAG;CACJ;AAGD,MAAa,mBAAmB;;AAGhC,MAAa,uBAAuB,IAAI,IAAI,CAAC,SAAS,CAAC;;AAGvD,MAAa,uBAAuB,IAAI,IAAI,CAAC,KAAK,UAAU,CAAC;AAG7D,MAAa,wCACX"}
@@ -1,6 +1,6 @@
1
1
  import { isStaticExpression } from "../../evaluateJsx.js";
2
2
  import { GT_ATTRIBUTES } from "../constants.js";
3
- import { warnInvalidMaxCharsSync, warnVariablePropSync } from "../../../../console/index.js";
3
+ import { warnInvalidMaxCharsSync, warnInvalidRequiresReviewSync, warnVariablePropSync } from "../../../../console/index.js";
4
4
  import { mapAttributeName } from "../mapAttributeName.js";
5
5
  import { isNumberLiteral } from "../isNumberLiteral.js";
6
6
  import { containsDeriveCall } from "../stringParsing/derivation/containsDeriveCall.js";
@@ -13,6 +13,19 @@ function parseTProps({ openingElement, metadata, componentErrors, file }) {
13
13
  if (!t.isJSXAttribute(attr)) return;
14
14
  const attrName = attr.name.name;
15
15
  if (typeof attrName !== "string") return;
16
+ if (attrName === "requiresReview" || attrName === "$requiresReview") {
17
+ if (!attr.value) {
18
+ metadata[mapAttributeName(attrName)] = true;
19
+ return;
20
+ }
21
+ if (t.isJSXExpressionContainer(attr.value) && t.isBooleanLiteral(attr.value.expression)) {
22
+ metadata[mapAttributeName(attrName)] = attr.value.expression.value;
23
+ return;
24
+ }
25
+ const code = t.isJSXExpressionContainer(attr.value) ? generate(attr.value.expression).code : generate(attr.value).code;
26
+ componentErrors.push(warnInvalidRequiresReviewSync(file, code, `${attr.value.loc?.start?.line}:${attr.value.loc?.start?.column}`));
27
+ return;
28
+ }
16
29
  if (attr.value) {
17
30
  if (t.isStringLiteral(attr.value)) metadata[attrName] = attr.value.value;
18
31
  else if (t.isJSXExpressionContainer(attr.value)) {
@@ -1 +1 @@
1
- {"version":3,"file":"parseTProps.js","names":[],"sources":["../../../../../src/react/jsx/utils/jsxParsing/parseTProps.ts"],"sourcesContent":["import { Metadata } from 'generaltranslation/types';\nimport * as t from '@babel/types';\nimport generateModule from '@babel/generator';\n// Handle CommonJS/ESM interop\nconst generate = generateModule.default || generateModule;\n\nimport { GT_ATTRIBUTES } from '../constants.js';\nimport { mapAttributeName } from '../mapAttributeName.js';\nimport { isStaticExpression } from '../../evaluateJsx.js';\nimport {\n warnInvalidMaxCharsSync,\n warnVariablePropSync,\n} from '../../../../console/index.js';\nimport { isNumberLiteral } from '../isNumberLiteral.js';\nimport { containsDeriveCall } from '../stringParsing/derivation/containsDeriveCall.js';\n\n// Parse the props of a <T> component\nexport function parseTProps({\n openingElement,\n metadata,\n componentErrors,\n file,\n}: {\n openingElement: t.JSXOpeningElement;\n metadata: Metadata;\n componentErrors: string[];\n file: string;\n}) {\n openingElement.attributes.forEach((attr) => {\n if (!t.isJSXAttribute(attr)) return;\n const attrName = attr.name.name;\n if (typeof attrName !== 'string') return;\n\n if (attr.value) {\n // If it's a plain string literal like id=\"hello\"\n if (t.isStringLiteral(attr.value)) {\n metadata[attrName] = attr.value.value;\n }\n // If it's an expression container like id={\"hello\"}, id={someVar}, etc.\n else if (t.isJSXExpressionContainer(attr.value)) {\n const expr = attr.value.expression;\n const code = generate(expr).code;\n\n // Only check for derivable expressions on id and context and maxChars props\n if (\n GT_ATTRIBUTES.includes(attrName as (typeof GT_ATTRIBUTES)[number])\n ) {\n const staticAnalysis = isStaticExpression(expr);\n if (!staticAnalysis.isStatic) {\n if (\n mapAttributeName(attrName) === 'context' &&\n t.isExpression(expr) &&\n containsDeriveCall(expr)\n ) {\n metadata._contextDeriveExpr = expr;\n } else {\n componentErrors.push(\n warnVariablePropSync(\n file,\n attrName,\n code,\n `${expr.loc?.start?.line}:${expr.loc?.start?.column}`\n )\n );\n }\n }\n // Use the derived value if available\n if (staticAnalysis.isStatic && staticAnalysis.value !== undefined) {\n // Check for invalid maxChars values\n if (attrName === '$maxChars' || attrName === 'maxChars') {\n if (\n typeof staticAnalysis.value === 'string' &&\n (isNaN(Number(staticAnalysis.value)) ||\n (t.isExpression(expr) && !isNumberLiteral(expr)) ||\n !Number.isInteger(Number(staticAnalysis.value)))\n ) {\n componentErrors.push(\n warnInvalidMaxCharsSync(\n file,\n code,\n `${expr.loc?.start?.line}:${expr.loc?.start?.column}`\n )\n );\n } else {\n // Add the maxChars value to the metadata\n metadata[mapAttributeName(attrName)] = Math.abs(\n Number(staticAnalysis.value)\n );\n }\n } else {\n // Add the $context or $id or other attributes value to the metadata\n metadata[mapAttributeName(attrName)] = staticAnalysis.value;\n }\n } else {\n // Only store the code if we couldn't extract a derivable value\n metadata[attrName] = code;\n }\n } else {\n // For other attributes that aren't id or context\n metadata[attrName] = code;\n }\n }\n }\n });\n}\n"],"mappings":";;;;;;;;;AAIA,MAAM,WAAW,eAAe,WAAW;AAa3C,SAAgB,YAAY,EAC1B,gBACA,UACA,iBACA,QAMC;AACD,gBAAe,WAAW,SAAS,SAAS;AAC1C,MAAI,CAAC,EAAE,eAAe,KAAK,CAAE;EAC7B,MAAM,WAAW,KAAK,KAAK;AAC3B,MAAI,OAAO,aAAa,SAAU;AAElC,MAAI,KAAK;OAEH,EAAE,gBAAgB,KAAK,MAAM,CAC/B,UAAS,YAAY,KAAK,MAAM;YAGzB,EAAE,yBAAyB,KAAK,MAAM,EAAE;IAC/C,MAAM,OAAO,KAAK,MAAM;IACxB,MAAM,OAAO,SAAS,KAAK,CAAC;AAG5B,QACE,cAAc,SAAS,SAA2C,EAClE;KACA,MAAM,iBAAiB,mBAAmB,KAAK;AAC/C,SAAI,CAAC,eAAe,SAClB,KACE,iBAAiB,SAAS,KAAK,aAC/B,EAAE,aAAa,KAAK,IACpB,mBAAmB,KAAK,CAExB,UAAS,qBAAqB;SAE9B,iBAAgB,KACd,qBACE,MACA,UACA,MACA,GAAG,KAAK,KAAK,OAAO,KAAK,GAAG,KAAK,KAAK,OAAO,SAC9C,CACF;AAIL,SAAI,eAAe,YAAY,eAAe,UAAU,KAAA,EAEtD,KAAI,aAAa,eAAe,aAAa,WAC3C,KACE,OAAO,eAAe,UAAU,aAC/B,MAAM,OAAO,eAAe,MAAM,CAAC,IACjC,EAAE,aAAa,KAAK,IAAI,CAAC,gBAAgB,KAAK,IAC/C,CAAC,OAAO,UAAU,OAAO,eAAe,MAAM,CAAC,EAEjD,iBAAgB,KACd,wBACE,MACA,MACA,GAAG,KAAK,KAAK,OAAO,KAAK,GAAG,KAAK,KAAK,OAAO,SAC9C,CACF;SAGD,UAAS,iBAAiB,SAAS,IAAI,KAAK,IAC1C,OAAO,eAAe,MAAM,CAC7B;SAIH,UAAS,iBAAiB,SAAS,IAAI,eAAe;SAIxD,UAAS,YAAY;UAIvB,UAAS,YAAY;;;GAI3B"}
1
+ {"version":3,"file":"parseTProps.js","names":[],"sources":["../../../../../src/react/jsx/utils/jsxParsing/parseTProps.ts"],"sourcesContent":["import { Metadata } from 'generaltranslation/types';\nimport * as t from '@babel/types';\nimport generateModule from '@babel/generator';\n// Handle CommonJS/ESM interop\nconst generate = generateModule.default || generateModule;\n\nimport { GT_ATTRIBUTES } from '../constants.js';\nimport { mapAttributeName } from '../mapAttributeName.js';\nimport { isStaticExpression } from '../../evaluateJsx.js';\nimport {\n warnInvalidMaxCharsSync,\n warnInvalidRequiresReviewSync,\n warnVariablePropSync,\n} from '../../../../console/index.js';\nimport { isNumberLiteral } from '../isNumberLiteral.js';\nimport { containsDeriveCall } from '../stringParsing/derivation/containsDeriveCall.js';\n\n// Parse the props of a <T> component\nexport function parseTProps({\n openingElement,\n metadata,\n componentErrors,\n file,\n}: {\n openingElement: t.JSXOpeningElement;\n metadata: Metadata;\n componentErrors: string[];\n file: string;\n}) {\n openingElement.attributes.forEach((attr) => {\n if (!t.isJSXAttribute(attr)) return;\n const attrName = attr.name.name;\n if (typeof attrName !== 'string') return;\n\n const isRequiresReviewAttr =\n attrName === 'requiresReview' || attrName === '$requiresReview';\n\n // requiresReview is boolean-only and hash-changing, so it gets strict\n // handling before the generic attribute logic:\n // - bare attribute (<T requiresReview>) compiles to `requiresReview: true`,\n // so it must mean true here too or CLI and compiler hashes disagree\n // - string values like requiresReview=\"false\" are rejected, never coerced\n if (isRequiresReviewAttr) {\n if (!attr.value) {\n metadata[mapAttributeName(attrName)] = true;\n return;\n }\n if (\n t.isJSXExpressionContainer(attr.value) &&\n t.isBooleanLiteral(attr.value.expression)\n ) {\n metadata[mapAttributeName(attrName)] = attr.value.expression.value;\n return;\n }\n const code = t.isJSXExpressionContainer(attr.value)\n ? generate(attr.value.expression).code\n : generate(attr.value).code;\n componentErrors.push(\n warnInvalidRequiresReviewSync(\n file,\n code,\n `${attr.value.loc?.start?.line}:${attr.value.loc?.start?.column}`\n )\n );\n return;\n }\n\n if (attr.value) {\n // If it's a plain string literal like id=\"hello\"\n if (t.isStringLiteral(attr.value)) {\n metadata[attrName] = attr.value.value;\n }\n // If it's an expression container like id={\"hello\"}, id={someVar}, etc.\n else if (t.isJSXExpressionContainer(attr.value)) {\n const expr = attr.value.expression;\n const code = generate(expr).code;\n\n // Only check for derivable expressions on id and context and maxChars props\n if (\n GT_ATTRIBUTES.includes(attrName as (typeof GT_ATTRIBUTES)[number])\n ) {\n const staticAnalysis = isStaticExpression(expr);\n if (!staticAnalysis.isStatic) {\n if (\n mapAttributeName(attrName) === 'context' &&\n t.isExpression(expr) &&\n containsDeriveCall(expr)\n ) {\n metadata._contextDeriveExpr = expr;\n } else {\n componentErrors.push(\n warnVariablePropSync(\n file,\n attrName,\n code,\n `${expr.loc?.start?.line}:${expr.loc?.start?.column}`\n )\n );\n }\n }\n // Use the derived value if available\n if (staticAnalysis.isStatic && staticAnalysis.value !== undefined) {\n // Check for invalid maxChars values\n if (attrName === '$maxChars' || attrName === 'maxChars') {\n if (\n typeof staticAnalysis.value === 'string' &&\n (isNaN(Number(staticAnalysis.value)) ||\n (t.isExpression(expr) && !isNumberLiteral(expr)) ||\n !Number.isInteger(Number(staticAnalysis.value)))\n ) {\n componentErrors.push(\n warnInvalidMaxCharsSync(\n file,\n code,\n `${expr.loc?.start?.line}:${expr.loc?.start?.column}`\n )\n );\n } else {\n // Add the maxChars value to the metadata\n metadata[mapAttributeName(attrName)] = Math.abs(\n Number(staticAnalysis.value)\n );\n }\n } else {\n // Add the $context or $id or other attributes value to the metadata\n metadata[mapAttributeName(attrName)] = staticAnalysis.value;\n }\n } else {\n // Only store the code if we couldn't extract a derivable value\n metadata[attrName] = code;\n }\n } else {\n // For other attributes that aren't id or context\n metadata[attrName] = code;\n }\n }\n }\n });\n}\n"],"mappings":";;;;;;;;;AAIA,MAAM,WAAW,eAAe,WAAW;AAc3C,SAAgB,YAAY,EAC1B,gBACA,UACA,iBACA,QAMC;AACD,gBAAe,WAAW,SAAS,SAAS;AAC1C,MAAI,CAAC,EAAE,eAAe,KAAK,CAAE;EAC7B,MAAM,WAAW,KAAK,KAAK;AAC3B,MAAI,OAAO,aAAa,SAAU;AAUlC,MAPE,aAAa,oBAAoB,aAAa,mBAOtB;AACxB,OAAI,CAAC,KAAK,OAAO;AACf,aAAS,iBAAiB,SAAS,IAAI;AACvC;;AAEF,OACE,EAAE,yBAAyB,KAAK,MAAM,IACtC,EAAE,iBAAiB,KAAK,MAAM,WAAW,EACzC;AACA,aAAS,iBAAiB,SAAS,IAAI,KAAK,MAAM,WAAW;AAC7D;;GAEF,MAAM,OAAO,EAAE,yBAAyB,KAAK,MAAM,GAC/C,SAAS,KAAK,MAAM,WAAW,CAAC,OAChC,SAAS,KAAK,MAAM,CAAC;AACzB,mBAAgB,KACd,8BACE,MACA,MACA,GAAG,KAAK,MAAM,KAAK,OAAO,KAAK,GAAG,KAAK,MAAM,KAAK,OAAO,SAC1D,CACF;AACD;;AAGF,MAAI,KAAK;OAEH,EAAE,gBAAgB,KAAK,MAAM,CAC/B,UAAS,YAAY,KAAK,MAAM;YAGzB,EAAE,yBAAyB,KAAK,MAAM,EAAE;IAC/C,MAAM,OAAO,KAAK,MAAM;IACxB,MAAM,OAAO,SAAS,KAAK,CAAC;AAG5B,QACE,cAAc,SAAS,SAA2C,EAClE;KACA,MAAM,iBAAiB,mBAAmB,KAAK;AAC/C,SAAI,CAAC,eAAe,SAClB,KACE,iBAAiB,SAAS,KAAK,aAC/B,EAAE,aAAa,KAAK,IACpB,mBAAmB,KAAK,CAExB,UAAS,qBAAqB;SAE9B,iBAAgB,KACd,qBACE,MACA,UACA,MACA,GAAG,KAAK,KAAK,OAAO,KAAK,GAAG,KAAK,KAAK,OAAO,SAC9C,CACF;AAIL,SAAI,eAAe,YAAY,eAAe,UAAU,KAAA,EAEtD,KAAI,aAAa,eAAe,aAAa,WAC3C,KACE,OAAO,eAAe,UAAU,aAC/B,MAAM,OAAO,eAAe,MAAM,CAAC,IACjC,EAAE,aAAa,KAAK,IAAI,CAAC,gBAAgB,KAAK,IAC/C,CAAC,OAAO,UAAU,OAAO,eAAe,MAAM,CAAC,EAEjD,iBAAgB,KACd,wBACE,MACA,MACA,GAAG,KAAK,KAAK,OAAO,KAAK,GAAG,KAAK,KAAK,OAAO,SAC9C,CACF;SAGD,UAAS,iBAAiB,SAAS,IAAI,KAAK,IAC1C,OAAO,eAAe,MAAM,CAC7B;SAIH,UAAS,iBAAiB,SAAS,IAAI,eAAe;SAIxD,UAAS,YAAY;UAIvB,UAAS,YAAY;;;GAI3B"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Type logic implementation
3
3
  */
4
- type MapAttributeName<T extends string> = T extends '$id' ? 'id' : T extends '$context' ? 'context' : T extends '$maxChars' ? 'maxChars' : T extends '$format' ? 'format' : T;
4
+ type MapAttributeName<T extends string> = T extends '$id' ? 'id' : T extends '$context' ? 'context' : T extends '$maxChars' ? 'maxChars' : T extends '$format' ? 'format' : T extends '$requiresReview' ? 'requiresReview' : T;
5
5
  /**
6
6
  * Map the attribute name to the corresponding attribute name in the metadata
7
7
  * @param attrName - The attribute name to map
@@ -5,6 +5,7 @@ function mapAttributeName(attrName) {
5
5
  case "$context": return "context";
6
6
  case "$maxChars": return "maxChars";
7
7
  case "$format": return "format";
8
+ case "$requiresReview": return "requiresReview";
8
9
  default: return attrName;
9
10
  }
10
11
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mapAttributeName.js","names":[],"sources":["../../../../src/react/jsx/utils/mapAttributeName.ts"],"sourcesContent":["/**\n * Type logic implementation\n */\ntype MapAttributeName<T extends string> = T extends '$id'\n ? 'id'\n : T extends '$context'\n ? 'context'\n : T extends '$maxChars'\n ? 'maxChars'\n : T extends '$format'\n ? 'format'\n : T;\n\n/**\n * Map the attribute name to the corresponding attribute name in the metadata\n * @param attrName - The attribute name to map\n * @returns The mapped attribute name\n */\nexport function mapAttributeName<T extends string>(\n attrName: T\n): MapAttributeName<T>;\nexport function mapAttributeName(attrName: string): string {\n switch (attrName) {\n case '$id':\n return 'id';\n case '$context':\n return 'context';\n case '$maxChars':\n return 'maxChars';\n case '$format':\n return 'format';\n default:\n return attrName;\n }\n}\n"],"mappings":";AAqBA,SAAgB,iBAAiB,UAA0B;AACzD,SAAQ,UAAR;EACE,KAAK,MACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,KAAK,UACH,QAAO;EACT,QACE,QAAO"}
1
+ {"version":3,"file":"mapAttributeName.js","names":[],"sources":["../../../../src/react/jsx/utils/mapAttributeName.ts"],"sourcesContent":["/**\n * Type logic implementation\n */\ntype MapAttributeName<T extends string> = T extends '$id'\n ? 'id'\n : T extends '$context'\n ? 'context'\n : T extends '$maxChars'\n ? 'maxChars'\n : T extends '$format'\n ? 'format'\n : T extends '$requiresReview'\n ? 'requiresReview'\n : T;\n\n/**\n * Map the attribute name to the corresponding attribute name in the metadata\n * @param attrName - The attribute name to map\n * @returns The mapped attribute name\n */\nexport function mapAttributeName<T extends string>(\n attrName: T\n): MapAttributeName<T>;\nexport function mapAttributeName(attrName: string): string {\n switch (attrName) {\n case '$id':\n return 'id';\n case '$context':\n return 'context';\n case '$maxChars':\n return 'maxChars';\n case '$format':\n return 'format';\n case '$requiresReview':\n return 'requiresReview';\n default:\n return attrName;\n }\n}\n"],"mappings":";AAuBA,SAAgB,iBAAiB,UAA0B;AACzD,SAAQ,UAAR;EACE,KAAK,MACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,KAAK,UACH,QAAO;EACT,KAAK,kBACH,QAAO;EACT,QACE,QAAO"}
@@ -11,6 +11,7 @@ export type InlineMetadata = {
11
11
  id?: string;
12
12
  hash?: string;
13
13
  format?: string;
14
+ requiresReview?: boolean;
14
15
  filePaths?: string[];
15
16
  sourceCode?: Record<string, SourceCode[]>;
16
17
  contextDeriveExpr?: t.Expression;
@@ -1,7 +1,7 @@
1
1
  import "../../../../../utils/constants.js";
2
2
  import { isStaticExpression } from "../../../evaluateJsx.js";
3
3
  import { GT_ATTRIBUTES_WITH_SUGAR } from "../../constants.js";
4
- import { warnInvalidFormatSync, warnInvalidMaxCharsSync, warnNonStaticExpressionSync } from "../../../../../console/index.js";
4
+ import { warnInvalidFormatSync, warnInvalidMaxCharsSync, warnInvalidRequiresReviewSync, warnNonStaticExpressionSync } from "../../../../../console/index.js";
5
5
  import { mapAttributeName } from "../../mapAttributeName.js";
6
6
  import { isNumberLiteral } from "../../isNumberLiteral.js";
7
7
  import { containsDeriveCall } from "../derivation/containsDeriveCall.js";
@@ -63,7 +63,9 @@ function extractInlineMetadata({ options, output, config }) {
63
63
  if (mappedKey === "maxChars") {
64
64
  if (typeof result.value === "string" && (isNaN(Number(result.value)) || !isNumberLiteral(prop.value)) || !Number.isInteger(Number(result.value))) output.errors.push(warnInvalidMaxCharsSync(config.file, generate(prop).code, `${prop.loc?.start?.line}:${prop.loc?.start?.column}`));
65
65
  else if (typeof result.value === "string") metadata[mappedKey] = Math.abs(Number(result.value));
66
- } else if (mappedKey === "format") if (typeof result.value === "string" && [
66
+ } else if (mappedKey === "requiresReview") if (t.isBooleanLiteral(prop.value)) metadata[mappedKey] = prop.value.value;
67
+ else output.errors.push(warnInvalidRequiresReviewSync(config.file, generate(prop).code, `${prop.loc?.start?.line}:${prop.loc?.start?.column}`));
68
+ else if (mappedKey === "format") if (typeof result.value === "string" && [
67
69
  "ICU",
68
70
  "STRING",
69
71
  "I18NEXT"