@intlayer/cli 7.5.12 → 7.5.14
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.
- package/dist/cjs/IntlayerEventListener.cjs.map +1 -1
- package/dist/cjs/build.cjs.map +1 -1
- package/dist/cjs/ci.cjs.map +1 -1
- package/dist/cjs/cli.cjs.map +1 -1
- package/dist/cjs/editor.cjs.map +1 -1
- package/dist/cjs/fill/deepMergeContent.cjs.map +1 -1
- package/dist/cjs/fill/fill.cjs.map +1 -1
- package/dist/cjs/fill/formatAutoFilledFilePath.cjs.map +1 -1
- package/dist/cjs/fill/formatFillData.cjs.map +1 -1
- package/dist/cjs/fill/getFilterMissingContentPerLocale.cjs.map +1 -1
- package/dist/cjs/fill/listTranslationsTasks.cjs.map +1 -1
- package/dist/cjs/fill/mergeChunks.cjs.map +1 -1
- package/dist/cjs/fill/translateDictionary.cjs.map +1 -1
- package/dist/cjs/fill/writeFill.cjs.map +1 -1
- package/dist/cjs/liveSync.cjs.map +1 -1
- package/dist/cjs/pull.cjs.map +1 -1
- package/dist/cjs/push/pullLog.cjs.map +1 -1
- package/dist/cjs/push/push.cjs.map +1 -1
- package/dist/cjs/pushLog.cjs.map +1 -1
- package/dist/cjs/reviewDoc/reviewDoc.cjs.map +1 -1
- package/dist/cjs/test/listMissingTranslations.cjs.map +1 -1
- package/dist/cjs/transform.cjs.map +1 -1
- package/dist/cjs/translateDoc/translateDoc.cjs.map +1 -1
- package/dist/cjs/translateDoc/translateFile.cjs.map +1 -1
- package/dist/cjs/translateDoc/validation.cjs.map +1 -1
- package/dist/cjs/translation-alignment/alignBlocks.cjs.map +1 -1
- package/dist/cjs/translation-alignment/pipeline.cjs.map +1 -1
- package/dist/cjs/translation-alignment/planActions.cjs.map +1 -1
- package/dist/cjs/translation-alignment/rebuildDocument.cjs.map +1 -1
- package/dist/cjs/translation-alignment/segmentDocument.cjs.map +1 -1
- package/dist/cjs/utils/calculateChunks.cjs.map +1 -1
- package/dist/cjs/utils/chunkInference.cjs.map +1 -1
- package/dist/cjs/utils/getIsFileUpdatedRecently.cjs.map +1 -1
- package/dist/cjs/utils/listSpecialChars.cjs.map +1 -1
- package/dist/cjs/utils/mapChunksBetweenFiles.cjs.map +1 -1
- package/dist/cjs/utils/reorderParagraphs.cjs.map +1 -1
- package/dist/cjs/utils/setupAI.cjs.map +1 -1
- package/dist/cjs/watch.cjs.map +1 -1
- package/dist/esm/IntlayerEventListener.mjs.map +1 -1
- package/dist/esm/build.mjs.map +1 -1
- package/dist/esm/ci.mjs.map +1 -1
- package/dist/esm/cli.mjs.map +1 -1
- package/dist/esm/editor.mjs.map +1 -1
- package/dist/esm/fill/deepMergeContent.mjs.map +1 -1
- package/dist/esm/fill/fill.mjs.map +1 -1
- package/dist/esm/fill/formatAutoFilledFilePath.mjs.map +1 -1
- package/dist/esm/fill/formatFillData.mjs.map +1 -1
- package/dist/esm/fill/getFilterMissingContentPerLocale.mjs.map +1 -1
- package/dist/esm/fill/listTranslationsTasks.mjs.map +1 -1
- package/dist/esm/fill/mergeChunks.mjs.map +1 -1
- package/dist/esm/fill/translateDictionary.mjs.map +1 -1
- package/dist/esm/fill/writeFill.mjs.map +1 -1
- package/dist/esm/liveSync.mjs.map +1 -1
- package/dist/esm/pull.mjs.map +1 -1
- package/dist/esm/push/pullLog.mjs.map +1 -1
- package/dist/esm/push/push.mjs.map +1 -1
- package/dist/esm/pushLog.mjs.map +1 -1
- package/dist/esm/reviewDoc/reviewDoc.mjs.map +1 -1
- package/dist/esm/test/listMissingTranslations.mjs.map +1 -1
- package/dist/esm/transform.mjs.map +1 -1
- package/dist/esm/translateDoc/translateDoc.mjs.map +1 -1
- package/dist/esm/translateDoc/translateFile.mjs.map +1 -1
- package/dist/esm/translateDoc/validation.mjs.map +1 -1
- package/dist/esm/translation-alignment/alignBlocks.mjs.map +1 -1
- package/dist/esm/translation-alignment/pipeline.mjs.map +1 -1
- package/dist/esm/translation-alignment/planActions.mjs.map +1 -1
- package/dist/esm/translation-alignment/rebuildDocument.mjs.map +1 -1
- package/dist/esm/translation-alignment/segmentDocument.mjs.map +1 -1
- package/dist/esm/utils/calculateChunks.mjs.map +1 -1
- package/dist/esm/utils/chunkInference.mjs.map +1 -1
- package/dist/esm/utils/getIsFileUpdatedRecently.mjs.map +1 -1
- package/dist/esm/utils/listSpecialChars.mjs.map +1 -1
- package/dist/esm/utils/mapChunksBetweenFiles.mjs.map +1 -1
- package/dist/esm/utils/reorderParagraphs.mjs.map +1 -1
- package/dist/esm/utils/setupAI.mjs.map +1 -1
- package/dist/esm/watch.mjs.map +1 -1
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/pushConfig.d.ts.map +1 -1
- package/dist/types/translation-alignment/rebuildDocument.d.ts.map +1 -1
- package/package.json +14 -14
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translateDictionary.cjs","names":["baseUnmergedDictionary: Dictionary | undefined","metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined","targetLocaleDictionary: Dictionary","getFilterMissingContentPerLocale","ANSIColors","chunkedJsonContent: JsonChunk[]","chunkResult: JsonChunk[]","translationResult: any","chunkPreset","mergeChunks","deepMergeContent","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} 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 { deepMergeContent } from './deepMergeContent';\nimport { getFilterMissingContentPerLocale } from './getFilterMissingContentPerLocale';\nimport type { TranslationTask } from './listTranslationsTasks';\nimport { mergeChunks } from './mergeChunks';\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 let targetLocaleDictionary: Dictionary;\n\n if (typeof baseUnmergedDictionary.locale === 'string') {\n // For per-locale files, the content is already in simple JSON format (not translation nodes)\n // The base dictionary is already the source locale content\n\n // Load the existing target locale dictionary\n const targetLocaleFilePath =\n baseUnmergedDictionary.filePath?.replace(\n new RegExp(`/${task.sourceLocale}/`, 'g'),\n `/${targetLocale}/`\n );\n\n // Find the target locale dictionary in unmerged dictionaries\n const targetUnmergedDictionary = targetLocaleFilePath\n ? unmergedDictionariesRecord[task.dictionaryKey]?.find(\n (dict) =>\n dict.filePath === targetLocaleFilePath &&\n dict.locale === targetLocale\n )\n : undefined;\n\n targetLocaleDictionary = targetUnmergedDictionary ?? {\n key: baseUnmergedDictionary.key,\n content: {},\n filePath: targetLocaleFilePath,\n locale: targetLocale,\n };\n\n // In complete mode, filter out already translated content\n if (mode === 'complete') {\n dictionaryToProcess = getFilterMissingContentPerLocale(\n dictionaryToProcess,\n targetUnmergedDictionary\n );\n }\n } else {\n // For multilingual dictionaries\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 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: 18 }\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 isContentStructured =\n (typeof dictionaryToProcess.content === 'object' &&\n dictionaryToProcess.content !== null) ||\n Array.isArray(dictionaryToProcess.content);\n\n const contentToProcess = isContentStructured\n ? dictionaryToProcess.content\n : {\n __INTLAYER_ROOT_PRIMITIVE_CONTENT__:\n dictionaryToProcess.content,\n };\n\n const chunkedJsonContent: JsonChunk[] = chunkJSON(\n contentToProcess as unknown as Record<string, any>,\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 isContentStructured\n ? targetLocaleDictionary.content\n : {\n __INTLAYER_ROOT_PRIMITIVE_CONTENT__:\n targetLocaleDictionary.content,\n },\n chunkContent\n ) as unknown as JSON;\n\n const executeTranslation = async () => {\n return await retryManager(\n async () => {\n let translationResult: any;\n\n if (aiClient && aiConfig) {\n 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 } else {\n translationResult = await intlayerAPI.ai\n .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 .then((result) => result.data);\n }\n\n if (!translationResult?.fileContent) {\n throw new Error('No content result');\n }\n\n const { isIdentic } = verifyIdenticObjectFormat(\n translationResult.fileContent,\n chunkContent\n );\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 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 mergedContent = mergeChunks(chunkResult);\n\n const merged = {\n ...dictionaryToProcess,\n content: mergedContent,\n };\n\n // For per-locale files, merge the newly translated content with existing target content\n let finalContent = merged.content;\n\n if (!isContentStructured) {\n finalContent = (finalContent as any)\n ?.__INTLAYER_ROOT_PRIMITIVE_CONTENT__;\n }\n\n if (typeof baseUnmergedDictionary.locale === 'string') {\n // Deep merge: existing content + newly translated content\n finalContent = deepMergeContent(\n targetLocaleDictionary.content ?? {},\n finalContent\n );\n }\n\n return [targetLocale, finalContent] 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 (\n baseUnmergedDictionary.locale &&\n (baseUnmergedDictionary.fill === true ||\n baseUnmergedDictionary.fill === undefined) &&\n baseUnmergedDictionary.location === 'local'\n ) {\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:', 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":["getFilterMissingContentPerLocale","ANSIColors","chunkPreset","mergeChunks","deepMergeContent"],"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} 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 { deepMergeContent } from './deepMergeContent';\nimport { getFilterMissingContentPerLocale } from './getFilterMissingContentPerLocale';\nimport type { TranslationTask } from './listTranslationsTasks';\nimport { mergeChunks } from './mergeChunks';\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 let targetLocaleDictionary: Dictionary;\n\n if (typeof baseUnmergedDictionary.locale === 'string') {\n // For per-locale files, the content is already in simple JSON format (not translation nodes)\n // The base dictionary is already the source locale content\n\n // Load the existing target locale dictionary\n const targetLocaleFilePath =\n baseUnmergedDictionary.filePath?.replace(\n new RegExp(`/${task.sourceLocale}/`, 'g'),\n `/${targetLocale}/`\n );\n\n // Find the target locale dictionary in unmerged dictionaries\n const targetUnmergedDictionary = targetLocaleFilePath\n ? unmergedDictionariesRecord[task.dictionaryKey]?.find(\n (dict) =>\n dict.filePath === targetLocaleFilePath &&\n dict.locale === targetLocale\n )\n : undefined;\n\n targetLocaleDictionary = targetUnmergedDictionary ?? {\n key: baseUnmergedDictionary.key,\n content: {},\n filePath: targetLocaleFilePath,\n locale: targetLocale,\n };\n\n // In complete mode, filter out already translated content\n if (mode === 'complete') {\n dictionaryToProcess = getFilterMissingContentPerLocale(\n dictionaryToProcess,\n targetUnmergedDictionary\n );\n }\n } else {\n // For multilingual dictionaries\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 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: 18 }\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 isContentStructured =\n (typeof dictionaryToProcess.content === 'object' &&\n dictionaryToProcess.content !== null) ||\n Array.isArray(dictionaryToProcess.content);\n\n const contentToProcess = isContentStructured\n ? dictionaryToProcess.content\n : {\n __INTLAYER_ROOT_PRIMITIVE_CONTENT__:\n dictionaryToProcess.content,\n };\n\n const chunkedJsonContent: JsonChunk[] = chunkJSON(\n contentToProcess as unknown as Record<string, any>,\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 isContentStructured\n ? targetLocaleDictionary.content\n : {\n __INTLAYER_ROOT_PRIMITIVE_CONTENT__:\n targetLocaleDictionary.content,\n },\n chunkContent\n ) as unknown as JSON;\n\n const executeTranslation = async () => {\n return await retryManager(\n async () => {\n let translationResult: any;\n\n if (aiClient && aiConfig) {\n 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 } else {\n translationResult = await intlayerAPI.ai\n .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 .then((result) => result.data);\n }\n\n if (!translationResult?.fileContent) {\n throw new Error('No content result');\n }\n\n const { isIdentic } = verifyIdenticObjectFormat(\n translationResult.fileContent,\n chunkContent\n );\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 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 mergedContent = mergeChunks(chunkResult);\n\n const merged = {\n ...dictionaryToProcess,\n content: mergedContent,\n };\n\n // For per-locale files, merge the newly translated content with existing target content\n let finalContent = merged.content;\n\n if (!isContentStructured) {\n finalContent = (finalContent as any)\n ?.__INTLAYER_ROOT_PRIMITIVE_CONTENT__;\n }\n\n if (typeof baseUnmergedDictionary.locale === 'string') {\n // Deep merge: existing content + newly translated content\n finalContent = deepMergeContent(\n targetLocaleDictionary.content ?? {},\n finalContent\n );\n }\n\n return [targetLocale, finalContent] 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 (\n baseUnmergedDictionary.locale &&\n (baseUnmergedDictionary.fill === true ||\n baseUnmergedDictionary.fill === undefined) &&\n baseUnmergedDictionary.location === 'local'\n ) {\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:', 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeFill.cjs","names":["
|
|
1
|
+
{"version":3,"file":"writeFill.cjs","names":["getAvailableLocalesInDictionary","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';\nimport { getAvailableLocalesInDictionary } from './getAvailableLocalesInDictionary';\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 fillOptions: Fill | undefined =\n contentDeclarationFile.fill ?? configuration.dictionary?.fill ?? true;\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 requestedLocales: Locale[] = (\n outputLocales ?? configuration.internationalization.locales\n ).filter((locale) => !parentLocales?.includes(locale));\n\n // Get locales that actually have translations in the content\n const availableLocales = getAvailableLocalesInDictionary(\n contentDeclarationFile\n );\n\n // Only write files for locales that have actual translations\n const localeList = requestedLocales.filter((locale) =>\n availableLocales.includes(locale)\n );\n\n if (localeList.length === 0) {\n appLogger(\n `No translations available for dictionary '${colorizeKey(fullDictionary.key)}'`,\n {\n level: 'info',\n }\n );\n return;\n }\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":";;;;;;;;;AAYA,MAAa,YAAY,OACvB,wBACA,eACA,eACA,kBACG;CACH,MAAM,+CAAyB,cAAc;CAG7C,MAAM,mEAF+B,cAAc,CAEf,uBAAuB;CAE3D,MAAM,EAAE,aAAa;AAErB,KAAI,CAAC,UAAU;AACb,YAAU,qCAAqC,EAC7C,OAAO,SACR,CAAC;AACF;;CAGF,MAAM,cACJ,uBAAuB,QAAQ,cAAc,YAAY,QAAQ;AAEnE,KAAK,gBAA4B,OAAO;AACtC,YACE,gEAA0C,eAAe,IAAI,CAAC,IAC9D,EACE,OAAO,QACR,CACF;AACD;;CAGF,MAAM,oBACJ,iBAAiB,cAAc,qBAAqB,SACpD,QAAQ,WAAW,CAAC,eAAe,SAAS,OAAO,CAAC;CAGtD,MAAM,mBAAmBA,6EACvB,uBACD;CAGD,MAAM,aAAa,iBAAiB,QAAQ,WAC1C,iBAAiB,SAAS,OAAO,CAClC;AAED,KAAI,WAAW,WAAW,GAAG;AAC3B,YACE,+EAAyD,eAAe,IAAI,CAAC,IAC7E,EACE,OAAO,QACR,CACF;AACD;;CAGF,MAAM,WAAuBC,2CAC3B,aACA,YACA,UACA,eAAe,KACf,cACD;AAED,YAAW,MAAM,UAAU,UAAU;AACnC,MAAI,CAAC,OAAO,UAAU;AACpB,aACE,iGAA2E,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,wDACE;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,qFAA+D,eAAe,IAAI,CAAC,kDAA0B,OAAO,SAAS,CAAC,mDAA2B,aAAa,IACtK,EACE,OAAO,QACR,CACF;QAED,WACE,0EAAoD,eAAe,IAAI,CAAC,kDAA0B,OAAO,SAAS,IAClH,EACE,OAAO,QACR,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"liveSync.cjs","names":["parallelProcess: ParallelHandle | null","eventListener: IntlayerEventListener | null","IntlayerEventListener","packageJson"],"sources":["../../src/liveSync.ts"],"sourcesContent":["import { createServer } from 'node:http';\n// @ts-ignore: @intlayer/backend is not built yet\nimport type { DictionaryAPI } from '@intlayer/backend';\nimport {\n buildDictionary,\n type ParallelHandle,\n runParallel,\n} from '@intlayer/chokidar';\nimport type { GetConfigurationOptions } from '@intlayer/config';\nimport { getAppLogger, getConfiguration } from '@intlayer/config';\nimport packageJson from '@intlayer/config/package.json';\nimport { getLocalizedContent } from '@intlayer/core';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { IntlayerConfig } from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { IntlayerEventListener } from './IntlayerEventListener';\n\ntype LiveSyncOptions = {\n with?: string | string[];\n configOptions?: GetConfigurationOptions;\n};\n\nconst writeDictionary = async (\n dictionary: DictionaryAPI,\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n appLogger(`Writing dictionary ${dictionary.key}`);\n await buildDictionary([dictionary], configuration);\n};\n\nexport const liveSync = async (options?: LiveSyncOptions) => {\n const configuration = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(configuration);\n\n const { liveSyncPort, liveSyncURL } = configuration.editor;\n\n let parallelProcess: ParallelHandle | null = null;\n let eventListener: IntlayerEventListener | null = null;\n let _isHotReloadConnected = false;\n let _connectionStatus = 'disconnected'; // 'connected', 'connecting', 'reconnecting', 'disconnected', 'error'\n\n // Start the parallel process if provided\n if (options?.with) {\n parallelProcess = runParallel(options.with);\n // Handle the promise to avoid unhandled rejection\n parallelProcess.result.catch(() => {\n // Parallel process failed or was terminated\n });\n }\n\n // Initialize the event listener for hot reload if configured\n if (\n configuration.editor.liveSync &&\n configuration.editor.backendURL &&\n configuration.editor.clientId &&\n configuration.editor.clientSecret\n ) {\n eventListener = new IntlayerEventListener(configuration);\n _connectionStatus = 'connecting';\n\n // Set up connection callbacks\n eventListener.onConnectionOpen = () => {\n _connectionStatus = 'connected';\n _isHotReloadConnected = true;\n appLogger('Live sync connection established');\n };\n\n eventListener.onConnectionError = (error) => {\n _connectionStatus = 'error';\n _isHotReloadConnected = false;\n const errorEvent = error as any;\n appLogger(\n `Live sync connection error: ${errorEvent.message ?? 'Unknown error'}`,\n {\n level: 'warn',\n }\n );\n\n // If this is a \"terminated: other side closed\" error, it's likely a server restart\n if (\n errorEvent.message?.includes('terminated') ||\n errorEvent.message?.includes('closed')\n ) {\n appLogger(\n 'Server connection was terminated, automatic reconnection will be attempted...',\n {\n level: 'info',\n }\n );\n _connectionStatus = 'reconnecting';\n }\n };\n\n // Set up dictionary change callbacks\n eventListener.onDictionaryAdded = (dictionary) =>\n writeDictionary(dictionary, configuration);\n eventListener.onDictionaryChange = (dictionary) =>\n writeDictionary(dictionary, configuration);\n eventListener.onDictionaryDeleted = (dictionary) =>\n writeDictionary(dictionary, configuration);\n\n try {\n await eventListener.initialize();\n } catch (error) {\n _connectionStatus = 'error';\n _isHotReloadConnected = false;\n appLogger('Failed to initialize IntlayerEventListener:', {\n level: 'error',\n });\n appLogger(\n `Error: ${error instanceof Error ? error.message : String(error)}`,\n {\n level: 'error',\n }\n );\n }\n } else if (!configuration.editor.liveSync) {\n appLogger(\n 'Hot reload is disabled. Please enable it in the configuration (editor.liveSync).'\n );\n } else if (\n !configuration.editor.clientId ||\n !configuration.editor.clientSecret\n ) {\n appLogger(\n 'Missing client credentials for hot reload. Please configure clientId and clientSecret'\n );\n }\n\n const server = createServer(async (req, res) => {\n // Handle CORS preflight requests\n if (req.method === 'OPTIONS') {\n res.writeHead(200, {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n\n res.end();\n return;\n }\n\n if (req.url?.startsWith('/dictionaries')) {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n const dictionaries = getDictionaries(configuration);\n\n const prefix = '/dictionaries/';\n if (req.url.startsWith(prefix)) {\n const [key, locale] = decodeURIComponent(req.url)\n .slice(prefix.length)\n .split('/');\n\n const dictionary = dictionaries[key] ?? null;\n\n if (locale) {\n // @ts-ignore Type instantiation is excessively deep and possibly infinite\n const sourceLocaleContent = getLocalizedContent(\n dictionary.content,\n locale,\n {\n dictionaryKey: key,\n keyPath: [],\n }\n );\n\n res.end(JSON.stringify(sourceLocaleContent));\n return;\n }\n\n res.end(JSON.stringify(dictionary));\n return;\n }\n\n res.end(JSON.stringify(dictionaries));\n return;\n }\n\n if (req.url?.startsWith('/unmerged_dictionaries')) {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n const unmergedDictionaries = getUnmergedDictionaries(configuration);\n\n const prefix = '/unmerged_dictionaries/';\n if (req.url.startsWith(prefix)) {\n const key = decodeURIComponent(req.url.slice(prefix.length));\n const one = unmergedDictionaries[key] ?? null;\n\n res.end(JSON.stringify(one));\n return;\n }\n\n res.end(JSON.stringify(unmergedDictionaries));\n return;\n }\n\n if (req.url === '/configuration') {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n res.end(JSON.stringify(configuration));\n return;\n }\n\n if (req.url === '/health') {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n });\n res.end(JSON.stringify({ status: 'ok' }));\n return;\n }\n\n res.end('Not found');\n return;\n });\n\n const getLiveSyncParam = () => {\n if (!configuration.editor.liveSync) return '\\x1b[31m✗ Disabled\\x1b[0m';\n\n return '\\x1b[32m✓ Enabled\\x1b[0m';\n };\n server.listen(liveSyncPort, () => {\n console.log(`\n \\x1b[1;90mINTLAYER v${packageJson.version}\\x1b[0m\n \n Live server running at: \\x1b[90m${liveSyncURL}\\x1b[0m\n - Backend URL: \\x1b[90m${configuration.editor.backendURL ?? '-'}\\x1b[0m\n - Live sync: ${getLiveSyncParam()}\n - Parallel process: ${options?.with ? `\\x1b[90m${Array.isArray(options.with) ? options.with.join(' ') : options.with}\\x1b[0m` : '-'}\n - Access key: ${configuration.editor.clientId ?? '-'}\n `);\n });\n\n // Cleanup function to terminate child process and event listener when the main process exits\n const cleanup = () => {\n // Clean up event listener\n if (eventListener) {\n appLogger('Closing SSE connection...');\n eventListener.cleanup();\n }\n\n if (parallelProcess) {\n parallelProcess.kill();\n }\n\n server.close(() => {\n appLogger('Live sync server stopped');\n process.exit(0);\n });\n };\n\n // Handle process termination signals\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n process.on('exit', cleanup);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"liveSync.cjs","names":["IntlayerEventListener","packageJson"],"sources":["../../src/liveSync.ts"],"sourcesContent":["import { createServer } from 'node:http';\n// @ts-ignore: @intlayer/backend is not built yet\nimport type { DictionaryAPI } from '@intlayer/backend';\nimport {\n buildDictionary,\n type ParallelHandle,\n runParallel,\n} from '@intlayer/chokidar';\nimport type { GetConfigurationOptions } from '@intlayer/config';\nimport { getAppLogger, getConfiguration } from '@intlayer/config';\nimport packageJson from '@intlayer/config/package.json';\nimport { getLocalizedContent } from '@intlayer/core';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { IntlayerConfig } from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { IntlayerEventListener } from './IntlayerEventListener';\n\ntype LiveSyncOptions = {\n with?: string | string[];\n configOptions?: GetConfigurationOptions;\n};\n\nconst writeDictionary = async (\n dictionary: DictionaryAPI,\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n appLogger(`Writing dictionary ${dictionary.key}`);\n await buildDictionary([dictionary], configuration);\n};\n\nexport const liveSync = async (options?: LiveSyncOptions) => {\n const configuration = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(configuration);\n\n const { liveSyncPort, liveSyncURL } = configuration.editor;\n\n let parallelProcess: ParallelHandle | null = null;\n let eventListener: IntlayerEventListener | null = null;\n let _isHotReloadConnected = false;\n let _connectionStatus = 'disconnected'; // 'connected', 'connecting', 'reconnecting', 'disconnected', 'error'\n\n // Start the parallel process if provided\n if (options?.with) {\n parallelProcess = runParallel(options.with);\n // Handle the promise to avoid unhandled rejection\n parallelProcess.result.catch(() => {\n // Parallel process failed or was terminated\n });\n }\n\n // Initialize the event listener for hot reload if configured\n if (\n configuration.editor.liveSync &&\n configuration.editor.backendURL &&\n configuration.editor.clientId &&\n configuration.editor.clientSecret\n ) {\n eventListener = new IntlayerEventListener(configuration);\n _connectionStatus = 'connecting';\n\n // Set up connection callbacks\n eventListener.onConnectionOpen = () => {\n _connectionStatus = 'connected';\n _isHotReloadConnected = true;\n appLogger('Live sync connection established');\n };\n\n eventListener.onConnectionError = (error) => {\n _connectionStatus = 'error';\n _isHotReloadConnected = false;\n const errorEvent = error as any;\n appLogger(\n `Live sync connection error: ${errorEvent.message ?? 'Unknown error'}`,\n {\n level: 'warn',\n }\n );\n\n // If this is a \"terminated: other side closed\" error, it's likely a server restart\n if (\n errorEvent.message?.includes('terminated') ||\n errorEvent.message?.includes('closed')\n ) {\n appLogger(\n 'Server connection was terminated, automatic reconnection will be attempted...',\n {\n level: 'info',\n }\n );\n _connectionStatus = 'reconnecting';\n }\n };\n\n // Set up dictionary change callbacks\n eventListener.onDictionaryAdded = (dictionary) =>\n writeDictionary(dictionary, configuration);\n eventListener.onDictionaryChange = (dictionary) =>\n writeDictionary(dictionary, configuration);\n eventListener.onDictionaryDeleted = (dictionary) =>\n writeDictionary(dictionary, configuration);\n\n try {\n await eventListener.initialize();\n } catch (error) {\n _connectionStatus = 'error';\n _isHotReloadConnected = false;\n appLogger('Failed to initialize IntlayerEventListener:', {\n level: 'error',\n });\n appLogger(\n `Error: ${error instanceof Error ? error.message : String(error)}`,\n {\n level: 'error',\n }\n );\n }\n } else if (!configuration.editor.liveSync) {\n appLogger(\n 'Hot reload is disabled. Please enable it in the configuration (editor.liveSync).'\n );\n } else if (\n !configuration.editor.clientId ||\n !configuration.editor.clientSecret\n ) {\n appLogger(\n 'Missing client credentials for hot reload. Please configure clientId and clientSecret'\n );\n }\n\n const server = createServer(async (req, res) => {\n // Handle CORS preflight requests\n if (req.method === 'OPTIONS') {\n res.writeHead(200, {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n\n res.end();\n return;\n }\n\n if (req.url?.startsWith('/dictionaries')) {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n const dictionaries = getDictionaries(configuration);\n\n const prefix = '/dictionaries/';\n if (req.url.startsWith(prefix)) {\n const [key, locale] = decodeURIComponent(req.url)\n .slice(prefix.length)\n .split('/');\n\n const dictionary = dictionaries[key] ?? null;\n\n if (locale) {\n // @ts-ignore Type instantiation is excessively deep and possibly infinite\n const sourceLocaleContent = getLocalizedContent(\n dictionary.content,\n locale,\n {\n dictionaryKey: key,\n keyPath: [],\n }\n );\n\n res.end(JSON.stringify(sourceLocaleContent));\n return;\n }\n\n res.end(JSON.stringify(dictionary));\n return;\n }\n\n res.end(JSON.stringify(dictionaries));\n return;\n }\n\n if (req.url?.startsWith('/unmerged_dictionaries')) {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n const unmergedDictionaries = getUnmergedDictionaries(configuration);\n\n const prefix = '/unmerged_dictionaries/';\n if (req.url.startsWith(prefix)) {\n const key = decodeURIComponent(req.url.slice(prefix.length));\n const one = unmergedDictionaries[key] ?? null;\n\n res.end(JSON.stringify(one));\n return;\n }\n\n res.end(JSON.stringify(unmergedDictionaries));\n return;\n }\n\n if (req.url === '/configuration') {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n res.end(JSON.stringify(configuration));\n return;\n }\n\n if (req.url === '/health') {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n });\n res.end(JSON.stringify({ status: 'ok' }));\n return;\n }\n\n res.end('Not found');\n return;\n });\n\n const getLiveSyncParam = () => {\n if (!configuration.editor.liveSync) return '\\x1b[31m✗ Disabled\\x1b[0m';\n\n return '\\x1b[32m✓ Enabled\\x1b[0m';\n };\n server.listen(liveSyncPort, () => {\n console.log(`\n \\x1b[1;90mINTLAYER v${packageJson.version}\\x1b[0m\n \n Live server running at: \\x1b[90m${liveSyncURL}\\x1b[0m\n - Backend URL: \\x1b[90m${configuration.editor.backendURL ?? '-'}\\x1b[0m\n - Live sync: ${getLiveSyncParam()}\n - Parallel process: ${options?.with ? `\\x1b[90m${Array.isArray(options.with) ? options.with.join(' ') : options.with}\\x1b[0m` : '-'}\n - Access key: ${configuration.editor.clientId ?? '-'}\n `);\n });\n\n // Cleanup function to terminate child process and event listener when the main process exits\n const cleanup = () => {\n // Clean up event listener\n if (eventListener) {\n appLogger('Closing SSE connection...');\n eventListener.cleanup();\n }\n\n if (parallelProcess) {\n parallelProcess.kill();\n }\n\n server.close(() => {\n appLogger('Live sync server stopped');\n process.exit(0);\n });\n };\n\n // Handle process termination signals\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n process.on('exit', cleanup);\n};\n"],"mappings":"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"}
|
package/dist/cjs/pull.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pull.cjs","names":["checkCMSAuth","distantDictionariesUpdateTimeStamp: Record<string, number>","remoteDictionariesRecord: Record<string, any>","dictionariesStatuses: DictionariesStatus[]","PullLogger","successfullyFetchedDictionaries: Dictionary[]","sourceDictionary: Dictionary | undefined","ANSIColors"],"sources":["../../src/pull.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n type DictionaryStatus,\n parallelize,\n writeContentDeclaration,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n getProjectRequire,\n} from '@intlayer/config';\nimport type { Dictionary } from '@intlayer/types';\nimport { PullLogger, type PullStatus } from './push/pullLog';\nimport { checkCMSAuth } from './utils/checkAccess';\n\ntype PullOptions = {\n dictionaries?: string[];\n newDictionariesPath?: string;\n configOptions?: GetConfigurationOptions;\n};\n\ntype DictionariesStatus = {\n dictionaryKey: string;\n status: DictionaryStatus | 'pending' | 'fetching' | 'error';\n error?: Error;\n errorMessage?: string;\n};\n\n/**\n * Fetch distant dictionaries and write them locally,\n * with progress indicators and concurrency control.\n */\nexport const pull = async (options?: PullOptions): Promise<void> => {\n const appLogger = getAppLogger(options?.configOptions?.override);\n\n try {\n const config = getConfiguration(options?.configOptions);\n\n const hasCMSAuth = await checkCMSAuth(config);\n\n if (!hasCMSAuth) return;\n\n const intlayerAPI = getIntlayerAPIProxy(undefined, config);\n\n // Get remote update timestamps map\n const getDictionariesUpdateTimestampResult =\n await intlayerAPI.dictionary.getDictionariesUpdateTimestamp();\n\n if (!getDictionariesUpdateTimestampResult.data) {\n throw new Error('No distant dictionaries found');\n }\n\n let distantDictionariesUpdateTimeStamp: Record<string, number> =\n getDictionariesUpdateTimestampResult.data;\n\n // Optional filtering by requested dictionaries\n if (options?.dictionaries) {\n distantDictionariesUpdateTimeStamp = Object.fromEntries(\n Object.entries(distantDictionariesUpdateTimeStamp).filter(([key]) =>\n options.dictionaries?.includes(key)\n )\n );\n }\n\n // Load local cached remote dictionaries (if any)\n const remoteDictionariesPath = join(\n config.content.mainDir,\n 'remote_dictionaries.cjs'\n );\n const requireFunction = config.build?.require ?? getProjectRequire();\n const remoteDictionariesRecord: Record<string, any> = existsSync(\n remoteDictionariesPath\n )\n ? (requireFunction(remoteDictionariesPath) as any)\n : {};\n\n // Determine which keys need fetching by comparing updatedAt with local cache\n const entries = Object.entries(distantDictionariesUpdateTimeStamp);\n const keysToFetch = entries\n .filter(([key, remoteUpdatedAt]) => {\n if (!remoteUpdatedAt) return true;\n const local = (remoteDictionariesRecord as any)[key];\n if (!local) return true;\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n if (typeof localUpdatedAt !== 'number') return true;\n return remoteUpdatedAt > localUpdatedAt;\n })\n .map(([key]) => key);\n\n const cachedKeys = entries\n .filter(([key, remoteUpdatedAt]) => {\n const local = (remoteDictionariesRecord as any)[key];\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n return (\n typeof localUpdatedAt === 'number' &&\n typeof remoteUpdatedAt === 'number' &&\n localUpdatedAt >= remoteUpdatedAt\n );\n })\n .map(([key]) => key);\n\n // Check if dictionaries list is empty\n if (entries.length === 0) {\n appLogger('No dictionaries to fetch', {\n level: 'error',\n });\n return;\n }\n\n appLogger('Fetching dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] = [\n ...cachedKeys.map((dictionaryKey) => ({\n dictionaryKey,\n status: 'imported' as DictionaryStatus,\n })),\n ...keysToFetch.map((dictionaryKey) => ({\n dictionaryKey,\n status: 'pending' as const,\n })),\n ];\n\n // Initialize aggregated logger\n const logger = new PullLogger();\n logger.update(\n dictionariesStatuses.map<PullStatus>((s) => ({\n dictionaryKey: s.dictionaryKey,\n status: s.status,\n }))\n );\n\n const successfullyFetchedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n const isCached =\n statusObj.status === 'imported' || statusObj.status === 'up-to-date';\n\n if (!isCached) {\n statusObj.status = 'fetching';\n logger.update([\n { dictionaryKey: statusObj.dictionaryKey, status: 'fetching' },\n ]);\n }\n\n try {\n let sourceDictionary: Dictionary | undefined;\n\n if (isCached) {\n sourceDictionary = remoteDictionariesRecord[\n statusObj.dictionaryKey\n ] as Dictionary | undefined;\n }\n\n if (!sourceDictionary) {\n // Fetch the dictionary\n const getDictionaryResult =\n await intlayerAPI.dictionary.getDictionary(statusObj.dictionaryKey);\n\n sourceDictionary = getDictionaryResult.data as Dictionary | undefined;\n }\n\n if (!sourceDictionary) {\n throw new Error(\n `Dictionary ${statusObj.dictionaryKey} not found on remote`\n );\n }\n\n // Now, write the dictionary to local file\n const { status } = await writeContentDeclaration(\n sourceDictionary,\n config,\n options\n );\n\n statusObj.status = status;\n logger.update([{ dictionaryKey: statusObj.dictionaryKey, status }]);\n\n successfullyFetchedDictionaries.push(sourceDictionary);\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error fetching dictionary ${statusObj.dictionaryKey}: ${error}`;\n logger.update([\n { dictionaryKey: statusObj.dictionaryKey, status: 'error' },\n ]);\n }\n };\n\n // Process dictionaries in parallel with concurrency limit\n await parallelize(dictionariesStatuses, processDictionary, 5);\n\n // Stop the logger and render final state\n logger.finish();\n\n // Per-dictionary summary\n const iconFor = (status: DictionariesStatus['status']) => {\n switch (status) {\n case 'fetched':\n case 'imported':\n case 'updated':\n case 'up-to-date':\n case 'reimported in JSON':\n case 'new content file':\n return '✔';\n case 'error':\n return '✖';\n default:\n return '⏲';\n }\n };\n\n const colorFor = (status: DictionariesStatus['status']) => {\n switch (status) {\n case 'fetched':\n case 'imported':\n case 'updated':\n case 'up-to-date':\n return ANSIColors.GREEN;\n case 'reimported in JSON':\n case 'new content file':\n return ANSIColors.YELLOW;\n case 'error':\n return ANSIColors.RED;\n default:\n return ANSIColors.BLUE;\n }\n };\n\n for (const s of dictionariesStatuses) {\n const icon = iconFor(s.status);\n const color = colorFor(s.status);\n appLogger(\n ` - ${s.dictionaryKey} ${ANSIColors.GREY}[${color}${icon} ${s.status}${ANSIColors.GREY}]${ANSIColors.RESET}`\n );\n }\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n appLogger(statusObj.errorMessage, {\n level: 'error',\n });\n }\n }\n } catch (error) {\n appLogger(error, {\n level: 'error',\n });\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pull.cjs","names":["checkCMSAuth","PullLogger","ANSIColors"],"sources":["../../src/pull.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n type DictionaryStatus,\n parallelize,\n writeContentDeclaration,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n getProjectRequire,\n} from '@intlayer/config';\nimport type { Dictionary } from '@intlayer/types';\nimport { PullLogger, type PullStatus } from './push/pullLog';\nimport { checkCMSAuth } from './utils/checkAccess';\n\ntype PullOptions = {\n dictionaries?: string[];\n newDictionariesPath?: string;\n configOptions?: GetConfigurationOptions;\n};\n\ntype DictionariesStatus = {\n dictionaryKey: string;\n status: DictionaryStatus | 'pending' | 'fetching' | 'error';\n error?: Error;\n errorMessage?: string;\n};\n\n/**\n * Fetch distant dictionaries and write them locally,\n * with progress indicators and concurrency control.\n */\nexport const pull = async (options?: PullOptions): Promise<void> => {\n const appLogger = getAppLogger(options?.configOptions?.override);\n\n try {\n const config = getConfiguration(options?.configOptions);\n\n const hasCMSAuth = await checkCMSAuth(config);\n\n if (!hasCMSAuth) return;\n\n const intlayerAPI = getIntlayerAPIProxy(undefined, config);\n\n // Get remote update timestamps map\n const getDictionariesUpdateTimestampResult =\n await intlayerAPI.dictionary.getDictionariesUpdateTimestamp();\n\n if (!getDictionariesUpdateTimestampResult.data) {\n throw new Error('No distant dictionaries found');\n }\n\n let distantDictionariesUpdateTimeStamp: Record<string, number> =\n getDictionariesUpdateTimestampResult.data;\n\n // Optional filtering by requested dictionaries\n if (options?.dictionaries) {\n distantDictionariesUpdateTimeStamp = Object.fromEntries(\n Object.entries(distantDictionariesUpdateTimeStamp).filter(([key]) =>\n options.dictionaries?.includes(key)\n )\n );\n }\n\n // Load local cached remote dictionaries (if any)\n const remoteDictionariesPath = join(\n config.content.mainDir,\n 'remote_dictionaries.cjs'\n );\n const requireFunction = config.build?.require ?? getProjectRequire();\n const remoteDictionariesRecord: Record<string, any> = existsSync(\n remoteDictionariesPath\n )\n ? (requireFunction(remoteDictionariesPath) as any)\n : {};\n\n // Determine which keys need fetching by comparing updatedAt with local cache\n const entries = Object.entries(distantDictionariesUpdateTimeStamp);\n const keysToFetch = entries\n .filter(([key, remoteUpdatedAt]) => {\n if (!remoteUpdatedAt) return true;\n const local = (remoteDictionariesRecord as any)[key];\n if (!local) return true;\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n if (typeof localUpdatedAt !== 'number') return true;\n return remoteUpdatedAt > localUpdatedAt;\n })\n .map(([key]) => key);\n\n const cachedKeys = entries\n .filter(([key, remoteUpdatedAt]) => {\n const local = (remoteDictionariesRecord as any)[key];\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n return (\n typeof localUpdatedAt === 'number' &&\n typeof remoteUpdatedAt === 'number' &&\n localUpdatedAt >= remoteUpdatedAt\n );\n })\n .map(([key]) => key);\n\n // Check if dictionaries list is empty\n if (entries.length === 0) {\n appLogger('No dictionaries to fetch', {\n level: 'error',\n });\n return;\n }\n\n appLogger('Fetching dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] = [\n ...cachedKeys.map((dictionaryKey) => ({\n dictionaryKey,\n status: 'imported' as DictionaryStatus,\n })),\n ...keysToFetch.map((dictionaryKey) => ({\n dictionaryKey,\n status: 'pending' as const,\n })),\n ];\n\n // Initialize aggregated logger\n const logger = new PullLogger();\n logger.update(\n dictionariesStatuses.map<PullStatus>((s) => ({\n dictionaryKey: s.dictionaryKey,\n status: s.status,\n }))\n );\n\n const successfullyFetchedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n const isCached =\n statusObj.status === 'imported' || statusObj.status === 'up-to-date';\n\n if (!isCached) {\n statusObj.status = 'fetching';\n logger.update([\n { dictionaryKey: statusObj.dictionaryKey, status: 'fetching' },\n ]);\n }\n\n try {\n let sourceDictionary: Dictionary | undefined;\n\n if (isCached) {\n sourceDictionary = remoteDictionariesRecord[\n statusObj.dictionaryKey\n ] as Dictionary | undefined;\n }\n\n if (!sourceDictionary) {\n // Fetch the dictionary\n const getDictionaryResult =\n await intlayerAPI.dictionary.getDictionary(statusObj.dictionaryKey);\n\n sourceDictionary = getDictionaryResult.data as Dictionary | undefined;\n }\n\n if (!sourceDictionary) {\n throw new Error(\n `Dictionary ${statusObj.dictionaryKey} not found on remote`\n );\n }\n\n // Now, write the dictionary to local file\n const { status } = await writeContentDeclaration(\n sourceDictionary,\n config,\n options\n );\n\n statusObj.status = status;\n logger.update([{ dictionaryKey: statusObj.dictionaryKey, status }]);\n\n successfullyFetchedDictionaries.push(sourceDictionary);\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error fetching dictionary ${statusObj.dictionaryKey}: ${error}`;\n logger.update([\n { dictionaryKey: statusObj.dictionaryKey, status: 'error' },\n ]);\n }\n };\n\n // Process dictionaries in parallel with concurrency limit\n await parallelize(dictionariesStatuses, processDictionary, 5);\n\n // Stop the logger and render final state\n logger.finish();\n\n // Per-dictionary summary\n const iconFor = (status: DictionariesStatus['status']) => {\n switch (status) {\n case 'fetched':\n case 'imported':\n case 'updated':\n case 'up-to-date':\n case 'reimported in JSON':\n case 'new content file':\n return '✔';\n case 'error':\n return '✖';\n default:\n return '⏲';\n }\n };\n\n const colorFor = (status: DictionariesStatus['status']) => {\n switch (status) {\n case 'fetched':\n case 'imported':\n case 'updated':\n case 'up-to-date':\n return ANSIColors.GREEN;\n case 'reimported in JSON':\n case 'new content file':\n return ANSIColors.YELLOW;\n case 'error':\n return ANSIColors.RED;\n default:\n return ANSIColors.BLUE;\n }\n };\n\n for (const s of dictionariesStatuses) {\n const icon = iconFor(s.status);\n const color = colorFor(s.status);\n appLogger(\n ` - ${s.dictionaryKey} ${ANSIColors.GREY}[${color}${icon} ${s.status}${ANSIColors.GREY}]${ANSIColors.RESET}`\n );\n }\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n appLogger(statusObj.errorMessage, {\n level: 'error',\n });\n }\n }\n } catch (error) {\n appLogger(error, {\n level: 'error',\n });\n }\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pullLog.cjs","names":["spinnerFrames","
|
|
1
|
+
{"version":3,"file":"pullLog.cjs","names":["spinnerFrames","ANSIColors"],"sources":["../../../src/push/pullLog.ts"],"sourcesContent":["import type { DictionaryStatus } from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colorize,\n getConfiguration,\n spinnerFrames,\n} from '@intlayer/config';\n\nexport type PullStatus = {\n dictionaryKey: string;\n status: DictionaryStatus | 'pending' | 'fetching';\n errorMessage?: string;\n};\n\nexport class PullLogger {\n private statuses: PullStatus[] = [];\n private spinnerTimer: NodeJS.Timeout | null = null;\n private spinnerIndex = 0;\n private renderedLines = 0;\n private readonly spinnerFrames = spinnerFrames;\n private isFinished = false;\n private readonly prefix: string;\n private lastRenderedState: string = '';\n\n constructor() {\n const configuration = getConfiguration();\n this.prefix = configuration.log.prefix;\n }\n\n update(newStatuses: PullStatus[]) {\n if (this.isFinished) return;\n for (const status of newStatuses) {\n const index = this.statuses.findIndex(\n (s) => s.dictionaryKey === status.dictionaryKey\n );\n if (index >= 0) {\n this.statuses[index] = status;\n } else {\n this.statuses.push(status);\n }\n }\n\n this.startSpinner();\n this.render();\n }\n\n finish() {\n this.isFinished = true;\n this.stopSpinner();\n this.render();\n }\n\n private startSpinner() {\n if (this.spinnerTimer || this.isFinished) return;\n this.spinnerTimer = setInterval(() => {\n this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;\n this.render();\n }, 100);\n }\n\n private stopSpinner() {\n if (!this.spinnerTimer) return;\n clearInterval(this.spinnerTimer);\n this.spinnerTimer = null;\n }\n\n private render() {\n const { total, done, success, errors } = this.computeProgress();\n\n const frame = this.spinnerFrames[this.spinnerIndex];\n const lines: string[] = [];\n\n const isDone = done === total;\n const progressLabel = `dictionaries: ${done}/${total}`;\n const details: string[] = [];\n if (success > 0) details.push(`ok: ${success}`);\n if (errors > 0) details.push(colorize(`errors: ${errors}`, ANSIColors.RED));\n\n const suffix = details.length > 0 ? ` (${details.join(', ')})` : '';\n\n if (isDone) {\n lines.push(\n `${this.prefix} ${colorize('✔', ANSIColors.GREEN)} fetched ${progressLabel}${suffix}`\n );\n } else {\n lines.push(\n `${this.prefix} ${colorize(frame, ANSIColors.BLUE)} fetching ${progressLabel}${suffix}`\n );\n }\n\n const currentState = lines.join('\\n');\n if (currentState === this.lastRenderedState) {\n return;\n }\n this.lastRenderedState = currentState;\n\n if (this.renderedLines > 0) {\n process.stdout.write(`\\x1b[${this.renderedLines}F`);\n }\n\n const totalLinesToClear = Math.max(this.renderedLines, lines.length);\n for (let i = 0; i < totalLinesToClear; i++) {\n process.stdout.write('\\x1b[2K');\n const line = lines[i];\n if (line !== undefined) {\n process.stdout.write(line);\n }\n process.stdout.write('\\n');\n }\n\n this.renderedLines = lines.length;\n }\n\n private computeProgress() {\n const keys = new Set(this.statuses.map((s) => s.dictionaryKey));\n\n const doneSet = new Set<DictionaryStatus | 'error'>([\n 'fetched',\n 'imported',\n 'updated',\n 'up-to-date',\n 'reimported in JSON',\n 'new content file',\n 'error',\n ] as const);\n\n const successesSet = new Set<DictionaryStatus>([\n 'fetched',\n 'imported',\n 'updated',\n 'up-to-date',\n 'reimported in JSON',\n 'new content file',\n ] as const);\n\n const done = this.statuses.filter((s) =>\n doneSet.has(s.status as any)\n ).length;\n const success = this.statuses.filter((s) =>\n successesSet.has(s.status as any)\n ).length;\n const errors = this.statuses.filter((s) => s.status === 'error').length;\n\n return {\n total: keys.size,\n done,\n success,\n errors,\n } as const;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push.cjs","names":["ANSIColors","checkCMSAuth","dictionaries: Dictionary[]","existingDictionariesKeys: string[]","dictionariesStatuses: DictionariesStatus[]","PushLogger","successfullyPushedDictionaries: Dictionary[]","readline","filePathsSet: Set<string>","fsPromises"],"sources":["../../../src/push/push.ts"],"sourcesContent":["import * as fsPromises from 'node:fs/promises';\nimport { join } from 'node:path';\nimport * as readline from 'node:readline';\nimport { getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n formatPath,\n type ListGitFilesOptions,\n listGitFiles,\n parallelize,\n prepareIntlayer,\n writeContentDeclaration,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport type { Dictionary } from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { PushLogger, type PushStatus } from '../pushLog';\nimport { checkCMSAuth } from '../utils/checkAccess';\n\ntype PushOptions = {\n deleteLocaleDictionary?: boolean;\n keepLocaleDictionary?: boolean;\n dictionaries?: string[];\n gitOptions?: ListGitFilesOptions;\n configOptions?: GetConfigurationOptions;\n build?: boolean;\n};\n\ntype DictionariesStatus = {\n dictionary: Dictionary;\n status: 'pending' | 'pushing' | 'modified' | 'pushed' | 'unknown' | 'error';\n error?: Error;\n errorMessage?: string;\n};\n\n// Print per-dictionary summary similar to loadDictionaries\nconst statusIconsAndColors = {\n pushed: { icon: '✔', color: ANSIColors.GREEN },\n modified: { icon: '✔', color: ANSIColors.GREEN },\n error: { icon: '✖', color: ANSIColors.RED },\n default: { icon: '⏲', color: ANSIColors.BLUE },\n};\n\nconst getIconAndColor = (status: DictionariesStatus['status']) => {\n return (\n statusIconsAndColors[status as keyof typeof statusIconsAndColors] ??\n statusIconsAndColors.default\n );\n};\n\n/**\n * Get all local dictionaries and push them simultaneously.\n */\nexport const push = async (options?: PushOptions): Promise<void> => {\n const config = getConfiguration(options?.configOptions);\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 try {\n const hasCMSAuth = await checkCMSAuth(config);\n\n if (!hasCMSAuth) return;\n\n const intlayerAPI = getIntlayerAPIProxy(undefined, config);\n\n const unmergedDictionariesRecord = getUnmergedDictionaries(config);\n let dictionaries: Dictionary[] = Object.values(\n unmergedDictionariesRecord\n ).flat();\n const existingDictionariesKeys: string[] = Object.keys(\n unmergedDictionariesRecord\n );\n\n if (options?.dictionaries) {\n // Check if the provided dictionaries exist\n const noneExistingDictionariesOption = options.dictionaries.filter(\n (dictionaryId) => !existingDictionariesKeys.includes(dictionaryId)\n );\n\n if (noneExistingDictionariesOption.length > 0) {\n appLogger(\n `The following dictionaries do not exist: ${noneExistingDictionariesOption.join(\n ', '\n )} and have been ignored.`,\n {\n level: 'error',\n }\n );\n }\n\n // Filter the dictionaries from the provided list of IDs\n dictionaries = dictionaries.filter((dictionary) =>\n options.dictionaries?.includes(dictionary.key)\n );\n }\n\n if (options?.gitOptions) {\n const gitFiles = await listGitFiles(options.gitOptions);\n\n dictionaries = dictionaries.filter((dictionary) =>\n gitFiles.includes(\n join(config.content.baseDir, dictionary.filePath ?? '')\n )\n );\n }\n\n // Check if the dictionaries list is empty\n if (dictionaries.length === 0) {\n appLogger('No local dictionaries found', {\n level: 'error',\n });\n return;\n }\n\n appLogger('Pushing dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] = dictionaries.map(\n (dictionary) => ({\n dictionary,\n status: 'pending',\n })\n );\n\n // Initialize aggregated logger similar to loadDictionaries\n const logger = new PushLogger();\n logger.update(\n dictionariesStatuses.map<PushStatus>((s) => ({\n dictionaryKey: s.dictionary.key,\n status: 'pending',\n }))\n );\n\n const successfullyPushedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n statusObj.status = 'pushing';\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'pushing' },\n ]);\n\n try {\n const pushResult = await intlayerAPI.dictionary.pushDictionaries([\n statusObj.dictionary,\n ]);\n\n const updatedDictionaries = pushResult.data?.updatedDictionaries ?? [];\n const newDictionaries = pushResult.data?.newDictionaries ?? [];\n\n const allDictionaries = [...updatedDictionaries, ...newDictionaries];\n\n for (const remoteDictionaryData of allDictionaries) {\n const localDictionary = unmergedDictionariesRecord[\n remoteDictionaryData.key\n ]?.find(\n (dictionary) => dictionary.localId === remoteDictionaryData.localId\n );\n\n if (!localDictionary) continue;\n\n await writeContentDeclaration(\n { ...localDictionary, id: remoteDictionaryData.id },\n config\n );\n }\n\n if (\n updatedDictionaries.some(\n (dictionary) => dictionary.key === statusObj.dictionary.key\n )\n ) {\n statusObj.status = 'modified';\n successfullyPushedDictionaries.push(statusObj.dictionary);\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'modified' },\n ]);\n } else if (\n newDictionaries.some(\n (dictionary) => dictionary.key === statusObj.dictionary.key\n )\n ) {\n statusObj.status = 'pushed';\n successfullyPushedDictionaries.push(statusObj.dictionary);\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'pushed' },\n ]);\n } else {\n statusObj.status = 'unknown';\n }\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error pushing dictionary ${statusObj.dictionary.key}: ${error}`;\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'error' },\n ]);\n }\n };\n\n // Process dictionaries in parallel with a concurrency limit (reuse parallelize)\n await parallelize(dictionariesStatuses, processDictionary, 5);\n\n // Stop the logger and render final state\n logger.finish();\n\n for (const dictionaryStatus of dictionariesStatuses) {\n const { icon, color } = getIconAndColor(dictionaryStatus.status);\n appLogger(\n ` - ${dictionaryStatus.dictionary.key} ${ANSIColors.GREY}[${color}${icon} ${dictionaryStatus.status}${ANSIColors.GREY}]${ANSIColors.RESET}`\n );\n }\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n appLogger(statusObj.errorMessage, {\n level: 'error',\n });\n }\n }\n\n // Handle delete or keep options\n const deleteOption = options?.deleteLocaleDictionary;\n const keepOption = options?.keepLocaleDictionary;\n\n if (deleteOption && keepOption) {\n throw new Error(\n 'Cannot specify both --deleteLocaleDictionary and --keepLocaleDictionary options.'\n );\n }\n\n if (deleteOption) {\n // Delete only the successfully pushed dictionaries\n await deleteLocalDictionaries(successfullyPushedDictionaries, options);\n } else if (keepOption) {\n // Do nothing, keep the local dictionaries\n } else {\n // Ask the user\n const answer = await askUser(\n 'Do you want to delete the local dictionaries that were successfully pushed? (yes/no): '\n );\n if (answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y') {\n await deleteLocalDictionaries(successfullyPushedDictionaries, options);\n }\n }\n } catch (error) {\n appLogger(error, {\n level: 'error',\n });\n }\n};\n\nconst askUser = (question: string): Promise<string> => {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n return new Promise((resolve) => {\n rl.question(question, (answer: string) => {\n rl.close();\n resolve(answer);\n });\n });\n};\n\nconst deleteLocalDictionaries = async (\n dictionariesToDelete: Dictionary[],\n options?: PushOptions\n): Promise<void> => {\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config, {\n config: {\n prefix: '',\n },\n });\n\n // Use a Set to collect all unique file paths\n const filePathsSet: Set<string> = new Set();\n\n for (const dictionary of dictionariesToDelete) {\n const { filePath } = dictionary;\n\n if (!filePath) {\n appLogger(`Dictionary ${dictionary.key} does not have a file path`, {\n level: 'error',\n });\n continue;\n }\n\n filePathsSet.add(filePath);\n }\n\n for (const filePath of filePathsSet) {\n try {\n const stats = await fsPromises.lstat(filePath);\n\n if (stats.isFile()) {\n await fsPromises.unlink(filePath);\n appLogger(`Deleted file ${formatPath(filePath)}`, {});\n } else if (stats.isDirectory()) {\n appLogger(`Path is a directory ${formatPath(filePath)}, skipping.`, {});\n } else {\n appLogger(\n `Unknown file type for ${formatPath(filePath)}, skipping.`,\n {}\n );\n }\n } catch (err) {\n appLogger(`Error deleting ${formatPath(filePath)}: ${err}`, {\n level: 'error',\n });\n }\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"push.cjs","names":["ANSIColors","checkCMSAuth","PushLogger","readline","fsPromises"],"sources":["../../../src/push/push.ts"],"sourcesContent":["import * as fsPromises from 'node:fs/promises';\nimport { join } from 'node:path';\nimport * as readline from 'node:readline';\nimport { getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n formatPath,\n type ListGitFilesOptions,\n listGitFiles,\n parallelize,\n prepareIntlayer,\n writeContentDeclaration,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport type { Dictionary } from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { PushLogger, type PushStatus } from '../pushLog';\nimport { checkCMSAuth } from '../utils/checkAccess';\n\ntype PushOptions = {\n deleteLocaleDictionary?: boolean;\n keepLocaleDictionary?: boolean;\n dictionaries?: string[];\n gitOptions?: ListGitFilesOptions;\n configOptions?: GetConfigurationOptions;\n build?: boolean;\n};\n\ntype DictionariesStatus = {\n dictionary: Dictionary;\n status: 'pending' | 'pushing' | 'modified' | 'pushed' | 'unknown' | 'error';\n error?: Error;\n errorMessage?: string;\n};\n\n// Print per-dictionary summary similar to loadDictionaries\nconst statusIconsAndColors = {\n pushed: { icon: '✔', color: ANSIColors.GREEN },\n modified: { icon: '✔', color: ANSIColors.GREEN },\n error: { icon: '✖', color: ANSIColors.RED },\n default: { icon: '⏲', color: ANSIColors.BLUE },\n};\n\nconst getIconAndColor = (status: DictionariesStatus['status']) => {\n return (\n statusIconsAndColors[status as keyof typeof statusIconsAndColors] ??\n statusIconsAndColors.default\n );\n};\n\n/**\n * Get all local dictionaries and push them simultaneously.\n */\nexport const push = async (options?: PushOptions): Promise<void> => {\n const config = getConfiguration(options?.configOptions);\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 try {\n const hasCMSAuth = await checkCMSAuth(config);\n\n if (!hasCMSAuth) return;\n\n const intlayerAPI = getIntlayerAPIProxy(undefined, config);\n\n const unmergedDictionariesRecord = getUnmergedDictionaries(config);\n let dictionaries: Dictionary[] = Object.values(\n unmergedDictionariesRecord\n ).flat();\n const existingDictionariesKeys: string[] = Object.keys(\n unmergedDictionariesRecord\n );\n\n if (options?.dictionaries) {\n // Check if the provided dictionaries exist\n const noneExistingDictionariesOption = options.dictionaries.filter(\n (dictionaryId) => !existingDictionariesKeys.includes(dictionaryId)\n );\n\n if (noneExistingDictionariesOption.length > 0) {\n appLogger(\n `The following dictionaries do not exist: ${noneExistingDictionariesOption.join(\n ', '\n )} and have been ignored.`,\n {\n level: 'error',\n }\n );\n }\n\n // Filter the dictionaries from the provided list of IDs\n dictionaries = dictionaries.filter((dictionary) =>\n options.dictionaries?.includes(dictionary.key)\n );\n }\n\n if (options?.gitOptions) {\n const gitFiles = await listGitFiles(options.gitOptions);\n\n dictionaries = dictionaries.filter((dictionary) =>\n gitFiles.includes(\n join(config.content.baseDir, dictionary.filePath ?? '')\n )\n );\n }\n\n // Check if the dictionaries list is empty\n if (dictionaries.length === 0) {\n appLogger('No local dictionaries found', {\n level: 'error',\n });\n return;\n }\n\n appLogger('Pushing dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] = dictionaries.map(\n (dictionary) => ({\n dictionary,\n status: 'pending',\n })\n );\n\n // Initialize aggregated logger similar to loadDictionaries\n const logger = new PushLogger();\n logger.update(\n dictionariesStatuses.map<PushStatus>((s) => ({\n dictionaryKey: s.dictionary.key,\n status: 'pending',\n }))\n );\n\n const successfullyPushedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n statusObj.status = 'pushing';\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'pushing' },\n ]);\n\n try {\n const pushResult = await intlayerAPI.dictionary.pushDictionaries([\n statusObj.dictionary,\n ]);\n\n const updatedDictionaries = pushResult.data?.updatedDictionaries ?? [];\n const newDictionaries = pushResult.data?.newDictionaries ?? [];\n\n const allDictionaries = [...updatedDictionaries, ...newDictionaries];\n\n for (const remoteDictionaryData of allDictionaries) {\n const localDictionary = unmergedDictionariesRecord[\n remoteDictionaryData.key\n ]?.find(\n (dictionary) => dictionary.localId === remoteDictionaryData.localId\n );\n\n if (!localDictionary) continue;\n\n await writeContentDeclaration(\n { ...localDictionary, id: remoteDictionaryData.id },\n config\n );\n }\n\n if (\n updatedDictionaries.some(\n (dictionary) => dictionary.key === statusObj.dictionary.key\n )\n ) {\n statusObj.status = 'modified';\n successfullyPushedDictionaries.push(statusObj.dictionary);\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'modified' },\n ]);\n } else if (\n newDictionaries.some(\n (dictionary) => dictionary.key === statusObj.dictionary.key\n )\n ) {\n statusObj.status = 'pushed';\n successfullyPushedDictionaries.push(statusObj.dictionary);\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'pushed' },\n ]);\n } else {\n statusObj.status = 'unknown';\n }\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error pushing dictionary ${statusObj.dictionary.key}: ${error}`;\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'error' },\n ]);\n }\n };\n\n // Process dictionaries in parallel with a concurrency limit (reuse parallelize)\n await parallelize(dictionariesStatuses, processDictionary, 5);\n\n // Stop the logger and render final state\n logger.finish();\n\n for (const dictionaryStatus of dictionariesStatuses) {\n const { icon, color } = getIconAndColor(dictionaryStatus.status);\n appLogger(\n ` - ${dictionaryStatus.dictionary.key} ${ANSIColors.GREY}[${color}${icon} ${dictionaryStatus.status}${ANSIColors.GREY}]${ANSIColors.RESET}`\n );\n }\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n appLogger(statusObj.errorMessage, {\n level: 'error',\n });\n }\n }\n\n // Handle delete or keep options\n const deleteOption = options?.deleteLocaleDictionary;\n const keepOption = options?.keepLocaleDictionary;\n\n if (deleteOption && keepOption) {\n throw new Error(\n 'Cannot specify both --deleteLocaleDictionary and --keepLocaleDictionary options.'\n );\n }\n\n if (deleteOption) {\n // Delete only the successfully pushed dictionaries\n await deleteLocalDictionaries(successfullyPushedDictionaries, options);\n } else if (keepOption) {\n // Do nothing, keep the local dictionaries\n } else {\n // Ask the user\n const answer = await askUser(\n 'Do you want to delete the local dictionaries that were successfully pushed? (yes/no): '\n );\n if (answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y') {\n await deleteLocalDictionaries(successfullyPushedDictionaries, options);\n }\n }\n } catch (error) {\n appLogger(error, {\n level: 'error',\n });\n }\n};\n\nconst askUser = (question: string): Promise<string> => {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n return new Promise((resolve) => {\n rl.question(question, (answer: string) => {\n rl.close();\n resolve(answer);\n });\n });\n};\n\nconst deleteLocalDictionaries = async (\n dictionariesToDelete: Dictionary[],\n options?: PushOptions\n): Promise<void> => {\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config, {\n config: {\n prefix: '',\n },\n });\n\n // Use a Set to collect all unique file paths\n const filePathsSet: Set<string> = new Set();\n\n for (const dictionary of dictionariesToDelete) {\n const { filePath } = dictionary;\n\n if (!filePath) {\n appLogger(`Dictionary ${dictionary.key} does not have a file path`, {\n level: 'error',\n });\n continue;\n }\n\n filePathsSet.add(filePath);\n }\n\n for (const filePath of filePathsSet) {\n try {\n const stats = await fsPromises.lstat(filePath);\n\n if (stats.isFile()) {\n await fsPromises.unlink(filePath);\n appLogger(`Deleted file ${formatPath(filePath)}`, {});\n } else if (stats.isDirectory()) {\n appLogger(`Path is a directory ${formatPath(filePath)}, skipping.`, {});\n } else {\n appLogger(\n `Unknown file type for ${formatPath(filePath)}, skipping.`,\n {}\n );\n }\n } catch (err) {\n appLogger(`Error deleting ${formatPath(filePath)}: ${err}`, {\n level: 'error',\n });\n }\n }\n};\n"],"mappings":"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"}
|
package/dist/cjs/pushLog.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pushLog.cjs","names":["spinnerFrames","
|
|
1
|
+
{"version":3,"file":"pushLog.cjs","names":["spinnerFrames","ANSIColors"],"sources":["../../src/pushLog.ts"],"sourcesContent":["import {\n ANSIColors,\n colorize,\n getConfiguration,\n spinnerFrames,\n} from '@intlayer/config';\n\nexport type PushStatus = {\n dictionaryKey: string;\n status: 'pending' | 'pushing' | 'pushed' | 'modified' | 'error';\n errorMessage?: string;\n};\n\nexport class PushLogger {\n private statuses: PushStatus[] = [];\n private spinnerTimer: NodeJS.Timeout | null = null;\n private spinnerIndex = 0;\n private renderedLines = 0;\n private readonly spinnerFrames = spinnerFrames;\n private isFinished = false;\n private readonly prefix: string;\n private lastRenderedState: string = '';\n\n constructor() {\n const configuration = getConfiguration();\n this.prefix = configuration.log.prefix;\n }\n\n update(newStatuses: PushStatus[]) {\n if (this.isFinished) return;\n for (const status of newStatuses) {\n const index = this.statuses.findIndex(\n (s) => s.dictionaryKey === status.dictionaryKey\n );\n if (index >= 0) {\n this.statuses[index] = status;\n } else {\n this.statuses.push(status);\n }\n }\n\n this.startSpinner();\n this.render();\n }\n\n finish() {\n this.isFinished = true;\n this.stopSpinner();\n this.render();\n }\n\n private startSpinner() {\n if (this.spinnerTimer || this.isFinished) return;\n this.spinnerTimer = setInterval(() => {\n this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;\n this.render();\n }, 100);\n }\n\n private stopSpinner() {\n if (!this.spinnerTimer) return;\n clearInterval(this.spinnerTimer);\n this.spinnerTimer = null;\n }\n\n private render() {\n const { total, done, pushed, modified, errors } = this.computeProgress();\n\n const frame = this.spinnerFrames[this.spinnerIndex];\n const lines: string[] = [];\n\n const isDone = done === total;\n\n const progressLabel = `dictionaries: ${done}/${total}`;\n const details: string[] = [];\n if (pushed > 0) details.push(`new: ${pushed}`);\n if (modified > 0) details.push(`modified: ${modified}`);\n if (errors > 0) details.push(colorize(`errors: ${errors}`, ANSIColors.RED));\n\n const suffix = details.length > 0 ? ` (${details.join(', ')})` : '';\n\n if (isDone) {\n lines.push(\n `${this.prefix} ${colorize('✔', ANSIColors.GREEN)} pushed ${progressLabel}${suffix}`\n );\n } else {\n lines.push(\n `${this.prefix} ${colorize(frame, ANSIColors.BLUE)} pushing ${progressLabel}${suffix}`\n );\n }\n\n const currentState = lines.join('\\n');\n if (currentState === this.lastRenderedState) {\n return;\n }\n this.lastRenderedState = currentState;\n\n if (this.renderedLines > 0) {\n process.stdout.write(`\\x1b[${this.renderedLines}F`);\n }\n\n const totalLinesToClear = Math.max(this.renderedLines, lines.length);\n for (let i = 0; i < totalLinesToClear; i++) {\n process.stdout.write('\\x1b[2K');\n const line = lines[i];\n if (line !== undefined) {\n process.stdout.write(line);\n }\n process.stdout.write('\\n');\n }\n\n this.renderedLines = lines.length;\n }\n\n private computeProgress() {\n const keys = new Set(this.statuses.map((s) => s.dictionaryKey));\n\n const pushed = this.statuses.filter((s) => s.status === 'pushed').length;\n const modified = this.statuses.filter(\n (s) => s.status === 'modified'\n ).length;\n const errors = this.statuses.filter((s) => s.status === 'error').length;\n const done = pushed + modified + errors;\n\n return {\n total: keys.size,\n done,\n pushed,\n modified,\n errors,\n } as const;\n }\n}\n"],"mappings":";;;;AAaA,IAAa,aAAb,MAAwB;CACtB,AAAQ,WAAyB,EAAE;CACnC,AAAQ,eAAsC;CAC9C,AAAQ,eAAe;CACvB,AAAQ,gBAAgB;CACxB,AAAiB,gBAAgBA;CACjC,AAAQ,aAAa;CACrB,AAAiB;CACjB,AAAQ,oBAA4B;CAEpC,cAAc;AAEZ,OAAK,iDADmC,CACZ,IAAI;;CAGlC,OAAO,aAA2B;AAChC,MAAI,KAAK,WAAY;AACrB,OAAK,MAAM,UAAU,aAAa;GAChC,MAAM,QAAQ,KAAK,SAAS,WACzB,MAAM,EAAE,kBAAkB,OAAO,cACnC;AACD,OAAI,SAAS,EACX,MAAK,SAAS,SAAS;OAEvB,MAAK,SAAS,KAAK,OAAO;;AAI9B,OAAK,cAAc;AACnB,OAAK,QAAQ;;CAGf,SAAS;AACP,OAAK,aAAa;AAClB,OAAK,aAAa;AAClB,OAAK,QAAQ;;CAGf,AAAQ,eAAe;AACrB,MAAI,KAAK,gBAAgB,KAAK,WAAY;AAC1C,OAAK,eAAe,kBAAkB;AACpC,QAAK,gBAAgB,KAAK,eAAe,KAAK,KAAK,cAAc;AACjE,QAAK,QAAQ;KACZ,IAAI;;CAGT,AAAQ,cAAc;AACpB,MAAI,CAAC,KAAK,aAAc;AACxB,gBAAc,KAAK,aAAa;AAChC,OAAK,eAAe;;CAGtB,AAAQ,SAAS;EACf,MAAM,EAAE,OAAO,MAAM,QAAQ,UAAU,WAAW,KAAK,iBAAiB;EAExE,MAAM,QAAQ,KAAK,cAAc,KAAK;EACtC,MAAM,QAAkB,EAAE;EAE1B,MAAM,SAAS,SAAS;EAExB,MAAM,gBAAgB,iBAAiB,KAAK,GAAG;EAC/C,MAAM,UAAoB,EAAE;AAC5B,MAAI,SAAS,EAAG,SAAQ,KAAK,QAAQ,SAAS;AAC9C,MAAI,WAAW,EAAG,SAAQ,KAAK,aAAa,WAAW;AACvD,MAAI,SAAS,EAAG,SAAQ,oCAAc,WAAW,UAAUC,4BAAW,IAAI,CAAC;EAE3E,MAAM,SAAS,QAAQ,SAAS,IAAI,KAAK,QAAQ,KAAK,KAAK,CAAC,KAAK;AAEjE,MAAI,OACF,OAAM,KACJ,GAAG,KAAK,OAAO,kCAAY,KAAKA,4BAAW,MAAM,CAAC,UAAU,gBAAgB,SAC7E;MAED,OAAM,KACJ,GAAG,KAAK,OAAO,kCAAY,OAAOA,4BAAW,KAAK,CAAC,WAAW,gBAAgB,SAC/E;EAGH,MAAM,eAAe,MAAM,KAAK,KAAK;AACrC,MAAI,iBAAiB,KAAK,kBACxB;AAEF,OAAK,oBAAoB;AAEzB,MAAI,KAAK,gBAAgB,EACvB,SAAQ,OAAO,MAAM,QAAQ,KAAK,cAAc,GAAG;EAGrD,MAAM,oBAAoB,KAAK,IAAI,KAAK,eAAe,MAAM,OAAO;AACpE,OAAK,IAAI,IAAI,GAAG,IAAI,mBAAmB,KAAK;AAC1C,WAAQ,OAAO,MAAM,UAAU;GAC/B,MAAM,OAAO,MAAM;AACnB,OAAI,SAAS,OACX,SAAQ,OAAO,MAAM,KAAK;AAE5B,WAAQ,OAAO,MAAM,KAAK;;AAG5B,OAAK,gBAAgB,MAAM;;CAG7B,AAAQ,kBAAkB;EACxB,MAAM,OAAO,IAAI,IAAI,KAAK,SAAS,KAAK,MAAM,EAAE,cAAc,CAAC;EAE/D,MAAM,SAAS,KAAK,SAAS,QAAQ,MAAM,EAAE,WAAW,SAAS,CAAC;EAClE,MAAM,WAAW,KAAK,SAAS,QAC5B,MAAM,EAAE,WAAW,WACrB,CAAC;EACF,MAAM,SAAS,KAAK,SAAS,QAAQ,MAAM,EAAE,WAAW,QAAQ,CAAC;EACjE,MAAM,OAAO,SAAS,WAAW;AAEjC,SAAO;GACL,OAAO,KAAK;GACZ;GACA;GACA;GACA;GACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewDoc.cjs","names":["setupAI","
|
|
1
|
+
{"version":3,"file":"reviewDoc.cjs","names":["setupAI","getOutputFilePath","ANSIColors","checkFileModifiedRange","reviewFileBlockAware"],"sources":["../../../src/reviewDoc/reviewDoc.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport type { AIOptions } from '@intlayer/api';\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 { checkFileModifiedRange } from '../utils/checkFileModifiedRange';\nimport { getOutputFilePath } from '../utils/getOutputFilePath';\nimport { setupAI } from '../utils/setupAI';\nimport { reviewFileBlockAware } from './reviewDocBlockAware';\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 // Check modification range only if the file exists\n if (existsSync(outputFilePath)) {\n const fileModificationData = checkFileModifiedRange(outputFilePath, {\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n });\n\n if (fileModificationData.isSkipped) {\n appLogger(fileModificationData.message);\n return;\n }\n } else if (skipIfModifiedBefore || skipIfModifiedAfter) {\n // Log if we intended to check modification time but couldn't because the file doesn't exist\n appLogger(\n `${colorize('!', ANSIColors.YELLOW)} File ${formatPath(outputFilePath)} does not exist, skipping modification date check.`\n );\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,uDAAiC,cAAc;CACrD,MAAM,+CAAyB,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,IAAI,UAAoB,6BAAS,YAAY,EAC3C,QAAQ,qBACT,CAAC;AAEF,KAAI,YAAY;EACd,MAAM,kBAAkB,2CAAmB,WAAW;AAEtD,MAAI,gBAIF,WAAU,QAAQ,QAAQ,SACxB,gBAAgB,MAAM,gCAAiB,QAAQ,KAAK,EAAE,KAAK,KAAK,QAAQ,CACzE;;AAML,WAAU,uDAA+B,WAAW,GAAG;AACvD,WACE,kDAA4B,QAAQ,OAAO,CAAC,mDAA2B,QAAQ,CAAC,IACjF;AAED,WAAU,kDAA4B,QAAQ,OAAO,CAAC,SAAS;AAC/D,WAAU,QAAQ,KAAK,SAAS,yCAAiB,KAAK,CAAC,IAAI,CAAC;AAyE5D,2CAtEiB,QAAQ,SAAS,YAChC,QAAQ,KAAK,WAAW,YAAY;AAClC,YACE,sDAA8B,QAAQ,CAAC,2CAAmB,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,kCAAY,KAAKC,4BAAW,OAAO,CAAC,2CAAmB,aAAa,CAAC,4BACtE;AACD;;AAIF,8BAAe,eAAe,EAAE;GAC9B,MAAM,uBAAuBC,4DAAuB,gBAAgB;IAClE;IACA;IACD,CAAC;AAEF,OAAI,qBAAqB,WAAW;AAClC,cAAU,qBAAqB,QAAQ;AACvC;;aAEO,wBAAwB,oBAEjC,WACE,kCAAY,KAAKD,4BAAW,OAAO,CAAC,2CAAmB,eAAe,CAAC,oDACxE;EAGH,IAAI;AAEJ,MAAI,YAAY;GACd,MAAM,kBAAkB,2CACtB,sBACA,WACD;AAED,aAAU,sBAAsB,gBAAgB,KAAK,KAAK,GAAG;AAC7D,kBAAe;;AAGjB,QAAME,2DACJ,sBACA,gBACA,QACA,YACA,WACA,eACA,oBACA,cACA,UACA,SACD;GACD,CACH,GAIE,SAAS,MAAM,EAChB,+BAA+B,EAChC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listMissingTranslations.cjs","names":["
|
|
1
|
+
{"version":3,"file":"listMissingTranslations.cjs","names":["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,gGAAqD,cAAc;CACzE,MAAM,uEAAqC,cAAc;CAEzD,MAAM,sBAKA,EAAE;CAER,MAAM,EAAE,SAAS,oBAAoB,cAAc;CAEnD,MAAM,mBAAmB,OAAO,KAAK,2BAA2B;AAEhE,MAAK,MAAM,iBAAiB,kBAAkB;EAC5C,MAAM,eACJ,2BAA2B;EAE7B,MAAM,yBAAuC,aAAa,QACvD,eAAe,CAAC,WAAW,OAC7B;AAGD,OAAK,MAAM,cAAc,wBAAwB;GAC/C,MAAMA,8EACJ,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,8EACJ,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,yEAFgC,qBAAqB,CAEL"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.cjs","names":[
|
|
1
|
+
{"version":3,"file":"transform.cjs","names":[],"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,uDAAiC,QAAQ,cAAc;CAC7D,MAAM,+CAAyB,cAAc;CAC7C,MAAM,EAAE,YAAY,cAAc;CAElC,MAAM,cAAc,SAAiB;AAEnC,oEAD8B,SAAS,KAAK,CACX;;CAInC,MAAM,eAAe,MAAM,gBAAgB,QAAQ;CACnD,IAAI,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,sCAAkB;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,8CAAqB,eAAe,aAAa;EAC/C,eAAe,QAAQ;EACvB,WAAW,QAAQ;EACnB,UAAU,QAAQ;EAClB,iBAAiB,QAAQ;EAC1B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translateDoc.cjs","names":["
|
|
1
|
+
{"version":3,"file":"translateDoc.cjs","names":["setupAI","performance","getOutputFilePath","checkFileModifiedRange","translateFile","ANSIColors"],"sources":["../../../src/translateDoc/translateDoc.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { performance } from 'node:perf_hooks';\nimport { listGitFiles, parallelize, pLimit } from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colorize,\n colorizeNumber,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport type { Locale } from '@intlayer/types';\nimport fg from 'fast-glob';\nimport { checkFileModifiedRange } from '../utils/checkFileModifiedRange';\nimport { getOutputFilePath } from '../utils/getOutputFilePath';\nimport { setupAI } from '../utils/setupAI';\nimport { translateFile } from './translateFile';\nimport type { ErrorState, TranslateDocOptions } from './types';\n\nexport const translateDoc = async ({\n docPattern,\n locales,\n excludedGlobPattern,\n baseLocale,\n aiOptions,\n nbSimultaneousFileProcessed = 20, // Default to a higher concurrency for chunks\n configOptions,\n customInstructions,\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n skipIfExists,\n gitOptions,\n flushStrategy = 'incremental',\n}: TranslateDocOptions) => {\n const configuration = getConfiguration(configOptions);\n const appLogger = getAppLogger(configuration);\n\n // 1. GLOBAL QUEUE SETUP\n // We use pLimit to create a single bottleneck for AI requests.\n // This queue is shared across all files and locales.\n const maxConcurrentChunks = nbSimultaneousFileProcessed;\n const globalChunkLimiter = pLimit(maxConcurrentChunks);\n\n let docList: string[] = await fg(docPattern, {\n ignore: excludedGlobPattern,\n });\n\n const aiResult = await setupAI(configuration, aiOptions);\n if (!aiResult?.hasAIAccess) return;\n const { aiClient, aiConfig } = aiResult;\n\n if (gitOptions) {\n const gitChangedFiles = await listGitFiles(gitOptions);\n if (gitChangedFiles) {\n docList = docList.filter((path) =>\n gitChangedFiles.some((gitFile) => join(process.cwd(), path) === gitFile)\n );\n }\n }\n\n const batchStartTime = performance.now();\n\n appLogger(\n `Translating ${colorizeNumber(docList.length)} files to ${colorizeNumber(locales.length)} locales. \\n` +\n `Global Concurrency: ${colorizeNumber(maxConcurrentChunks)} chunks in parallel.`\n );\n\n const errorState: ErrorState = {\n count: 0,\n maxErrors: 5,\n shouldStop: false,\n };\n\n // 2. FLATTENED TASK LIST\n // We create a task for every File x Locale combination.\n const allTasks = docList.flatMap((docPath) =>\n locales.map((locale) => async () => {\n if (errorState.shouldStop) return;\n\n const absoluteBaseFilePath = join(configuration.content.baseDir, docPath);\n const outputFilePath = getOutputFilePath(\n absoluteBaseFilePath,\n locale,\n baseLocale\n );\n\n // Skip logic\n if (skipIfExists && existsSync(outputFilePath)) return;\n\n if (flushStrategy === 'incremental' && !existsSync(outputFilePath)) {\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(outputFilePath, '');\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 // Execute translation using the SHARED limiter\n await translateFile({\n baseFilePath: absoluteBaseFilePath,\n outputFilePath,\n locale: locale as Locale,\n baseLocale,\n configuration,\n errorState,\n aiOptions,\n customInstructions,\n aiClient,\n aiConfig,\n flushStrategy,\n limit: globalChunkLimiter, // Pass the global queue\n });\n })\n );\n\n // 3. HIGH-THROUGHPUT FILE OPENER\n // We open many files simultaneously (e.g., 50) to ensure the global chunk queue\n // is always saturated with work.\n // If we open too few files, the chunk queue might drain faster than we can read new files.\n const FILE_OPEN_LIMIT = 50;\n\n await parallelize(allTasks, (task) => task(), FILE_OPEN_LIMIT);\n\n const batchEndTime = performance.now();\n const batchDuration = ((batchEndTime - batchStartTime) / 1000).toFixed(2);\n\n if (errorState.count > 0) {\n appLogger(`Finished with ${errorState.count} errors in ${batchDuration}s.`);\n } else {\n appLogger(\n `${colorize('✔', ANSIColors.GREEN)} Batch completed successfully in ${colorizeNumber(batchDuration)}s.`\n );\n }\n};\n"],"mappings":";;;;;;;;;;;;;;AAmBA,MAAa,eAAe,OAAO,EACjC,YACA,SACA,qBACA,YACA,WACA,8BAA8B,IAC9B,eACA,oBACA,sBACA,qBACA,cACA,YACA,gBAAgB,oBACS;CACzB,MAAM,uDAAiC,cAAc;CACrD,MAAM,+CAAyB,cAAc;CAK7C,MAAM,sBAAsB;CAC5B,MAAM,oDAA4B,oBAAoB;CAEtD,IAAI,UAAoB,6BAAS,YAAY,EAC3C,QAAQ,qBACT,CAAC;CAEF,MAAM,WAAW,MAAMA,8BAAQ,eAAe,UAAU;AACxD,KAAI,CAAC,UAAU,YAAa;CAC5B,MAAM,EAAE,UAAU,aAAa;AAE/B,KAAI,YAAY;EACd,MAAM,kBAAkB,2CAAmB,WAAW;AACtD,MAAI,gBACF,WAAU,QAAQ,QAAQ,SACxB,gBAAgB,MAAM,gCAAiB,QAAQ,KAAK,EAAE,KAAK,KAAK,QAAQ,CACzE;;CAIL,MAAM,iBAAiBC,4BAAY,KAAK;AAExC,WACE,oDAA8B,QAAQ,OAAO,CAAC,iDAA2B,QAAQ,OAAO,CAAC,uEACjD,oBAAoB,CAAC,sBAC9D;CAED,MAAM,aAAyB;EAC7B,OAAO;EACP,WAAW;EACX,YAAY;EACb;AAyDD,2CArDiB,QAAQ,SAAS,YAChC,QAAQ,KAAK,WAAW,YAAY;AAClC,MAAI,WAAW,WAAY;EAE3B,MAAM,2CAA4B,cAAc,QAAQ,SAAS,QAAQ;EACzE,MAAM,iBAAiBC,kDACrB,sBACA,QACA,WACD;AAGD,MAAI,wCAA2B,eAAe,CAAE;AAEhD,MAAI,kBAAkB,iBAAiB,yBAAY,eAAe,EAAE;AAClE,iDAAkB,eAAe,EAAE,EAAE,WAAW,MAAM,CAAC;AACvD,8BAAc,gBAAgB,GAAG;;EAGnC,MAAM,uBAAuBC,4DAAuB,gBAAgB;GAClE;GACA;GACD,CAAC;AAEF,MAAI,qBAAqB,WAAW;AAClC,aAAU,qBAAqB,QAAQ;AACvC;;AAIF,QAAMC,iDAAc;GAClB,cAAc;GACd;GACQ;GACR;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,OAAO;GACR,CAAC;GACF,CACH,GAQ4B,SAAS,MAAM,EAFpB,GAEsC;CAG9D,MAAM,kBADeH,4BAAY,KAAK,GACC,kBAAkB,KAAM,QAAQ,EAAE;AAEzE,KAAI,WAAW,QAAQ,EACrB,WAAU,iBAAiB,WAAW,MAAM,aAAa,cAAc,IAAI;KAE3E,WACE,kCAAY,KAAKI,4BAAW,MAAM,CAAC,wEAAkD,cAAc,CAAC,IACrG"}
|