gt 2.21.5 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/api/collectUserEditDiffs.js +1 -1
  3. package/dist/auth/callbackPage.d.ts +1 -0
  4. package/dist/auth/callbackPage.js +77 -0
  5. package/dist/auth/callbackPage.js.map +1 -0
  6. package/dist/auth/credentialStore.d.ts +15 -0
  7. package/dist/auth/credentialStore.js +107 -0
  8. package/dist/auth/credentialStore.js.map +1 -0
  9. package/dist/auth/errors.d.ts +7 -0
  10. package/dist/auth/errors.js +23 -0
  11. package/dist/auth/errors.js.map +1 -0
  12. package/dist/auth/loopback.d.ts +9 -0
  13. package/dist/auth/loopback.js +73 -0
  14. package/dist/auth/loopback.js.map +1 -0
  15. package/dist/auth/loopback.test.d.ts +1 -0
  16. package/dist/auth/oauth.d.ts +39 -0
  17. package/dist/auth/oauth.js +260 -0
  18. package/dist/auth/oauth.js.map +1 -0
  19. package/dist/bin/bin-entry.js +2 -10
  20. package/dist/bin/bin-entry.js.map +1 -1
  21. package/dist/cli/base.d.ts +4 -8
  22. package/dist/cli/base.js +119 -60
  23. package/dist/cli/base.js.map +1 -1
  24. package/dist/cli/commands/api.js +6 -2
  25. package/dist/cli/commands/api.js.map +1 -1
  26. package/dist/cli/commands/upload.js +2 -2
  27. package/dist/cli/commands/upload.js.map +1 -1
  28. package/dist/cli/commands/utils/validation.d.ts +7 -7
  29. package/dist/cli/commands/utils/validation.js +7 -20
  30. package/dist/cli/commands/utils/validation.js.map +1 -1
  31. package/dist/config/generateSettings.js +4 -9
  32. package/dist/config/generateSettings.js.map +1 -1
  33. package/dist/config/optionPresets.js +16 -6
  34. package/dist/config/optionPresets.js.map +1 -1
  35. package/dist/config/validateSettings.d.ts +4 -2
  36. package/dist/config/validateSettings.js +9 -0
  37. package/dist/config/validateSettings.js.map +1 -1
  38. package/dist/console/index.d.ts +0 -2
  39. package/dist/console/index.js +2 -4
  40. package/dist/console/index.js.map +1 -1
  41. package/dist/console/inkPrompts.js +8 -6
  42. package/dist/console/inkPrompts.js.map +1 -1
  43. package/dist/console/inkTypes.d.ts +3 -0
  44. package/dist/console/logger.d.ts +13 -0
  45. package/dist/console/logger.js +60 -20
  46. package/dist/console/logger.js.map +1 -1
  47. package/dist/console/logging.d.ts +5 -3
  48. package/dist/console/logging.js +8 -6
  49. package/dist/console/logging.js.map +1 -1
  50. package/dist/console/promptParsing.d.ts +4 -3
  51. package/dist/console/promptParsing.js +7 -7
  52. package/dist/console/promptParsing.js.map +1 -1
  53. package/dist/formats/files/aggregateFiles.js +4 -4
  54. package/dist/formats/files/aggregateFiles.js.map +1 -1
  55. package/dist/formats/files/localeContent.js +2 -2
  56. package/dist/formats/files/localeContent.js.map +1 -1
  57. package/dist/formats/files/transformFormat.d.ts +2 -0
  58. package/dist/formats/files/transformFormat.js +8 -2
  59. package/dist/formats/files/transformFormat.js.map +1 -1
  60. package/dist/formats/json/extractJson.js +3 -3
  61. package/dist/formats/json/extractJson.js.map +1 -1
  62. package/dist/formats/json/mergeJson.js +6 -6
  63. package/dist/formats/json/mergeJson.js.map +1 -1
  64. package/dist/formats/xcstrings/mergeXcstrings.js +2 -2
  65. package/dist/formats/xcstrings/mergeXcstrings.js.map +1 -1
  66. package/dist/generated/version.d.ts +1 -1
  67. package/dist/generated/version.js +1 -1
  68. package/dist/generated/version.js.map +1 -1
  69. package/dist/git/setupMergeDrivers.js +1 -1
  70. package/dist/git/setupMergeDrivers.js.map +1 -1
  71. package/dist/index.js +2 -0
  72. package/dist/index.js.map +1 -1
  73. package/dist/main.js +2 -10
  74. package/dist/main.js.map +1 -1
  75. package/dist/react/jsx/utils/jsxParsing/addGTIdentifierToSyntaxTree.js +1 -1
  76. package/dist/react/jsx/utils/jsxParsing/parseJsx.js +1 -1
  77. package/dist/react/jsx/utils/parseString.js +1 -1
  78. package/dist/react/jsx/utils/stringParsing/derivation/index.js +1 -1
  79. package/dist/setup/developmentCredentials.d.ts +8 -0
  80. package/dist/setup/developmentCredentials.js +82 -0
  81. package/dist/setup/developmentCredentials.js.map +1 -0
  82. package/dist/setup/setupViteSPA.js +1 -1
  83. package/dist/setup/setupViteSPA.js.map +1 -1
  84. package/dist/setup/userInput.d.ts +1 -0
  85. package/dist/setup/userInput.js +7 -2
  86. package/dist/setup/userInput.js.map +1 -1
  87. package/dist/translation/extractInline.js +1 -1
  88. package/dist/translation/extractInline.js.map +1 -1
  89. package/dist/types/index.d.ts +4 -1
  90. package/dist/utils/api.d.ts +146 -67
  91. package/dist/utils/api.js +5 -94
  92. package/dist/utils/api.js.map +1 -1
  93. package/dist/utils/credentials.d.ts +37 -10
  94. package/dist/utils/credentials.js +267 -58
  95. package/dist/utils/credentials.js.map +1 -1
  96. package/dist/utils/loadEnv.d.ts +2 -0
  97. package/dist/utils/loadEnv.js +24 -0
  98. package/dist/utils/loadEnv.js.map +1 -0
  99. package/dist/utils/reactPackageCompatibility.js +1 -1
  100. package/dist/utils/reactPackageCompatibility.js.map +1 -1
  101. package/dist/workflows/steps/PollJobsStep.js +40 -53
  102. package/dist/workflows/steps/PollJobsStep.js.map +1 -1
  103. package/dist/workflows/utils/syncFonts.js +2 -2
  104. package/dist/workflows/utils/syncFonts.js.map +1 -1
  105. package/package.json +8 -7
  106. package/dist/next/config/parseNextConfig.d.ts +0 -10
  107. package/dist/next/config/parseNextConfig.js +0 -34
  108. package/dist/next/config/parseNextConfig.js.map +0 -1
  109. package/dist/utils/fetch.d.ts +0 -11
  110. package/dist/utils/fetch.js +0 -24
  111. package/dist/utils/fetch.js.map +0 -1
  112. package/dist/utils/gt.d.ts +0 -2
  113. package/dist/utils/gt.js +0 -7
  114. package/dist/utils/gt.js.map +0 -1
@@ -96,27 +96,29 @@ async function promptText({ message, defaultValue, validate }) {
96
96
  }
97
97
  return result;
98
98
  }
99
- async function promptLocale({ message, defaultValue }) {
99
+ async function promptLocale({ message, defaultValue, customMapping }) {
100
100
  if (shouldUseInkPrompts()) return await runInkPrompt((prompts) => prompts.inkPromptLocale({
101
101
  message,
102
- defaultValue
102
+ defaultValue,
103
+ customMapping
103
104
  })) ?? "";
104
105
  return promptText({
105
106
  message,
106
107
  defaultValue,
107
- validate: validateLocale
108
+ validate: (value) => validateLocale(value, customMapping)
108
109
  });
109
110
  }
110
- async function promptLocaleList({ message, defaultValue, required = true }) {
111
+ async function promptLocaleList({ message, defaultValue, required = true, customMapping }) {
111
112
  if (shouldUseInkPrompts()) return await runInkPrompt((prompts) => prompts.inkPromptLocaleMulti({
112
113
  message,
113
114
  defaultValue,
114
- required
115
+ required,
116
+ customMapping
115
117
  })) ?? [];
116
118
  return promptText({
117
119
  message,
118
120
  defaultValue: defaultValue?.join(" "),
119
- validate: validateLocaleList
121
+ validate: (value) => validateLocaleList(value, customMapping)
120
122
  }).then(parseLocaleList);
121
123
  }
122
124
  async function promptGlobPatterns({ label, message, defaultValue }) {
@@ -1 +1 @@
1
- {"version":3,"file":"logging.js","names":[],"sources":["../../src/console/logging.ts"],"sourcesContent":["import {\n text,\n select,\n confirm,\n isCancel,\n cancel,\n multiselect,\n} from '@clack/prompts';\nimport type { Option } from '@clack/prompts';\nimport chalk from 'chalk';\nimport { getCLIVersion } from '../utils/packageJson.js';\nimport { logger } from './logger.js';\nimport { TEMPLATE_FILE_NAME } from '../utils/constants.js';\nimport { FileToUpload } from 'generaltranslation/types';\nimport { endTerminalSession, shouldUseInkPrompts } from './terminalSession.js';\nimport {\n parseLocaleList,\n validateLocale,\n validateLocaleList,\n} from './promptParsing.js';\nimport {\n getInlineElementsLabel,\n type InlineLibrary,\n} from '../types/libraries.js';\n\nfunction cancelPromptAndExit(message: string): never {\n endTerminalSession();\n cancel(message);\n return exitSync(0);\n}\n\nasync function loadInkPrompts() {\n return import('./inkPrompts.js');\n}\n\nasync function runInkPrompt<T>(\n prompt: (prompts: Awaited<ReturnType<typeof loadInkPrompts>>) => Promise<{\n value?: T;\n cancelled: boolean;\n }>,\n cancelMessage = 'Operation cancelled'\n) {\n const result = await prompt(await loadInkPrompts());\n if (result.cancelled) {\n return cancelPromptAndExit(cancelMessage);\n }\n return result.value;\n}\n\n/**\n * Strip ANSI escape codes from a string (e.g., chalk color codes)\n */\nexport function stripAnsi(str: string): string {\n // eslint-disable-next-line no-control-regex, no-useless-escape\n return str.replace(/\\x1B\\[[0-9;]*m/g, '');\n}\n\nexport function logErrorAndExit(message: string): never {\n logger.error(message);\n return exitSync(1);\n}\n\nexport function exitSync(code: number): never {\n // Flush logs before exit\n logger.flush();\n process.exit(code);\n}\n\n// GT specific logging\nexport function displayHeader(introString?: string) {\n // The ASCII banner is pure chatter and writes to console directly, bypassing\n // the logger, so gate it here to honor --quiet. startCommand stays outside\n // the gate: it is quiet-aware itself and must still run so the file log\n // gets its [START] marker.\n if (!logger.isQuiet()) {\n displayAsciiTitle();\n displayInitializingText();\n }\n\n if (introString) {\n logger.startCommand(introString);\n }\n}\n\nfunction displayAsciiTitle() {\n // eslint-disable-next-line no-console\n console.log(\n chalk.cyan(\n `\\n @@@@@@@@@@@@@@@@@@@@@@\n @@ @@@@@@@@@@@@ @@@@@@@\n @@ @@@ @ @\n@@ @@ @ @\n@@ @ @@@@@ @ @\n@@ @@ @@@ @ @ @\n @@ @@ @ @ @ @\n @@ @@@@@@@ @ @ @\n @@@@@@@@@ @ @`\n )\n );\n}\n\nfunction displayInitializingText() {\n const version = getCLIVersion();\n // eslint-disable-next-line no-console\n console.log(\n `\\n${chalk.bold.blue('General Translation, Inc.')}\n${chalk.dim('https://generaltranslation.com/docs')}\n${chalk.dim(`CLI Version: ${version}\\n`)}`\n );\n}\n\nexport function displayProjectId(projectId: string) {\n logger.message(\n chalk.dim(`Project ID: ${chalk.bold(projectId)}`),\n chalk.cyan('~')\n );\n}\n\nexport function displayResolvedPaths(resolvedPaths: [string, string][]) {\n const paths = resolvedPaths.map(([key, resolvedPath]) => {\n return chalk.dim(`'${chalk.white(key)}' → '${chalk.green(resolvedPath)}'`);\n });\n logger.step(`Resolved path aliases:\\n${paths.join('\\n')}`);\n}\n\nexport function displayCreatedConfigFile(configFilepath: string) {\n logger.step(`Created config file ${chalk.cyan(configFilepath)}`);\n}\n\nexport function displayUpdatedConfigFile(configFilepath: string) {\n logger.success(`Updated config file ${chalk.cyan(configFilepath)}`);\n}\n\nexport function displayUpdatedVersionsFile(versionFilepath: string) {\n logger.success(`Updated versions file ${chalk.cyan(versionFilepath)}`);\n}\n\n// Input prompts\nexport async function promptText({\n message,\n defaultValue,\n validate,\n}: {\n message: string;\n defaultValue?: string;\n validate?: (value: string) => boolean | string;\n}) {\n if (shouldUseInkPrompts()) {\n return (\n (await runInkPrompt<string>((prompts) =>\n prompts.inkPromptText({ message, defaultValue, validate })\n )) ?? ''\n );\n }\n\n const result = await text({\n message,\n placeholder: defaultValue,\n validate: validate\n ? (value) => {\n const validation = validate(value || '');\n return validation === true ? undefined : validation.toString();\n }\n : undefined,\n });\n\n if (isCancel(result)) {\n cancel('Operation cancelled');\n return exitSync(0);\n }\n\n return result;\n}\n\nexport async function promptLocale({\n message,\n defaultValue,\n}: {\n message: string;\n defaultValue?: string;\n}) {\n if (shouldUseInkPrompts()) {\n return (\n (await runInkPrompt<string>((prompts) =>\n prompts.inkPromptLocale({ message, defaultValue })\n )) ?? ''\n );\n }\n\n return promptText({\n message,\n defaultValue,\n validate: validateLocale,\n });\n}\n\nexport async function promptLocaleList({\n message,\n defaultValue,\n required = true,\n}: {\n message: string;\n defaultValue?: string[];\n required?: boolean;\n}) {\n if (shouldUseInkPrompts()) {\n return (\n (await runInkPrompt<string[]>((prompts) =>\n prompts.inkPromptLocaleMulti({\n message,\n defaultValue,\n required,\n })\n )) ?? []\n );\n }\n\n return promptText({\n message,\n defaultValue: defaultValue?.join(' '),\n validate: validateLocaleList,\n }).then(parseLocaleList);\n}\n\nexport async function promptGlobPatterns({\n label,\n message,\n defaultValue,\n}: {\n label: string;\n message: string;\n defaultValue?: string;\n}) {\n if (shouldUseInkPrompts()) {\n return (\n (await runInkPrompt<string>((prompts) =>\n prompts.inkPromptGlob({ label, message, defaultValue })\n )) ?? ''\n );\n }\n\n return promptText({\n message,\n defaultValue,\n });\n}\n\nexport async function promptSelect<T>({\n message,\n options,\n defaultValue,\n}: {\n message: string;\n options: Array<{ value: T; label: string; hint?: string }>;\n defaultValue?: T;\n}) {\n if (shouldUseInkPrompts()) {\n return (await runInkPrompt<T>((prompts) =>\n prompts.inkPromptSelect({ message, options, defaultValue })\n )) as T;\n }\n\n // Convert options to the format expected by clack\n const clackOptions = options.map((opt) => ({\n value: opt.value,\n label: opt.label,\n hint: opt.hint,\n })) as Option<T>[];\n\n const result = await select({\n message,\n options: clackOptions,\n initialValue: defaultValue,\n });\n\n if (isCancel(result)) {\n cancel('Operation cancelled');\n return exitSync(0);\n }\n\n return result as T;\n}\n\nexport async function promptMultiSelect<T extends string>({\n message,\n options,\n required = true,\n}: {\n message: string;\n options: Array<{ value: T; label: string; hint?: string }>;\n required?: boolean;\n}) {\n if (shouldUseInkPrompts()) {\n return (\n (await runInkPrompt<Array<T>>((prompts) =>\n prompts.inkPromptMultiSelect({\n message,\n options,\n required,\n })\n )) ?? []\n );\n }\n\n // Convert options to the format expected by clack\n const clackOptions = options.map((opt) => ({\n value: opt.value,\n label: opt.label,\n hint: opt.hint,\n })) as Option<T>[];\n\n const result = await multiselect({\n message,\n options: clackOptions,\n required,\n });\n\n if (isCancel(result)) {\n cancel('Operation cancelled');\n return exitSync(0);\n }\n\n return result as Array<T>;\n}\n\nexport async function promptConfirm({\n message,\n defaultValue = true,\n cancelMessage = 'Operation cancelled',\n}: {\n message: string;\n defaultValue?: boolean;\n cancelMessage?: string;\n}) {\n if (shouldUseInkPrompts()) {\n return (\n (await runInkPrompt<boolean>(\n (prompts) => prompts.inkPromptConfirm({ message, defaultValue }),\n cancelMessage\n )) ?? defaultValue\n );\n }\n\n const result = await confirm({\n message,\n initialValue: defaultValue,\n });\n\n if (isCancel(result)) {\n cancel(cancelMessage);\n return exitSync(0);\n }\n\n return result;\n}\n\n// Warning display functions\nexport function warnApiKeyInConfig(optionsFilepath: string) {\n logger.warn(\n `Found ${chalk.cyan('apiKey')} in \"${chalk.green(optionsFilepath)}\". ` +\n chalk.white(\n 'Your API key is exposed! Remove it from the file and include it as an environment variable.'\n )\n );\n}\n\nexport function warnVariableProp(\n file: string,\n attrName: string,\n value: string\n) {\n logger.warn(\n `Found ${chalk.green('<T>')} component in ${chalk.cyan(file)} with variable ${attrName}: \"${chalk.white(value)}\". ` +\n `Change \"${attrName}\" to ensure this content is translated.`\n );\n}\n\nexport function warnNoId(file: string) {\n logger.warn(\n `Found ${chalk.green('<T>')} component in ${chalk.cyan(file)} with no id. ` +\n chalk.white('Add an id to ensure the content is translated.')\n );\n}\n\nexport function warnHasUnwrappedExpression(\n file: string,\n id: string,\n unwrappedExpressions: string[]\n) {\n logger.warn(\n `${chalk.green('<T>')} with id \"${id}\" in ${chalk.cyan(file)} has children: ${unwrappedExpressions.join(', ')} that could change at runtime. ` +\n chalk.white('Use a variable component like ') +\n chalk.green('<Var>') +\n chalk.white(' (') +\n chalk.blue('https://generaltranslation.com/docs') +\n chalk.white(') to translate this properly.')\n );\n}\n\nexport function warnTemplateLiteral(file: string, value: string) {\n logger.warn(\n `Found template literal with quasis (${value}) in ${chalk.cyan(file)}. ` +\n chalk.white(\n 'Change the template literal to a string to ensure this content is translated.'\n )\n );\n}\n\nexport function warnTernary(file: string) {\n logger.warn(\n `Found ternary expression in ${chalk.cyan(file)}. ` +\n chalk.white('A Branch component may be more appropriate here.')\n );\n}\n\nexport function warnDeprecatedField(\n deprecatedField: string,\n replacement: string\n) {\n logger.warn(\n `${chalk.green(deprecatedField)} is deprecated. ` +\n chalk.white(`Use ${chalk.green(replacement)} instead.`)\n );\n}\n\n/**\n * Helper: Log all collected files\n */\nexport function logCollectedFiles(\n files: Pick<FileToUpload, 'fileName'>[],\n inlineComponents?: number,\n inlineLibrary?: InlineLibrary\n): void {\n const elementLabel = getInlineElementsLabel(inlineLibrary);\n logger.message(\n chalk.cyan('Files found in project:') +\n '\\n' +\n files\n .map((file) => {\n if (file.fileName === TEMPLATE_FILE_NAME) {\n return `- <${elementLabel}>${inlineComponents ? ` (${inlineComponents})` : ''}`;\n }\n return `- ${file.fileName}`;\n })\n .join('\\n')\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"logging.js","names":[],"sources":["../../src/console/logging.ts"],"sourcesContent":["import {\n text,\n select,\n confirm,\n isCancel,\n cancel,\n multiselect,\n} from '@clack/prompts';\nimport type { Option } from '@clack/prompts';\nimport chalk from 'chalk';\nimport { getCLIVersion } from '../utils/packageJson.js';\nimport { logger } from './logger.js';\nimport { TEMPLATE_FILE_NAME } from '../utils/constants.js';\nimport type { CustomMapping, FileToUpload } from 'generaltranslation/types';\nimport { endTerminalSession, shouldUseInkPrompts } from './terminalSession.js';\nimport {\n parseLocaleList,\n validateLocale,\n validateLocaleList,\n} from './promptParsing.js';\nimport {\n getInlineElementsLabel,\n type InlineLibrary,\n} from '../types/libraries.js';\n\nfunction cancelPromptAndExit(message: string): never {\n endTerminalSession();\n cancel(message);\n return exitSync(0);\n}\n\nasync function loadInkPrompts() {\n return import('./inkPrompts.js');\n}\n\nasync function runInkPrompt<T>(\n prompt: (prompts: Awaited<ReturnType<typeof loadInkPrompts>>) => Promise<{\n value?: T;\n cancelled: boolean;\n }>,\n cancelMessage = 'Operation cancelled'\n) {\n const result = await prompt(await loadInkPrompts());\n if (result.cancelled) {\n return cancelPromptAndExit(cancelMessage);\n }\n return result.value;\n}\n\n/**\n * Strip ANSI escape codes from a string (e.g., chalk color codes)\n */\nexport function stripAnsi(str: string): string {\n // eslint-disable-next-line no-control-regex, no-useless-escape\n return str.replace(/\\x1B\\[[0-9;]*m/g, '');\n}\n\nexport function logErrorAndExit(message: string): never {\n logger.error(message);\n return exitSync(1);\n}\n\nexport function exitSync(code: number): never {\n // Flush logs before exit\n logger.flush();\n process.exit(code);\n}\n\n// GT specific logging\nexport function displayHeader(introString?: string) {\n // The ASCII banner is pure chatter and writes to console directly, bypassing\n // the logger, so gate it here to honor --quiet. startCommand stays outside\n // the gate: it is quiet-aware itself and must still run so the file log\n // gets its [START] marker.\n if (!logger.isQuiet()) {\n displayAsciiTitle();\n displayInitializingText();\n }\n\n if (introString) {\n logger.startCommand(introString);\n }\n}\n\nfunction displayAsciiTitle() {\n // eslint-disable-next-line no-console\n console.log(\n chalk.cyan(\n `\\n @@@@@@@@@@@@@@@@@@@@@@\n @@ @@@@@@@@@@@@ @@@@@@@\n @@ @@@ @ @\n@@ @@ @ @\n@@ @ @@@@@ @ @\n@@ @@ @@@ @ @ @\n @@ @@ @ @ @ @\n @@ @@@@@@@ @ @ @\n @@@@@@@@@ @ @`\n )\n );\n}\n\nfunction displayInitializingText() {\n const version = getCLIVersion();\n // eslint-disable-next-line no-console\n console.log(\n `\\n${chalk.bold.blue('General Translation, Inc.')}\n${chalk.dim('https://generaltranslation.com/docs')}\n${chalk.dim(`CLI Version: ${version}\\n`)}`\n );\n}\n\nexport function displayProjectId(projectId: string) {\n logger.message(\n chalk.dim(`Project ID: ${chalk.bold(projectId)}`),\n chalk.cyan('~')\n );\n}\n\nexport function displayResolvedPaths(resolvedPaths: [string, string][]) {\n const paths = resolvedPaths.map(([key, resolvedPath]) => {\n return chalk.dim(`'${chalk.white(key)}' → '${chalk.green(resolvedPath)}'`);\n });\n logger.step(`Resolved path aliases:\\n${paths.join('\\n')}`);\n}\n\nexport function displayCreatedConfigFile(configFilepath: string) {\n logger.step(`Created config file ${chalk.cyan(configFilepath)}`);\n}\n\nexport function displayUpdatedConfigFile(configFilepath: string) {\n logger.success(`Updated config file ${chalk.cyan(configFilepath)}`);\n}\n\nexport function displayUpdatedVersionsFile(versionFilepath: string) {\n logger.success(`Updated versions file ${chalk.cyan(versionFilepath)}`);\n}\n\n// Input prompts\nexport async function promptText({\n message,\n defaultValue,\n validate,\n}: {\n message: string;\n defaultValue?: string;\n validate?: (value: string) => boolean | string;\n}) {\n if (shouldUseInkPrompts()) {\n return (\n (await runInkPrompt<string>((prompts) =>\n prompts.inkPromptText({ message, defaultValue, validate })\n )) ?? ''\n );\n }\n\n const result = await text({\n message,\n placeholder: defaultValue,\n validate: validate\n ? (value) => {\n const validation = validate(value || '');\n return validation === true ? undefined : validation.toString();\n }\n : undefined,\n });\n\n if (isCancel(result)) {\n cancel('Operation cancelled');\n return exitSync(0);\n }\n\n return result;\n}\n\nexport async function promptLocale({\n message,\n defaultValue,\n customMapping,\n}: {\n message: string;\n defaultValue?: string;\n customMapping?: CustomMapping;\n}) {\n if (shouldUseInkPrompts()) {\n return (\n (await runInkPrompt<string>((prompts) =>\n prompts.inkPromptLocale({ message, defaultValue, customMapping })\n )) ?? ''\n );\n }\n\n return promptText({\n message,\n defaultValue,\n validate: (value) => validateLocale(value, customMapping),\n });\n}\n\nexport async function promptLocaleList({\n message,\n defaultValue,\n required = true,\n customMapping,\n}: {\n message: string;\n defaultValue?: string[];\n required?: boolean;\n customMapping?: CustomMapping;\n}) {\n if (shouldUseInkPrompts()) {\n return (\n (await runInkPrompt<string[]>((prompts) =>\n prompts.inkPromptLocaleMulti({\n message,\n defaultValue,\n required,\n customMapping,\n })\n )) ?? []\n );\n }\n\n return promptText({\n message,\n defaultValue: defaultValue?.join(' '),\n validate: (value) => validateLocaleList(value, customMapping),\n }).then(parseLocaleList);\n}\n\nexport async function promptGlobPatterns({\n label,\n message,\n defaultValue,\n}: {\n label: string;\n message: string;\n defaultValue?: string;\n}) {\n if (shouldUseInkPrompts()) {\n return (\n (await runInkPrompt<string>((prompts) =>\n prompts.inkPromptGlob({ label, message, defaultValue })\n )) ?? ''\n );\n }\n\n return promptText({\n message,\n defaultValue,\n });\n}\n\nexport async function promptSelect<T>({\n message,\n options,\n defaultValue,\n}: {\n message: string;\n options: Array<{ value: T; label: string; hint?: string }>;\n defaultValue?: T;\n}) {\n if (shouldUseInkPrompts()) {\n return (await runInkPrompt<T>((prompts) =>\n prompts.inkPromptSelect({ message, options, defaultValue })\n )) as T;\n }\n\n // Convert options to the format expected by clack\n const clackOptions = options.map((opt) => ({\n value: opt.value,\n label: opt.label,\n hint: opt.hint,\n })) as Option<T>[];\n\n const result = await select({\n message,\n options: clackOptions,\n initialValue: defaultValue,\n });\n\n if (isCancel(result)) {\n cancel('Operation cancelled');\n return exitSync(0);\n }\n\n return result as T;\n}\n\nexport async function promptMultiSelect<T extends string>({\n message,\n options,\n required = true,\n}: {\n message: string;\n options: Array<{ value: T; label: string; hint?: string }>;\n required?: boolean;\n}) {\n if (shouldUseInkPrompts()) {\n return (\n (await runInkPrompt<Array<T>>((prompts) =>\n prompts.inkPromptMultiSelect({\n message,\n options,\n required,\n })\n )) ?? []\n );\n }\n\n // Convert options to the format expected by clack\n const clackOptions = options.map((opt) => ({\n value: opt.value,\n label: opt.label,\n hint: opt.hint,\n })) as Option<T>[];\n\n const result = await multiselect({\n message,\n options: clackOptions,\n required,\n });\n\n if (isCancel(result)) {\n cancel('Operation cancelled');\n return exitSync(0);\n }\n\n return result as Array<T>;\n}\n\nexport async function promptConfirm({\n message,\n defaultValue = true,\n cancelMessage = 'Operation cancelled',\n}: {\n message: string;\n defaultValue?: boolean;\n cancelMessage?: string;\n}) {\n if (shouldUseInkPrompts()) {\n return (\n (await runInkPrompt<boolean>(\n (prompts) => prompts.inkPromptConfirm({ message, defaultValue }),\n cancelMessage\n )) ?? defaultValue\n );\n }\n\n const result = await confirm({\n message,\n initialValue: defaultValue,\n });\n\n if (isCancel(result)) {\n cancel(cancelMessage);\n return exitSync(0);\n }\n\n return result;\n}\n\n// Warning display functions\nexport function warnApiKeyInConfig(optionsFilepath: string) {\n logger.warn(\n `Found ${chalk.cyan('apiKey')} in \"${chalk.green(optionsFilepath)}\". ` +\n chalk.white(\n 'Your API key is exposed! Remove it from the file and include it as an environment variable.'\n )\n );\n}\n\nexport function warnVariableProp(\n file: string,\n attrName: string,\n value: string\n) {\n logger.warn(\n `Found ${chalk.green('<T>')} component in ${chalk.cyan(file)} with variable ${attrName}: \"${chalk.white(value)}\". ` +\n `Change \"${attrName}\" to ensure this content is translated.`\n );\n}\n\nexport function warnNoId(file: string) {\n logger.warn(\n `Found ${chalk.green('<T>')} component in ${chalk.cyan(file)} with no id. ` +\n chalk.white('Add an id to ensure the content is translated.')\n );\n}\n\nexport function warnHasUnwrappedExpression(\n file: string,\n id: string,\n unwrappedExpressions: string[]\n) {\n logger.warn(\n `${chalk.green('<T>')} with id \"${id}\" in ${chalk.cyan(file)} has children: ${unwrappedExpressions.join(', ')} that could change at runtime. ` +\n chalk.white('Use a variable component like ') +\n chalk.green('<Var>') +\n chalk.white(' (') +\n chalk.blue('https://generaltranslation.com/docs') +\n chalk.white(') to translate this properly.')\n );\n}\n\nexport function warnTemplateLiteral(file: string, value: string) {\n logger.warn(\n `Found template literal with quasis (${value}) in ${chalk.cyan(file)}. ` +\n chalk.white(\n 'Change the template literal to a string to ensure this content is translated.'\n )\n );\n}\n\nexport function warnTernary(file: string) {\n logger.warn(\n `Found ternary expression in ${chalk.cyan(file)}. ` +\n chalk.white('A Branch component may be more appropriate here.')\n );\n}\n\nexport function warnDeprecatedField(\n deprecatedField: string,\n replacement: string\n) {\n logger.warn(\n `${chalk.green(deprecatedField)} is deprecated. ` +\n chalk.white(`Use ${chalk.green(replacement)} instead.`)\n );\n}\n\n/**\n * Helper: Log all collected files\n */\nexport function logCollectedFiles(\n files: Pick<FileToUpload, 'fileName'>[],\n inlineComponents?: number,\n inlineLibrary?: InlineLibrary\n): void {\n const elementLabel = getInlineElementsLabel(inlineLibrary);\n logger.message(\n chalk.cyan('Files found in project:') +\n '\\n' +\n files\n .map((file) => {\n if (file.fileName === TEMPLATE_FILE_NAME) {\n return `- <${elementLabel}>${inlineComponents ? ` (${inlineComponents})` : ''}`;\n }\n return `- ${file.fileName}`;\n })\n .join('\\n')\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
- export declare function validateLocale(value: string): true | "Enter a valid locale (e.g., en)";
2
- export declare function parseTypedLocale(value: string): string | null;
1
+ import type { CustomMapping } from 'generaltranslation/types';
2
+ export declare function validateLocale(value: string, customMapping?: CustomMapping): true | "Enter a valid locale (e.g., en)";
3
+ export declare function parseTypedLocale(value: string, customMapping?: CustomMapping): string | null;
3
4
  export declare function parseLocaleList(value: string): string[];
4
- export declare function validateLocaleList(value: string): true | "Enter at least one locale" | "Enter a valid locale (e.g., es fr de)";
5
+ export declare function validateLocaleList(value: string, customMapping?: CustomMapping): true | "Enter at least one locale" | "Enter a valid locale (e.g., es fr de)";
5
6
  export declare function parseGlobPatterns(value: string): string[];
@@ -1,22 +1,22 @@
1
- import { gt } from "../utils/gt.js";
1
+ import { isValidLocale } from "generaltranslation";
2
2
  //#region src/console/promptParsing.ts
3
- function validateLocale(value) {
3
+ function validateLocale(value, customMapping) {
4
4
  const locale = value.trim();
5
5
  if (!locale) return "Enter a valid locale (e.g., en)";
6
- return gt.isValidLocale(locale) || "Enter a valid locale (e.g., en)";
6
+ return isValidLocale(locale, customMapping) || "Enter a valid locale (e.g., en)";
7
7
  }
8
- function parseTypedLocale(value) {
8
+ function parseTypedLocale(value, customMapping) {
9
9
  const locale = value.trim();
10
10
  if (!locale) return null;
11
- return validateLocale(locale) === true ? locale : null;
11
+ return validateLocale(locale, customMapping) === true ? locale : null;
12
12
  }
13
13
  function parseLocaleList(value) {
14
14
  return value.trim().split(/\s+/).filter(Boolean);
15
15
  }
16
- function validateLocaleList(value) {
16
+ function validateLocaleList(value, customMapping) {
17
17
  const locales = parseLocaleList(value);
18
18
  if (locales.length === 0) return "Enter at least one locale";
19
- for (const locale of locales) if (!gt.isValidLocale(locale)) return "Enter a valid locale (e.g., es fr de)";
19
+ for (const locale of locales) if (!isValidLocale(locale, customMapping)) return "Enter a valid locale (e.g., es fr de)";
20
20
  return true;
21
21
  }
22
22
  function parseGlobPatterns(value) {
@@ -1 +1 @@
1
- {"version":3,"file":"promptParsing.js","names":[],"sources":["../../src/console/promptParsing.ts"],"sourcesContent":["import { gt } from '../utils/gt.js';\n\nexport function validateLocale(value: string) {\n const locale = value.trim();\n if (!locale) return 'Enter a valid locale (e.g., en)';\n return gt.isValidLocale(locale) || 'Enter a valid locale (e.g., en)';\n}\n\nexport function parseTypedLocale(value: string) {\n const locale = value.trim();\n if (!locale) return null;\n return validateLocale(locale) === true ? locale : null;\n}\n\nexport function parseLocaleList(value: string) {\n return value.trim().split(/\\s+/).filter(Boolean);\n}\n\nexport function validateLocaleList(value: string) {\n const locales = parseLocaleList(value);\n if (locales.length === 0) {\n return 'Enter at least one locale';\n }\n for (const locale of locales) {\n if (!gt.isValidLocale(locale)) {\n return 'Enter a valid locale (e.g., es fr de)';\n }\n }\n return true;\n}\n\nexport function parseGlobPatterns(value: string) {\n return value\n .split(/\\s+/)\n .map((path) => path.trim())\n .filter(Boolean);\n}\n"],"mappings":";;AAEA,SAAgB,eAAe,OAAe;CAC5C,MAAM,SAAS,MAAM,MAAM;AAC3B,KAAI,CAAC,OAAQ,QAAO;AACpB,QAAO,GAAG,cAAc,OAAO,IAAI;;AAGrC,SAAgB,iBAAiB,OAAe;CAC9C,MAAM,SAAS,MAAM,MAAM;AAC3B,KAAI,CAAC,OAAQ,QAAO;AACpB,QAAO,eAAe,OAAO,KAAK,OAAO,SAAS;;AAGpD,SAAgB,gBAAgB,OAAe;AAC7C,QAAO,MAAM,MAAM,CAAC,MAAM,MAAM,CAAC,OAAO,QAAQ;;AAGlD,SAAgB,mBAAmB,OAAe;CAChD,MAAM,UAAU,gBAAgB,MAAM;AACtC,KAAI,QAAQ,WAAW,EACrB,QAAO;AAET,MAAK,MAAM,UAAU,QACnB,KAAI,CAAC,GAAG,cAAc,OAAO,CAC3B,QAAO;AAGX,QAAO;;AAGT,SAAgB,kBAAkB,OAAe;AAC/C,QAAO,MACJ,MAAM,MAAM,CACZ,KAAK,SAAS,KAAK,MAAM,CAAC,CAC1B,OAAO,QAAQ"}
1
+ {"version":3,"file":"promptParsing.js","names":[],"sources":["../../src/console/promptParsing.ts"],"sourcesContent":["import { isValidLocale } from 'generaltranslation';\nimport type { CustomMapping } from 'generaltranslation/types';\n\nexport function validateLocale(value: string, customMapping?: CustomMapping) {\n const locale = value.trim();\n if (!locale) return 'Enter a valid locale (e.g., en)';\n return (\n isValidLocale(locale, customMapping) || 'Enter a valid locale (e.g., en)'\n );\n}\n\nexport function parseTypedLocale(value: string, customMapping?: CustomMapping) {\n const locale = value.trim();\n if (!locale) return null;\n return validateLocale(locale, customMapping) === true ? locale : null;\n}\n\nexport function parseLocaleList(value: string) {\n return value.trim().split(/\\s+/).filter(Boolean);\n}\n\nexport function validateLocaleList(\n value: string,\n customMapping?: CustomMapping\n) {\n const locales = parseLocaleList(value);\n if (locales.length === 0) {\n return 'Enter at least one locale';\n }\n for (const locale of locales) {\n if (!isValidLocale(locale, customMapping)) {\n return 'Enter a valid locale (e.g., es fr de)';\n }\n }\n return true;\n}\n\nexport function parseGlobPatterns(value: string) {\n return value\n .split(/\\s+/)\n .map((path) => path.trim())\n .filter(Boolean);\n}\n"],"mappings":";;AAGA,SAAgB,eAAe,OAAe,eAA+B;CAC3E,MAAM,SAAS,MAAM,MAAM;AAC3B,KAAI,CAAC,OAAQ,QAAO;AACpB,QACE,cAAc,QAAQ,cAAc,IAAI;;AAI5C,SAAgB,iBAAiB,OAAe,eAA+B;CAC7E,MAAM,SAAS,MAAM,MAAM;AAC3B,KAAI,CAAC,OAAQ,QAAO;AACpB,QAAO,eAAe,QAAQ,cAAc,KAAK,OAAO,SAAS;;AAGnE,SAAgB,gBAAgB,OAAe;AAC7C,QAAO,MAAM,MAAM,CAAC,MAAM,MAAM,CAAC,OAAO,QAAQ;;AAGlD,SAAgB,mBACd,OACA,eACA;CACA,MAAM,UAAU,gBAAgB,MAAM;AACtC,KAAI,QAAQ,WAAW,EACrB,QAAO;AAET,MAAK,MAAM,UAAU,QACnB,KAAI,CAAC,cAAc,QAAQ,cAAc,CACvC,QAAO;AAGX,QAAO;;AAGT,SAAgB,kBAAkB,OAAe;AAC/C,QAAO,MACJ,MAAM,MAAM,CACZ,KAAK,SAAS,KAAK,MAAM,CAAC,CAC1B,OAAO,QAAQ"}
@@ -1,11 +1,11 @@
1
1
  import { logger } from "../../console/logger.js";
2
2
  import { hashStringSync, hashVersionId } from "../../utils/hash.js";
3
- import { gt } from "../../utils/gt.js";
4
3
  import { logErrorAndExit } from "../../console/logging.js";
5
4
  import { getRelative, readBinaryFileBase64, readFile } from "../../fs/findFilepath.js";
6
5
  import { fileEncodingSkipReason, lottieExpressionsError, xcstringsSourceLanguageMismatchError } from "../../console/index.js";
7
6
  import { SUPPORTED_FILE_EXTENSIONS } from "./supportedFiles.js";
8
- import { getTransformFormatProperty } from "./transformFormat.js";
7
+ import { CONFIG_FILE_TYPE_TO_FILE_FORMAT, getTransformFormatProperty } from "./transformFormat.js";
8
+ import { api } from "../../utils/api.js";
9
9
  import { determineLibrary } from "../../fs/determineFramework/index.js";
10
10
  import { readFileContent } from "../../fs/fileContent.js";
11
11
  import { flattenJson } from "../json/flattenJson.js";
@@ -261,7 +261,7 @@ async function aggregateFiles(settings) {
261
261
  recordWarning("skipped_file", relativePath, reason);
262
262
  return null;
263
263
  }
264
- if (gt.resolveCanonicalLocale(catalog.sourceLanguage) !== gt.resolveCanonicalLocale(settings.defaultLocale)) {
264
+ if (api.resolveCanonicalLocale(catalog.sourceLanguage) !== api.resolveCanonicalLocale(settings.defaultLocale)) {
265
265
  sourceLanguageMismatches.push({
266
266
  file: relativePath,
267
267
  sourceLanguage: catalog.sourceLanguage
@@ -297,7 +297,7 @@ async function aggregateFiles(settings) {
297
297
  return {
298
298
  content: processed,
299
299
  fileName: relativePath,
300
- fileFormat: fileType.toUpperCase(),
300
+ fileFormat: CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType],
301
301
  ...getTransformFormatProperty(settings, fileType),
302
302
  fileId: hashStringSync(relativePath),
303
303
  versionId: hashVersionId(processed, requiresReviewPaths.has(filePath)),
@@ -1 +1 @@
1
- {"version":3,"file":"aggregateFiles.js","names":[],"sources":["../../../src/formats/files/aggregateFiles.ts"],"sourcesContent":["import { logger } from '../../console/logger.js';\nimport { logErrorAndExit } from '../../console/logging.js';\nimport {\n fileEncodingSkipReason,\n lottieExpressionsError,\n xcstringsSourceLanguageMismatchError,\n} from '../../console/index.js';\nimport { recordWarning } from '../../state/translateWarnings.js';\nimport { lottieHasExpressions } from './detectLottieExpressions.js';\nimport {\n getRelative,\n readFile,\n readBinaryFileBase64,\n} from '../../fs/findFilepath.js';\nimport { readFileContent } from '../../fs/fileContent.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 parseXcstringsCatalog,\n serializeXcstringsSlice,\n sliceSourceCatalog,\n type XcstringsCatalog,\n} from '../xcstrings/parseXcstrings.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 { gt } from '../../utils/gt.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}\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 // Process Lottie files (binary zip bundles). Content is read as raw bytes and\n // carried base64-encoded end-to-end; the downloaded translation is written\n // back as bytes. No text parsing/merge.\n if (filePaths.lottie) {\n // Lottie files carrying After Effects expressions (executable JS) are\n // rejected outright — collect every offender, then fail once with all of\n // them named rather than aborting on the first.\n const expressionOffenders: string[] = [];\n const lottieFiles = filePaths.lottie\n .map((filePath) => {\n const content = readBinaryFileBase64(filePath);\n const relativePath = getRelative(filePath);\n if (!content) {\n logger.warn(`Skipping ${relativePath}: file is empty or unreadable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'File is empty or unreadable'\n );\n return null;\n }\n if (lottieHasExpressions(content)) {\n expressionOffenders.push(relativePath);\n return null;\n }\n return {\n content,\n fileName: relativePath,\n fileFormat: 'LOTTIE' as const,\n ...getTransformFormatProperty(settings, 'lottie'),\n fileId: hashStringSync(relativePath),\n versionId: hashStringSync(content),\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => file !== null);\n if (expressionOffenders.length > 0) {\n logErrorAndExit(lottieExpressionsError(expressionOffenders));\n }\n files.push(...lottieFiles);\n }\n\n // These formats are uploaded verbatim. Their backslash escapes and format\n // specifiers must survive byte-for-byte, so they skip the generic\n // markdown-oriented preprocessing below.\n for (const [fileType, fileFormat] of [\n ['dotStrings', 'DOT_STRINGS'],\n ['dotStringsdict', 'DOT_STRINGSDICT'],\n ['androidStrings', 'ANDROID_STRINGS'],\n ] as const) {\n if (!filePaths[fileType]) continue;\n const verbatimFiles = filePaths[fileType]\n .map((filePath) => {\n const relativePath = getRelative(filePath);\n // Reading is the one place an encoding is resolved; from here on the\n // content is a UTF-8 string like every other format's.\n let content: string;\n try {\n content = readFileContent(filePath, fileFormat);\n } catch (error) {\n // One unreadable file should not abort the whole run: skip it and\n // report it in the summary\n const reason = fileEncodingSkipReason(error);\n logger.warn(`Skipping ${relativePath}: ${reason}`);\n recordWarning('skipped_file', relativePath, reason);\n return null;\n }\n return {\n content,\n fileName: relativePath,\n fileFormat,\n ...getTransformFormatProperty(settings, fileType),\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(content, requiresReviewPaths.has(filePath)),\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file) return false;\n if (!file.content.trim()) {\n logger.warn(`Skipping ${file.fileName}: File is empty`);\n recordWarning('skipped_file', file.fileName, 'File is empty');\n return false;\n }\n return true;\n });\n files.push(...verbatimFiles.filter((file) => file !== null));\n }\n\n // Process Apple .xcstrings catalogs. One catalog holds every locale; only the\n // source-language slice is uploaded as the source document. The slice is\n // hashed into versionId, so an unchanged catalog re-slices byte-identically\n // and does not re-upload.\n if (filePaths.xcstrings) {\n const sourceLanguageMismatches: { file: string; sourceLanguage: string }[] =\n [];\n const xcstringsFiles = filePaths.xcstrings\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n let catalog: XcstringsCatalog;\n try {\n catalog = parseXcstringsCatalog(content);\n } catch (error) {\n const reason =\n error instanceof Error\n ? error.message\n : 'xcstrings file is not parsable';\n logger.warn(`Skipping ${relativePath}: ${reason}`);\n recordWarning('skipped_file', relativePath, reason);\n return null;\n }\n // Slicing follows the catalog's own sourceLanguage while the upload is\n // labeled settings.defaultLocale; a mismatch would upload mislabeled\n // source content, so it is a configuration error that stops the run\n // rather than a skipped file. The configured locale may be a custom\n // alias, so canonical forms are compared.\n if (\n gt.resolveCanonicalLocale(catalog.sourceLanguage) !==\n gt.resolveCanonicalLocale(settings.defaultLocale)\n ) {\n sourceLanguageMismatches.push({\n file: relativePath,\n sourceLanguage: catalog.sourceLanguage,\n });\n return null;\n }\n const sourceSlice = serializeXcstringsSlice(\n sliceSourceCatalog(catalog)\n );\n\n return {\n content: sourceSlice,\n fileName: relativePath,\n fileFormat: 'XCSTRINGS' as const,\n ...getTransformFormatProperty(settings, 'xcstrings'),\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n sourceSlice,\n requiresReviewPaths.has(filePath)\n ),\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => file !== null);\n if (sourceLanguageMismatches.length > 0) {\n logErrorAndExit(\n xcstringsSourceLanguageMismatchError(\n sourceLanguageMismatches,\n settings.defaultLocale\n )\n );\n }\n files.push(...xcstringsFiles);\n }\n\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n if (\n fileType === 'json' ||\n fileType === 'yaml' ||\n fileType === 'twilioContentJson' ||\n fileType === 'lottie' ||\n fileType === 'dotStrings' ||\n fileType === 'dotStringsdict' ||\n fileType === 'androidStrings' ||\n fileType === 'xcstrings'\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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"}
1
+ {"version":3,"file":"aggregateFiles.js","names":[],"sources":["../../../src/formats/files/aggregateFiles.ts"],"sourcesContent":["import { logger } from '../../console/logger.js';\nimport { logErrorAndExit } from '../../console/logging.js';\nimport {\n fileEncodingSkipReason,\n lottieExpressionsError,\n xcstringsSourceLanguageMismatchError,\n} from '../../console/index.js';\nimport { recordWarning } from '../../state/translateWarnings.js';\nimport { lottieHasExpressions } from './detectLottieExpressions.js';\nimport {\n getRelative,\n readFile,\n readBinaryFileBase64,\n} from '../../fs/findFilepath.js';\nimport { readFileContent } from '../../fs/fileContent.js';\nimport { Settings } from '../../types/index.js';\nimport type { DataFormat, FileToUpload } from '../../types/data.js';\nimport { SUPPORTED_FILE_EXTENSIONS } from './supportedFiles.js';\nimport { parseJson } from '../json/parseJson.js';\nimport {\n parseXcstringsCatalog,\n serializeXcstringsSlice,\n sliceSourceCatalog,\n type XcstringsCatalog,\n} from '../xcstrings/parseXcstrings.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 { api } from '../../utils/api.js';\nimport { preprocessContent } from './preprocessContent.js';\nimport {\n parseKeyedMetadata,\n type KeyedMetadata,\n} from '../parseKeyedMetadata.js';\nimport { buildPublishMap } from '../../utils/resolvePublish.js';\nimport {\n CONFIG_FILE_TYPE_TO_FILE_FORMAT,\n getTransformFormatProperty,\n} 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}\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 // Process Lottie files (binary zip bundles). Content is read as raw bytes and\n // carried base64-encoded end-to-end; the downloaded translation is written\n // back as bytes. No text parsing/merge.\n if (filePaths.lottie) {\n // Lottie files carrying After Effects expressions (executable JS) are\n // rejected outright — collect every offender, then fail once with all of\n // them named rather than aborting on the first.\n const expressionOffenders: string[] = [];\n const lottieFiles = filePaths.lottie\n .map((filePath) => {\n const content = readBinaryFileBase64(filePath);\n const relativePath = getRelative(filePath);\n if (!content) {\n logger.warn(`Skipping ${relativePath}: file is empty or unreadable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'File is empty or unreadable'\n );\n return null;\n }\n if (lottieHasExpressions(content)) {\n expressionOffenders.push(relativePath);\n return null;\n }\n return {\n content,\n fileName: relativePath,\n fileFormat: 'LOTTIE' as const,\n ...getTransformFormatProperty(settings, 'lottie'),\n fileId: hashStringSync(relativePath),\n versionId: hashStringSync(content),\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => file !== null);\n if (expressionOffenders.length > 0) {\n logErrorAndExit(lottieExpressionsError(expressionOffenders));\n }\n files.push(...lottieFiles);\n }\n\n // These formats are uploaded verbatim. Their backslash escapes and format\n // specifiers must survive byte-for-byte, so they skip the generic\n // markdown-oriented preprocessing below.\n for (const [fileType, fileFormat] of [\n ['dotStrings', 'DOT_STRINGS'],\n ['dotStringsdict', 'DOT_STRINGSDICT'],\n ['androidStrings', 'ANDROID_STRINGS'],\n ] as const) {\n if (!filePaths[fileType]) continue;\n const verbatimFiles = filePaths[fileType]\n .map((filePath) => {\n const relativePath = getRelative(filePath);\n // Reading is the one place an encoding is resolved; from here on the\n // content is a UTF-8 string like every other format's.\n let content: string;\n try {\n content = readFileContent(filePath, fileFormat);\n } catch (error) {\n // One unreadable file should not abort the whole run: skip it and\n // report it in the summary\n const reason = fileEncodingSkipReason(error);\n logger.warn(`Skipping ${relativePath}: ${reason}`);\n recordWarning('skipped_file', relativePath, reason);\n return null;\n }\n return {\n content,\n fileName: relativePath,\n fileFormat,\n ...getTransformFormatProperty(settings, fileType),\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(content, requiresReviewPaths.has(filePath)),\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file) return false;\n if (!file.content.trim()) {\n logger.warn(`Skipping ${file.fileName}: File is empty`);\n recordWarning('skipped_file', file.fileName, 'File is empty');\n return false;\n }\n return true;\n });\n files.push(...verbatimFiles.filter((file) => file !== null));\n }\n\n // Process Apple .xcstrings catalogs. One catalog holds every locale; only the\n // source-language slice is uploaded as the source document. The slice is\n // hashed into versionId, so an unchanged catalog re-slices byte-identically\n // and does not re-upload.\n if (filePaths.xcstrings) {\n const sourceLanguageMismatches: { file: string; sourceLanguage: string }[] =\n [];\n const xcstringsFiles = filePaths.xcstrings\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n let catalog: XcstringsCatalog;\n try {\n catalog = parseXcstringsCatalog(content);\n } catch (error) {\n const reason =\n error instanceof Error\n ? error.message\n : 'xcstrings file is not parsable';\n logger.warn(`Skipping ${relativePath}: ${reason}`);\n recordWarning('skipped_file', relativePath, reason);\n return null;\n }\n // Slicing follows the catalog's own sourceLanguage while the upload is\n // labeled settings.defaultLocale; a mismatch would upload mislabeled\n // source content, so it is a configuration error that stops the run\n // rather than a skipped file. The configured locale may be a custom\n // alias, so canonical forms are compared.\n if (\n api.resolveCanonicalLocale(catalog.sourceLanguage) !==\n api.resolveCanonicalLocale(settings.defaultLocale)\n ) {\n sourceLanguageMismatches.push({\n file: relativePath,\n sourceLanguage: catalog.sourceLanguage,\n });\n return null;\n }\n const sourceSlice = serializeXcstringsSlice(\n sliceSourceCatalog(catalog)\n );\n\n return {\n content: sourceSlice,\n fileName: relativePath,\n fileFormat: 'XCSTRINGS' as const,\n ...getTransformFormatProperty(settings, 'xcstrings'),\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n sourceSlice,\n requiresReviewPaths.has(filePath)\n ),\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => file !== null);\n if (sourceLanguageMismatches.length > 0) {\n logErrorAndExit(\n xcstringsSourceLanguageMismatchError(\n sourceLanguageMismatches,\n settings.defaultLocale\n )\n );\n }\n files.push(...xcstringsFiles);\n }\n\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n if (\n fileType === 'json' ||\n fileType === 'yaml' ||\n fileType === 'twilioContentJson' ||\n fileType === 'lottie' ||\n fileType === 'dotStrings' ||\n fileType === 'dotStringsdict' ||\n fileType === 'androidStrings' ||\n fileType === 'xcstrings'\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: CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType],\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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"}
@@ -1,4 +1,4 @@
1
- import { gt } from "../../utils/gt.js";
1
+ import { api } from "../../utils/api.js";
2
2
  import { parseXcstringsCatalog, serializeXcstringsSlice, sliceTranslationCatalog } from "../xcstrings/parseXcstrings.js";
3
3
  //#region src/formats/files/localeContent.ts
4
4
  /**
@@ -12,7 +12,7 @@ import { parseXcstringsCatalog, serializeXcstringsSlice, sliceTranslationCatalog
12
12
  */
13
13
  function localeContent(content, fileFormat, locale) {
14
14
  if (fileFormat !== "XCSTRINGS") return content;
15
- const slice = sliceTranslationCatalog(parseXcstringsCatalog(content), gt.resolveCanonicalLocale(locale));
15
+ const slice = sliceTranslationCatalog(parseXcstringsCatalog(content), api.resolveCanonicalLocale(locale));
16
16
  return slice === void 0 ? void 0 : serializeXcstringsSlice(slice);
17
17
  }
18
18
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"localeContent.js","names":[],"sources":["../../../src/formats/files/localeContent.ts"],"sourcesContent":["import type { FileFormat } from 'generaltranslation/types';\nimport { gt } from '../../utils/gt.js';\nimport {\n parseXcstringsCatalog,\n serializeXcstringsSlice,\n sliceTranslationCatalog,\n} from '../xcstrings/parseXcstrings.js';\n\n/**\n * The content one locale of a translation file stands for on the platform.\n * A file that holds every locale (an .xcstrings catalog) stands for its\n * single-locale slice — what upload sends and download merges back — so the\n * on-disk file and a download compare slice to slice. Every other format\n * stands for the whole file. The catalog is keyed by canonical tags, so a\n * configured alias is resolved first. Undefined when the content carries\n * nothing for the locale. Throws on invalid content.\n */\nexport function localeContent(\n content: string,\n fileFormat: FileFormat,\n locale: string\n): string | undefined {\n if (fileFormat !== 'XCSTRINGS') return content;\n const slice = sliceTranslationCatalog(\n parseXcstringsCatalog(content),\n gt.resolveCanonicalLocale(locale)\n );\n return slice === undefined ? undefined : serializeXcstringsSlice(slice);\n}\n\n/**\n * The content a file stands for when it carries nothing for a locale: for a\n * file that holds every locale, the catalog with no entries and its other\n * fields intact; for every other format, empty content. Throws on invalid\n * content.\n */\nexport function emptyLocaleContent(\n content: string,\n fileFormat: FileFormat\n): string {\n if (fileFormat !== 'XCSTRINGS') return '';\n return serializeXcstringsSlice({\n ...parseXcstringsCatalog(content),\n strings: {},\n });\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,SAAgB,cACd,SACA,YACA,QACoB;AACpB,KAAI,eAAe,YAAa,QAAO;CACvC,MAAM,QAAQ,wBACZ,sBAAsB,QAAQ,EAC9B,GAAG,uBAAuB,OAAO,CAClC;AACD,QAAO,UAAU,KAAA,IAAY,KAAA,IAAY,wBAAwB,MAAM;;;;;;;;AASzE,SAAgB,mBACd,SACA,YACQ;AACR,KAAI,eAAe,YAAa,QAAO;AACvC,QAAO,wBAAwB;EAC7B,GAAG,sBAAsB,QAAQ;EACjC,SAAS,EAAE;EACZ,CAAC"}
1
+ {"version":3,"file":"localeContent.js","names":[],"sources":["../../../src/formats/files/localeContent.ts"],"sourcesContent":["import type { FileFormat } from 'generaltranslation/types';\nimport { api } from '../../utils/api.js';\nimport {\n parseXcstringsCatalog,\n serializeXcstringsSlice,\n sliceTranslationCatalog,\n} from '../xcstrings/parseXcstrings.js';\n\n/**\n * The content one locale of a translation file stands for on the platform.\n * A file that holds every locale (an .xcstrings catalog) stands for its\n * single-locale slice — what upload sends and download merges back — so the\n * on-disk file and a download compare slice to slice. Every other format\n * stands for the whole file. The catalog is keyed by canonical tags, so a\n * configured alias is resolved first. Undefined when the content carries\n * nothing for the locale. Throws on invalid content.\n */\nexport function localeContent(\n content: string,\n fileFormat: FileFormat,\n locale: string\n): string | undefined {\n if (fileFormat !== 'XCSTRINGS') return content;\n const slice = sliceTranslationCatalog(\n parseXcstringsCatalog(content),\n api.resolveCanonicalLocale(locale)\n );\n return slice === undefined ? undefined : serializeXcstringsSlice(slice);\n}\n\n/**\n * The content a file stands for when it carries nothing for a locale: for a\n * file that holds every locale, the catalog with no entries and its other\n * fields intact; for every other format, empty content. Throws on invalid\n * content.\n */\nexport function emptyLocaleContent(\n content: string,\n fileFormat: FileFormat\n): string {\n if (fileFormat !== 'XCSTRINGS') return '';\n return serializeXcstringsSlice({\n ...parseXcstringsCatalog(content),\n strings: {},\n });\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,SAAgB,cACd,SACA,YACA,QACoB;AACpB,KAAI,eAAe,YAAa,QAAO;CACvC,MAAM,QAAQ,wBACZ,sBAAsB,QAAQ,EAC9B,IAAI,uBAAuB,OAAO,CACnC;AACD,QAAO,UAAU,KAAA,IAAY,KAAA,IAAY,wBAAwB,MAAM;;;;;;;;AASzE,SAAgB,mBACd,SACA,YACQ;AACR,KAAI,eAAe,YAAa,QAAO;AACvC,QAAO,wBAAwB;EAC7B,GAAG,sBAAsB,QAAQ;EACjC,SAAS,EAAE;EACZ,CAAC"}
@@ -1,5 +1,7 @@
1
1
  import type { FileFormat } from '../../types/data.js';
2
2
  import type { FilesOptions, Settings, SupportedFileExtension } from '../../types/index.js';
3
+ /** Narrows a user-supplied string to a FileFormat the API contract knows. */
4
+ export declare function isFileFormat(value: string): value is FileFormat;
3
5
  /**
4
6
  * Maps CLI config file keys to API file format enum values.
5
7
  */
@@ -1,5 +1,11 @@
1
1
  import { isSupportedFileFormatTransform } from "generaltranslation/internal";
2
+ import { FileFormat } from "generaltranslation/api";
2
3
  //#region src/formats/files/transformFormat.ts
4
+ const fileFormatValues = new Set(Object.values(FileFormat));
5
+ /** Narrows a user-supplied string to a FileFormat the API contract knows. */
6
+ function isFileFormat(value) {
7
+ return fileFormatValues.has(value);
8
+ }
3
9
  /**
4
10
  * Maps CLI config file keys to API file format enum values.
5
11
  */
@@ -88,7 +94,7 @@ function resolveTransformationFormat(fileType, transformationFormat) {
88
94
  if (!transformationFormat) return void 0;
89
95
  const normalized = transformationFormat.toUpperCase();
90
96
  const fileFormat = CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType];
91
- if (!isSupportedFileFormatTransform(fileFormat, normalized)) throw new Error(`Unsupported file format transform: ${fileFormat} -> ${normalized} in files.${fileType}. "${normalized}" is not a valid transformationFormat for ${fileFormat} source files.`);
97
+ if (!isFileFormat(normalized) || !isSupportedFileFormatTransform(fileFormat, normalized)) throw new Error(`Unsupported file format transform: ${fileFormat} -> ${normalized} in files.${fileType}. "${normalized}" is not a valid transformationFormat for ${fileFormat} source files.`);
92
98
  return normalized;
93
99
  }
94
100
  /**
@@ -125,6 +131,6 @@ function hasNonIdentityFileFormatTransformForType(settings, fileType) {
125
131
  return !!(transformFormat && CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType] !== transformFormat);
126
132
  }
127
133
  //#endregion
128
- export { CONFIG_FILE_TYPE_TO_FILE_FORMAT, FILE_FORMAT_TO_CONFIG_FILE_TYPE, getFileExtensionForFormat, getTransformFormatProperty, hasNonIdentityFileFormatTransform, hasNonIdentityFileFormatTransformForType, normalizeFilesOptions, replaceFileExtensionForFormat, resolveTransformationFormat };
134
+ export { CONFIG_FILE_TYPE_TO_FILE_FORMAT, FILE_FORMAT_TO_CONFIG_FILE_TYPE, getFileExtensionForFormat, getTransformFormatProperty, hasNonIdentityFileFormatTransform, hasNonIdentityFileFormatTransformForType, isFileFormat, normalizeFilesOptions, replaceFileExtensionForFormat, resolveTransformationFormat };
129
135
 
130
136
  //# sourceMappingURL=transformFormat.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"transformFormat.js","names":[],"sources":["../../../src/formats/files/transformFormat.ts"],"sourcesContent":["import type { FileFormat } from '../../types/data.js';\nimport type {\n FilesOptions,\n Settings,\n SupportedFileExtension,\n} from '../../types/index.js';\nimport { isSupportedFileFormatTransform } from 'generaltranslation/internal';\n\n/**\n * Maps CLI config file keys to API file format enum values.\n */\nexport const CONFIG_FILE_TYPE_TO_FILE_FORMAT = {\n json: 'JSON',\n pot: 'POT',\n mdx: 'MDX',\n md: 'MD',\n ts: 'TS',\n js: 'JS',\n yaml: 'YAML',\n html: 'HTML',\n txt: 'TXT',\n twilioContentJson: 'TWILIO_CONTENT_JSON',\n lottie: 'LOTTIE',\n dotStrings: 'DOT_STRINGS',\n dotStringsdict: 'DOT_STRINGSDICT',\n androidStrings: 'ANDROID_STRINGS',\n xcstrings: 'XCSTRINGS',\n} as const satisfies Record<SupportedFileExtension, FileFormat>;\n\n/**\n * Maps uppercase config aliases to the CLI's canonical lowercase file keys.\n */\nexport const FILE_FORMAT_TO_CONFIG_FILE_TYPE = {\n JSON: 'json',\n POT: 'pot',\n MDX: 'mdx',\n MD: 'md',\n TS: 'ts',\n JS: 'js',\n YAML: 'yaml',\n HTML: 'html',\n TXT: 'txt',\n TWILIO_CONTENT_JSON: 'twilioContentJson',\n LOTTIE: 'lottie',\n DOT_STRINGS: 'dotStrings',\n DOT_STRINGSDICT: 'dotStringsdict',\n ANDROID_STRINGS: 'androidStrings',\n XCSTRINGS: 'xcstrings',\n} as const satisfies Partial<Record<FileFormat, SupportedFileExtension>>;\n\n/**\n * Maps API file format enum values to the extension the CLI should write.\n */\nconst FILE_FORMAT_EXTENSIONS = {\n GTJSON: 'json',\n JSON: 'json',\n PO: 'po',\n POT: 'pot',\n YAML: 'yaml',\n MDX: 'mdx',\n MD: 'md',\n TS: 'ts',\n JS: 'js',\n HTML: 'html',\n TXT: 'txt',\n TWILIO_CONTENT_JSON: 'json',\n LOTTIE: 'lottie',\n SVG: 'svg',\n DOT_STRINGS: 'strings',\n DOT_STRINGSDICT: 'stringsdict',\n ANDROID_STRINGS: 'xml',\n XCSTRINGS: 'xcstrings',\n} as const satisfies Record<FileFormat, string>;\n\n/**\n * Converts uppercase file format config keys into the CLI's canonical lowercase keys.\n *\n * This lets users write either `files.POT` or `files.pot` while keeping the\n * rest of the CLI on its existing lowercase file-type convention.\n */\nexport function normalizeFilesOptions(files: FilesOptions): FilesOptions {\n const normalized = { ...files } as FilesOptions & Record<string, unknown>;\n\n for (const [fileFormat, fileType] of Object.entries(\n FILE_FORMAT_TO_CONFIG_FILE_TYPE\n ) as [string, SupportedFileExtension][]) {\n const uppercaseConfig = normalized[fileFormat] as\n | FilesOptions[SupportedFileExtension]\n | undefined;\n if (!normalized[fileType] && uppercaseConfig) {\n normalized[fileType] = uppercaseConfig;\n }\n delete normalized[fileFormat];\n }\n\n return normalized;\n}\n\n/**\n * Validates and resolves a configured output format for a source file type.\n *\n * Throws when the requested source -> output format is not supported by\n * `generaltranslation/internal`.\n */\nexport function resolveTransformationFormat(\n fileType: SupportedFileExtension,\n transformationFormat: string | undefined\n): FileFormat | undefined {\n if (!transformationFormat) return undefined;\n\n // Normalize to uppercase to match the FileFormat enum (e.g. \"po\" -> \"PO\")\n const normalized = transformationFormat.toUpperCase() as FileFormat;\n const fileFormat = CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType];\n\n if (!isSupportedFileFormatTransform(fileFormat, normalized)) {\n throw new Error(\n `Unsupported file format transform: ${fileFormat} -> ${normalized} in files.${fileType}. ` +\n `\"${normalized}\" is not a valid transformationFormat for ${fileFormat} source files.`\n );\n }\n\n return normalized;\n}\n\n/**\n * Returns the API upload/enqueue property for a file type when one is configured.\n */\nexport function getTransformFormatProperty(\n settings: Settings,\n fileType: SupportedFileExtension\n): { transformFormat?: FileFormat } {\n const transformFormat = settings.files?.transformFormats?.[fileType];\n return transformFormat ? { transformFormat } : {};\n}\n\n/**\n * Returns the preferred file extension for a translated file format.\n */\nexport function getFileExtensionForFormat(format: FileFormat): string {\n return FILE_FORMAT_EXTENSIONS[format];\n}\n\n/**\n * Rewrites a path's extension to match the translated file format.\n */\nexport function replaceFileExtensionForFormat(\n filePath: string,\n format: FileFormat\n): string {\n const extension = getFileExtensionForFormat(format);\n return /\\.[^/.]+$/.test(filePath)\n ? filePath.replace(/\\.[^/.]+$/, `.${extension}`)\n : `${filePath}.${extension}`;\n}\n\n/**\n * Detects whether any configured format transform changes the output file type.\n */\nexport function hasNonIdentityFileFormatTransform(settings: Settings): boolean {\n return Object.entries(settings.files?.transformFormats || {}).some(\n ([fileType, transformFormat]) =>\n transformFormat &&\n CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType as SupportedFileExtension] !==\n transformFormat\n );\n}\n\n/**\n * Returns true when the configured transform for a file type changes its format.\n */\nexport function hasNonIdentityFileFormatTransformForType(\n settings: Settings,\n fileType: SupportedFileExtension\n): boolean {\n const transformFormat = settings.files?.transformFormats?.[fileType];\n return !!(\n transformFormat &&\n CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType] !== transformFormat\n );\n}\n"],"mappings":";;;;;AAWA,MAAa,kCAAkC;CAC7C,MAAM;CACN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,mBAAmB;CACnB,QAAQ;CACR,YAAY;CACZ,gBAAgB;CAChB,gBAAgB;CAChB,WAAW;CACZ;;;;AAKD,MAAa,kCAAkC;CAC7C,MAAM;CACN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,qBAAqB;CACrB,QAAQ;CACR,aAAa;CACb,iBAAiB;CACjB,iBAAiB;CACjB,WAAW;CACZ;;;;AAKD,MAAM,yBAAyB;CAC7B,QAAQ;CACR,MAAM;CACN,IAAI;CACJ,KAAK;CACL,MAAM;CACN,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,KAAK;CACL,qBAAqB;CACrB,QAAQ;CACR,KAAK;CACL,aAAa;CACb,iBAAiB;CACjB,iBAAiB;CACjB,WAAW;CACZ;;;;;;;AAQD,SAAgB,sBAAsB,OAAmC;CACvE,MAAM,aAAa,EAAE,GAAG,OAAO;AAE/B,MAAK,MAAM,CAAC,YAAY,aAAa,OAAO,QAC1C,gCACD,EAAwC;EACvC,MAAM,kBAAkB,WAAW;AAGnC,MAAI,CAAC,WAAW,aAAa,gBAC3B,YAAW,YAAY;AAEzB,SAAO,WAAW;;AAGpB,QAAO;;;;;;;;AAST,SAAgB,4BACd,UACA,sBACwB;AACxB,KAAI,CAAC,qBAAsB,QAAO,KAAA;CAGlC,MAAM,aAAa,qBAAqB,aAAa;CACrD,MAAM,aAAa,gCAAgC;AAEnD,KAAI,CAAC,+BAA+B,YAAY,WAAW,CACzD,OAAM,IAAI,MACR,sCAAsC,WAAW,MAAM,WAAW,YAAY,SAAS,KACjF,WAAW,4CAA4C,WAAW,gBACzE;AAGH,QAAO;;;;;AAMT,SAAgB,2BACd,UACA,UACkC;CAClC,MAAM,kBAAkB,SAAS,OAAO,mBAAmB;AAC3D,QAAO,kBAAkB,EAAE,iBAAiB,GAAG,EAAE;;;;;AAMnD,SAAgB,0BAA0B,QAA4B;AACpE,QAAO,uBAAuB;;;;;AAMhC,SAAgB,8BACd,UACA,QACQ;CACR,MAAM,YAAY,0BAA0B,OAAO;AACnD,QAAO,YAAY,KAAK,SAAS,GAC7B,SAAS,QAAQ,aAAa,IAAI,YAAY,GAC9C,GAAG,SAAS,GAAG;;;;;AAMrB,SAAgB,kCAAkC,UAA6B;AAC7E,QAAO,OAAO,QAAQ,SAAS,OAAO,oBAAoB,EAAE,CAAC,CAAC,MAC3D,CAAC,UAAU,qBACV,mBACA,gCAAgC,cAC9B,gBACL;;;;;AAMH,SAAgB,yCACd,UACA,UACS;CACT,MAAM,kBAAkB,SAAS,OAAO,mBAAmB;AAC3D,QAAO,CAAC,EACN,mBACA,gCAAgC,cAAc"}
1
+ {"version":3,"file":"transformFormat.js","names":["FILE_FORMATS"],"sources":["../../../src/formats/files/transformFormat.ts"],"sourcesContent":["import type { FileFormat } from '../../types/data.js';\nimport type {\n FilesOptions,\n Settings,\n SupportedFileExtension,\n} from '../../types/index.js';\nimport { FileFormat as FILE_FORMATS } from 'generaltranslation/api';\nimport { isSupportedFileFormatTransform } from 'generaltranslation/internal';\n\nconst fileFormatValues = new Set<string>(Object.values(FILE_FORMATS));\n\n/** Narrows a user-supplied string to a FileFormat the API contract knows. */\nexport function isFileFormat(value: string): value is FileFormat {\n return fileFormatValues.has(value);\n}\n\n/**\n * Maps CLI config file keys to API file format enum values.\n */\nexport const CONFIG_FILE_TYPE_TO_FILE_FORMAT = {\n json: 'JSON',\n pot: 'POT',\n mdx: 'MDX',\n md: 'MD',\n ts: 'TS',\n js: 'JS',\n yaml: 'YAML',\n html: 'HTML',\n txt: 'TXT',\n twilioContentJson: 'TWILIO_CONTENT_JSON',\n lottie: 'LOTTIE',\n dotStrings: 'DOT_STRINGS',\n dotStringsdict: 'DOT_STRINGSDICT',\n androidStrings: 'ANDROID_STRINGS',\n xcstrings: 'XCSTRINGS',\n} as const satisfies Record<SupportedFileExtension, FileFormat>;\n\n/**\n * Maps uppercase config aliases to the CLI's canonical lowercase file keys.\n */\nexport const FILE_FORMAT_TO_CONFIG_FILE_TYPE = {\n JSON: 'json',\n POT: 'pot',\n MDX: 'mdx',\n MD: 'md',\n TS: 'ts',\n JS: 'js',\n YAML: 'yaml',\n HTML: 'html',\n TXT: 'txt',\n TWILIO_CONTENT_JSON: 'twilioContentJson',\n LOTTIE: 'lottie',\n DOT_STRINGS: 'dotStrings',\n DOT_STRINGSDICT: 'dotStringsdict',\n ANDROID_STRINGS: 'androidStrings',\n XCSTRINGS: 'xcstrings',\n} as const satisfies Partial<Record<FileFormat, SupportedFileExtension>>;\n\n/**\n * Maps API file format enum values to the extension the CLI should write.\n */\nconst FILE_FORMAT_EXTENSIONS = {\n GTJSON: 'json',\n JSON: 'json',\n PO: 'po',\n POT: 'pot',\n YAML: 'yaml',\n MDX: 'mdx',\n MD: 'md',\n TS: 'ts',\n JS: 'js',\n HTML: 'html',\n TXT: 'txt',\n TWILIO_CONTENT_JSON: 'json',\n LOTTIE: 'lottie',\n SVG: 'svg',\n DOT_STRINGS: 'strings',\n DOT_STRINGSDICT: 'stringsdict',\n ANDROID_STRINGS: 'xml',\n XCSTRINGS: 'xcstrings',\n} as const satisfies Record<FileFormat, string>;\n\n/**\n * Converts uppercase file format config keys into the CLI's canonical lowercase keys.\n *\n * This lets users write either `files.POT` or `files.pot` while keeping the\n * rest of the CLI on its existing lowercase file-type convention.\n */\nexport function normalizeFilesOptions(files: FilesOptions): FilesOptions {\n const normalized = { ...files } as FilesOptions & Record<string, unknown>;\n\n for (const [fileFormat, fileType] of Object.entries(\n FILE_FORMAT_TO_CONFIG_FILE_TYPE\n ) as [string, SupportedFileExtension][]) {\n const uppercaseConfig = normalized[fileFormat] as\n | FilesOptions[SupportedFileExtension]\n | undefined;\n if (!normalized[fileType] && uppercaseConfig) {\n normalized[fileType] = uppercaseConfig;\n }\n delete normalized[fileFormat];\n }\n\n return normalized;\n}\n\n/**\n * Validates and resolves a configured output format for a source file type.\n *\n * Throws when the requested source -> output format is not supported by\n * `generaltranslation/internal`.\n */\nexport function resolveTransformationFormat(\n fileType: SupportedFileExtension,\n transformationFormat: string | undefined\n): FileFormat | undefined {\n if (!transformationFormat) return undefined;\n\n // Normalize to uppercase to match the FileFormat enum (e.g. \"po\" -> \"PO\")\n const normalized = transformationFormat.toUpperCase();\n const fileFormat = CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType];\n\n if (\n !isFileFormat(normalized) ||\n !isSupportedFileFormatTransform(fileFormat, normalized)\n ) {\n throw new Error(\n `Unsupported file format transform: ${fileFormat} -> ${normalized} in files.${fileType}. ` +\n `\"${normalized}\" is not a valid transformationFormat for ${fileFormat} source files.`\n );\n }\n\n return normalized;\n}\n\n/**\n * Returns the API upload/enqueue property for a file type when one is configured.\n */\nexport function getTransformFormatProperty(\n settings: Settings,\n fileType: SupportedFileExtension\n): { transformFormat?: FileFormat } {\n const transformFormat = settings.files?.transformFormats?.[fileType];\n return transformFormat ? { transformFormat } : {};\n}\n\n/**\n * Returns the preferred file extension for a translated file format.\n */\nexport function getFileExtensionForFormat(format: FileFormat): string {\n return FILE_FORMAT_EXTENSIONS[format];\n}\n\n/**\n * Rewrites a path's extension to match the translated file format.\n */\nexport function replaceFileExtensionForFormat(\n filePath: string,\n format: FileFormat\n): string {\n const extension = getFileExtensionForFormat(format);\n return /\\.[^/.]+$/.test(filePath)\n ? filePath.replace(/\\.[^/.]+$/, `.${extension}`)\n : `${filePath}.${extension}`;\n}\n\n/**\n * Detects whether any configured format transform changes the output file type.\n */\nexport function hasNonIdentityFileFormatTransform(settings: Settings): boolean {\n return Object.entries(settings.files?.transformFormats || {}).some(\n ([fileType, transformFormat]) =>\n transformFormat &&\n CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType as SupportedFileExtension] !==\n transformFormat\n );\n}\n\n/**\n * Returns true when the configured transform for a file type changes its format.\n */\nexport function hasNonIdentityFileFormatTransformForType(\n settings: Settings,\n fileType: SupportedFileExtension\n): boolean {\n const transformFormat = settings.files?.transformFormats?.[fileType];\n return !!(\n transformFormat &&\n CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType] !== transformFormat\n );\n}\n"],"mappings":";;;AASA,MAAM,mBAAmB,IAAI,IAAY,OAAO,OAAOA,WAAa,CAAC;;AAGrE,SAAgB,aAAa,OAAoC;AAC/D,QAAO,iBAAiB,IAAI,MAAM;;;;;AAMpC,MAAa,kCAAkC;CAC7C,MAAM;CACN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,mBAAmB;CACnB,QAAQ;CACR,YAAY;CACZ,gBAAgB;CAChB,gBAAgB;CAChB,WAAW;CACZ;;;;AAKD,MAAa,kCAAkC;CAC7C,MAAM;CACN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,qBAAqB;CACrB,QAAQ;CACR,aAAa;CACb,iBAAiB;CACjB,iBAAiB;CACjB,WAAW;CACZ;;;;AAKD,MAAM,yBAAyB;CAC7B,QAAQ;CACR,MAAM;CACN,IAAI;CACJ,KAAK;CACL,MAAM;CACN,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,KAAK;CACL,qBAAqB;CACrB,QAAQ;CACR,KAAK;CACL,aAAa;CACb,iBAAiB;CACjB,iBAAiB;CACjB,WAAW;CACZ;;;;;;;AAQD,SAAgB,sBAAsB,OAAmC;CACvE,MAAM,aAAa,EAAE,GAAG,OAAO;AAE/B,MAAK,MAAM,CAAC,YAAY,aAAa,OAAO,QAC1C,gCACD,EAAwC;EACvC,MAAM,kBAAkB,WAAW;AAGnC,MAAI,CAAC,WAAW,aAAa,gBAC3B,YAAW,YAAY;AAEzB,SAAO,WAAW;;AAGpB,QAAO;;;;;;;;AAST,SAAgB,4BACd,UACA,sBACwB;AACxB,KAAI,CAAC,qBAAsB,QAAO,KAAA;CAGlC,MAAM,aAAa,qBAAqB,aAAa;CACrD,MAAM,aAAa,gCAAgC;AAEnD,KACE,CAAC,aAAa,WAAW,IACzB,CAAC,+BAA+B,YAAY,WAAW,CAEvD,OAAM,IAAI,MACR,sCAAsC,WAAW,MAAM,WAAW,YAAY,SAAS,KACjF,WAAW,4CAA4C,WAAW,gBACzE;AAGH,QAAO;;;;;AAMT,SAAgB,2BACd,UACA,UACkC;CAClC,MAAM,kBAAkB,SAAS,OAAO,mBAAmB;AAC3D,QAAO,kBAAkB,EAAE,iBAAiB,GAAG,EAAE;;;;;AAMnD,SAAgB,0BAA0B,QAA4B;AACpE,QAAO,uBAAuB;;;;;AAMhC,SAAgB,8BACd,UACA,QACQ;CACR,MAAM,YAAY,0BAA0B,OAAO;AACnD,QAAO,YAAY,KAAK,SAAS,GAC7B,SAAS,QAAQ,aAAa,IAAI,YAAY,GAC9C,GAAG,SAAS,GAAG;;;;;AAMrB,SAAgB,kCAAkC,UAA6B;AAC7E,QAAO,OAAO,QAAQ,SAAS,OAAO,oBAAoB,EAAE,CAAC,CAAC,MAC3D,CAAC,UAAU,qBACV,mBACA,gCAAgC,cAC9B,gBACL;;;;;AAMH,SAAgB,yCACd,UACA,UACS;CACT,MAAM,kBAAkB,SAAS,OAAO,mBAAmB;AAC3D,QAAO,CAAC,EACN,mBACA,gCAAgC,cAAc"}
@@ -1,5 +1,5 @@
1
1
  import { logger } from "../../console/logger.js";
2
- import { gt } from "../../utils/gt.js";
2
+ import { api } from "../../utils/api.js";
3
3
  import { flattenJsonWithStringFilter } from "./flattenJson.js";
4
4
  import { findMatchingItemArray, findMatchingItemObject, generateSourceObjectPointers, validateJsonSchema } from "./utils.js";
5
5
  import { applyStructuralTransforms } from "./transformJson.js";
@@ -28,8 +28,8 @@ function extractJson(localContent, inputPath, options, targetLocale, defaultLoca
28
28
  return null;
29
29
  }
30
30
  const useCanonicalLocaleKeys = options?.experimentalCanonicalLocaleKeys ?? false;
31
- const canonicalTargetLocale = useCanonicalLocaleKeys ? gt.resolveCanonicalLocale(targetLocale) : targetLocale;
32
- const canonicalDefaultLocale = useCanonicalLocaleKeys ? gt.resolveCanonicalLocale(defaultLocale) : defaultLocale;
31
+ const canonicalTargetLocale = useCanonicalLocaleKeys ? api.resolveCanonicalLocale(targetLocale) : targetLocale;
32
+ const canonicalDefaultLocale = useCanonicalLocaleKeys ? api.resolveCanonicalLocale(defaultLocale) : defaultLocale;
33
33
  if (jsonSchema.structuralTransform && jsonSchema.composite) applyStructuralTransforms(localJson, jsonSchema.structuralTransform, jsonSchema.composite);
34
34
  if (jsonSchema.include) {
35
35
  const extracted = flattenJsonWithStringFilter(localJson, jsonSchema.include);
@@ -1 +1 @@
1
- {"version":3,"file":"extractJson.js","names":["sourceObjectRecord"],"sources":["../../../src/formats/json/extractJson.ts"],"sourcesContent":["import { AdditionalOptions } from '../../types/index.js';\nimport { logger } from '../../console/logger.js';\nimport {\n findMatchingItemArray,\n findMatchingItemObject,\n generateSourceObjectPointers,\n validateJsonSchema,\n} from './utils.js';\nimport { flattenJsonWithStringFilter } from './flattenJson.js';\nimport { gt } from '../../utils/gt.js';\nimport { applyStructuralTransforms } from './transformJson.js';\nimport type { JSONObject, JSONValue } from '../../types/data/json.js';\n\n/**\n * Extracts translated values from a full JSON file back into composite JSON format.\n * This is the inverse of mergeJson - it takes a merged/reconstructed JSON file\n * and extracts the values for a specific locale into the composite structure\n * that the server expects.\n *\n * @param localContent - The full JSON content from the user's local file\n * @param inputPath - The path to the file (used for matching jsonSchema)\n * @param options - Additional options containing jsonSchema config\n * @param targetLocale - The locale to extract values for\n * @param defaultLocale - The default/source locale\n * @returns The composite JSON string, or null if no extraction needed\n */\nexport function extractJson(\n localContent: string,\n inputPath: string,\n options: AdditionalOptions,\n targetLocale: string,\n defaultLocale: string\n): string | null {\n const jsonSchema = validateJsonSchema(options, inputPath);\n if (!jsonSchema) {\n // No schema\n return null;\n }\n\n let localJson: JSONValue;\n try {\n localJson = JSON.parse(localContent);\n } catch {\n logger.error(`Invalid JSON file: ${inputPath}`);\n return null;\n }\n\n const useCanonicalLocaleKeys =\n options?.experimentalCanonicalLocaleKeys ?? false;\n const canonicalTargetLocale = useCanonicalLocaleKeys\n ? gt.resolveCanonicalLocale(targetLocale)\n : targetLocale;\n const canonicalDefaultLocale = useCanonicalLocaleKeys\n ? gt.resolveCanonicalLocale(defaultLocale)\n : defaultLocale;\n\n if (jsonSchema.structuralTransform && jsonSchema.composite) {\n applyStructuralTransforms(\n localJson,\n jsonSchema.structuralTransform,\n jsonSchema.composite\n );\n }\n\n // Handle include-style schemas (simple path-based extraction)\n if (jsonSchema.include) {\n const extracted = flattenJsonWithStringFilter(\n localJson,\n jsonSchema.include\n );\n return JSON.stringify(extracted, null, 2);\n }\n\n if (!jsonSchema.composite) {\n logger.error('No include or composite property found in JSON schema');\n return null;\n }\n\n // Handle composite schemas\n const compositeResult: Record<string, JSONValue> = {};\n\n // Generate source object pointers from the local JSON\n const sourceObjectPointers = generateSourceObjectPointers(\n jsonSchema.composite,\n localJson\n );\n\n for (const [\n sourceObjectPointer,\n { sourceObjectValue, sourceObjectOptions },\n ] of Object.entries(sourceObjectPointers)) {\n if (sourceObjectOptions.type === 'array') {\n if (!Array.isArray(sourceObjectValue)) {\n logger.warn(\n `Source object value is not an array at path: ${sourceObjectPointer}`\n );\n continue;\n }\n\n // Find the matching items for the target locale\n const matchingTargetLocaleItems = findMatchingItemArray(\n canonicalTargetLocale,\n sourceObjectOptions,\n sourceObjectPointer,\n sourceObjectValue\n );\n\n if (!Object.keys(matchingTargetLocaleItems).length) {\n logger.warn(\n `No matching items found for locale ${targetLocale} at path: ${sourceObjectPointer}`\n );\n continue;\n }\n\n // Also find default locale items\n const matchingDefaultLocaleItems = findMatchingItemArray(\n canonicalDefaultLocale,\n sourceObjectOptions,\n sourceObjectPointer,\n sourceObjectValue\n );\n\n const defaultKeys = Object.keys(matchingDefaultLocaleItems);\n const targetEntries = Object.entries(matchingTargetLocaleItems);\n\n // Initialize the nested structure for this source object pointer\n if (!compositeResult[sourceObjectPointer]) {\n compositeResult[sourceObjectPointer] = {};\n }\n\n // For each target item, use the default locale's key position\n for (let i = 0; i < targetEntries.length; i++) {\n const [, { sourceItem }] = targetEntries[i];\n // Extract values at the include paths\n const extractedValues = flattenJsonWithStringFilter(\n sourceItem,\n sourceObjectOptions.include\n );\n\n // Use default locale key\n const outputKey =\n i < defaultKeys.length ? defaultKeys[i] : targetEntries[i][0];\n (compositeResult[sourceObjectPointer] as JSONObject)[outputKey] =\n extractedValues;\n }\n } else {\n // Object type\n if (typeof sourceObjectValue !== 'object' || sourceObjectValue === null) {\n logger.warn(\n `Source object value is not an object at path: ${sourceObjectPointer}`\n );\n continue;\n }\n const sourceObjectRecord = sourceObjectValue as JSONObject;\n\n // Find the matching item for the target locale\n const matchingTargetItem = findMatchingItemObject(\n canonicalTargetLocale,\n sourceObjectPointer,\n sourceObjectOptions,\n sourceObjectRecord\n );\n\n if (!matchingTargetItem.sourceItem) {\n logger.warn(\n `No matching item found for locale ${targetLocale} at path: ${sourceObjectPointer}`\n );\n continue;\n }\n\n // If the source item is a string, use it directly\n if (typeof matchingTargetItem.sourceItem === 'string') {\n compositeResult[sourceObjectPointer] = matchingTargetItem.sourceItem;\n continue;\n }\n\n // Extract values at the include paths\n const extractedValues = flattenJsonWithStringFilter(\n matchingTargetItem.sourceItem as JSONObject,\n sourceObjectOptions.include\n );\n\n // Store the extracted values\n compositeResult[sourceObjectPointer] = extractedValues;\n }\n }\n\n return JSON.stringify(compositeResult, null, 2);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,YACd,cACA,WACA,SACA,cACA,eACe;CACf,MAAM,aAAa,mBAAmB,SAAS,UAAU;AACzD,KAAI,CAAC,WAEH,QAAO;CAGT,IAAI;AACJ,KAAI;AACF,cAAY,KAAK,MAAM,aAAa;SAC9B;AACN,SAAO,MAAM,sBAAsB,YAAY;AAC/C,SAAO;;CAGT,MAAM,yBACJ,SAAS,mCAAmC;CAC9C,MAAM,wBAAwB,yBAC1B,GAAG,uBAAuB,aAAa,GACvC;CACJ,MAAM,yBAAyB,yBAC3B,GAAG,uBAAuB,cAAc,GACxC;AAEJ,KAAI,WAAW,uBAAuB,WAAW,UAC/C,2BACE,WACA,WAAW,qBACX,WAAW,UACZ;AAIH,KAAI,WAAW,SAAS;EACtB,MAAM,YAAY,4BAChB,WACA,WAAW,QACZ;AACD,SAAO,KAAK,UAAU,WAAW,MAAM,EAAE;;AAG3C,KAAI,CAAC,WAAW,WAAW;AACzB,SAAO,MAAM,wDAAwD;AACrE,SAAO;;CAIT,MAAM,kBAA6C,EAAE;CAGrD,MAAM,uBAAuB,6BAC3B,WAAW,WACX,UACD;AAED,MAAK,MAAM,CACT,qBACA,EAAE,mBAAmB,0BAClB,OAAO,QAAQ,qBAAqB,CACvC,KAAI,oBAAoB,SAAS,SAAS;AACxC,MAAI,CAAC,MAAM,QAAQ,kBAAkB,EAAE;AACrC,UAAO,KACL,gDAAgD,sBACjD;AACD;;EAIF,MAAM,4BAA4B,sBAChC,uBACA,qBACA,qBACA,kBACD;AAED,MAAI,CAAC,OAAO,KAAK,0BAA0B,CAAC,QAAQ;AAClD,UAAO,KACL,sCAAsC,aAAa,YAAY,sBAChE;AACD;;EAIF,MAAM,6BAA6B,sBACjC,wBACA,qBACA,qBACA,kBACD;EAED,MAAM,cAAc,OAAO,KAAK,2BAA2B;EAC3D,MAAM,gBAAgB,OAAO,QAAQ,0BAA0B;AAG/D,MAAI,CAAC,gBAAgB,qBACnB,iBAAgB,uBAAuB,EAAE;AAI3C,OAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;GAC7C,MAAM,GAAG,EAAE,gBAAgB,cAAc;GAEzC,MAAM,kBAAkB,4BACtB,YACA,oBAAoB,QACrB;GAGD,MAAM,YACJ,IAAI,YAAY,SAAS,YAAY,KAAK,cAAc,GAAG;AAC5D,mBAAgB,qBAAoC,aACnD;;QAEC;AAEL,MAAI,OAAO,sBAAsB,YAAY,sBAAsB,MAAM;AACvE,UAAO,KACL,iDAAiD,sBAClD;AACD;;EAKF,MAAM,qBAAqB,uBACzB,uBACA,qBACA,qBACAA,kBACD;AAED,MAAI,CAAC,mBAAmB,YAAY;AAClC,UAAO,KACL,qCAAqC,aAAa,YAAY,sBAC/D;AACD;;AAIF,MAAI,OAAO,mBAAmB,eAAe,UAAU;AACrD,mBAAgB,uBAAuB,mBAAmB;AAC1D;;AAUF,kBAAgB,uBANQ,4BACtB,mBAAmB,YACnB,oBAAoB,QAIgC;;AAI1D,QAAO,KAAK,UAAU,iBAAiB,MAAM,EAAE"}
1
+ {"version":3,"file":"extractJson.js","names":["sourceObjectRecord"],"sources":["../../../src/formats/json/extractJson.ts"],"sourcesContent":["import { AdditionalOptions } from '../../types/index.js';\nimport { logger } from '../../console/logger.js';\nimport {\n findMatchingItemArray,\n findMatchingItemObject,\n generateSourceObjectPointers,\n validateJsonSchema,\n} from './utils.js';\nimport { flattenJsonWithStringFilter } from './flattenJson.js';\nimport { api } from '../../utils/api.js';\nimport { applyStructuralTransforms } from './transformJson.js';\nimport type { JSONObject, JSONValue } from '../../types/data/json.js';\n\n/**\n * Extracts translated values from a full JSON file back into composite JSON format.\n * This is the inverse of mergeJson - it takes a merged/reconstructed JSON file\n * and extracts the values for a specific locale into the composite structure\n * that the server expects.\n *\n * @param localContent - The full JSON content from the user's local file\n * @param inputPath - The path to the file (used for matching jsonSchema)\n * @param options - Additional options containing jsonSchema config\n * @param targetLocale - The locale to extract values for\n * @param defaultLocale - The default/source locale\n * @returns The composite JSON string, or null if no extraction needed\n */\nexport function extractJson(\n localContent: string,\n inputPath: string,\n options: AdditionalOptions,\n targetLocale: string,\n defaultLocale: string\n): string | null {\n const jsonSchema = validateJsonSchema(options, inputPath);\n if (!jsonSchema) {\n // No schema\n return null;\n }\n\n let localJson: JSONValue;\n try {\n localJson = JSON.parse(localContent);\n } catch {\n logger.error(`Invalid JSON file: ${inputPath}`);\n return null;\n }\n\n const useCanonicalLocaleKeys =\n options?.experimentalCanonicalLocaleKeys ?? false;\n const canonicalTargetLocale = useCanonicalLocaleKeys\n ? api.resolveCanonicalLocale(targetLocale)\n : targetLocale;\n const canonicalDefaultLocale = useCanonicalLocaleKeys\n ? api.resolveCanonicalLocale(defaultLocale)\n : defaultLocale;\n\n if (jsonSchema.structuralTransform && jsonSchema.composite) {\n applyStructuralTransforms(\n localJson,\n jsonSchema.structuralTransform,\n jsonSchema.composite\n );\n }\n\n // Handle include-style schemas (simple path-based extraction)\n if (jsonSchema.include) {\n const extracted = flattenJsonWithStringFilter(\n localJson,\n jsonSchema.include\n );\n return JSON.stringify(extracted, null, 2);\n }\n\n if (!jsonSchema.composite) {\n logger.error('No include or composite property found in JSON schema');\n return null;\n }\n\n // Handle composite schemas\n const compositeResult: Record<string, JSONValue> = {};\n\n // Generate source object pointers from the local JSON\n const sourceObjectPointers = generateSourceObjectPointers(\n jsonSchema.composite,\n localJson\n );\n\n for (const [\n sourceObjectPointer,\n { sourceObjectValue, sourceObjectOptions },\n ] of Object.entries(sourceObjectPointers)) {\n if (sourceObjectOptions.type === 'array') {\n if (!Array.isArray(sourceObjectValue)) {\n logger.warn(\n `Source object value is not an array at path: ${sourceObjectPointer}`\n );\n continue;\n }\n\n // Find the matching items for the target locale\n const matchingTargetLocaleItems = findMatchingItemArray(\n canonicalTargetLocale,\n sourceObjectOptions,\n sourceObjectPointer,\n sourceObjectValue\n );\n\n if (!Object.keys(matchingTargetLocaleItems).length) {\n logger.warn(\n `No matching items found for locale ${targetLocale} at path: ${sourceObjectPointer}`\n );\n continue;\n }\n\n // Also find default locale items\n const matchingDefaultLocaleItems = findMatchingItemArray(\n canonicalDefaultLocale,\n sourceObjectOptions,\n sourceObjectPointer,\n sourceObjectValue\n );\n\n const defaultKeys = Object.keys(matchingDefaultLocaleItems);\n const targetEntries = Object.entries(matchingTargetLocaleItems);\n\n // Initialize the nested structure for this source object pointer\n if (!compositeResult[sourceObjectPointer]) {\n compositeResult[sourceObjectPointer] = {};\n }\n\n // For each target item, use the default locale's key position\n for (let i = 0; i < targetEntries.length; i++) {\n const [, { sourceItem }] = targetEntries[i];\n // Extract values at the include paths\n const extractedValues = flattenJsonWithStringFilter(\n sourceItem,\n sourceObjectOptions.include\n );\n\n // Use default locale key\n const outputKey =\n i < defaultKeys.length ? defaultKeys[i] : targetEntries[i][0];\n (compositeResult[sourceObjectPointer] as JSONObject)[outputKey] =\n extractedValues;\n }\n } else {\n // Object type\n if (typeof sourceObjectValue !== 'object' || sourceObjectValue === null) {\n logger.warn(\n `Source object value is not an object at path: ${sourceObjectPointer}`\n );\n continue;\n }\n const sourceObjectRecord = sourceObjectValue as JSONObject;\n\n // Find the matching item for the target locale\n const matchingTargetItem = findMatchingItemObject(\n canonicalTargetLocale,\n sourceObjectPointer,\n sourceObjectOptions,\n sourceObjectRecord\n );\n\n if (!matchingTargetItem.sourceItem) {\n logger.warn(\n `No matching item found for locale ${targetLocale} at path: ${sourceObjectPointer}`\n );\n continue;\n }\n\n // If the source item is a string, use it directly\n if (typeof matchingTargetItem.sourceItem === 'string') {\n compositeResult[sourceObjectPointer] = matchingTargetItem.sourceItem;\n continue;\n }\n\n // Extract values at the include paths\n const extractedValues = flattenJsonWithStringFilter(\n matchingTargetItem.sourceItem as JSONObject,\n sourceObjectOptions.include\n );\n\n // Store the extracted values\n compositeResult[sourceObjectPointer] = extractedValues;\n }\n }\n\n return JSON.stringify(compositeResult, null, 2);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,YACd,cACA,WACA,SACA,cACA,eACe;CACf,MAAM,aAAa,mBAAmB,SAAS,UAAU;AACzD,KAAI,CAAC,WAEH,QAAO;CAGT,IAAI;AACJ,KAAI;AACF,cAAY,KAAK,MAAM,aAAa;SAC9B;AACN,SAAO,MAAM,sBAAsB,YAAY;AAC/C,SAAO;;CAGT,MAAM,yBACJ,SAAS,mCAAmC;CAC9C,MAAM,wBAAwB,yBAC1B,IAAI,uBAAuB,aAAa,GACxC;CACJ,MAAM,yBAAyB,yBAC3B,IAAI,uBAAuB,cAAc,GACzC;AAEJ,KAAI,WAAW,uBAAuB,WAAW,UAC/C,2BACE,WACA,WAAW,qBACX,WAAW,UACZ;AAIH,KAAI,WAAW,SAAS;EACtB,MAAM,YAAY,4BAChB,WACA,WAAW,QACZ;AACD,SAAO,KAAK,UAAU,WAAW,MAAM,EAAE;;AAG3C,KAAI,CAAC,WAAW,WAAW;AACzB,SAAO,MAAM,wDAAwD;AACrE,SAAO;;CAIT,MAAM,kBAA6C,EAAE;CAGrD,MAAM,uBAAuB,6BAC3B,WAAW,WACX,UACD;AAED,MAAK,MAAM,CACT,qBACA,EAAE,mBAAmB,0BAClB,OAAO,QAAQ,qBAAqB,CACvC,KAAI,oBAAoB,SAAS,SAAS;AACxC,MAAI,CAAC,MAAM,QAAQ,kBAAkB,EAAE;AACrC,UAAO,KACL,gDAAgD,sBACjD;AACD;;EAIF,MAAM,4BAA4B,sBAChC,uBACA,qBACA,qBACA,kBACD;AAED,MAAI,CAAC,OAAO,KAAK,0BAA0B,CAAC,QAAQ;AAClD,UAAO,KACL,sCAAsC,aAAa,YAAY,sBAChE;AACD;;EAIF,MAAM,6BAA6B,sBACjC,wBACA,qBACA,qBACA,kBACD;EAED,MAAM,cAAc,OAAO,KAAK,2BAA2B;EAC3D,MAAM,gBAAgB,OAAO,QAAQ,0BAA0B;AAG/D,MAAI,CAAC,gBAAgB,qBACnB,iBAAgB,uBAAuB,EAAE;AAI3C,OAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;GAC7C,MAAM,GAAG,EAAE,gBAAgB,cAAc;GAEzC,MAAM,kBAAkB,4BACtB,YACA,oBAAoB,QACrB;GAGD,MAAM,YACJ,IAAI,YAAY,SAAS,YAAY,KAAK,cAAc,GAAG;AAC5D,mBAAgB,qBAAoC,aACnD;;QAEC;AAEL,MAAI,OAAO,sBAAsB,YAAY,sBAAsB,MAAM;AACvE,UAAO,KACL,iDAAiD,sBAClD;AACD;;EAKF,MAAM,qBAAqB,uBACzB,uBACA,qBACA,qBACAA,kBACD;AAED,MAAI,CAAC,mBAAmB,YAAY;AAClC,UAAO,KACL,qCAAqC,aAAa,YAAY,sBAC/D;AACD;;AAIF,MAAI,OAAO,mBAAmB,eAAe,UAAU;AACrD,mBAAgB,uBAAuB,mBAAmB;AAC1D;;AAUF,kBAAgB,uBANQ,4BACtB,mBAAmB,YACnB,oBAAoB,QAIgC;;AAI1D,QAAO,KAAK,UAAU,iBAAiB,MAAM,EAAE"}
@@ -1,6 +1,6 @@
1
1
  import { logger } from "../../console/logger.js";
2
- import { gt } from "../../utils/gt.js";
3
2
  import { exitSync } from "../../console/logging.js";
3
+ import { api } from "../../utils/api.js";
4
4
  import { getConfiguredLocaleProperties, replaceLocalePlaceholders } from "../utils.js";
5
5
  import { getJSONPathMatches, getJSONPathValues } from "./jsonPath.js";
6
6
  import { findMatchingItemArray, findMatchingItemObject, generateSourceObjectPointers, getIdentifyingLocaleProperty, getSourceObjectOptionsArray, validateJsonSchema } from "./utils.js";
@@ -18,8 +18,8 @@ function mergeJson(originalContent, inputPath, options, targets, defaultLocale,
18
18
  return exitSync(1);
19
19
  }
20
20
  const useCanonicalLocaleKeys = options?.experimentalCanonicalLocaleKeys ?? false;
21
- const canonicalDefaultLocale = useCanonicalLocaleKeys ? gt.resolveCanonicalLocale(defaultLocale) : defaultLocale;
22
- const canonicalLocaleOrder = useCanonicalLocaleKeys ? localeOrder.map((locale) => gt.resolveCanonicalLocale(locale)) : localeOrder;
21
+ const canonicalDefaultLocale = useCanonicalLocaleKeys ? api.resolveCanonicalLocale(defaultLocale) : defaultLocale;
22
+ const canonicalLocaleOrder = useCanonicalLocaleKeys ? localeOrder.map((locale) => api.resolveCanonicalLocale(locale)) : localeOrder;
23
23
  if (jsonSchema.structuralTransform && jsonSchema.composite) applyStructuralTransforms(originalJson, jsonSchema.structuralTransform, jsonSchema.composite);
24
24
  if (jsonSchema.include) {
25
25
  const output = [];
@@ -60,7 +60,7 @@ function mergeJson(originalContent, inputPath, options, targets, defaultLocale,
60
60
  for (const target of parsedTargets) {
61
61
  let targetItems = target.parsedContent[sourceObjectPointer];
62
62
  if (!targetItems) targetItems = {};
63
- const targetItemsToRemove = findMatchingItemArray(useCanonicalLocaleKeys ? gt.resolveCanonicalLocale(target.targetLocale) : target.targetLocale, sourceObjectOptions, sourceObjectPointer, sourceObjectValue);
63
+ const targetItemsToRemove = findMatchingItemArray(useCanonicalLocaleKeys ? api.resolveCanonicalLocale(target.targetLocale) : target.targetLocale, sourceObjectOptions, sourceObjectPointer, sourceObjectValue);
64
64
  Object.values(targetItemsToRemove).forEach(({ index }) => indiciesToRemove.add(index));
65
65
  const sourceKeys = [...matchingDefaultLocaleItemKeys];
66
66
  const remappedTargetItems = {};
@@ -83,7 +83,7 @@ function mergeJson(originalContent, inputPath, options, targets, defaultLocale,
83
83
  const defaultLocaleSourceItem = matchingDefaultLocaleItems[sourceItemPointer].sourceItem;
84
84
  const defaultLocaleKeyPointer = matchingDefaultLocaleItems[sourceItemPointer].keyPointer;
85
85
  const mutatedSourceItem = structuredClone(defaultLocaleSourceItem);
86
- const { identifyingLocaleProperty: targetLocaleKeyProperty } = getSourceObjectOptionsArray(useCanonicalLocaleKeys ? gt.resolveCanonicalLocale(target.targetLocale) : target.targetLocale, sourceObjectPointer, sourceObjectOptions);
86
+ const { identifyingLocaleProperty: targetLocaleKeyProperty } = getSourceObjectOptionsArray(useCanonicalLocaleKeys ? api.resolveCanonicalLocale(target.targetLocale) : target.targetLocale, sourceObjectPointer, sourceObjectOptions);
87
87
  setJSONPointerValue(mutatedSourceItem, defaultLocaleKeyPointer, targetLocaleKeyProperty);
88
88
  omitProperties(mutatedSourceItem, sourceObjectOptions.omitProperties);
89
89
  for (const [translatedKeyJsonPointer, translatedValue] of Object.entries(targetItem || {})) {
@@ -116,7 +116,7 @@ function mergeJson(originalContent, inputPath, options, targets, defaultLocale,
116
116
  for (const target of parsedTargets) {
117
117
  let targetItems = target.parsedContent[sourceObjectPointer];
118
118
  if (targetItems == null) targetItems = {};
119
- const mutateSourceItemKey = findMatchingItemObject(useCanonicalLocaleKeys ? gt.resolveCanonicalLocale(target.targetLocale) : target.targetLocale, sourceObjectPointer, sourceObjectOptions, sourceObjectRecord).keyParentProperty;
119
+ const mutateSourceItemKey = findMatchingItemObject(useCanonicalLocaleKeys ? api.resolveCanonicalLocale(target.targetLocale) : target.targetLocale, sourceObjectPointer, sourceObjectOptions, sourceObjectRecord).keyParentProperty;
120
120
  if (typeof defaultLocaleSourceItem === "string") {
121
121
  if (typeof targetItems === "string") sourceObjectRecord[mutateSourceItemKey] = targetItems;
122
122
  continue;