@intlayer/cli 7.2.2 → 7.3.0-canary.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 (96) hide show
  1. package/README.md +2 -0
  2. package/dist/cjs/cli.cjs +18 -3
  3. package/dist/cjs/cli.cjs.map +1 -1
  4. package/dist/cjs/fill/fill.cjs +9 -10
  5. package/dist/cjs/fill/fill.cjs.map +1 -1
  6. package/dist/cjs/fill/formatFillData.cjs +9 -9
  7. package/dist/cjs/fill/formatFillData.cjs.map +1 -1
  8. package/dist/cjs/fill/listTranslationsTasks.cjs +4 -10
  9. package/dist/cjs/fill/listTranslationsTasks.cjs.map +1 -1
  10. package/dist/cjs/fill/translateDictionary.cjs +58 -19
  11. package/dist/cjs/fill/translateDictionary.cjs.map +1 -1
  12. package/dist/cjs/fill/writeFill.cjs +7 -4
  13. package/dist/cjs/fill/writeFill.cjs.map +1 -1
  14. package/dist/cjs/index.cjs +6 -3
  15. package/dist/cjs/reviewDoc.cjs +5 -3
  16. package/dist/cjs/reviewDoc.cjs.map +1 -1
  17. package/dist/cjs/reviewDocBlockAware.cjs +2 -2
  18. package/dist/cjs/reviewDocBlockAware.cjs.map +1 -1
  19. package/dist/cjs/test/index.cjs +3 -49
  20. package/dist/cjs/test/listMissingTranslations.cjs +5 -2
  21. package/dist/cjs/test/listMissingTranslations.cjs.map +1 -1
  22. package/dist/cjs/test/test.cjs +51 -0
  23. package/dist/cjs/test/test.cjs.map +1 -0
  24. package/dist/cjs/transform.cjs +94 -0
  25. package/dist/cjs/transform.cjs.map +1 -0
  26. package/dist/cjs/translateDoc.cjs +7 -5
  27. package/dist/cjs/translateDoc.cjs.map +1 -1
  28. package/dist/cjs/utils/checkAccess.cjs +12 -1
  29. package/dist/cjs/utils/checkAccess.cjs.map +1 -1
  30. package/dist/cjs/utils/chunkInference.cjs +18 -2
  31. package/dist/cjs/utils/chunkInference.cjs.map +1 -1
  32. package/dist/cjs/utils/setupAI.cjs +39 -0
  33. package/dist/cjs/utils/setupAI.cjs.map +1 -0
  34. package/dist/esm/cli.mjs +16 -1
  35. package/dist/esm/cli.mjs.map +1 -1
  36. package/dist/esm/fill/fill.mjs +10 -11
  37. package/dist/esm/fill/fill.mjs.map +1 -1
  38. package/dist/esm/fill/formatFillData.mjs +9 -9
  39. package/dist/esm/fill/formatFillData.mjs.map +1 -1
  40. package/dist/esm/fill/listTranslationsTasks.mjs +5 -11
  41. package/dist/esm/fill/listTranslationsTasks.mjs.map +1 -1
  42. package/dist/esm/fill/translateDictionary.mjs +59 -20
  43. package/dist/esm/fill/translateDictionary.mjs.map +1 -1
  44. package/dist/esm/fill/writeFill.mjs +7 -4
  45. package/dist/esm/fill/writeFill.mjs.map +1 -1
  46. package/dist/esm/index.mjs +4 -3
  47. package/dist/esm/reviewDoc.mjs +5 -3
  48. package/dist/esm/reviewDoc.mjs.map +1 -1
  49. package/dist/esm/reviewDocBlockAware.mjs +2 -2
  50. package/dist/esm/reviewDocBlockAware.mjs.map +1 -1
  51. package/dist/esm/test/index.mjs +3 -49
  52. package/dist/esm/test/listMissingTranslations.mjs +5 -3
  53. package/dist/esm/test/listMissingTranslations.mjs.map +1 -1
  54. package/dist/esm/test/test.mjs +50 -0
  55. package/dist/esm/test/test.mjs.map +1 -0
  56. package/dist/esm/transform.mjs +92 -0
  57. package/dist/esm/transform.mjs.map +1 -0
  58. package/dist/esm/translateDoc.mjs +7 -5
  59. package/dist/esm/translateDoc.mjs.map +1 -1
  60. package/dist/esm/utils/checkAccess.mjs +13 -2
  61. package/dist/esm/utils/checkAccess.mjs.map +1 -1
  62. package/dist/esm/utils/chunkInference.mjs +18 -2
  63. package/dist/esm/utils/chunkInference.mjs.map +1 -1
  64. package/dist/esm/utils/setupAI.mjs +38 -0
  65. package/dist/esm/utils/setupAI.mjs.map +1 -0
  66. package/dist/types/cli.d.ts.map +1 -1
  67. package/dist/types/fill/fill.d.ts.map +1 -1
  68. package/dist/types/fill/formatFillData.d.ts +1 -1
  69. package/dist/types/fill/formatFillData.d.ts.map +1 -1
  70. package/dist/types/fill/listTranslationsTasks.d.ts.map +1 -1
  71. package/dist/types/fill/translateDictionary.d.ts +4 -0
  72. package/dist/types/fill/translateDictionary.d.ts.map +1 -1
  73. package/dist/types/index.d.ts +5 -3
  74. package/dist/types/pull.d.ts.map +1 -1
  75. package/dist/types/push/pullLog.d.ts.map +1 -1
  76. package/dist/types/reviewDoc.d.ts.map +1 -1
  77. package/dist/types/reviewDocBlockAware.d.ts +3 -1
  78. package/dist/types/reviewDocBlockAware.d.ts.map +1 -1
  79. package/dist/types/test/index.d.ts +3 -12
  80. package/dist/types/test/listMissingTranslations.d.ts +12 -2
  81. package/dist/types/test/listMissingTranslations.d.ts.map +1 -1
  82. package/dist/types/test/test.d.ts +11 -0
  83. package/dist/types/test/test.d.ts.map +1 -0
  84. package/dist/types/transform.d.ts +14 -0
  85. package/dist/types/transform.d.ts.map +1 -0
  86. package/dist/types/translateDoc.d.ts +3 -2
  87. package/dist/types/translateDoc.d.ts.map +1 -1
  88. package/dist/types/utils/checkAccess.d.ts.map +1 -1
  89. package/dist/types/utils/chunkInference.d.ts +4 -2
  90. package/dist/types/utils/chunkInference.d.ts.map +1 -1
  91. package/dist/types/utils/setupAI.d.ts +21 -0
  92. package/dist/types/utils/setupAI.d.ts.map +1 -0
  93. package/package.json +12 -10
  94. package/dist/cjs/test/index.cjs.map +0 -1
  95. package/dist/esm/test/index.mjs.map +0 -1
  96. package/dist/types/test/index.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"translateDictionary.cjs","names":["baseUnmergedDictionary: Dictionary | undefined","metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined","translatedContent: Partial<Record<Locale, Dictionary['content']>>","ANSIColors","chunkedJsonContent: JsonChunk[]","chunkResult: JsonChunk[]","chunkPreset","dictionaryOutput: Dictionary"],"sources":["../../../src/fill/translateDictionary.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport { type AIOptions, getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n chunkJSON,\n formatLocale,\n type JsonChunk,\n reconstructFromSingleChunk,\n reduceObjectFormat,\n verifyIdenticObjectFormat,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeNumber,\n colorizePath,\n getAppLogger,\n retryManager,\n} from '@intlayer/config';\nimport {\n getFilterMissingTranslationsDictionary,\n getPerLocaleDictionary,\n insertContentInDictionary,\n mergeDictionaries,\n} from '@intlayer/core';\nimport type { Dictionary, IntlayerConfig, Locale } from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport type { TranslationTask } from './listTranslationsTasks';\n\ntype TranslateDictionaryResult = TranslationTask & {\n dictionaryOutput: Dictionary | null;\n};\n\ntype TranslateDictionaryOptions = {\n mode: 'complete' | 'review';\n aiOptions?: AIOptions;\n fillMetadata?: boolean;\n onHandle?: ReturnType<typeof import('@intlayer/chokidar').getGlobalLimiter>;\n onSuccess?: () => void;\n onError?: (error: unknown) => void;\n getAbortError?: () => Error | null;\n};\n\nconst hasMissingMetadata = (dictionary: Dictionary) =>\n !dictionary.description || !dictionary.title || !dictionary.tags;\n\nconst CHUNK_SIZE = 7000; // GPT-5 Mini safe input size\nconst GROUP_MAX_RETRY = 2;\nconst MAX_RETRY = 3;\nconst RETRY_DELAY = 1000 * 10; // 10 seconds\n\nconst MAX_FOLLOWING_ERRORS = 10; // 10 errors in a row, hard exit the process\nlet followingErrors = 0;\n\nexport const translateDictionary = async (\n task: TranslationTask,\n configuration: IntlayerConfig,\n options?: TranslateDictionaryOptions\n): Promise<TranslateDictionaryResult> => {\n const appLogger = getAppLogger(configuration);\n const intlayerAPI = getIntlayerAPIProxy(undefined, configuration);\n\n const { mode, aiOptions, fillMetadata } = {\n mode: 'complete',\n fillMetadata: true,\n ...options,\n } as const;\n\n const notifySuccess = () => {\n followingErrors = 0;\n options?.onSuccess?.();\n };\n\n const result = await retryManager(\n async () => {\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n\n const baseUnmergedDictionary: Dictionary | undefined =\n unmergedDictionariesRecord[task.dictionaryKey].find(\n (dict) => dict.localId === task.dictionaryLocalId\n );\n\n if (!baseUnmergedDictionary) {\n appLogger(\n `${task.dictionaryPreset}Dictionary not found in unmergedDictionariesRecord. Skipping.`,\n {\n level: 'warn',\n }\n );\n return { ...task, dictionaryOutput: null };\n }\n\n let metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined;\n\n if (\n fillMetadata &&\n (hasMissingMetadata(baseUnmergedDictionary) || mode === 'review')\n ) {\n const defaultLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n configuration.internationalization.defaultLocale\n );\n\n appLogger(\n `${task.dictionaryPreset} Filling missing metadata for ${colorizePath(basename(baseUnmergedDictionary.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n const runAudit = async () => {\n try {\n return await intlayerAPI.ai.auditContentDeclarationMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiOptions,\n });\n } catch (error) {\n throw error;\n }\n };\n\n const metadataResult = options?.onHandle\n ? await options.onHandle(runAudit)\n : await runAudit();\n\n metadata = metadataResult.data?.fileContent;\n }\n\n const translatedContent: Partial<Record<Locale, Dictionary['content']>> =\n {};\n\n for await (const targetLocale of task.targetLocales) {\n /**\n * In complete mode, for large dictionaries, we want to filter all content that is already translated\n *\n * targetLocale: fr\n *\n * { test1: t({ ar: 'Hello', en: 'Hello', fr: 'Bonjour' } }) -> {}\n * { test2: t({ ar: 'Hello', en: 'Hello' }) } -> { test2: t({ ar: 'Hello', en: 'Hello' }) }\n *\n */\n // Reset to base dictionary for each locale to ensure we filter from the original\n let dictionaryToProcess = baseUnmergedDictionary;\n\n if (mode === 'complete') {\n // Remove all nodes that don't have any content to translate\n dictionaryToProcess = getFilterMissingTranslationsDictionary(\n dictionaryToProcess,\n targetLocale\n );\n }\n\n dictionaryToProcess = getPerLocaleDictionary(\n dictionaryToProcess,\n task.sourceLocale\n );\n\n const targetLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n targetLocale\n );\n\n const localePreset = colon(\n [\n colorize('[', ANSIColors.GREY_DARK),\n formatLocale(targetLocale),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: 10 }\n );\n\n const createChunkPreset = (chunkIndex: number, totalChunks: number) => {\n if (totalChunks <= 1) return '';\n return colon(\n [\n colorize('[', ANSIColors.GREY_DARK),\n colorizeNumber(chunkIndex + 1),\n colorize(`/${totalChunks}`, ANSIColors.GREY_DARK),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: 5 }\n );\n };\n\n appLogger(\n `${task.dictionaryPreset}${localePreset} Preparing ${colorizePath(basename(targetLocaleDictionary.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n const chunkedJsonContent: JsonChunk[] = chunkJSON(\n dictionaryToProcess.content,\n CHUNK_SIZE\n );\n\n const nbOfChunks = chunkedJsonContent.length;\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset} Split into ${colorizeNumber(nbOfChunks)} chunks for translation`,\n {\n level: 'info',\n }\n );\n }\n\n const chunkResult: JsonChunk[] = [];\n\n // Process chunks in parallel (globally throttled) to allow concurrent translation\n const chunkPromises = chunkedJsonContent.map((chunk) => {\n const chunkPreset = createChunkPreset(chunk.index, chunk.total);\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset}${chunkPreset} Translating chunk`,\n {\n level: 'info',\n }\n );\n }\n\n // Reconstruct partial JSON content from this chunk's patches\n const chunkContent = reconstructFromSingleChunk(chunk);\n const presetOutputContent = reduceObjectFormat(\n targetLocaleDictionary.content,\n chunkContent\n ) as unknown as JSON;\n\n const executeTranslation = async () => {\n return await retryManager(\n async () => {\n const translationResult = await intlayerAPI.ai.translateJSON({\n entryFileContent: chunkContent as unknown as JSON,\n presetOutputContent,\n dictionaryDescription:\n dictionaryToProcess.description ??\n metadata?.description ??\n '',\n entryLocale: task.sourceLocale,\n outputLocale: targetLocale,\n mode,\n aiOptions,\n });\n\n if (!translationResult.data?.fileContent) {\n throw new Error('No content result');\n }\n\n const { isIdentic } = verifyIdenticObjectFormat(\n translationResult.data.fileContent,\n chunkContent\n );\n if (!isIdentic) {\n throw new Error(\n 'Translation result does not match expected format'\n );\n }\n\n notifySuccess();\n return translationResult.data.fileContent;\n },\n {\n maxRetry: MAX_RETRY,\n delay: RETRY_DELAY,\n onError: ({ error, attempt, maxRetry }) => {\n const chunkPreset = createChunkPreset(\n chunk.index,\n chunk.total\n );\n appLogger(\n `${task.dictionaryPreset}${localePreset}${chunkPreset} ${colorize('Error filling:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)} - Attempt ${colorizeNumber(attempt + 1)} of ${colorizeNumber(maxRetry)}`,\n {\n level: 'error',\n }\n );\n\n followingErrors += 1;\n\n if (followingErrors >= MAX_FOLLOWING_ERRORS) {\n appLogger(`There is something wrong.`, {\n level: 'error',\n });\n process.exit(1); // 1 for error\n }\n },\n }\n )();\n };\n\n const wrapped = options?.onHandle\n ? options.onHandle(executeTranslation) // queued in global limiter\n : executeTranslation(); // no global limiter\n\n return wrapped.then((result) => ({ chunk, result }));\n });\n\n // Wait for all chunks for this locale in parallel (still capped by global limiter)\n const chunkResults = await Promise.all(chunkPromises);\n\n // Maintain order\n chunkResults\n .sort((a, b) => a.chunk.index - b.chunk.index)\n .forEach(({ result }) => {\n chunkResult.push(result);\n });\n\n // Merge partial JSON objects produced from each chunk into a single object\n const merged = mergeDictionaries(\n chunkResult.map((chunk) => ({\n ...dictionaryToProcess,\n content: chunk,\n }))\n );\n\n translatedContent[targetLocale] =\n merged.content as Dictionary['content'];\n }\n\n let dictionaryOutput: Dictionary = {\n ...baseUnmergedDictionary,\n ...metadata,\n };\n\n for (const targetLocale of task.targetLocales) {\n if (translatedContent[targetLocale]) {\n dictionaryOutput = insertContentInDictionary(\n dictionaryOutput,\n translatedContent[targetLocale],\n targetLocale\n );\n }\n }\n\n appLogger(\n `${task.dictionaryPreset} ${colorize('Translation completed successfully', ANSIColors.GREEN)} for ${colorizePath(basename(dictionaryOutput.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n return {\n ...task,\n dictionaryOutput,\n };\n },\n {\n maxRetry: GROUP_MAX_RETRY,\n delay: RETRY_DELAY,\n onError: ({ error, attempt, maxRetry }) =>\n appLogger(\n `${task.dictionaryPreset} ${colorize('Error fill command:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)} - Attempt ${colorizeNumber(attempt + 1)} of ${colorizeNumber(maxRetry)}`,\n {\n level: 'error',\n }\n ),\n onMaxTryReached: ({ error }) =>\n appLogger(\n `${task.dictionaryPreset} ${colorize('Maximum number of retries reached:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)}`,\n {\n level: 'error',\n }\n ),\n }\n )();\n\n return result as TranslateDictionaryResult;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"translateDictionary.cjs","names":["baseUnmergedDictionary: Dictionary | undefined","metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined","targetLocaleDictionary: Dictionary","ANSIColors","chunkedJsonContent: JsonChunk[]","chunkResult: JsonChunk[]","translationResult","isIdentic","chunkPreset","translatedContent: Partial<Record<Locale, Dictionary['content']>>","dictionaryOutput: Dictionary"],"sources":["../../../src/fill/translateDictionary.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport type { AIConfig } from '@intlayer/ai';\nimport { type AIOptions, getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n chunkJSON,\n formatLocale,\n type JsonChunk,\n reconstructFromSingleChunk,\n reduceObjectFormat,\n verifyIdenticObjectFormat,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeNumber,\n colorizePath,\n getAppLogger,\n retryManager,\n} from '@intlayer/config';\nimport {\n getFilterMissingTranslationsDictionary,\n getMultilingualDictionary,\n getPerLocaleDictionary,\n insertContentInDictionary,\n mergeDictionaries,\n} from '@intlayer/core';\nimport type { Dictionary, IntlayerConfig, Locale } from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport type { AIClient } from '../utils/setupAI';\nimport type { TranslationTask } from './listTranslationsTasks';\n\ntype TranslateDictionaryResult = TranslationTask & {\n dictionaryOutput: Dictionary | null;\n};\n\ntype TranslateDictionaryOptions = {\n mode: 'complete' | 'review';\n aiOptions?: AIOptions;\n fillMetadata?: boolean;\n onHandle?: ReturnType<typeof import('@intlayer/chokidar').getGlobalLimiter>;\n onSuccess?: () => void;\n onError?: (error: unknown) => void;\n getAbortError?: () => Error | null;\n aiClient?: AIClient;\n aiConfig?: AIConfig;\n};\n\nconst hasMissingMetadata = (dictionary: Dictionary) =>\n !dictionary.description || !dictionary.title || !dictionary.tags;\n\nconst CHUNK_SIZE = 7000; // GPT-5 Mini safe input size\nconst GROUP_MAX_RETRY = 2;\nconst MAX_RETRY = 3;\nconst RETRY_DELAY = 1000 * 10; // 10 seconds\n\nconst MAX_FOLLOWING_ERRORS = 10; // 10 errors in a row, hard exit the process\nlet followingErrors = 0;\n\nexport const translateDictionary = async (\n task: TranslationTask,\n configuration: IntlayerConfig,\n options?: TranslateDictionaryOptions\n): Promise<TranslateDictionaryResult> => {\n const appLogger = getAppLogger(configuration);\n const intlayerAPI = getIntlayerAPIProxy(undefined, configuration);\n\n const { mode, aiOptions, fillMetadata, aiClient, aiConfig } = {\n mode: 'complete',\n fillMetadata: true,\n ...options,\n } as const;\n\n const notifySuccess = () => {\n followingErrors = 0;\n options?.onSuccess?.();\n };\n\n const result = await retryManager(\n async () => {\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n\n const baseUnmergedDictionary: Dictionary | undefined =\n unmergedDictionariesRecord[task.dictionaryKey].find(\n (dict) => dict.localId === task.dictionaryLocalId\n );\n\n if (!baseUnmergedDictionary) {\n appLogger(\n `${task.dictionaryPreset}Dictionary not found in unmergedDictionariesRecord. Skipping.`,\n {\n level: 'warn',\n }\n );\n return { ...task, dictionaryOutput: null };\n }\n\n let metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined;\n\n if (\n fillMetadata &&\n (hasMissingMetadata(baseUnmergedDictionary) || mode === 'review')\n ) {\n const defaultLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n configuration.internationalization.defaultLocale\n );\n\n appLogger(\n `${task.dictionaryPreset} Filling missing metadata for ${colorizePath(basename(baseUnmergedDictionary.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n const runAudit = async () => {\n if (aiClient && aiConfig) {\n const result = await aiClient.auditDictionaryMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiConfig,\n });\n\n return {\n data: result,\n };\n }\n\n return await intlayerAPI.ai.auditContentDeclarationMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiOptions,\n });\n };\n\n const metadataResult = options?.onHandle\n ? await options.onHandle(runAudit)\n : await runAudit();\n\n metadata = metadataResult.data?.fileContent;\n }\n\n const translatedContentResults = await Promise.all(\n task.targetLocales.map(async (targetLocale) => {\n /**\n * In complete mode, for large dictionaries, we want to filter all content that is already translated\n *\n * targetLocale: fr\n *\n * { test1: t({ ar: 'Hello', en: 'Hello', fr: 'Bonjour' } }) -> {}\n * { test2: t({ ar: 'Hello', en: 'Hello' }) } -> { test2: t({ ar: 'Hello', en: 'Hello' }) }\n *\n */\n // Reset to base dictionary for each locale to ensure we filter from the original\n let dictionaryToProcess = structuredClone(baseUnmergedDictionary);\n\n if (\n mode === 'complete' &&\n typeof baseUnmergedDictionary.locale !== 'string'\n ) {\n // Remove all nodes that don't have any content to translate\n dictionaryToProcess = getFilterMissingTranslationsDictionary(\n dictionaryToProcess,\n targetLocale\n );\n }\n\n dictionaryToProcess = getPerLocaleDictionary(\n dictionaryToProcess,\n task.sourceLocale\n );\n\n let targetLocaleDictionary: Dictionary;\n\n if (typeof baseUnmergedDictionary.locale === 'string') {\n targetLocaleDictionary = {\n key: baseUnmergedDictionary.key,\n content: {},\n filePath: baseUnmergedDictionary.filePath,\n };\n } else {\n targetLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n targetLocale\n );\n }\n\n const localePreset = colon(\n [\n colorize('[', ANSIColors.GREY_DARK),\n formatLocale(targetLocale),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: 10 }\n );\n\n const createChunkPreset = (\n chunkIndex: number,\n totalChunks: number\n ) => {\n if (totalChunks <= 1) return '';\n return colon(\n [\n colorize('[', ANSIColors.GREY_DARK),\n colorizeNumber(chunkIndex + 1),\n colorize(`/${totalChunks}`, ANSIColors.GREY_DARK),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: 5 }\n );\n };\n\n appLogger(\n `${task.dictionaryPreset}${localePreset} Preparing ${colorizePath(basename(targetLocaleDictionary.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n const chunkedJsonContent: JsonChunk[] = chunkJSON(\n dictionaryToProcess.content,\n CHUNK_SIZE\n );\n\n const nbOfChunks = chunkedJsonContent.length;\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset} Split into ${colorizeNumber(nbOfChunks)} chunks for translation`,\n {\n level: 'info',\n }\n );\n }\n\n const chunkResult: JsonChunk[] = [];\n\n // Process chunks in parallel (globally throttled) to allow concurrent translation\n const chunkPromises = chunkedJsonContent.map((chunk) => {\n const chunkPreset = createChunkPreset(chunk.index, chunk.total);\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset}${chunkPreset} Translating chunk`,\n {\n level: 'info',\n }\n );\n }\n\n // Reconstruct partial JSON content from this chunk's patches\n const chunkContent = reconstructFromSingleChunk(chunk);\n const presetOutputContent = reduceObjectFormat(\n targetLocaleDictionary.content,\n chunkContent\n ) as unknown as JSON;\n\n const executeTranslation = async () => {\n return await retryManager(\n async () => {\n if (aiClient && aiConfig) {\n const translationResult = await aiClient.translateJSON({\n entryFileContent: chunkContent as unknown as JSON,\n presetOutputContent,\n dictionaryDescription:\n dictionaryToProcess.description ??\n metadata?.description ??\n '',\n entryLocale: task.sourceLocale,\n outputLocale: targetLocale,\n mode,\n aiConfig,\n });\n\n if (!translationResult) {\n throw new Error('No content result');\n }\n\n const { isIdentic } = verifyIdenticObjectFormat(\n translationResult.fileContent,\n chunkContent\n );\n if (!isIdentic) {\n throw new Error(\n 'Translation result does not match expected format'\n );\n }\n\n notifySuccess();\n return translationResult.fileContent;\n }\n\n const translationResult = await intlayerAPI.ai.translateJSON({\n entryFileContent: chunkContent as unknown as JSON,\n presetOutputContent,\n dictionaryDescription:\n dictionaryToProcess.description ??\n metadata?.description ??\n '',\n entryLocale: task.sourceLocale,\n outputLocale: targetLocale,\n mode,\n aiOptions,\n });\n\n if (!translationResult.data?.fileContent) {\n throw new Error('No content result');\n }\n\n const { isIdentic } = verifyIdenticObjectFormat(\n translationResult.data.fileContent,\n chunkContent\n );\n if (!isIdentic) {\n throw new Error(\n 'Translation result does not match expected format'\n );\n }\n\n notifySuccess();\n return translationResult.data.fileContent;\n },\n {\n maxRetry: MAX_RETRY,\n delay: RETRY_DELAY,\n onError: ({ error, attempt, maxRetry }) => {\n const chunkPreset = createChunkPreset(\n chunk.index,\n chunk.total\n );\n appLogger(\n `${task.dictionaryPreset}${localePreset}${chunkPreset} ${colorize('Error filling:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)} - Attempt ${colorizeNumber(attempt + 1)} of ${colorizeNumber(maxRetry)}`,\n {\n level: 'error',\n }\n );\n\n followingErrors += 1;\n\n if (followingErrors >= MAX_FOLLOWING_ERRORS) {\n appLogger(`There is something wrong.`, {\n level: 'error',\n });\n process.exit(1); // 1 for error\n }\n },\n }\n )();\n };\n\n const wrapped = options?.onHandle\n ? options.onHandle(executeTranslation) // queued in global limiter\n : executeTranslation(); // no global limiter\n\n return wrapped.then((result) => ({ chunk, result }));\n });\n\n // Wait for all chunks for this locale in parallel (still capped by global limiter)\n const chunkResults = await Promise.all(chunkPromises);\n\n // Maintain order\n chunkResults\n .sort((chunkA, chunkB) => chunkA.chunk.index - chunkB.chunk.index)\n .forEach(({ result }) => {\n chunkResult.push(result);\n });\n\n // Merge partial JSON objects produced from each chunk into a single object\n const merged = mergeDictionaries(\n chunkResult.map((chunk) => ({\n ...dictionaryToProcess,\n content: chunk,\n }))\n );\n\n return [targetLocale, merged.content] as const;\n })\n );\n\n const translatedContent: Partial<Record<Locale, Dictionary['content']>> =\n Object.fromEntries(translatedContentResults);\n\n const baseDictionary = baseUnmergedDictionary.locale\n ? {\n ...baseUnmergedDictionary,\n key: baseUnmergedDictionary.key!,\n content: {},\n }\n : baseUnmergedDictionary;\n\n let dictionaryOutput: Dictionary = {\n ...getMultilingualDictionary(baseDictionary),\n locale: undefined, // Ensure the dictionary is multilingual\n ...metadata,\n };\n\n for (const targetLocale of task.targetLocales) {\n if (translatedContent[targetLocale]) {\n dictionaryOutput = insertContentInDictionary(\n dictionaryOutput,\n translatedContent[targetLocale],\n targetLocale\n );\n }\n }\n\n appLogger(\n `${task.dictionaryPreset} ${colorize('Translation completed successfully', ANSIColors.GREEN)} for ${colorizePath(basename(dictionaryOutput.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n if (baseUnmergedDictionary.locale) {\n const dictionaryFilePath = baseUnmergedDictionary\n .filePath!.split('.')\n .slice(0, -1);\n\n const contentIndex = dictionaryFilePath[dictionaryFilePath.length - 1];\n\n return JSON.parse(\n JSON.stringify({\n ...task,\n dictionaryOutput: {\n ...dictionaryOutput,\n fill: undefined,\n filled: true,\n },\n }).replaceAll(\n new RegExp(`\\\\.${contentIndex}\\\\.[a-zA-Z0-9]+`, 'g'),\n `.filled.${contentIndex}.json`\n )\n );\n }\n\n return {\n ...task,\n dictionaryOutput,\n };\n },\n {\n maxRetry: GROUP_MAX_RETRY,\n delay: RETRY_DELAY,\n onError: ({ error, attempt, maxRetry }) =>\n appLogger(\n `${task.dictionaryPreset} ${colorize('Error fill command:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)} - Attempt ${colorizeNumber(attempt + 1)} of ${colorizeNumber(maxRetry)}`,\n {\n level: 'error',\n }\n ),\n onMaxTryReached: ({ error }) =>\n appLogger(\n `${task.dictionaryPreset} ${colorize('Maximum number of retries reached:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)}`,\n {\n level: 'error',\n }\n ),\n }\n )();\n\n return result as TranslateDictionaryResult;\n};\n"],"mappings":"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"}
@@ -14,13 +14,16 @@ const writeFill = async (contentDeclarationFile, outputLocales, parentLocales, c
14
14
  appLogger("No file path found for dictionary", { level: "error" });
15
15
  return;
16
16
  }
17
- const autoFillOptions = contentDeclarationFile.fill ?? configuration.dictionary?.fill ?? false;
18
- if (typeof autoFillOptions === "boolean" && autoFillOptions === false) {
17
+ let fillOptions = contentDeclarationFile.fill ?? configuration.dictionary?.fill;
18
+ if (typeof fillOptions === "undefined" && typeof contentDeclarationFile.locale === "string") fillOptions = "./{{key}}.filled.content.json";
19
+ else fillOptions = true;
20
+ if (fillOptions === false) {
19
21
  appLogger(`Auto fill is disabled for '${(0, __intlayer_config.colorizeKey)(fullDictionary.key)}'`, { level: "info" });
20
22
  return;
21
23
  }
22
- const autoFillData = require_fill_formatFillData.formatFillData(autoFillOptions, (outputLocales ?? configuration.internationalization.locales).filter((locale) => !parentLocales?.includes(locale)), filePath, fullDictionary.key, configuration);
23
- for await (const output of autoFillData) {
24
+ const localeList = (outputLocales ?? configuration.internationalization.locales).filter((locale) => !parentLocales?.includes(locale));
25
+ const fillData = require_fill_formatFillData.formatFillData(fillOptions, localeList, filePath, fullDictionary.key, configuration);
26
+ for await (const output of fillData) {
24
27
  if (!output.filePath) {
25
28
  appLogger(`No file path found for auto filled content declaration for '${(0, __intlayer_config.colorizeKey)(fullDictionary.key)}'`, { level: "error" });
26
29
  continue;
@@ -1 +1 @@
1
- {"version":3,"file":"writeFill.cjs","names":["autoFillData: FillData[]","formatFillData"],"sources":["../../../src/fill/writeFill.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport {\n formatLocale,\n formatPath,\n writeContentDeclaration,\n} from '@intlayer/chokidar';\nimport { colorizeKey, getAppLogger } from '@intlayer/config';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { Dictionary, Fill, IntlayerConfig, Locale } from '@intlayer/types';\nimport { type FillData, formatFillData } from './formatFillData';\n\nexport const writeFill = async (\n contentDeclarationFile: Dictionary,\n outputLocales: Locale[],\n parentLocales: Locale[],\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n const dictionaries = getDictionaries(configuration);\n\n const fullDictionary = dictionaries[contentDeclarationFile.key];\n\n const { filePath } = contentDeclarationFile;\n\n if (!filePath) {\n appLogger('No file path found for dictionary', {\n level: 'error',\n });\n return;\n }\n\n const autoFillOptions =\n contentDeclarationFile.fill ?? configuration.dictionary?.fill ?? false;\n\n if (\n typeof autoFillOptions === 'boolean' &&\n (autoFillOptions as boolean) === false\n ) {\n appLogger(\n `Auto fill is disabled for '${colorizeKey(fullDictionary.key)}'`,\n {\n level: 'info',\n }\n );\n return;\n }\n\n const localeList: Locale[] = (\n outputLocales ?? configuration.internationalization.locales\n ).filter((locale) => !parentLocales?.includes(locale));\n\n const autoFillData: FillData[] = formatFillData(\n autoFillOptions as Fill,\n localeList,\n filePath,\n fullDictionary.key,\n configuration\n );\n\n for await (const output of autoFillData) {\n if (!output.filePath) {\n appLogger(\n `No file path found for auto filled content declaration for '${colorizeKey(fullDictionary.key)}'`,\n {\n level: 'error',\n }\n );\n continue;\n }\n\n // biome-ignore lint/correctness/noUnusedVariables: Just filtering out the fill property\n const { fill, ...rest } = contentDeclarationFile;\n\n const relativeFilePath = relative(\n configuration.content.baseDir,\n output.filePath\n );\n\n // write file\n await writeContentDeclaration(\n {\n ...rest,\n filled: true,\n locale: output.isPerLocale ? output.localeList[0] : undefined,\n localId: `${contentDeclarationFile.key}::local::${relativeFilePath}`,\n filePath: relativeFilePath,\n },\n configuration,\n {\n localeList: output.localeList,\n }\n );\n\n if (output.isPerLocale) {\n const sourceLocale = output.localeList[0];\n\n appLogger(\n `Auto filled per-locale content declaration for '${colorizeKey(fullDictionary.key)}' written to ${formatPath(output.filePath)} for locale ${formatLocale(sourceLocale)}`,\n {\n level: 'info',\n }\n );\n } else {\n appLogger(\n `Auto filled content declaration for '${colorizeKey(fullDictionary.key)}' written to ${formatPath(output.filePath)}`,\n {\n level: 'info',\n }\n );\n }\n }\n};\n"],"mappings":";;;;;;;;AAWA,MAAa,YAAY,OACvB,wBACA,eACA,eACA,kBACG;CACH,MAAM,gDAAyB,cAAc;CAG7C,MAAM,oEAF+B,cAAc,CAEf,uBAAuB;CAE3D,MAAM,EAAE,aAAa;AAErB,KAAI,CAAC,UAAU;AACb,YAAU,qCAAqC,EAC7C,OAAO,SACR,CAAC;AACF;;CAGF,MAAM,kBACJ,uBAAuB,QAAQ,cAAc,YAAY,QAAQ;AAEnE,KACE,OAAO,oBAAoB,aAC1B,oBAAgC,OACjC;AACA,YACE,iEAA0C,eAAe,IAAI,CAAC,IAC9D,EACE,OAAO,QACR,CACF;AACD;;CAOF,MAAMA,eAA2BC,2CAC/B,kBAJA,iBAAiB,cAAc,qBAAqB,SACpD,QAAQ,WAAW,CAAC,eAAe,SAAS,OAAO,CAAC,EAKpD,UACA,eAAe,KACf,cACD;AAED,YAAW,MAAM,UAAU,cAAc;AACvC,MAAI,CAAC,OAAO,UAAU;AACpB,aACE,kGAA2E,eAAe,IAAI,CAAC,IAC/F,EACE,OAAO,SACR,CACF;AACD;;EAIF,MAAM,EAAE,MAAM,GAAG,SAAS;EAE1B,MAAM,2CACJ,cAAc,QAAQ,SACtB,OAAO,SACR;AAGD,yDACE;GACE,GAAG;GACH,QAAQ;GACR,QAAQ,OAAO,cAAc,OAAO,WAAW,KAAK;GACpD,SAAS,GAAG,uBAAuB,IAAI,WAAW;GAClD,UAAU;GACX,EACD,eACA,EACE,YAAY,OAAO,YACpB,CACF;AAED,MAAI,OAAO,aAAa;GACtB,MAAM,eAAe,OAAO,WAAW;AAEvC,aACE,sFAA+D,eAAe,IAAI,CAAC,mDAA0B,OAAO,SAAS,CAAC,oDAA2B,aAAa,IACtK,EACE,OAAO,QACR,CACF;QAED,WACE,2EAAoD,eAAe,IAAI,CAAC,mDAA0B,OAAO,SAAS,IAClH,EACE,OAAO,QACR,CACF"}
1
+ {"version":3,"file":"writeFill.cjs","names":["fillOptions: Fill | undefined","localeList: Locale[]","fillData: FillData[]","formatFillData"],"sources":["../../../src/fill/writeFill.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport {\n formatLocale,\n formatPath,\n writeContentDeclaration,\n} from '@intlayer/chokidar';\nimport { colorizeKey, getAppLogger } from '@intlayer/config';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { Dictionary, Fill, IntlayerConfig, Locale } from '@intlayer/types';\nimport { type FillData, formatFillData } from './formatFillData';\n\nexport const writeFill = async (\n contentDeclarationFile: Dictionary,\n outputLocales: Locale[],\n parentLocales: Locale[],\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n const dictionaries = getDictionaries(configuration);\n\n const fullDictionary = dictionaries[contentDeclarationFile.key];\n\n const { filePath } = contentDeclarationFile;\n\n if (!filePath) {\n appLogger('No file path found for dictionary', {\n level: 'error',\n });\n return;\n }\n\n let fillOptions: Fill | undefined =\n contentDeclarationFile.fill ?? configuration.dictionary?.fill;\n\n if (\n typeof fillOptions === 'undefined' &&\n typeof contentDeclarationFile.locale === 'string'\n ) {\n // If it's a per-locale dictionary, we create another file to fill the content\n fillOptions = './{{key}}.filled.content.json';\n } else {\n fillOptions = true;\n }\n\n if ((fillOptions as boolean) === false) {\n appLogger(\n `Auto fill is disabled for '${colorizeKey(fullDictionary.key)}'`,\n {\n level: 'info',\n }\n );\n return;\n }\n\n const localeList: Locale[] = (\n outputLocales ?? configuration.internationalization.locales\n ).filter((locale) => !parentLocales?.includes(locale));\n\n const fillData: FillData[] = formatFillData(\n fillOptions as Fill,\n localeList,\n filePath,\n fullDictionary.key,\n configuration\n );\n\n for await (const output of fillData) {\n if (!output.filePath) {\n appLogger(\n `No file path found for auto filled content declaration for '${colorizeKey(fullDictionary.key)}'`,\n {\n level: 'error',\n }\n );\n continue;\n }\n\n // biome-ignore lint/correctness/noUnusedVariables: Just filtering out the fill property\n const { fill, ...rest } = contentDeclarationFile;\n\n const relativeFilePath = relative(\n configuration.content.baseDir,\n output.filePath\n );\n\n // write file\n await writeContentDeclaration(\n {\n ...rest,\n filled: true,\n locale: output.isPerLocale ? output.localeList[0] : undefined,\n localId: `${contentDeclarationFile.key}::local::${relativeFilePath}`,\n filePath: relativeFilePath,\n },\n configuration,\n {\n localeList: output.localeList,\n }\n );\n\n if (output.isPerLocale) {\n const sourceLocale = output.localeList[0];\n\n appLogger(\n `Auto filled per-locale content declaration for '${colorizeKey(fullDictionary.key)}' written to ${formatPath(output.filePath)} for locale ${formatLocale(sourceLocale)}`,\n {\n level: 'info',\n }\n );\n } else {\n appLogger(\n `Auto filled content declaration for '${colorizeKey(fullDictionary.key)}' written to ${formatPath(output.filePath)}`,\n {\n level: 'info',\n }\n );\n }\n }\n};\n"],"mappings":";;;;;;;;AAWA,MAAa,YAAY,OACvB,wBACA,eACA,eACA,kBACG;CACH,MAAM,gDAAyB,cAAc;CAG7C,MAAM,oEAF+B,cAAc,CAEf,uBAAuB;CAE3D,MAAM,EAAE,aAAa;AAErB,KAAI,CAAC,UAAU;AACb,YAAU,qCAAqC,EAC7C,OAAO,SACR,CAAC;AACF;;CAGF,IAAIA,cACF,uBAAuB,QAAQ,cAAc,YAAY;AAE3D,KACE,OAAO,gBAAgB,eACvB,OAAO,uBAAuB,WAAW,SAGzC,eAAc;KAEd,eAAc;AAGhB,KAAK,gBAA4B,OAAO;AACtC,YACE,iEAA0C,eAAe,IAAI,CAAC,IAC9D,EACE,OAAO,QACR,CACF;AACD;;CAGF,MAAMC,cACJ,iBAAiB,cAAc,qBAAqB,SACpD,QAAQ,WAAW,CAAC,eAAe,SAAS,OAAO,CAAC;CAEtD,MAAMC,WAAuBC,2CAC3B,aACA,YACA,UACA,eAAe,KACf,cACD;AAED,YAAW,MAAM,UAAU,UAAU;AACnC,MAAI,CAAC,OAAO,UAAU;AACpB,aACE,kGAA2E,eAAe,IAAI,CAAC,IAC/F,EACE,OAAO,SACR,CACF;AACD;;EAIF,MAAM,EAAE,MAAM,GAAG,SAAS;EAE1B,MAAM,2CACJ,cAAc,QAAQ,SACtB,OAAO,SACR;AAGD,yDACE;GACE,GAAG;GACH,QAAQ;GACR,QAAQ,OAAO,cAAc,OAAO,WAAW,KAAK;GACpD,SAAS,GAAG,uBAAuB,IAAI,WAAW;GAClD,UAAU;GACX,EACD,eACA,EACE,YAAY,OAAO,YACpB,CACF;AAED,MAAI,OAAO,aAAa;GACtB,MAAM,eAAe,OAAO,WAAW;AAEvC,aACE,sFAA+D,eAAe,IAAI,CAAC,mDAA0B,OAAO,SAAS,CAAC,oDAA2B,aAAa,IACtK,EACE,OAAO,QACR,CACF;QAED,WACE,2EAAoD,eAAe,IAAI,CAAC,mDAA0B,OAAO,SAAS,IAClH,EACE,OAAO,QACR,CACF"}
@@ -1,5 +1,7 @@
1
1
  const require_build = require('./build.cjs');
2
2
  const require_editor = require('./editor.cjs');
3
+ const require_test_listMissingTranslations = require('./test/listMissingTranslations.cjs');
4
+ const require_test_test = require('./test/test.cjs');
3
5
  const require_fill_fill = require('./fill/fill.cjs');
4
6
  const require_listContentDeclaration = require('./listContentDeclaration.cjs');
5
7
  const require_liveSync = require('./liveSync.cjs');
@@ -7,8 +9,7 @@ const require_pull = require('./pull.cjs');
7
9
  const require_push_push = require('./push/push.cjs');
8
10
  const require_pushConfig = require('./pushConfig.cjs');
9
11
  const require_reviewDoc = require('./reviewDoc.cjs');
10
- const require_test_listMissingTranslations = require('./test/listMissingTranslations.cjs');
11
- const require_test_index = require('./test/index.cjs');
12
+ const require_transform = require('./transform.cjs');
12
13
  const require_translateDoc = require('./translateDoc.cjs');
13
14
  const require_cli = require('./cli.cjs');
14
15
 
@@ -18,6 +19,7 @@ exports.fill = require_fill_fill.fill;
18
19
  exports.listContentDeclaration = require_listContentDeclaration.listContentDeclaration;
19
20
  exports.listContentDeclarationRows = require_listContentDeclaration.listContentDeclarationRows;
20
21
  exports.listMissingTranslations = require_test_listMissingTranslations.listMissingTranslations;
22
+ exports.listMissingTranslationsWithConfig = require_test_listMissingTranslations.listMissingTranslationsWithConfig;
21
23
  exports.liveSync = require_liveSync.liveSync;
22
24
  exports.pull = require_pull.pull;
23
25
  exports.push = require_push_push.push;
@@ -25,6 +27,7 @@ exports.pushConfig = require_pushConfig.pushConfig;
25
27
  exports.reviewDoc = require_reviewDoc.reviewDoc;
26
28
  exports.setAPI = require_cli.setAPI;
27
29
  exports.startEditor = require_editor.startEditor;
28
- exports.testMissingTranslations = require_test_index.testMissingTranslations;
30
+ exports.testMissingTranslations = require_test_test.testMissingTranslations;
31
+ exports.transform = require_transform.transform;
29
32
  exports.translateDoc = require_translateDoc.translateDoc;
30
33
  exports.translateFile = require_translateDoc.translateFile;
@@ -1,5 +1,5 @@
1
1
  const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
- const require_utils_checkAccess = require('./utils/checkAccess.cjs');
2
+ const require_utils_setupAI = require('./utils/setupAI.cjs');
3
3
  const require_reviewDocBlockAware = require('./reviewDocBlockAware.cjs');
4
4
  const require_utils_checkFileModifiedRange = require('./utils/checkFileModifiedRange.cjs');
5
5
  const require_utils_getOutputFilePath = require('./utils/getOutputFilePath.cjs');
@@ -18,7 +18,9 @@ fast_glob = require_rolldown_runtime.__toESM(fast_glob);
18
18
  const reviewDoc = async ({ docPattern, locales, excludedGlobPattern, baseLocale, aiOptions, nbSimultaneousFileProcessed, configOptions, customInstructions, skipIfModifiedBefore, skipIfModifiedAfter, skipIfExists, gitOptions }) => {
19
19
  const configuration = (0, __intlayer_config.getConfiguration)(configOptions);
20
20
  const appLogger = (0, __intlayer_config.getAppLogger)(configuration);
21
- if (!await require_utils_checkAccess.checkAIAccess(configuration, aiOptions)) return;
21
+ const aiResult = await require_utils_setupAI.setupAI(configuration, aiOptions);
22
+ if (!aiResult?.hasAIAccess) return;
23
+ const { aiClient, aiConfig } = aiResult;
22
24
  if (nbSimultaneousFileProcessed && nbSimultaneousFileProcessed > 10) {
23
25
  appLogger(`Warning: nbSimultaneousFileProcessed is set to ${nbSimultaneousFileProcessed}, which is greater than 10. Setting it to 10.`);
24
26
  nbSimultaneousFileProcessed = 10;
@@ -55,7 +57,7 @@ const reviewDoc = async ({ docPattern, locales, excludedGlobPattern, baseLocale,
55
57
  appLogger(`Git changed lines: ${gitChangedLines.join(", ")}`);
56
58
  changedLines = gitChangedLines;
57
59
  }
58
- await require_reviewDocBlockAware.reviewFileBlockAware(absoluteBaseFilePath, outputFilePath, locale, baseLocale, aiOptions, configOptions, customInstructions, changedLines);
60
+ await require_reviewDocBlockAware.reviewFileBlockAware(absoluteBaseFilePath, outputFilePath, locale, baseLocale, aiOptions, configOptions, customInstructions, changedLines, aiClient, aiConfig);
59
61
  })), (task) => task(), nbSimultaneousFileProcessed ?? 3);
60
62
  };
61
63
 
@@ -1 +1 @@
1
- {"version":3,"file":"reviewDoc.cjs","names":["checkAIAccess","docList: string[]","getOutputFilePath","ANSIColors","checkFileModifiedRange","changedLines: number[] | undefined","reviewFileBlockAware"],"sources":["../../src/reviewDoc.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport type { AIOptions } from '@intlayer/api'; // OAuth handled by API proxy\nimport {\n formatLocale,\n formatPath,\n type ListGitFilesOptions,\n listGitFiles,\n listGitLines,\n parallelize,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colorize,\n colorizeNumber,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport type { Locale } from '@intlayer/types';\nimport fg from 'fast-glob';\nimport { reviewFileBlockAware } from './reviewDocBlockAware';\nimport { checkAIAccess } from './utils/checkAccess';\nimport { checkFileModifiedRange } from './utils/checkFileModifiedRange';\nimport { getOutputFilePath } from './utils/getOutputFilePath';\n\ntype ReviewDocOptions = {\n docPattern: string[];\n locales: Locale[];\n excludedGlobPattern: string[];\n baseLocale: Locale;\n aiOptions?: AIOptions;\n nbSimultaneousFileProcessed?: number;\n configOptions?: GetConfigurationOptions;\n customInstructions?: string;\n skipIfModifiedBefore?: number | string | Date;\n skipIfModifiedAfter?: number | string | Date;\n skipIfExists?: boolean;\n gitOptions?: ListGitFilesOptions;\n};\n\n/**\n * Main audit function: scans all .md files in \"en/\" (unless you specified DOC_LIST),\n * then audits them to each locale in LOCALE_LIST.\n */\nexport const reviewDoc = async ({\n docPattern,\n locales,\n excludedGlobPattern,\n baseLocale,\n aiOptions,\n nbSimultaneousFileProcessed,\n configOptions,\n customInstructions,\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n skipIfExists,\n gitOptions,\n}: ReviewDocOptions) => {\n const configuration = getConfiguration(configOptions);\n const appLogger = getAppLogger(configuration);\n\n const hasCMSAuth = await checkAIAccess(configuration, aiOptions);\n\n if (!hasCMSAuth) return;\n\n if (nbSimultaneousFileProcessed && nbSimultaneousFileProcessed > 10) {\n appLogger(\n `Warning: nbSimultaneousFileProcessed is set to ${nbSimultaneousFileProcessed}, which is greater than 10. Setting it to 10.`\n );\n nbSimultaneousFileProcessed = 10; // Limit the number of simultaneous file processed to 10\n }\n\n let docList: string[] = await fg(docPattern, {\n ignore: excludedGlobPattern,\n });\n\n if (gitOptions) {\n const gitChangedFiles = await listGitFiles(gitOptions);\n\n if (gitChangedFiles) {\n // Convert dictionary file paths to be relative to git root for comparison\n\n // Filter dictionaries based on git changed files\n docList = docList.filter((path) =>\n gitChangedFiles.some((gitFile) => join(process.cwd(), path) === gitFile)\n );\n }\n }\n\n // OAuth handled by API proxy internally\n\n appLogger(`Base locale is ${formatLocale(baseLocale)}`);\n appLogger(\n `Reviewing ${colorizeNumber(locales.length)} locales: [ ${formatLocale(locales)} ]`\n );\n\n appLogger(`Reviewing ${colorizeNumber(docList.length)} files:`);\n appLogger(docList.map((path) => ` - ${formatPath(path)}\\n`));\n\n // Create all tasks to be processed\n const allTasks = docList.flatMap((docPath) =>\n locales.map((locale) => async () => {\n appLogger(\n `Reviewing file: ${formatPath(docPath)} to ${formatLocale(locale)}`\n );\n\n const absoluteBaseFilePath = join(configuration.content.baseDir, docPath);\n const outputFilePath = getOutputFilePath(\n absoluteBaseFilePath,\n locale,\n baseLocale\n );\n\n // Skip if file exists and skipIfExists option is enabled\n if (skipIfExists && existsSync(outputFilePath)) {\n const relativePath = relative(\n configuration.content.baseDir,\n outputFilePath\n );\n appLogger(\n `${colorize('⊘', ANSIColors.YELLOW)} File ${formatPath(relativePath)} already exists, skipping.`\n );\n return;\n }\n\n const fileModificationData = checkFileModifiedRange(outputFilePath, {\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n });\n\n if (fileModificationData.isSkipped) {\n appLogger(fileModificationData.message);\n return;\n }\n\n let changedLines: number[] | undefined;\n // FIXED: Enable git optimization that was previously commented out\n if (gitOptions) {\n const gitChangedLines = await listGitLines(\n absoluteBaseFilePath,\n gitOptions\n );\n\n appLogger(`Git changed lines: ${gitChangedLines.join(', ')}`);\n changedLines = gitChangedLines;\n }\n\n await reviewFileBlockAware(\n absoluteBaseFilePath,\n outputFilePath,\n locale as Locale,\n baseLocale,\n aiOptions,\n configOptions,\n customInstructions,\n changedLines\n );\n })\n );\n\n await parallelize(\n allTasks,\n (task) => task(),\n nbSimultaneousFileProcessed ?? 3\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA6CA,MAAa,YAAY,OAAO,EAC9B,YACA,SACA,qBACA,YACA,WACA,6BACA,eACA,oBACA,sBACA,qBACA,cACA,iBACsB;CACtB,MAAM,wDAAiC,cAAc;CACrD,MAAM,gDAAyB,cAAc;AAI7C,KAAI,CAFe,MAAMA,wCAAc,eAAe,UAAU,CAE/C;AAEjB,KAAI,+BAA+B,8BAA8B,IAAI;AACnE,YACE,kDAAkD,4BAA4B,+CAC/E;AACD,gCAA8B;;CAGhC,IAAIC,UAAoB,6BAAS,YAAY,EAC3C,QAAQ,qBACT,CAAC;AAEF,KAAI,YAAY;EACd,MAAM,kBAAkB,4CAAmB,WAAW;AAEtD,MAAI,gBAIF,WAAU,QAAQ,QAAQ,SACxB,gBAAgB,MAAM,gCAAiB,QAAQ,KAAK,EAAE,KAAK,KAAK,QAAQ,CACzE;;AAML,WAAU,wDAA+B,WAAW,GAAG;AACvD,WACE,mDAA4B,QAAQ,OAAO,CAAC,oDAA2B,QAAQ,CAAC,IACjF;AAED,WAAU,mDAA4B,QAAQ,OAAO,CAAC,SAAS;AAC/D,WAAU,QAAQ,KAAK,SAAS,0CAAiB,KAAK,CAAC,IAAI,CAAC;AA+D5D,4CA5DiB,QAAQ,SAAS,YAChC,QAAQ,KAAK,WAAW,YAAY;AAClC,YACE,uDAA8B,QAAQ,CAAC,4CAAmB,OAAO,GAClE;EAED,MAAM,2CAA4B,cAAc,QAAQ,SAAS,QAAQ;EACzE,MAAM,iBAAiBC,kDACrB,sBACA,QACA,WACD;AAGD,MAAI,wCAA2B,eAAe,EAAE;GAC9C,MAAM,uCACJ,cAAc,QAAQ,SACtB,eACD;AACD,aACE,mCAAY,KAAKC,6BAAW,OAAO,CAAC,4CAAmB,aAAa,CAAC,4BACtE;AACD;;EAGF,MAAM,uBAAuBC,4DAAuB,gBAAgB;GAClE;GACA;GACD,CAAC;AAEF,MAAI,qBAAqB,WAAW;AAClC,aAAU,qBAAqB,QAAQ;AACvC;;EAGF,IAAIC;AAEJ,MAAI,YAAY;GACd,MAAM,kBAAkB,4CACtB,sBACA,WACD;AAED,aAAU,sBAAsB,gBAAgB,KAAK,KAAK,GAAG;AAC7D,kBAAe;;AAGjB,QAAMC,iDACJ,sBACA,gBACA,QACA,YACA,WACA,eACA,oBACA,aACD;GACD,CACH,GAIE,SAAS,MAAM,EAChB,+BAA+B,EAChC"}
1
+ {"version":3,"file":"reviewDoc.cjs","names":["setupAI","docList: string[]","getOutputFilePath","ANSIColors","checkFileModifiedRange","changedLines: number[] | undefined","reviewFileBlockAware"],"sources":["../../src/reviewDoc.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport type { AIOptions } from '@intlayer/api'; // OAuth handled by API proxy\nimport {\n formatLocale,\n formatPath,\n type ListGitFilesOptions,\n listGitFiles,\n listGitLines,\n parallelize,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colorize,\n colorizeNumber,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport type { Locale } from '@intlayer/types';\nimport fg from 'fast-glob';\nimport { reviewFileBlockAware } from './reviewDocBlockAware';\nimport { checkFileModifiedRange } from './utils/checkFileModifiedRange';\nimport { getOutputFilePath } from './utils/getOutputFilePath';\nimport { setupAI } from './utils/setupAI';\n\ntype ReviewDocOptions = {\n docPattern: string[];\n locales: Locale[];\n excludedGlobPattern: string[];\n baseLocale: Locale;\n aiOptions?: AIOptions;\n nbSimultaneousFileProcessed?: number;\n configOptions?: GetConfigurationOptions;\n customInstructions?: string;\n skipIfModifiedBefore?: number | string | Date;\n skipIfModifiedAfter?: number | string | Date;\n skipIfExists?: boolean;\n gitOptions?: ListGitFilesOptions;\n};\n\n/**\n * Main audit function: scans all .md files in \"en/\" (unless you specified DOC_LIST),\n * then audits them to each locale in LOCALE_LIST.\n */\nexport const reviewDoc = async ({\n docPattern,\n locales,\n excludedGlobPattern,\n baseLocale,\n aiOptions,\n nbSimultaneousFileProcessed,\n configOptions,\n customInstructions,\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n skipIfExists,\n gitOptions,\n}: ReviewDocOptions) => {\n const configuration = getConfiguration(configOptions);\n const appLogger = getAppLogger(configuration);\n\n const aiResult = await setupAI(configuration, aiOptions);\n\n if (!aiResult?.hasAIAccess) return;\n\n const { aiClient, aiConfig } = aiResult;\n\n if (nbSimultaneousFileProcessed && nbSimultaneousFileProcessed > 10) {\n appLogger(\n `Warning: nbSimultaneousFileProcessed is set to ${nbSimultaneousFileProcessed}, which is greater than 10. Setting it to 10.`\n );\n nbSimultaneousFileProcessed = 10; // Limit the number of simultaneous file processed to 10\n }\n\n let docList: string[] = await fg(docPattern, {\n ignore: excludedGlobPattern,\n });\n\n if (gitOptions) {\n const gitChangedFiles = await listGitFiles(gitOptions);\n\n if (gitChangedFiles) {\n // Convert dictionary file paths to be relative to git root for comparison\n\n // Filter dictionaries based on git changed files\n docList = docList.filter((path) =>\n gitChangedFiles.some((gitFile) => join(process.cwd(), path) === gitFile)\n );\n }\n }\n\n // OAuth handled by API proxy internally\n\n appLogger(`Base locale is ${formatLocale(baseLocale)}`);\n appLogger(\n `Reviewing ${colorizeNumber(locales.length)} locales: [ ${formatLocale(locales)} ]`\n );\n\n appLogger(`Reviewing ${colorizeNumber(docList.length)} files:`);\n appLogger(docList.map((path) => ` - ${formatPath(path)}\\n`));\n\n // Create all tasks to be processed\n const allTasks = docList.flatMap((docPath) =>\n locales.map((locale) => async () => {\n appLogger(\n `Reviewing file: ${formatPath(docPath)} to ${formatLocale(locale)}`\n );\n\n const absoluteBaseFilePath = join(configuration.content.baseDir, docPath);\n const outputFilePath = getOutputFilePath(\n absoluteBaseFilePath,\n locale,\n baseLocale\n );\n\n // Skip if file exists and skipIfExists option is enabled\n if (skipIfExists && existsSync(outputFilePath)) {\n const relativePath = relative(\n configuration.content.baseDir,\n outputFilePath\n );\n appLogger(\n `${colorize('⊘', ANSIColors.YELLOW)} File ${formatPath(relativePath)} already exists, skipping.`\n );\n return;\n }\n\n const fileModificationData = checkFileModifiedRange(outputFilePath, {\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n });\n\n if (fileModificationData.isSkipped) {\n appLogger(fileModificationData.message);\n return;\n }\n\n let changedLines: number[] | undefined;\n // FIXED: Enable git optimization that was previously commented out\n if (gitOptions) {\n const gitChangedLines = await listGitLines(\n absoluteBaseFilePath,\n gitOptions\n );\n\n appLogger(`Git changed lines: ${gitChangedLines.join(', ')}`);\n changedLines = gitChangedLines;\n }\n\n await reviewFileBlockAware(\n absoluteBaseFilePath,\n outputFilePath,\n locale as Locale,\n baseLocale,\n aiOptions,\n configOptions,\n customInstructions,\n changedLines,\n aiClient,\n aiConfig\n );\n })\n );\n\n await parallelize(\n allTasks,\n (task) => task(),\n nbSimultaneousFileProcessed ?? 3\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA6CA,MAAa,YAAY,OAAO,EAC9B,YACA,SACA,qBACA,YACA,WACA,6BACA,eACA,oBACA,sBACA,qBACA,cACA,iBACsB;CACtB,MAAM,wDAAiC,cAAc;CACrD,MAAM,gDAAyB,cAAc;CAE7C,MAAM,WAAW,MAAMA,8BAAQ,eAAe,UAAU;AAExD,KAAI,CAAC,UAAU,YAAa;CAE5B,MAAM,EAAE,UAAU,aAAa;AAE/B,KAAI,+BAA+B,8BAA8B,IAAI;AACnE,YACE,kDAAkD,4BAA4B,+CAC/E;AACD,gCAA8B;;CAGhC,IAAIC,UAAoB,6BAAS,YAAY,EAC3C,QAAQ,qBACT,CAAC;AAEF,KAAI,YAAY;EACd,MAAM,kBAAkB,4CAAmB,WAAW;AAEtD,MAAI,gBAIF,WAAU,QAAQ,QAAQ,SACxB,gBAAgB,MAAM,gCAAiB,QAAQ,KAAK,EAAE,KAAK,KAAK,QAAQ,CACzE;;AAML,WAAU,wDAA+B,WAAW,GAAG;AACvD,WACE,mDAA4B,QAAQ,OAAO,CAAC,oDAA2B,QAAQ,CAAC,IACjF;AAED,WAAU,mDAA4B,QAAQ,OAAO,CAAC,SAAS;AAC/D,WAAU,QAAQ,KAAK,SAAS,0CAAiB,KAAK,CAAC,IAAI,CAAC;AAiE5D,4CA9DiB,QAAQ,SAAS,YAChC,QAAQ,KAAK,WAAW,YAAY;AAClC,YACE,uDAA8B,QAAQ,CAAC,4CAAmB,OAAO,GAClE;EAED,MAAM,2CAA4B,cAAc,QAAQ,SAAS,QAAQ;EACzE,MAAM,iBAAiBC,kDACrB,sBACA,QACA,WACD;AAGD,MAAI,wCAA2B,eAAe,EAAE;GAC9C,MAAM,uCACJ,cAAc,QAAQ,SACtB,eACD;AACD,aACE,mCAAY,KAAKC,6BAAW,OAAO,CAAC,4CAAmB,aAAa,CAAC,4BACtE;AACD;;EAGF,MAAM,uBAAuBC,4DAAuB,gBAAgB;GAClE;GACA;GACD,CAAC;AAEF,MAAI,qBAAqB,WAAW;AAClC,aAAU,qBAAqB,QAAQ;AACvC;;EAGF,IAAIC;AAEJ,MAAI,YAAY;GACd,MAAM,kBAAkB,4CACtB,sBACA,WACD;AAED,aAAU,sBAAsB,gBAAgB,KAAK,KAAK,GAAG;AAC7D,kBAAe;;AAGjB,QAAMC,iDACJ,sBACA,gBACA,QACA,YACA,WACA,eACA,oBACA,cACA,UACA,SACD;GACD,CACH,GAIE,SAAS,MAAM,EAChB,+BAA+B,EAChC"}
@@ -22,7 +22,7 @@ let __intlayer_types = require("@intlayer/types");
22
22
  * 4. Only sends changed/new blocks to AI for translation
23
23
  * 5. Handles reordering automatically
24
24
  */
25
- const reviewFileBlockAware = async (baseFilePath, outputFilePath, locale, baseLocale, aiOptions, configOptions, customInstructions, changedLines) => {
25
+ const reviewFileBlockAware = async (baseFilePath, outputFilePath, locale, baseLocale, aiOptions, configOptions, customInstructions, changedLines, aiClient, aiConfig) => {
26
26
  const configuration = (0, __intlayer_config.getConfiguration)(configOptions);
27
27
  const applicationLogger = (0, __intlayer_config.getAppLogger)(configuration);
28
28
  const englishText = await (0, node_fs_promises.readFile)(baseFilePath, "utf-8");
@@ -73,7 +73,7 @@ const reviewFileBlockAware = async (baseFilePath, outputFilePath, locale, baseLo
73
73
  role: "user",
74
74
  content: englishBlock.content
75
75
  }
76
- ], aiOptions, configuration);
76
+ ], aiOptions, configuration, aiClient, aiConfig);
77
77
  applicationLogger(`${prefix}${(0, __intlayer_config.colorizeNumber)(result.tokenUsed)} tokens used - Block ${(0, __intlayer_config.colorizeNumber)(segmentNumber)} of ${(0, __intlayer_config.colorizeNumber)(segmentsToReview.length)}`);
78
78
  return require_utils_fixChunkStartEndChars.fixChunkStartEndChars(result?.fileContent, englishBlock.content);
79
79
  })();
@@ -1 +1 @@
1
- {"version":3,"file":"reviewDocBlockAware.cjs","names":["readAsset","ANSIColors","buildAlignmentPlan","mergeReviewedSegments","chunkInference","Locales","fixChunkStartEndChars"],"sources":["../../src/reviewDocBlockAware.ts"],"sourcesContent":["import { mkdirSync, writeFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { readAsset } from 'utils:asset';\nimport type { AIOptions } from '@intlayer/api';\nimport { formatLocale, formatPath } from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeNumber,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n retryManager,\n} from '@intlayer/config';\nimport { getLocaleName } from '@intlayer/core';\nimport { type Locale, Locales } from '@intlayer/types';\nimport {\n buildAlignmentPlan,\n mergeReviewedSegments,\n} from './translation-alignment/pipeline';\nimport { chunkInference } from './utils/chunkInference';\nimport { fixChunkStartEndChars } from './utils/fixChunkStartEndChars';\n\n/**\n * Review a file using block-aware alignment.\n * This approach:\n * 1. Segments both English and French documents into semantic blocks\n * 2. Aligns blocks using structure (special chars, numbers) and context\n * 3. Detects which blocks changed, were added, or deleted\n * 4. Only sends changed/new blocks to AI for translation\n * 5. Handles reordering automatically\n */\nexport const reviewFileBlockAware = async (\n baseFilePath: string,\n outputFilePath: string,\n locale: Locale,\n baseLocale: Locale,\n aiOptions?: AIOptions,\n configOptions?: GetConfigurationOptions,\n customInstructions?: string,\n changedLines?: number[]\n) => {\n const configuration = getConfiguration(configOptions);\n const applicationLogger = getAppLogger(configuration);\n\n const englishText = await readFile(baseFilePath, 'utf-8');\n const frenchText = await readFile(outputFilePath, 'utf-8').catch(() => '');\n\n const basePrompt = readAsset('./prompts/REVIEW_PROMPT.md', 'utf-8')\n .replaceAll('{{localeName}}', `${formatLocale(locale, false)}`)\n .replaceAll('{{baseLocaleName}}', `${formatLocale(baseLocale, false)}`)\n .replace('{{applicationContext}}', aiOptions?.applicationContext ?? '-')\n .replace('{{customInstructions}}', customInstructions ?? '-');\n\n const filePrefixText = `${ANSIColors.GREY_DARK}[${formatPath(baseFilePath)}${ANSIColors.GREY_DARK}] `;\n const filePrefix = [\n colon(filePrefixText, { colSize: 40 }),\n `→ ${ANSIColors.RESET}`,\n ].join('');\n const prefixText = `${ANSIColors.GREY_DARK}[${formatPath(baseFilePath)}${ANSIColors.GREY_DARK}][${formatLocale(locale)}${ANSIColors.GREY_DARK}] `;\n const prefix = [\n colon(prefixText, { colSize: 40 }),\n `→ ${ANSIColors.RESET}`,\n ].join('');\n\n // Build block-aware alignment and plan\n const { englishBlocks, frenchBlocks, plan, segmentsToReview } =\n buildAlignmentPlan({\n englishText,\n frenchText,\n changedLines,\n });\n\n applicationLogger(\n `${filePrefix}Block-aware alignment complete. Total blocks: EN=${colorizeNumber(englishBlocks.length)}, FR=${colorizeNumber(frenchBlocks.length)}`\n );\n applicationLogger(\n `${filePrefix}Actions: reuse=${colorizeNumber(plan.actions.filter((a) => a.kind === 'reuse').length)}, review=${colorizeNumber(plan.actions.filter((a) => a.kind === 'review').length)}, new=${colorizeNumber(plan.actions.filter((a) => a.kind === 'insert_new').length)}, delete=${colorizeNumber(plan.actions.filter((a) => a.kind === 'delete').length)}`\n );\n\n if (segmentsToReview.length === 0) {\n applicationLogger(\n `${filePrefix}No segments need review, reusing existing translation`\n );\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(\n outputFilePath,\n mergeReviewedSegments(plan, frenchBlocks, new Map())\n );\n applicationLogger(\n `${colorize('✔', ANSIColors.GREEN)} File ${formatPath(outputFilePath)} updated successfully (no changes needed).`\n );\n return;\n }\n\n applicationLogger(\n `${filePrefix}Segments to review: ${colorizeNumber(segmentsToReview.length)}`\n );\n\n // Review segments that need AI translation\n const reviewedSegmentsMap = new Map<number, string>();\n\n for (const segment of segmentsToReview) {\n const segmentNumber = segmentsToReview.indexOf(segment) + 1;\n const englishBlock = segment.englishBlock;\n\n const getBaseChunkContextPrompt = () =>\n `**BLOCK ${segmentNumber} of ${segmentsToReview.length}** is the base block in ${formatLocale(baseLocale, false)} as reference.\\n` +\n `///chunksStart///\\n` +\n englishBlock.content +\n `///chunksEnd///`;\n\n const getFrenchChunkPrompt = () =>\n `**BLOCK ${segmentNumber} of ${segmentsToReview.length}** is the current block to review in ${formatLocale(locale, false)}.\\n` +\n `///chunksStart///\\n` +\n (segment.frenchBlockText ?? '') +\n `///chunksEnd///`;\n\n const reviewedChunkResult = await retryManager(async () => {\n const result = await chunkInference(\n [\n { role: 'system', content: basePrompt },\n { role: 'system', content: getBaseChunkContextPrompt() },\n { role: 'system', content: getFrenchChunkPrompt() },\n {\n role: 'system',\n content: `The next user message will be the **BLOCK ${colorizeNumber(segmentNumber)} of ${colorizeNumber(segmentsToReview.length)}** that should be translated in ${getLocaleName(locale, Locales.ENGLISH)} (${locale}).`,\n },\n { role: 'user', content: englishBlock.content },\n ],\n aiOptions,\n configuration\n );\n\n applicationLogger(\n `${prefix}${colorizeNumber(result.tokenUsed)} tokens used - Block ${colorizeNumber(segmentNumber)} of ${colorizeNumber(segmentsToReview.length)}`\n );\n\n const fixed = fixChunkStartEndChars(\n result?.fileContent,\n englishBlock.content\n );\n return fixed;\n })();\n\n reviewedSegmentsMap.set(segment.actionIndex, reviewedChunkResult);\n }\n\n // Merge reviewed segments back into final document\n const finalFrenchOutput = mergeReviewedSegments(\n plan,\n frenchBlocks,\n reviewedSegmentsMap\n );\n\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(outputFilePath, finalFrenchOutput);\n\n applicationLogger(\n `${colorize('✔', ANSIColors.GREEN)} File ${formatPath(outputFilePath)} created/updated successfully.`\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"reviewDocBlockAware.cjs","names":["readAsset","ANSIColors","buildAlignmentPlan","mergeReviewedSegments","chunkInference","Locales","fixChunkStartEndChars"],"sources":["../../src/reviewDocBlockAware.ts"],"sourcesContent":["import { mkdirSync, writeFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { readAsset } from 'utils:asset';\nimport type { AIConfig } from '@intlayer/ai';\nimport type { AIOptions } from '@intlayer/api';\nimport { formatLocale, formatPath } from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeNumber,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n retryManager,\n} from '@intlayer/config';\nimport { getLocaleName } from '@intlayer/core';\nimport { type Locale, Locales } from '@intlayer/types';\nimport {\n buildAlignmentPlan,\n mergeReviewedSegments,\n} from './translation-alignment/pipeline';\nimport { chunkInference } from './utils/chunkInference';\nimport { fixChunkStartEndChars } from './utils/fixChunkStartEndChars';\nimport type { AIClient } from './utils/setupAI';\n\n/**\n * Review a file using block-aware alignment.\n * This approach:\n * 1. Segments both English and French documents into semantic blocks\n * 2. Aligns blocks using structure (special chars, numbers) and context\n * 3. Detects which blocks changed, were added, or deleted\n * 4. Only sends changed/new blocks to AI for translation\n * 5. Handles reordering automatically\n */\nexport const reviewFileBlockAware = async (\n baseFilePath: string,\n outputFilePath: string,\n locale: Locale,\n baseLocale: Locale,\n aiOptions?: AIOptions,\n configOptions?: GetConfigurationOptions,\n customInstructions?: string,\n changedLines?: number[],\n aiClient?: AIClient,\n aiConfig?: AIConfig\n) => {\n const configuration = getConfiguration(configOptions);\n const applicationLogger = getAppLogger(configuration);\n\n const englishText = await readFile(baseFilePath, 'utf-8');\n const frenchText = await readFile(outputFilePath, 'utf-8').catch(() => '');\n\n const basePrompt = readAsset('./prompts/REVIEW_PROMPT.md', 'utf-8')\n .replaceAll('{{localeName}}', `${formatLocale(locale, false)}`)\n .replaceAll('{{baseLocaleName}}', `${formatLocale(baseLocale, false)}`)\n .replace('{{applicationContext}}', aiOptions?.applicationContext ?? '-')\n .replace('{{customInstructions}}', customInstructions ?? '-');\n\n const filePrefixText = `${ANSIColors.GREY_DARK}[${formatPath(baseFilePath)}${ANSIColors.GREY_DARK}] `;\n const filePrefix = [\n colon(filePrefixText, { colSize: 40 }),\n `→ ${ANSIColors.RESET}`,\n ].join('');\n const prefixText = `${ANSIColors.GREY_DARK}[${formatPath(baseFilePath)}${ANSIColors.GREY_DARK}][${formatLocale(locale)}${ANSIColors.GREY_DARK}] `;\n const prefix = [\n colon(prefixText, { colSize: 40 }),\n `→ ${ANSIColors.RESET}`,\n ].join('');\n\n // Build block-aware alignment and plan\n const { englishBlocks, frenchBlocks, plan, segmentsToReview } =\n buildAlignmentPlan({\n englishText,\n frenchText,\n changedLines,\n });\n\n applicationLogger(\n `${filePrefix}Block-aware alignment complete. Total blocks: EN=${colorizeNumber(englishBlocks.length)}, FR=${colorizeNumber(frenchBlocks.length)}`\n );\n applicationLogger(\n `${filePrefix}Actions: reuse=${colorizeNumber(plan.actions.filter((a) => a.kind === 'reuse').length)}, review=${colorizeNumber(plan.actions.filter((a) => a.kind === 'review').length)}, new=${colorizeNumber(plan.actions.filter((a) => a.kind === 'insert_new').length)}, delete=${colorizeNumber(plan.actions.filter((a) => a.kind === 'delete').length)}`\n );\n\n if (segmentsToReview.length === 0) {\n applicationLogger(\n `${filePrefix}No segments need review, reusing existing translation`\n );\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(\n outputFilePath,\n mergeReviewedSegments(plan, frenchBlocks, new Map())\n );\n applicationLogger(\n `${colorize('✔', ANSIColors.GREEN)} File ${formatPath(outputFilePath)} updated successfully (no changes needed).`\n );\n return;\n }\n\n applicationLogger(\n `${filePrefix}Segments to review: ${colorizeNumber(segmentsToReview.length)}`\n );\n\n // Review segments that need AI translation\n const reviewedSegmentsMap = new Map<number, string>();\n\n for (const segment of segmentsToReview) {\n const segmentNumber = segmentsToReview.indexOf(segment) + 1;\n const englishBlock = segment.englishBlock;\n\n const getBaseChunkContextPrompt = () =>\n `**BLOCK ${segmentNumber} of ${segmentsToReview.length}** is the base block in ${formatLocale(baseLocale, false)} as reference.\\n` +\n `///chunksStart///\\n` +\n englishBlock.content +\n `///chunksEnd///`;\n\n const getFrenchChunkPrompt = () =>\n `**BLOCK ${segmentNumber} of ${segmentsToReview.length}** is the current block to review in ${formatLocale(locale, false)}.\\n` +\n `///chunksStart///\\n` +\n (segment.frenchBlockText ?? '') +\n `///chunksEnd///`;\n\n const reviewedChunkResult = await retryManager(async () => {\n const result = await chunkInference(\n [\n { role: 'system', content: basePrompt },\n { role: 'system', content: getBaseChunkContextPrompt() },\n { role: 'system', content: getFrenchChunkPrompt() },\n {\n role: 'system',\n content: `The next user message will be the **BLOCK ${colorizeNumber(segmentNumber)} of ${colorizeNumber(segmentsToReview.length)}** that should be translated in ${getLocaleName(locale, Locales.ENGLISH)} (${locale}).`,\n },\n { role: 'user', content: englishBlock.content },\n ],\n aiOptions,\n configuration,\n aiClient,\n aiConfig\n );\n\n applicationLogger(\n `${prefix}${colorizeNumber(result.tokenUsed)} tokens used - Block ${colorizeNumber(segmentNumber)} of ${colorizeNumber(segmentsToReview.length)}`\n );\n\n const fixed = fixChunkStartEndChars(\n result?.fileContent,\n englishBlock.content\n );\n return fixed;\n })();\n\n reviewedSegmentsMap.set(segment.actionIndex, reviewedChunkResult);\n }\n\n // Merge reviewed segments back into final document\n const finalFrenchOutput = mergeReviewedSegments(\n plan,\n frenchBlocks,\n reviewedSegmentsMap\n );\n\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(outputFilePath, finalFrenchOutput);\n\n applicationLogger(\n `${colorize('✔', ANSIColors.GREEN)} File ${formatPath(outputFilePath)} created/updated successfully.`\n );\n};\n"],"mappings":"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"}
@@ -1,52 +1,6 @@
1
- const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
2
1
  const require_test_listMissingTranslations = require('./listMissingTranslations.cjs');
3
- let __intlayer_chokidar = require("@intlayer/chokidar");
4
- let __intlayer_config = require("@intlayer/config");
2
+ const require_test_test = require('./test.cjs');
5
3
 
6
- //#region src/test/index.ts
7
- const testMissingTranslations = async (options) => {
8
- const config = (0, __intlayer_config.getConfiguration)(options?.configOptions);
9
- const { locales, requiredLocales } = config.internationalization;
10
- const appLogger = (0, __intlayer_config.getAppLogger)(config, { config: { prefix: "" } });
11
- if (options?.build === true) await (0, __intlayer_chokidar.prepareIntlayer)(config, { forceRun: true });
12
- else if (typeof options?.build === "undefined") await (0, __intlayer_chokidar.prepareIntlayer)(config);
13
- const result = require_test_listMissingTranslations.listMissingTranslations(options?.configOptions);
14
- const maxKeyColSize = result.missingTranslations.map((t) => ` - ${t.key}`).reduce((max, t) => Math.max(max, t.length), 0);
15
- const maxLocalesColSize = result.missingTranslations.map((t) => (0, __intlayer_chokidar.formatLocale)(t.locales, false)).reduce((max, t) => Math.max(max, t.length), 0);
16
- const formattedMissingTranslations = result.missingTranslations.map((translation) => [
17
- (0, __intlayer_config.colon)(` - ${(0, __intlayer_config.colorizeKey)(translation.key)}`, {
18
- colSize: maxKeyColSize,
19
- maxSize: 40
20
- }),
21
- " - ",
22
- (0, __intlayer_config.colon)((0, __intlayer_chokidar.formatLocale)(translation.locales, __intlayer_config.ANSIColors.RED), {
23
- colSize: maxLocalesColSize,
24
- maxSize: 40
25
- }),
26
- translation.filePath ? ` - ${(0, __intlayer_chokidar.formatPath)(translation.filePath)}` : "",
27
- translation.id ? " - remote" : ""
28
- ].join(""));
29
- appLogger(`Missing translations:`, { level: "info" });
30
- formattedMissingTranslations.forEach((t) => {
31
- appLogger(t, { level: "info" });
32
- });
33
- appLogger(`Locales: ${(0, __intlayer_chokidar.formatLocale)(locales)}`);
34
- appLogger(`Required locales: ${(0, __intlayer_chokidar.formatLocale)(requiredLocales ?? locales)}`);
35
- appLogger(`Missing locales: ${result.missingLocales.length === 0 ? (0, __intlayer_config.colorize)("-", __intlayer_config.ANSIColors.GREEN) : (0, __intlayer_chokidar.formatLocale)(result.missingLocales, __intlayer_config.ANSIColors.RED)}`);
36
- appLogger(`Missing required locales: ${result.missingRequiredLocales.length === 0 ? (0, __intlayer_config.colorize)("-", __intlayer_config.ANSIColors.GREEN) : (0, __intlayer_chokidar.formatLocale)(result.missingRequiredLocales, __intlayer_config.ANSIColors.RED)}`);
37
- appLogger(`Total missing locales: ${(0, __intlayer_config.colorizeNumber)(result.missingLocales.length, {
38
- one: __intlayer_config.ANSIColors.RED,
39
- other: __intlayer_config.ANSIColors.RED,
40
- zero: __intlayer_config.ANSIColors.GREEN
41
- })}`);
42
- appLogger(`Total missing required locales: ${(0, __intlayer_config.colorizeNumber)(result.missingRequiredLocales.length, {
43
- one: __intlayer_config.ANSIColors.RED,
44
- other: __intlayer_config.ANSIColors.RED,
45
- zero: __intlayer_config.ANSIColors.GREEN
46
- })}`);
47
- };
48
-
49
- //#endregion
50
4
  exports.listMissingTranslations = require_test_listMissingTranslations.listMissingTranslations;
51
- exports.testMissingTranslations = testMissingTranslations;
52
- //# sourceMappingURL=index.cjs.map
5
+ exports.listMissingTranslationsWithConfig = require_test_listMissingTranslations.listMissingTranslationsWithConfig;
6
+ exports.testMissingTranslations = require_test_test.testMissingTranslations;
@@ -5,8 +5,7 @@ let __intlayer_core = require("@intlayer/core");
5
5
  let __intlayer_dictionaries_entry = require("@intlayer/dictionaries-entry");
6
6
 
7
7
  //#region src/test/listMissingTranslations.ts
8
- const listMissingTranslations = (configurationOptions) => {
9
- const configuration = (0, __intlayer_config.getConfiguration)(configurationOptions);
8
+ const listMissingTranslationsWithConfig = (configuration) => {
10
9
  const unmergedDictionariesRecord = (0, __intlayer_unmerged_dictionaries_entry.getUnmergedDictionaries)(configuration);
11
10
  const mergedDictionaries = (0, __intlayer_dictionaries_entry.getDictionaries)(configuration);
12
11
  const missingTranslations = [];
@@ -40,7 +39,11 @@ const listMissingTranslations = (configurationOptions) => {
40
39
  missingRequiredLocales: missingLocales.filter((locale) => (requiredLocales ?? locales).includes(locale))
41
40
  };
42
41
  };
42
+ const listMissingTranslations = (configurationOptions) => {
43
+ return listMissingTranslationsWithConfig((0, __intlayer_config.getConfiguration)(configurationOptions));
44
+ };
43
45
 
44
46
  //#endregion
45
47
  exports.listMissingTranslations = listMissingTranslations;
48
+ exports.listMissingTranslationsWithConfig = listMissingTranslationsWithConfig;
46
49
  //# sourceMappingURL=listMissingTranslations.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"listMissingTranslations.cjs","names":["missingTranslations: {\n key: string;\n filePath?: string;\n id?: string;\n locales: Locale[];\n }[]","dictionaries: Dictionary[]","multilingualDictionary: Dictionary[]","missingLocales"],"sources":["../../../src/test/listMissingTranslations.ts"],"sourcesContent":["import {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config';\nimport { getMissingLocalesContentFromDictionary } from '@intlayer/core';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { Dictionary, Locale } from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\n\nexport const listMissingTranslations = (\n configurationOptions?: GetConfigurationOptions\n) => {\n const configuration = getConfiguration(configurationOptions);\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n const mergedDictionaries = getDictionaries(configuration);\n\n const missingTranslations: {\n key: string;\n filePath?: string;\n id?: string;\n locales: Locale[];\n }[] = [];\n\n const { locales, requiredLocales } = configuration.internationalization;\n\n const dictionariesKeys = Object.keys(unmergedDictionariesRecord);\n\n for (const dictionaryKey of dictionariesKeys) {\n const dictionaries: Dictionary[] =\n unmergedDictionariesRecord[dictionaryKey];\n\n const multilingualDictionary: Dictionary[] = dictionaries.filter(\n (dictionary) => !dictionary.locale\n );\n\n // 2 - Test all by merging all dictionaries to ensure no per-locale dictionary is missing\n for (const dictionary of multilingualDictionary) {\n const missingLocales = getMissingLocalesContentFromDictionary(\n dictionary,\n locales\n );\n\n if (missingLocales.length > 0) {\n missingTranslations.push({\n key: dictionaryKey,\n id: dictionary.id,\n filePath: dictionary.filePath,\n locales: missingLocales,\n });\n }\n }\n\n const perLocaleDictionary: Dictionary[] = dictionaries.filter(\n (dictionary) => dictionary.locale\n );\n\n if (perLocaleDictionary.length === 0) {\n continue;\n }\n\n const mergedDictionary = mergedDictionaries[dictionaryKey];\n\n const missingLocales = getMissingLocalesContentFromDictionary(\n mergedDictionary,\n locales\n );\n\n if (missingLocales.length > 0) {\n missingTranslations.push({\n key: dictionaryKey,\n locales: missingLocales,\n });\n }\n }\n\n const missingLocalesSet = new Set(\n missingTranslations.flatMap((t) => t.locales)\n );\n const missingLocales = Array.from(missingLocalesSet);\n\n const missingRequiredLocales = missingLocales.filter((locale) =>\n (requiredLocales ?? locales).includes(locale)\n );\n\n return { missingTranslations, missingLocales, missingRequiredLocales };\n};\n"],"mappings":";;;;;;;AASA,MAAa,2BACX,yBACG;CACH,MAAM,wDAAiC,qBAAqB;CAC5D,MAAM,iGAAqD,cAAc;CACzE,MAAM,wEAAqC,cAAc;CAEzD,MAAMA,sBAKA,EAAE;CAER,MAAM,EAAE,SAAS,oBAAoB,cAAc;CAEnD,MAAM,mBAAmB,OAAO,KAAK,2BAA2B;AAEhE,MAAK,MAAM,iBAAiB,kBAAkB;EAC5C,MAAMC,eACJ,2BAA2B;EAE7B,MAAMC,yBAAuC,aAAa,QACvD,eAAe,CAAC,WAAW,OAC7B;AAGD,OAAK,MAAM,cAAc,wBAAwB;GAC/C,MAAMC,+EACJ,YACA,QACD;AAED,OAAIA,iBAAe,SAAS,EAC1B,qBAAoB,KAAK;IACvB,KAAK;IACL,IAAI,WAAW;IACf,UAAU,WAAW;IACrB,SAASA;IACV,CAAC;;AAQN,MAJ0C,aAAa,QACpD,eAAe,WAAW,OAC5B,CAEuB,WAAW,EACjC;EAGF,MAAM,mBAAmB,mBAAmB;EAE5C,MAAMA,+EACJ,kBACA,QACD;AAED,MAAIA,iBAAe,SAAS,EAC1B,qBAAoB,KAAK;GACvB,KAAK;GACL,SAASA;GACV,CAAC;;CAIN,MAAM,oBAAoB,IAAI,IAC5B,oBAAoB,SAAS,MAAM,EAAE,QAAQ,CAC9C;CACD,MAAM,iBAAiB,MAAM,KAAK,kBAAkB;AAMpD,QAAO;EAAE;EAAqB;EAAgB,wBAJf,eAAe,QAAQ,YACnD,mBAAmB,SAAS,SAAS,OAAO,CAC9C;EAEqE"}
1
+ {"version":3,"file":"listMissingTranslations.cjs","names":["missingTranslations: {\n key: string;\n filePath?: string;\n id?: string;\n locales: Locale[];\n }[]","dictionaries: Dictionary[]","multilingualDictionary: Dictionary[]","missingLocales"],"sources":["../../../src/test/listMissingTranslations.ts"],"sourcesContent":["import {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config';\nimport { getMissingLocalesContentFromDictionary } from '@intlayer/core';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { Dictionary, IntlayerConfig, Locale } from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\n\nexport const listMissingTranslationsWithConfig = (\n configuration: IntlayerConfig\n) => {\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n const mergedDictionaries = getDictionaries(configuration);\n\n const missingTranslations: {\n key: string;\n filePath?: string;\n id?: string;\n locales: Locale[];\n }[] = [];\n\n const { locales, requiredLocales } = configuration.internationalization;\n\n const dictionariesKeys = Object.keys(unmergedDictionariesRecord);\n\n for (const dictionaryKey of dictionariesKeys) {\n const dictionaries: Dictionary[] =\n unmergedDictionariesRecord[dictionaryKey];\n\n const multilingualDictionary: Dictionary[] = dictionaries.filter(\n (dictionary) => !dictionary.locale\n );\n\n // Test all by merging all dictionaries to ensure no per-locale dictionary is missing\n for (const dictionary of multilingualDictionary) {\n const missingLocales = getMissingLocalesContentFromDictionary(\n dictionary,\n locales\n );\n\n if (missingLocales.length > 0) {\n missingTranslations.push({\n key: dictionaryKey,\n id: dictionary.id,\n filePath: dictionary.filePath,\n locales: missingLocales,\n });\n }\n }\n\n const perLocaleDictionary: Dictionary[] = dictionaries.filter(\n (dictionary) => dictionary.locale\n );\n\n if (perLocaleDictionary.length === 0) {\n continue;\n }\n\n const mergedDictionary = mergedDictionaries[dictionaryKey];\n\n const missingLocales = getMissingLocalesContentFromDictionary(\n mergedDictionary,\n locales\n );\n\n if (missingLocales.length > 0) {\n missingTranslations.push({\n key: dictionaryKey,\n locales: missingLocales,\n });\n }\n }\n\n const missingLocalesSet = new Set(\n missingTranslations.flatMap((t) => t.locales)\n );\n const missingLocales = Array.from(missingLocalesSet);\n\n const missingRequiredLocales = missingLocales.filter((locale) =>\n (requiredLocales ?? locales).includes(locale)\n );\n\n return { missingTranslations, missingLocales, missingRequiredLocales };\n};\n\nexport const listMissingTranslations = (\n configurationOptions?: GetConfigurationOptions\n) => {\n const configuration = getConfiguration(configurationOptions);\n\n return listMissingTranslationsWithConfig(configuration);\n};\n"],"mappings":";;;;;;;AASA,MAAa,qCACX,kBACG;CACH,MAAM,iGAAqD,cAAc;CACzE,MAAM,wEAAqC,cAAc;CAEzD,MAAMA,sBAKA,EAAE;CAER,MAAM,EAAE,SAAS,oBAAoB,cAAc;CAEnD,MAAM,mBAAmB,OAAO,KAAK,2BAA2B;AAEhE,MAAK,MAAM,iBAAiB,kBAAkB;EAC5C,MAAMC,eACJ,2BAA2B;EAE7B,MAAMC,yBAAuC,aAAa,QACvD,eAAe,CAAC,WAAW,OAC7B;AAGD,OAAK,MAAM,cAAc,wBAAwB;GAC/C,MAAMC,+EACJ,YACA,QACD;AAED,OAAIA,iBAAe,SAAS,EAC1B,qBAAoB,KAAK;IACvB,KAAK;IACL,IAAI,WAAW;IACf,UAAU,WAAW;IACrB,SAASA;IACV,CAAC;;AAQN,MAJ0C,aAAa,QACpD,eAAe,WAAW,OAC5B,CAEuB,WAAW,EACjC;EAGF,MAAM,mBAAmB,mBAAmB;EAE5C,MAAMA,+EACJ,kBACA,QACD;AAED,MAAIA,iBAAe,SAAS,EAC1B,qBAAoB,KAAK;GACvB,KAAK;GACL,SAASA;GACV,CAAC;;CAIN,MAAM,oBAAoB,IAAI,IAC5B,oBAAoB,SAAS,MAAM,EAAE,QAAQ,CAC9C;CACD,MAAM,iBAAiB,MAAM,KAAK,kBAAkB;AAMpD,QAAO;EAAE;EAAqB;EAAgB,wBAJf,eAAe,QAAQ,YACnD,mBAAmB,SAAS,SAAS,OAAO,CAC9C;EAEqE;;AAGxE,MAAa,2BACX,yBACG;AAGH,QAAO,0EAFgC,qBAAqB,CAEL"}
@@ -0,0 +1,51 @@
1
+ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
2
+ const require_test_listMissingTranslations = require('./listMissingTranslations.cjs');
3
+ let __intlayer_chokidar = require("@intlayer/chokidar");
4
+ let __intlayer_config = require("@intlayer/config");
5
+
6
+ //#region src/test/test.ts
7
+ const testMissingTranslations = async (options) => {
8
+ const config = (0, __intlayer_config.getConfiguration)(options?.configOptions);
9
+ const { locales, requiredLocales } = config.internationalization;
10
+ const appLogger = (0, __intlayer_config.getAppLogger)(config, { config: { prefix: "" } });
11
+ if (options?.build === true) await (0, __intlayer_chokidar.prepareIntlayer)(config, { forceRun: true });
12
+ else if (typeof options?.build === "undefined") await (0, __intlayer_chokidar.prepareIntlayer)(config);
13
+ const result = require_test_listMissingTranslations.listMissingTranslations(options?.configOptions);
14
+ const maxKeyColSize = result.missingTranslations.map((t) => ` - ${t.key}`).reduce((max, t) => Math.max(max, t.length), 0);
15
+ const maxLocalesColSize = result.missingTranslations.map((t) => (0, __intlayer_chokidar.formatLocale)(t.locales, false)).reduce((max, t) => Math.max(max, t.length), 0);
16
+ const formattedMissingTranslations = result.missingTranslations.map((translation) => [
17
+ (0, __intlayer_config.colon)(` - ${(0, __intlayer_config.colorizeKey)(translation.key)}`, {
18
+ colSize: maxKeyColSize,
19
+ maxSize: 40
20
+ }),
21
+ " - ",
22
+ (0, __intlayer_config.colon)((0, __intlayer_chokidar.formatLocale)(translation.locales, __intlayer_config.ANSIColors.RED), {
23
+ colSize: maxLocalesColSize,
24
+ maxSize: 40
25
+ }),
26
+ translation.filePath ? ` - ${(0, __intlayer_chokidar.formatPath)(translation.filePath)}` : "",
27
+ translation.id ? " - remote" : ""
28
+ ].join(""));
29
+ appLogger(`Missing translations:`, { level: "info" });
30
+ formattedMissingTranslations.forEach((t) => {
31
+ appLogger(t, { level: "info" });
32
+ });
33
+ appLogger(`Locales: ${(0, __intlayer_chokidar.formatLocale)(locales)}`);
34
+ appLogger(`Required locales: ${(0, __intlayer_chokidar.formatLocale)(requiredLocales ?? locales)}`);
35
+ appLogger(`Missing locales: ${result.missingLocales.length === 0 ? (0, __intlayer_config.colorize)("-", __intlayer_config.ANSIColors.GREEN) : (0, __intlayer_chokidar.formatLocale)(result.missingLocales, __intlayer_config.ANSIColors.RED)}`);
36
+ appLogger(`Missing required locales: ${result.missingRequiredLocales.length === 0 ? (0, __intlayer_config.colorize)("-", __intlayer_config.ANSIColors.GREEN) : (0, __intlayer_chokidar.formatLocale)(result.missingRequiredLocales, __intlayer_config.ANSIColors.RED)}`);
37
+ appLogger(`Total missing locales: ${(0, __intlayer_config.colorizeNumber)(result.missingLocales.length, {
38
+ one: __intlayer_config.ANSIColors.RED,
39
+ other: __intlayer_config.ANSIColors.RED,
40
+ zero: __intlayer_config.ANSIColors.GREEN
41
+ })}`);
42
+ appLogger(`Total missing required locales: ${(0, __intlayer_config.colorizeNumber)(result.missingRequiredLocales.length, {
43
+ one: __intlayer_config.ANSIColors.RED,
44
+ other: __intlayer_config.ANSIColors.RED,
45
+ zero: __intlayer_config.ANSIColors.GREEN
46
+ })}`);
47
+ };
48
+
49
+ //#endregion
50
+ exports.testMissingTranslations = testMissingTranslations;
51
+ //# sourceMappingURL=test.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.cjs","names":["listMissingTranslations","ANSIColors"],"sources":["../../../src/test/test.ts"],"sourcesContent":["import { formatLocale, formatPath, prepareIntlayer } from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeKey,\n colorizeNumber,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport { listMissingTranslations } from './listMissingTranslations';\n\ntype ListMissingTranslationsOptions = {\n configOptions?: GetConfigurationOptions;\n build?: boolean;\n};\n\nexport const testMissingTranslations = async (\n options?: ListMissingTranslationsOptions\n) => {\n const config = getConfiguration(options?.configOptions);\n const { locales, requiredLocales } = config.internationalization;\n\n const appLogger = getAppLogger(config, {\n config: {\n prefix: '',\n },\n });\n\n if (options?.build === true) {\n await prepareIntlayer(config, { forceRun: true });\n } else if (typeof options?.build === 'undefined') {\n await prepareIntlayer(config);\n }\n\n const result = listMissingTranslations(options?.configOptions);\n\n const maxKeyColSize = result.missingTranslations\n .map((t) => ` - ${t.key}`)\n .reduce((max, t) => Math.max(max, t.length), 0);\n const maxLocalesColSize = result.missingTranslations\n .map((t) => formatLocale(t.locales, false))\n .reduce((max, t) => Math.max(max, t.length), 0);\n\n const formattedMissingTranslations = result.missingTranslations.map(\n (translation) =>\n [\n colon(` - ${colorizeKey(translation.key)}`, {\n colSize: maxKeyColSize,\n maxSize: 40,\n }),\n ' - ',\n colon(formatLocale(translation.locales, ANSIColors.RED), {\n colSize: maxLocalesColSize,\n maxSize: 40,\n }),\n\n translation.filePath ? ` - ${formatPath(translation.filePath)}` : '',\n translation.id ? ' - remote' : '',\n ].join('')\n );\n\n appLogger(`Missing translations:`, {\n level: 'info',\n });\n\n formattedMissingTranslations.forEach((t) => {\n appLogger(t, {\n level: 'info',\n });\n });\n\n appLogger(`Locales: ${formatLocale(locales)}`);\n appLogger(`Required locales: ${formatLocale(requiredLocales ?? locales)}`);\n appLogger(\n `Missing locales: ${result.missingLocales.length === 0 ? colorize('-', ANSIColors.GREEN) : formatLocale(result.missingLocales, ANSIColors.RED)}`\n );\n\n appLogger(\n `Missing required locales: ${result.missingRequiredLocales.length === 0 ? colorize('-', ANSIColors.GREEN) : formatLocale(result.missingRequiredLocales, ANSIColors.RED)}`\n );\n appLogger(\n `Total missing locales: ${colorizeNumber(result.missingLocales.length, {\n one: ANSIColors.RED,\n other: ANSIColors.RED,\n zero: ANSIColors.GREEN,\n })}`\n );\n appLogger(\n `Total missing required locales: ${colorizeNumber(\n result.missingRequiredLocales.length,\n {\n one: ANSIColors.RED,\n other: ANSIColors.RED,\n zero: ANSIColors.GREEN,\n }\n )}`\n );\n};\n"],"mappings":";;;;;;AAkBA,MAAa,0BAA0B,OACrC,YACG;CACH,MAAM,iDAA0B,SAAS,cAAc;CACvD,MAAM,EAAE,SAAS,oBAAoB,OAAO;CAE5C,MAAM,gDAAyB,QAAQ,EACrC,QAAQ,EACN,QAAQ,IACT,EACF,CAAC;AAEF,KAAI,SAAS,UAAU,KACrB,gDAAsB,QAAQ,EAAE,UAAU,MAAM,CAAC;UACxC,OAAO,SAAS,UAAU,YACnC,gDAAsB,OAAO;CAG/B,MAAM,SAASA,6DAAwB,SAAS,cAAc;CAE9D,MAAM,gBAAgB,OAAO,oBAC1B,KAAK,MAAM,MAAM,EAAE,MAAM,CACzB,QAAQ,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,OAAO,EAAE,EAAE;CACjD,MAAM,oBAAoB,OAAO,oBAC9B,KAAK,4CAAmB,EAAE,SAAS,MAAM,CAAC,CAC1C,QAAQ,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,OAAO,EAAE,EAAE;CAEjD,MAAM,+BAA+B,OAAO,oBAAoB,KAC7D,gBACC;+BACQ,yCAAkB,YAAY,IAAI,IAAI;GAC1C,SAAS;GACT,SAAS;GACV,CAAC;EACF;qEACmB,YAAY,SAASC,6BAAW,IAAI,EAAE;GACvD,SAAS;GACT,SAAS;GACV,CAAC;EAEF,YAAY,WAAW,0CAAiB,YAAY,SAAS,KAAK;EAClE,YAAY,KAAK,cAAc;EAChC,CAAC,KAAK,GAAG,CACb;AAED,WAAU,yBAAyB,EACjC,OAAO,QACR,CAAC;AAEF,8BAA6B,SAAS,MAAM;AAC1C,YAAU,GAAG,EACX,OAAO,QACR,CAAC;GACF;AAEF,WAAU,kDAAyB,QAAQ,GAAG;AAC9C,WAAU,2DAAkC,mBAAmB,QAAQ,GAAG;AAC1E,WACE,oBAAoB,OAAO,eAAe,WAAW,oCAAa,KAAKA,6BAAW,MAAM,yCAAgB,OAAO,gBAAgBA,6BAAW,IAAI,GAC/I;AAED,WACE,6BAA6B,OAAO,uBAAuB,WAAW,oCAAa,KAAKA,6BAAW,MAAM,yCAAgB,OAAO,wBAAwBA,6BAAW,IAAI,GACxK;AACD,WACE,gEAAyC,OAAO,eAAe,QAAQ;EACrE,KAAKA,6BAAW;EAChB,OAAOA,6BAAW;EAClB,MAAMA,6BAAW;EAClB,CAAC,GACH;AACD,WACE,yEACE,OAAO,uBAAuB,QAC9B;EACE,KAAKA,6BAAW;EAChB,OAAOA,6BAAW;EAClB,MAAMA,6BAAW;EAClB,CACF,GACF"}
@@ -0,0 +1,94 @@
1
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
+ let __intlayer_chokidar = require("@intlayer/chokidar");
3
+ let __intlayer_config = require("@intlayer/config");
4
+ let node_path = require("node:path");
5
+ let node_fs = require("node:fs");
6
+ let node_fs_promises = require("node:fs/promises");
7
+ let fast_glob = require("fast-glob");
8
+ fast_glob = require_rolldown_runtime.__toESM(fast_glob);
9
+ let __clack_prompts = require("@clack/prompts");
10
+
11
+ //#region src/transform.ts
12
+ const getDependencies = async (baseDir) => {
13
+ try {
14
+ const packageJsonPath = (0, node_path.resolve)(baseDir, "package.json");
15
+ if (!(0, node_fs.existsSync)(packageJsonPath)) return {};
16
+ const file = await (0, node_fs_promises.readFile)(packageJsonPath, "utf8");
17
+ return JSON.parse(file).dependencies;
18
+ } catch {
19
+ return {};
20
+ }
21
+ };
22
+ const transform = async (options) => {
23
+ const configuration = (0, __intlayer_config.getConfiguration)(options.configOptions);
24
+ const appLogger = (0, __intlayer_config.getAppLogger)(configuration);
25
+ const { baseDir } = configuration.content;
26
+ const formatPath = (path) => {
27
+ return (0, __intlayer_config.colorizePath)((0, node_path.relative)(baseDir, path));
28
+ };
29
+ const dependencies = await getDependencies(baseDir);
30
+ let packageName = "react-intlayer";
31
+ if (dependencies["next-intlayer"]) packageName = "next-intlayer";
32
+ else if (dependencies["vue-intlayer"]) packageName = "vue-intlayer";
33
+ else if (dependencies["svelte-intlayer"]) packageName = "svelte-intlayer";
34
+ else if (dependencies["react-intlayer"]) packageName = "react-intlayer";
35
+ else if (dependencies["preact-intlayer"]) packageName = "preact-intlayer";
36
+ else if (dependencies["solid-intlayer"]) packageName = "solid-intlayer";
37
+ else if (dependencies["angular-intlayer"]) packageName = "angular-intlayer";
38
+ else if (dependencies["express-intlayer"]) packageName = "express-intlayer";
39
+ let filesToTransform = options.files ?? [];
40
+ if (filesToTransform.length === 0) {
41
+ const allFiles = await (0, fast_glob.default)("**/*.{tsx,jsx,vue,svelte,ts,js}", {
42
+ cwd: baseDir,
43
+ ignore: [
44
+ "**/*.content.{ts,tsx,js,jsx,mjs,cjs}",
45
+ "**/*.config.{ts,tsx,js,jsx,mjs,cjs}",
46
+ "**/*.test.{ts,tsx,js,jsx,mjs,cjs}",
47
+ "**/*.stories.{ts,tsx,js,jsx,mjs,cjs}",
48
+ "**/node_modules/**",
49
+ "**/dist/**",
50
+ "**/build/**"
51
+ ],
52
+ absolute: true
53
+ });
54
+ const choices = [...new Set(allFiles)].filter((file) => (0, node_fs.existsSync)(file)).map((file) => {
55
+ return {
56
+ value: file,
57
+ label: (0, node_path.relative)(baseDir, file)
58
+ };
59
+ });
60
+ if (choices.length === 0) {
61
+ appLogger("No transformable files found in the project.");
62
+ return;
63
+ }
64
+ const selectedFiles = await (0, __clack_prompts.multiselect)({
65
+ message: "Select files to transform:",
66
+ options: choices,
67
+ required: false
68
+ });
69
+ if (typeof selectedFiles === "symbol") process.exit(0);
70
+ filesToTransform = selectedFiles;
71
+ }
72
+ if (filesToTransform.length === 0) {
73
+ appLogger("No files selected for transformation.");
74
+ return;
75
+ }
76
+ const absoluteFiles = filesToTransform.map((file) => (0, node_path.resolve)(baseDir, file)).filter((file) => {
77
+ if (!(0, node_fs.existsSync)(file)) {
78
+ appLogger(`File not found: ${formatPath(file)}`);
79
+ return false;
80
+ }
81
+ return true;
82
+ });
83
+ if (absoluteFiles.length === 0) return;
84
+ await (0, __intlayer_chokidar.transformFiles)(absoluteFiles, packageName, {
85
+ configOptions: options.configOptions,
86
+ outputDir: options.outputContentDeclarations,
87
+ codeOnly: options.codeOnly,
88
+ declarationOnly: options.declarationOnly
89
+ });
90
+ };
91
+
92
+ //#endregion
93
+ exports.transform = transform;
94
+ //# sourceMappingURL=transform.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.cjs","names":["packageName: PackageName"],"sources":["../../src/transform.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport { multiselect } from '@clack/prompts';\nimport { type PackageName, transformFiles } from '@intlayer/chokidar';\nimport {\n colorizePath,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport fg from 'fast-glob';\n\ntype TransformOptions = {\n files?: string[];\n outputContentDeclarations?: string;\n configOptions?: GetConfigurationOptions;\n codeOnly?: boolean;\n declarationOnly?: boolean;\n};\n\n// Helper to read package.json dependencies\nconst getDependencies = async (baseDir: string) => {\n try {\n const packageJsonPath = resolve(baseDir, 'package.json');\n if (!existsSync(packageJsonPath)) {\n // Try parent directory if not found in baseDir\n return {};\n }\n const file = await readFile(packageJsonPath, 'utf8');\n const packageJSON = JSON.parse(file);\n\n return packageJSON.dependencies;\n } catch {\n return {};\n }\n};\n\nexport const transform = async (options: TransformOptions) => {\n const configuration = getConfiguration(options.configOptions);\n const appLogger = getAppLogger(configuration);\n const { baseDir } = configuration.content;\n\n const formatPath = (path: string) => {\n const relativePath = relative(baseDir, path);\n return colorizePath(relativePath);\n };\n\n // Detect package\n const dependencies = await getDependencies(baseDir);\n let packageName: PackageName = 'react-intlayer';\n\n if (dependencies['next-intlayer']) {\n packageName = 'next-intlayer';\n } else if (dependencies['vue-intlayer']) {\n packageName = 'vue-intlayer';\n } else if (dependencies['svelte-intlayer']) {\n packageName = 'svelte-intlayer';\n } else if (dependencies['react-intlayer']) {\n packageName = 'react-intlayer';\n } else if (dependencies['preact-intlayer']) {\n packageName = 'preact-intlayer';\n } else if (dependencies['solid-intlayer']) {\n packageName = 'solid-intlayer';\n } else if (dependencies['angular-intlayer']) {\n packageName = 'angular-intlayer';\n } else if (dependencies['express-intlayer']) {\n packageName = 'express-intlayer';\n }\n\n let filesToTransform = options.files ?? [];\n\n if (filesToTransform.length === 0) {\n const globPattern = '**/*.{tsx,jsx,vue,svelte,ts,js}';\n const excludePattern = [\n '**/*.content.{ts,tsx,js,jsx,mjs,cjs}',\n '**/*.config.{ts,tsx,js,jsx,mjs,cjs}',\n '**/*.test.{ts,tsx,js,jsx,mjs,cjs}',\n '**/*.stories.{ts,tsx,js,jsx,mjs,cjs}',\n '**/node_modules/**',\n '**/dist/**',\n '**/build/**',\n ];\n\n const allFiles = await fg(globPattern, {\n cwd: baseDir,\n ignore: excludePattern,\n absolute: true,\n });\n\n // Remove duplicates and non-existing files\n const uniqueFiles = [...new Set(allFiles)].filter((file) =>\n existsSync(file)\n );\n\n // Relative paths for selection\n const choices = uniqueFiles.map((file) => {\n const relPath = relative(baseDir, file);\n return {\n value: file,\n label: relPath,\n };\n });\n\n if (choices.length === 0) {\n appLogger('No transformable files found in the project.');\n return;\n }\n\n const selectedFiles = await multiselect({\n message: 'Select files to transform:',\n options: choices,\n required: false,\n });\n\n if (typeof selectedFiles === 'symbol') {\n // User cancelled\n process.exit(0);\n }\n\n filesToTransform = selectedFiles as string[];\n }\n\n if (filesToTransform.length === 0) {\n appLogger('No files selected for transformation.');\n return;\n }\n\n const absoluteFiles = filesToTransform\n .map((file) => resolve(baseDir, file))\n .filter((file) => {\n if (!existsSync(file)) {\n appLogger(`File not found: ${formatPath(file)}`);\n return false;\n }\n return true;\n });\n\n if (absoluteFiles.length === 0) {\n return;\n }\n\n await transformFiles(absoluteFiles, packageName, {\n configOptions: options.configOptions,\n outputDir: options.outputContentDeclarations,\n codeOnly: options.codeOnly,\n declarationOnly: options.declarationOnly,\n });\n};\n"],"mappings":";;;;;;;;;;;AAsBA,MAAM,kBAAkB,OAAO,YAAoB;AACjD,KAAI;EACF,MAAM,yCAA0B,SAAS,eAAe;AACxD,MAAI,yBAAY,gBAAgB,CAE9B,QAAO,EAAE;EAEX,MAAM,OAAO,qCAAe,iBAAiB,OAAO;AAGpD,SAFoB,KAAK,MAAM,KAAK,CAEjB;SACb;AACN,SAAO,EAAE;;;AAIb,MAAa,YAAY,OAAO,YAA8B;CAC5D,MAAM,wDAAiC,QAAQ,cAAc;CAC7D,MAAM,gDAAyB,cAAc;CAC7C,MAAM,EAAE,YAAY,cAAc;CAElC,MAAM,cAAc,SAAiB;AAEnC,qEAD8B,SAAS,KAAK,CACX;;CAInC,MAAM,eAAe,MAAM,gBAAgB,QAAQ;CACnD,IAAIA,cAA2B;AAE/B,KAAI,aAAa,iBACf,eAAc;UACL,aAAa,gBACtB,eAAc;UACL,aAAa,mBACtB,eAAc;UACL,aAAa,kBACtB,eAAc;UACL,aAAa,mBACtB,eAAc;UACL,aAAa,kBACtB,eAAc;UACL,aAAa,oBACtB,eAAc;UACL,aAAa,oBACtB,eAAc;CAGhB,IAAI,mBAAmB,QAAQ,SAAS,EAAE;AAE1C,KAAI,iBAAiB,WAAW,GAAG;EAYjC,MAAM,WAAW,6BAXG,mCAWmB;GACrC,KAAK;GACL,QAZqB;IACrB;IACA;IACA;IACA;IACA;IACA;IACA;IACD;GAKC,UAAU;GACX,CAAC;EAQF,MAAM,UALc,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC,CAAC,QAAQ,iCACtC,KAAK,CACjB,CAG2B,KAAK,SAAS;AAExC,UAAO;IACL,OAAO;IACP,+BAHuB,SAAS,KAAK;IAItC;IACD;AAEF,MAAI,QAAQ,WAAW,GAAG;AACxB,aAAU,+CAA+C;AACzD;;EAGF,MAAM,gBAAgB,uCAAkB;GACtC,SAAS;GACT,SAAS;GACT,UAAU;GACX,CAAC;AAEF,MAAI,OAAO,kBAAkB,SAE3B,SAAQ,KAAK,EAAE;AAGjB,qBAAmB;;AAGrB,KAAI,iBAAiB,WAAW,GAAG;AACjC,YAAU,wCAAwC;AAClD;;CAGF,MAAM,gBAAgB,iBACnB,KAAK,gCAAiB,SAAS,KAAK,CAAC,CACrC,QAAQ,SAAS;AAChB,MAAI,yBAAY,KAAK,EAAE;AACrB,aAAU,mBAAmB,WAAW,KAAK,GAAG;AAChD,UAAO;;AAET,SAAO;GACP;AAEJ,KAAI,cAAc,WAAW,EAC3B;AAGF,+CAAqB,eAAe,aAAa;EAC/C,eAAe,QAAQ;EACvB,WAAW,QAAQ;EACnB,UAAU,QAAQ;EAClB,iBAAiB,QAAQ;EAC1B,CAAC"}