@intlayer/cli 7.3.0 → 7.3.2-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/dist/cjs/fill/formatAutoFilledFilePath.cjs +1 -1
- package/dist/cjs/fill/formatAutoFilledFilePath.cjs.map +1 -1
- package/dist/cjs/fill/listTranslationsTasks.cjs +1 -0
- package/dist/cjs/fill/listTranslationsTasks.cjs.map +1 -1
- package/dist/cjs/fill/translateDictionary.cjs +21 -24
- package/dist/cjs/fill/translateDictionary.cjs.map +1 -1
- package/dist/cjs/fill/writeFill.cjs +2 -5
- package/dist/cjs/fill/writeFill.cjs.map +1 -1
- package/dist/cjs/listContentDeclaration.cjs +6 -6
- package/dist/cjs/listContentDeclaration.cjs.map +1 -1
- package/dist/esm/fill/formatAutoFilledFilePath.mjs +1 -1
- package/dist/esm/fill/formatAutoFilledFilePath.mjs.map +1 -1
- package/dist/esm/fill/listTranslationsTasks.mjs +1 -0
- package/dist/esm/fill/listTranslationsTasks.mjs.map +1 -1
- package/dist/esm/fill/translateDictionary.mjs +21 -24
- package/dist/esm/fill/translateDictionary.mjs.map +1 -1
- package/dist/esm/fill/writeFill.mjs +2 -5
- package/dist/esm/fill/writeFill.mjs.map +1 -1
- package/dist/esm/listContentDeclaration.mjs +6 -6
- package/dist/esm/listContentDeclaration.mjs.map +1 -1
- package/dist/types/pushConfig.d.ts.map +1 -1
- package/package.json +12 -11
package/README.md
CHANGED
|
@@ -24,8 +24,6 @@
|
|
|
24
24
|
<a href="https://github.com/aymericzip/intlayer/blob/main/LICENSE" target="_blank" rel="noopener noreferrer nofollow"><img src="https://img.shields.io/github/license/aymericzip/intlayer?style=for-the-badge&labelColor=000000&color=FFFFFF&logoColor=000000&cacheSeconds=86400" alt="license"/></a>
|
|
25
25
|
<a href="https://github.com/aymericzip/intlayer/commits/main" target="_blank" rel="noopener noreferrer nofollow"><img src="https://img.shields.io/github/last-commit/aymericzip/intlayer?style=for-the-badge&labelColor=000000&color=FFFFFF&logoColor=000000&cacheSeconds=86400" alt="last commit"/>
|
|
26
26
|
</a>
|
|
27
|
-
<a href="https://bountyhub.dev/bounties?repo=intlayer" target="_blank" rel="noopener noreferrer nofollow"><img src="https://img.shields.io/badge/Bounties-on%20BountyHub-yellow?style=for-the-badge&labelColor=000000&color=FFFFFF&logoColor=000000&cacheSeconds=86400" alt="Bounties on BountyHub"/>
|
|
28
|
-
</a>
|
|
29
27
|
</p>
|
|
30
28
|
|
|
31
29
|

|
|
@@ -8,7 +8,7 @@ const formatAutoFilledFilePath = (autoFillField, dictionaryKey, dictionaryFilePa
|
|
|
8
8
|
if (!dictionaryFilePath || typeof dictionaryFilePath !== "string") throw new Error("dictionaryFilePath must be a non-empty string");
|
|
9
9
|
if (!baseDir || typeof baseDir !== "string") throw new Error("baseDir must be a non-empty string");
|
|
10
10
|
const originalFileName = (0, node_path.basename)(dictionaryFilePath).split(".").slice(0, -2).join(".");
|
|
11
|
-
let result = autoFillField.replace(/\{\{key\}\}/g,
|
|
11
|
+
let result = autoFillField.replace(/\{\{key\}\}/g, dictionaryKey).replace(/\{\{fileName\}\}/g, originalFileName);
|
|
12
12
|
if (locale) result = result.replace(/\{\{locale\}\}/g, locale);
|
|
13
13
|
const absoluteDictionaryPath = (0, node_path.isAbsolute)(dictionaryFilePath) ? dictionaryFilePath : (0, node_path.resolve)(baseDir, dictionaryFilePath);
|
|
14
14
|
if (result.startsWith("./") || result.startsWith("../")) return (0, node_path.resolve)((0, node_path.dirname)(absoluteDictionaryPath), result);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatAutoFilledFilePath.cjs","names":["result: string"],"sources":["../../../src/fill/formatAutoFilledFilePath.ts"],"sourcesContent":["import { basename, dirname, isAbsolute, normalize, resolve } from 'node:path';\nimport type { LocalesValues } from '@intlayer/types';\n\nexport const formatAutoFilledFilePath = (\n autoFillField: string,\n dictionaryKey: string,\n dictionaryFilePath: string,\n baseDir: string,\n locale?: LocalesValues\n): string => {\n // Validate inputs\n if (!autoFillField || typeof autoFillField !== 'string') {\n throw new Error('autoFillField must be a non-empty string');\n }\n if (!dictionaryKey || typeof dictionaryKey !== 'string') {\n throw new Error('dictionaryKey must be a non-empty string');\n }\n if (!dictionaryFilePath || typeof dictionaryFilePath !== 'string') {\n throw new Error('dictionaryFilePath must be a non-empty string');\n }\n if (!baseDir || typeof baseDir !== 'string') {\n throw new Error('baseDir must be a non-empty string');\n }\n\n // Extract the original filename without extensions (.content.ts -> dictionaryFieldEditor)\n const originalFileName = basename(dictionaryFilePath)\n .split('.')\n .slice(0, -2) // Remove last 2 extensions (.content.tsx)\n .join('.');\n\n // Replace placeholders in autoFillField\n let result: string = autoFillField\n .replace(/\\{\\{key\\}\\}/g,
|
|
1
|
+
{"version":3,"file":"formatAutoFilledFilePath.cjs","names":["result: string"],"sources":["../../../src/fill/formatAutoFilledFilePath.ts"],"sourcesContent":["import { basename, dirname, isAbsolute, normalize, resolve } from 'node:path';\nimport type { LocalesValues } from '@intlayer/types';\n\nexport const formatAutoFilledFilePath = (\n autoFillField: string,\n dictionaryKey: string,\n dictionaryFilePath: string,\n baseDir: string,\n locale?: LocalesValues\n): string => {\n // Validate inputs\n if (!autoFillField || typeof autoFillField !== 'string') {\n throw new Error('autoFillField must be a non-empty string');\n }\n if (!dictionaryKey || typeof dictionaryKey !== 'string') {\n throw new Error('dictionaryKey must be a non-empty string');\n }\n if (!dictionaryFilePath || typeof dictionaryFilePath !== 'string') {\n throw new Error('dictionaryFilePath must be a non-empty string');\n }\n if (!baseDir || typeof baseDir !== 'string') {\n throw new Error('baseDir must be a non-empty string');\n }\n\n // Extract the original filename without extensions (.content.ts -> dictionaryFieldEditor)\n const originalFileName = basename(dictionaryFilePath)\n .split('.')\n .slice(0, -2) // Remove last 2 extensions (.content.tsx)\n .join('.');\n\n // Replace placeholders in autoFillField\n let result: string = autoFillField\n .replace(/\\{\\{key\\}\\}/g, dictionaryKey) // Use original filename, not dictionaryKey\n .replace(/\\{\\{fileName\\}\\}/g, originalFileName);\n\n if (locale) {\n result = result.replace(/\\{\\{locale\\}\\}/g, locale);\n }\n\n // Normalize the dictionary file path - if it's relative, make it absolute relative to baseDir\n const absoluteDictionaryPath = isAbsolute(dictionaryFilePath)\n ? dictionaryFilePath\n : resolve(baseDir, dictionaryFilePath);\n\n // Handle relative paths (starting with ./ or ../)\n if (result.startsWith('./') || result.startsWith('../')) {\n const fileDir = dirname(absoluteDictionaryPath);\n const resolvedPath = resolve(fileDir, result);\n\n return resolvedPath;\n }\n\n // Handle absolute paths\n if (isAbsolute(result)) {\n const normalizedResult = normalize(result);\n const normalizedBaseDir = normalize(baseDir);\n\n // Check if it's relative to baseDir (starts with /)\n // but not a system path (like /usr/local)\n if (\n result.startsWith('/') &&\n !normalizedResult.startsWith(normalizedBaseDir)\n ) {\n // Try to resolve it relative to baseDir first\n const relativeToBase = resolve(baseDir, result.substring(1));\n\n // If the path doesn't exist in common system directories, treat as relative to baseDir\n if (\n !result.startsWith('/usr/') &&\n !result.startsWith('/etc/') &&\n !result.startsWith('/var/') &&\n !result.startsWith('/home/') &&\n !result.startsWith('/Users/')\n ) {\n return relativeToBase;\n }\n }\n\n // It's a true system absolute path\n return normalizedResult;\n }\n\n // Default case: treat as relative to baseDir\n return normalize(result);\n};\n"],"mappings":";;;;AAGA,MAAa,4BACX,eACA,eACA,oBACA,SACA,WACW;AAEX,KAAI,CAAC,iBAAiB,OAAO,kBAAkB,SAC7C,OAAM,IAAI,MAAM,2CAA2C;AAE7D,KAAI,CAAC,iBAAiB,OAAO,kBAAkB,SAC7C,OAAM,IAAI,MAAM,2CAA2C;AAE7D,KAAI,CAAC,sBAAsB,OAAO,uBAAuB,SACvD,OAAM,IAAI,MAAM,gDAAgD;AAElE,KAAI,CAAC,WAAW,OAAO,YAAY,SACjC,OAAM,IAAI,MAAM,qCAAqC;CAIvD,MAAM,2CAA4B,mBAAmB,CAClD,MAAM,IAAI,CACV,MAAM,GAAG,GAAG,CACZ,KAAK,IAAI;CAGZ,IAAIA,SAAiB,cAClB,QAAQ,gBAAgB,cAAc,CACtC,QAAQ,qBAAqB,iBAAiB;AAEjD,KAAI,OACF,UAAS,OAAO,QAAQ,mBAAmB,OAAO;CAIpD,MAAM,mDAAoC,mBAAmB,GACzD,4CACQ,SAAS,mBAAmB;AAGxC,KAAI,OAAO,WAAW,KAAK,IAAI,OAAO,WAAW,MAAM,CAIrD,sDAHwB,uBAAuB,EACT,OAAO;AAM/C,+BAAe,OAAO,EAAE;EACtB,MAAM,4CAA6B,OAAO;EAC1C,MAAM,6CAA8B,QAAQ;AAI5C,MACE,OAAO,WAAW,IAAI,IACtB,CAAC,iBAAiB,WAAW,kBAAkB,EAC/C;GAEA,MAAM,wCAAyB,SAAS,OAAO,UAAU,EAAE,CAAC;AAG5D,OACE,CAAC,OAAO,WAAW,QAAQ,IAC3B,CAAC,OAAO,WAAW,QAAQ,IAC3B,CAAC,OAAO,WAAW,QAAQ,IAC3B,CAAC,OAAO,WAAW,SAAS,IAC5B,CAAC,OAAO,WAAW,UAAU,CAE7B,QAAO;;AAKX,SAAO;;AAIT,iCAAiB,OAAO"}
|
|
@@ -26,6 +26,7 @@ const listTranslationsTasks = (localIds, outputLocales, mode, baseLocale, config
|
|
|
26
26
|
const dictionaryKey = targetUnmergedDictionary.key;
|
|
27
27
|
const dictionaryLocalId = targetUnmergedDictionary.localId;
|
|
28
28
|
const mainDictionaryToProcess = mergedDictionariesRecord[dictionaryKey];
|
|
29
|
+
if ((targetUnmergedDictionary.filled ?? false) === true) continue;
|
|
29
30
|
if ((targetUnmergedDictionary.fill ?? configuration.dictionary?.fill ?? false) === false) continue;
|
|
30
31
|
const sourceLocale = targetUnmergedDictionary.locale ?? baseLocale;
|
|
31
32
|
if (!mainDictionaryToProcess) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listTranslationsTasks.cjs","names":["listMissingTranslationsWithConfig","translationTasks: TranslationTask[]","ANSIColors","mainDictionaryToProcess: Dictionary","sourceLocale: Locale","outputLocalesList: Locale[]"],"sources":["../../../src/fill/listTranslationsTasks.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport { formatLocale } from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeKey,\n colorizePath,\n getAppLogger,\n} from '@intlayer/config';\nimport { getFilterTranslationsOnlyDictionary } from '@intlayer/core';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type {\n Dictionary,\n IntlayerConfig,\n LocalDictionaryId,\n Locale,\n} from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { listMissingTranslationsWithConfig } from '../test';\n\nexport type TranslationTask = {\n dictionaryKey: string;\n dictionaryLocalId: LocalDictionaryId;\n sourceLocale: Locale;\n targetLocales: Locale[];\n dictionaryPreset: string;\n dictionaryFilePath: string;\n};\n\nexport const listTranslationsTasks = (\n localIds: LocalDictionaryId[],\n outputLocales: Locale[],\n mode: 'complete' | 'review',\n baseLocale: Locale,\n configuration: IntlayerConfig\n): TranslationTask[] => {\n const appLogger = getAppLogger(configuration);\n\n const mergedDictionariesRecord = getDictionaries(configuration);\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n\n const allFlatDictionaries = Object.values(unmergedDictionariesRecord).flat();\n const dictionariesToProcess = allFlatDictionaries.filter((dictionary) =>\n localIds.includes(dictionary.localId!)\n );\n\n const { missingTranslations } =\n listMissingTranslationsWithConfig(configuration);\n\n const maxKeyLength = Math.max(\n ...dictionariesToProcess.map((dictionary) => dictionary.key.length)\n );\n\n const translationTasks: TranslationTask[] = [];\n\n for (const targetUnmergedDictionary of dictionariesToProcess) {\n const dictionaryPreset = colon(\n [\n ' - ',\n colorize('[', ANSIColors.GREY_DARK),\n colorizeKey(targetUnmergedDictionary.key),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: maxKeyLength + 6 }\n );\n\n const dictionaryKey = targetUnmergedDictionary.key;\n const dictionaryLocalId = targetUnmergedDictionary.localId!;\n const mainDictionaryToProcess: Dictionary =\n mergedDictionariesRecord[dictionaryKey];\n const dictionaryFill =\n targetUnmergedDictionary.fill ?? configuration.dictionary?.fill ?? false;\n\n if (dictionaryFill === false) continue;\n\n const sourceLocale: Locale = (targetUnmergedDictionary.locale ??\n baseLocale) as Locale;\n\n if (!mainDictionaryToProcess) {\n appLogger(\n `${dictionaryPreset} Dictionary not found in dictionariesRecord. Skipping.`,\n {\n level: 'warn',\n }\n );\n continue;\n }\n\n if (!targetUnmergedDictionary.filePath) {\n appLogger(`${dictionaryPreset} Dictionary has no file path. Skipping.`, {\n level: 'warn',\n });\n continue;\n }\n\n const sourceLocaleContent = getFilterTranslationsOnlyDictionary(\n mainDictionaryToProcess,\n sourceLocale\n );\n\n if (\n Object.keys(sourceLocaleContent as Record<string, unknown>).length === 0\n ) {\n appLogger(\n `${dictionaryPreset} No content found for dictionary in source locale ${formatLocale(sourceLocale)}. Skipping translation for this dictionary.`,\n {\n level: 'warn',\n }\n );\n continue;\n }\n\n /**\n * In 'complete' mode, filter only the missing locales to translate\n *\n * Skip the dictionary if there are no missing locales to translate\n */\n let outputLocalesList: Locale[] = outputLocales as Locale[];\n\n if (mode === 'complete') {\n outputLocalesList =\n missingTranslations\n .find(\n (missingTranslation) => missingTranslation.key === dictionaryKey\n )\n ?.locales.filter((locale) => outputLocales.includes(locale)) ?? [];\n }\n\n if (outputLocalesList.length === 0) {\n appLogger(\n `${dictionaryPreset} No locales to fill, Skipping ${colorizePath(basename(targetUnmergedDictionary.filePath))}`,\n {\n level: 'warn',\n }\n );\n continue;\n }\n\n translationTasks.push({\n dictionaryKey,\n dictionaryLocalId,\n sourceLocale,\n targetLocales: outputLocalesList,\n dictionaryPreset,\n dictionaryFilePath: targetUnmergedDictionary.filePath,\n });\n }\n\n // Return the list of tasks to execute\n
|
|
1
|
+
{"version":3,"file":"listTranslationsTasks.cjs","names":["listMissingTranslationsWithConfig","translationTasks: TranslationTask[]","ANSIColors","mainDictionaryToProcess: Dictionary","sourceLocale: Locale","outputLocalesList: Locale[]"],"sources":["../../../src/fill/listTranslationsTasks.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport { formatLocale } from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeKey,\n colorizePath,\n getAppLogger,\n} from '@intlayer/config';\nimport { getFilterTranslationsOnlyDictionary } from '@intlayer/core';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type {\n Dictionary,\n IntlayerConfig,\n LocalDictionaryId,\n Locale,\n} from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { listMissingTranslationsWithConfig } from '../test';\n\nexport type TranslationTask = {\n dictionaryKey: string;\n dictionaryLocalId: LocalDictionaryId;\n sourceLocale: Locale;\n targetLocales: Locale[];\n dictionaryPreset: string;\n dictionaryFilePath: string;\n};\n\nexport const listTranslationsTasks = (\n localIds: LocalDictionaryId[],\n outputLocales: Locale[],\n mode: 'complete' | 'review',\n baseLocale: Locale,\n configuration: IntlayerConfig\n): TranslationTask[] => {\n const appLogger = getAppLogger(configuration);\n\n const mergedDictionariesRecord = getDictionaries(configuration);\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n\n const allFlatDictionaries = Object.values(unmergedDictionariesRecord).flat();\n const dictionariesToProcess = allFlatDictionaries.filter((dictionary) =>\n localIds.includes(dictionary.localId!)\n );\n\n const { missingTranslations } =\n listMissingTranslationsWithConfig(configuration);\n\n const maxKeyLength = Math.max(\n ...dictionariesToProcess.map((dictionary) => dictionary.key.length)\n );\n\n const translationTasks: TranslationTask[] = [];\n\n for (const targetUnmergedDictionary of dictionariesToProcess) {\n const dictionaryPreset = colon(\n [\n ' - ',\n colorize('[', ANSIColors.GREY_DARK),\n colorizeKey(targetUnmergedDictionary.key),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: maxKeyLength + 6 }\n );\n\n const dictionaryKey = targetUnmergedDictionary.key;\n const dictionaryLocalId = targetUnmergedDictionary.localId!;\n const mainDictionaryToProcess: Dictionary =\n mergedDictionariesRecord[dictionaryKey];\n const dictionaryFilled = targetUnmergedDictionary.filled ?? false;\n\n if (dictionaryFilled === true) {\n continue;\n }\n\n const dictionaryFill =\n targetUnmergedDictionary.fill ?? configuration.dictionary?.fill ?? false;\n\n if (dictionaryFill === false) continue;\n\n const sourceLocale: Locale = (targetUnmergedDictionary.locale ??\n baseLocale) as Locale;\n\n if (!mainDictionaryToProcess) {\n appLogger(\n `${dictionaryPreset} Dictionary not found in dictionariesRecord. Skipping.`,\n {\n level: 'warn',\n }\n );\n continue;\n }\n\n if (!targetUnmergedDictionary.filePath) {\n appLogger(`${dictionaryPreset} Dictionary has no file path. Skipping.`, {\n level: 'warn',\n });\n continue;\n }\n\n const sourceLocaleContent = getFilterTranslationsOnlyDictionary(\n mainDictionaryToProcess,\n sourceLocale\n );\n\n if (\n Object.keys(sourceLocaleContent as Record<string, unknown>).length === 0\n ) {\n appLogger(\n `${dictionaryPreset} No content found for dictionary in source locale ${formatLocale(sourceLocale)}. Skipping translation for this dictionary.`,\n {\n level: 'warn',\n }\n );\n continue;\n }\n\n /**\n * In 'complete' mode, filter only the missing locales to translate\n *\n * Skip the dictionary if there are no missing locales to translate\n */\n let outputLocalesList: Locale[] = outputLocales as Locale[];\n\n if (mode === 'complete') {\n outputLocalesList =\n missingTranslations\n .find(\n (missingTranslation) => missingTranslation.key === dictionaryKey\n )\n ?.locales.filter((locale) => outputLocales.includes(locale)) ?? [];\n }\n\n if (outputLocalesList.length === 0) {\n appLogger(\n `${dictionaryPreset} No locales to fill, Skipping ${colorizePath(basename(targetUnmergedDictionary.filePath))}`,\n {\n level: 'warn',\n }\n );\n continue;\n }\n\n translationTasks.push({\n dictionaryKey,\n dictionaryLocalId,\n sourceLocale,\n targetLocales: outputLocalesList,\n dictionaryPreset,\n dictionaryFilePath: targetUnmergedDictionary.filePath,\n });\n }\n\n // Return the list of tasks to execute\n return translationTasks;\n};\n"],"mappings":";;;;;;;;;;AA8BA,MAAa,yBACX,UACA,eACA,MACA,YACA,kBACsB;CACtB,MAAM,gDAAyB,cAAc;CAE7C,MAAM,8EAA2C,cAAc;CAC/D,MAAM,iGAAqD,cAAc;CAGzE,MAAM,wBADsB,OAAO,OAAO,2BAA2B,CAAC,MAAM,CAC1B,QAAQ,eACxD,SAAS,SAAS,WAAW,QAAS,CACvC;CAED,MAAM,EAAE,wBACNA,uEAAkC,cAAc;CAElD,MAAM,eAAe,KAAK,IACxB,GAAG,sBAAsB,KAAK,eAAe,WAAW,IAAI,OAAO,CACpE;CAED,MAAMC,mBAAsC,EAAE;AAE9C,MAAK,MAAM,4BAA4B,uBAAuB;EAC5D,MAAM,gDACJ;GACE;mCACS,KAAKC,6BAAW,UAAU;sCACvB,yBAAyB,IAAI;mCAChC,KAAKA,6BAAW,UAAU;GACpC,CAAC,KAAK,GAAG,EACV,EAAE,SAAS,eAAe,GAAG,CAC9B;EAED,MAAM,gBAAgB,yBAAyB;EAC/C,MAAM,oBAAoB,yBAAyB;EACnD,MAAMC,0BACJ,yBAAyB;AAG3B,OAFyB,yBAAyB,UAAU,WAEnC,KACvB;AAMF,OAFE,yBAAyB,QAAQ,cAAc,YAAY,QAAQ,WAE9C,MAAO;EAE9B,MAAMC,eAAwB,yBAAyB,UACrD;AAEF,MAAI,CAAC,yBAAyB;AAC5B,aACE,GAAG,iBAAiB,yDACpB,EACE,OAAO,QACR,CACF;AACD;;AAGF,MAAI,CAAC,yBAAyB,UAAU;AACtC,aAAU,GAAG,iBAAiB,0CAA0C,EACtE,OAAO,QACR,CAAC;AACF;;EAGF,MAAM,+EACJ,yBACA,aACD;AAED,MACE,OAAO,KAAK,oBAA+C,CAAC,WAAW,GACvE;AACA,aACE,GAAG,iBAAiB,0FAAiE,aAAa,CAAC,8CACnG,EACE,OAAO,QACR,CACF;AACD;;;;;;;EAQF,IAAIC,oBAA8B;AAElC,MAAI,SAAS,WACX,qBACE,oBACG,MACE,uBAAuB,mBAAmB,QAAQ,cACpD,EACC,QAAQ,QAAQ,WAAW,cAAc,SAAS,OAAO,CAAC,IAAI,EAAE;AAGxE,MAAI,kBAAkB,WAAW,GAAG;AAClC,aACE,GAAG,iBAAiB,4FAAsD,yBAAyB,SAAS,CAAC,IAC7G,EACE,OAAO,QACR,CACF;AACD;;AAGF,mBAAiB,KAAK;GACpB;GACA;GACA;GACA,eAAe;GACf;GACA,oBAAoB,yBAAyB;GAC9C,CAAC;;AAIJ,QAAO"}
|
|
@@ -75,7 +75,7 @@ const translateDictionary = async (task, configuration, options) => {
|
|
|
75
75
|
(0, __intlayer_config.colorize)("[", __intlayer_config.ANSIColors.GREY_DARK),
|
|
76
76
|
(0, __intlayer_chokidar.formatLocale)(targetLocale),
|
|
77
77
|
(0, __intlayer_config.colorize)("]", __intlayer_config.ANSIColors.GREY_DARK)
|
|
78
|
-
].join(""), { colSize:
|
|
78
|
+
].join(""), { colSize: 18 });
|
|
79
79
|
const createChunkPreset = (chunkIndex, totalChunks) => {
|
|
80
80
|
if (totalChunks <= 1) return "";
|
|
81
81
|
return (0, __intlayer_config.colon)([
|
|
@@ -97,36 +97,33 @@ const translateDictionary = async (task, configuration, options) => {
|
|
|
97
97
|
const presetOutputContent = (0, __intlayer_chokidar.reduceObjectFormat)(targetLocaleDictionary.content, chunkContent);
|
|
98
98
|
const executeTranslation = async () => {
|
|
99
99
|
return await (0, __intlayer_config.retryManager)(async () => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
entryFileContent: chunkContent,
|
|
103
|
-
presetOutputContent,
|
|
104
|
-
dictionaryDescription: dictionaryToProcess.description ?? metadata?.description ?? "",
|
|
105
|
-
entryLocale: task.sourceLocale,
|
|
106
|
-
outputLocale: targetLocale,
|
|
107
|
-
mode,
|
|
108
|
-
aiConfig
|
|
109
|
-
});
|
|
110
|
-
if (!translationResult$1) throw new Error("No content result");
|
|
111
|
-
const { isIdentic: isIdentic$1 } = (0, __intlayer_chokidar.verifyIdenticObjectFormat)(translationResult$1.fileContent, chunkContent);
|
|
112
|
-
if (!isIdentic$1) throw new Error("Translation result does not match expected format");
|
|
113
|
-
notifySuccess();
|
|
114
|
-
return translationResult$1.fileContent;
|
|
115
|
-
}
|
|
116
|
-
const translationResult = await intlayerAPI.ai.translateJSON({
|
|
100
|
+
let translationResult;
|
|
101
|
+
if (aiClient && aiConfig) translationResult = await aiClient.translateJSON({
|
|
117
102
|
entryFileContent: chunkContent,
|
|
118
103
|
presetOutputContent,
|
|
119
104
|
dictionaryDescription: dictionaryToProcess.description ?? metadata?.description ?? "",
|
|
120
105
|
entryLocale: task.sourceLocale,
|
|
121
106
|
outputLocale: targetLocale,
|
|
122
107
|
mode,
|
|
123
|
-
|
|
108
|
+
aiConfig
|
|
124
109
|
});
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
110
|
+
else translationResult = await intlayerAPI.ai.translateJSON({
|
|
111
|
+
entryFileContent: chunkContent,
|
|
112
|
+
presetOutputContent,
|
|
113
|
+
dictionaryDescription: dictionaryToProcess.description ?? metadata?.description ?? "",
|
|
114
|
+
entryLocale: task.sourceLocale,
|
|
115
|
+
outputLocale: targetLocale,
|
|
116
|
+
mode,
|
|
117
|
+
aiOptions
|
|
118
|
+
}).then((result) => result.data);
|
|
119
|
+
if (!translationResult?.fileContent) throw new Error("No content result");
|
|
120
|
+
const { isIdentic } = (0, __intlayer_chokidar.verifyIdenticObjectFormat)(translationResult.fileContent, chunkContent);
|
|
121
|
+
if (!isIdentic) {
|
|
122
|
+
console.log(translationResult.fileContent);
|
|
123
|
+
throw new Error("Translation result does not match expected format");
|
|
124
|
+
}
|
|
128
125
|
notifySuccess();
|
|
129
|
-
return translationResult.
|
|
126
|
+
return translationResult.fileContent;
|
|
130
127
|
}, {
|
|
131
128
|
maxRetry: MAX_RETRY,
|
|
132
129
|
delay: RETRY_DELAY,
|
|
@@ -166,7 +163,7 @@ const translateDictionary = async (task, configuration, options) => {
|
|
|
166
163
|
};
|
|
167
164
|
for (const targetLocale of task.targetLocales) if (translatedContent[targetLocale]) dictionaryOutput = (0, __intlayer_core.insertContentInDictionary)(dictionaryOutput, translatedContent[targetLocale], targetLocale);
|
|
168
165
|
appLogger(`${task.dictionaryPreset} ${(0, __intlayer_config.colorize)("Translation completed successfully", __intlayer_config.ANSIColors.GREEN)} for ${(0, __intlayer_config.colorizePath)((0, node_path.basename)(dictionaryOutput.filePath))}`, { level: "info" });
|
|
169
|
-
if (baseUnmergedDictionary.locale) {
|
|
166
|
+
if (baseUnmergedDictionary.locale && (baseUnmergedDictionary.fill === true || baseUnmergedDictionary.fill === void 0) && baseUnmergedDictionary.location === "local") {
|
|
170
167
|
const dictionaryFilePath = baseUnmergedDictionary.filePath.split(".").slice(0, -1);
|
|
171
168
|
const contentIndex = dictionaryFilePath[dictionaryFilePath.length - 1];
|
|
172
169
|
return JSON.parse(JSON.stringify({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translateDictionary.cjs","names":["baseUnmergedDictionary: Dictionary | undefined","metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined","targetLocaleDictionary: Dictionary","ANSIColors","chunkedJsonContent: JsonChunk[]","chunkResult: JsonChunk[]","translationResult","isIdentic","chunkPreset","translatedContent: Partial<Record<Locale, Dictionary['content']>>","dictionaryOutput: Dictionary"],"sources":["../../../src/fill/translateDictionary.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport type { AIConfig } from '@intlayer/ai';\nimport { type AIOptions, getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n chunkJSON,\n formatLocale,\n type JsonChunk,\n reconstructFromSingleChunk,\n reduceObjectFormat,\n verifyIdenticObjectFormat,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeNumber,\n colorizePath,\n getAppLogger,\n retryManager,\n} from '@intlayer/config';\nimport {\n getFilterMissingTranslationsDictionary,\n getMultilingualDictionary,\n getPerLocaleDictionary,\n insertContentInDictionary,\n mergeDictionaries,\n} from '@intlayer/core';\nimport type { Dictionary, IntlayerConfig, Locale } from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport type { AIClient } from '../utils/setupAI';\nimport type { TranslationTask } from './listTranslationsTasks';\n\ntype TranslateDictionaryResult = TranslationTask & {\n dictionaryOutput: Dictionary | null;\n};\n\ntype TranslateDictionaryOptions = {\n mode: 'complete' | 'review';\n aiOptions?: AIOptions;\n fillMetadata?: boolean;\n onHandle?: ReturnType<typeof import('@intlayer/chokidar').getGlobalLimiter>;\n onSuccess?: () => void;\n onError?: (error: unknown) => void;\n getAbortError?: () => Error | null;\n aiClient?: AIClient;\n aiConfig?: AIConfig;\n};\n\nconst hasMissingMetadata = (dictionary: Dictionary) =>\n !dictionary.description || !dictionary.title || !dictionary.tags;\n\nconst CHUNK_SIZE = 7000; // GPT-5 Mini safe input size\nconst GROUP_MAX_RETRY = 2;\nconst MAX_RETRY = 3;\nconst RETRY_DELAY = 1000 * 10; // 10 seconds\n\nconst MAX_FOLLOWING_ERRORS = 10; // 10 errors in a row, hard exit the process\nlet followingErrors = 0;\n\nexport const translateDictionary = async (\n task: TranslationTask,\n configuration: IntlayerConfig,\n options?: TranslateDictionaryOptions\n): Promise<TranslateDictionaryResult> => {\n const appLogger = getAppLogger(configuration);\n const intlayerAPI = getIntlayerAPIProxy(undefined, configuration);\n\n const { mode, aiOptions, fillMetadata, aiClient, aiConfig } = {\n mode: 'complete',\n fillMetadata: true,\n ...options,\n } as const;\n\n const notifySuccess = () => {\n followingErrors = 0;\n options?.onSuccess?.();\n };\n\n const result = await retryManager(\n async () => {\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n\n const baseUnmergedDictionary: Dictionary | undefined =\n unmergedDictionariesRecord[task.dictionaryKey].find(\n (dict) => dict.localId === task.dictionaryLocalId\n );\n\n if (!baseUnmergedDictionary) {\n appLogger(\n `${task.dictionaryPreset}Dictionary not found in unmergedDictionariesRecord. Skipping.`,\n {\n level: 'warn',\n }\n );\n return { ...task, dictionaryOutput: null };\n }\n\n let metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined;\n\n if (\n fillMetadata &&\n (hasMissingMetadata(baseUnmergedDictionary) || mode === 'review')\n ) {\n const defaultLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n configuration.internationalization.defaultLocale\n );\n\n appLogger(\n `${task.dictionaryPreset} Filling missing metadata for ${colorizePath(basename(baseUnmergedDictionary.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n const runAudit = async () => {\n if (aiClient && aiConfig) {\n const result = await aiClient.auditDictionaryMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiConfig,\n });\n\n return {\n data: result,\n };\n }\n\n return await intlayerAPI.ai.auditContentDeclarationMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiOptions,\n });\n };\n\n const metadataResult = options?.onHandle\n ? await options.onHandle(runAudit)\n : await runAudit();\n\n metadata = metadataResult.data?.fileContent;\n }\n\n const translatedContentResults = await Promise.all(\n task.targetLocales.map(async (targetLocale) => {\n /**\n * In complete mode, for large dictionaries, we want to filter all content that is already translated\n *\n * targetLocale: fr\n *\n * { test1: t({ ar: 'Hello', en: 'Hello', fr: 'Bonjour' } }) -> {}\n * { test2: t({ ar: 'Hello', en: 'Hello' }) } -> { test2: t({ ar: 'Hello', en: 'Hello' }) }\n *\n */\n // Reset to base dictionary for each locale to ensure we filter from the original\n let dictionaryToProcess = structuredClone(baseUnmergedDictionary);\n\n if (\n mode === 'complete' &&\n typeof baseUnmergedDictionary.locale !== 'string'\n ) {\n // Remove all nodes that don't have any content to translate\n dictionaryToProcess = getFilterMissingTranslationsDictionary(\n dictionaryToProcess,\n targetLocale\n );\n }\n\n dictionaryToProcess = getPerLocaleDictionary(\n dictionaryToProcess,\n task.sourceLocale\n );\n\n let targetLocaleDictionary: Dictionary;\n\n if (typeof baseUnmergedDictionary.locale === 'string') {\n targetLocaleDictionary = {\n key: baseUnmergedDictionary.key,\n content: {},\n filePath: baseUnmergedDictionary.filePath,\n };\n } else {\n targetLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n targetLocale\n );\n }\n\n const localePreset = colon(\n [\n colorize('[', ANSIColors.GREY_DARK),\n formatLocale(targetLocale),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: 10 }\n );\n\n const createChunkPreset = (\n chunkIndex: number,\n totalChunks: number\n ) => {\n if (totalChunks <= 1) return '';\n return colon(\n [\n colorize('[', ANSIColors.GREY_DARK),\n colorizeNumber(chunkIndex + 1),\n colorize(`/${totalChunks}`, ANSIColors.GREY_DARK),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: 5 }\n );\n };\n\n appLogger(\n `${task.dictionaryPreset}${localePreset} Preparing ${colorizePath(basename(targetLocaleDictionary.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n const chunkedJsonContent: JsonChunk[] = chunkJSON(\n dictionaryToProcess.content,\n CHUNK_SIZE\n );\n\n const nbOfChunks = chunkedJsonContent.length;\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset} Split into ${colorizeNumber(nbOfChunks)} chunks for translation`,\n {\n level: 'info',\n }\n );\n }\n\n const chunkResult: JsonChunk[] = [];\n\n // Process chunks in parallel (globally throttled) to allow concurrent translation\n const chunkPromises = chunkedJsonContent.map((chunk) => {\n const chunkPreset = createChunkPreset(chunk.index, chunk.total);\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset}${chunkPreset} Translating chunk`,\n {\n level: 'info',\n }\n );\n }\n\n // Reconstruct partial JSON content from this chunk's patches\n const chunkContent = reconstructFromSingleChunk(chunk);\n const presetOutputContent = reduceObjectFormat(\n targetLocaleDictionary.content,\n chunkContent\n ) as unknown as JSON;\n\n const executeTranslation = async () => {\n return await retryManager(\n async () => {\n if (aiClient && aiConfig) {\n const translationResult = await aiClient.translateJSON({\n entryFileContent: chunkContent as unknown as JSON,\n presetOutputContent,\n dictionaryDescription:\n dictionaryToProcess.description ??\n metadata?.description ??\n '',\n entryLocale: task.sourceLocale,\n outputLocale: targetLocale,\n mode,\n aiConfig,\n });\n\n if (!translationResult) {\n throw new Error('No content result');\n }\n\n const { isIdentic } = verifyIdenticObjectFormat(\n translationResult.fileContent,\n chunkContent\n );\n if (!isIdentic) {\n throw new Error(\n 'Translation result does not match expected format'\n );\n }\n\n notifySuccess();\n return translationResult.fileContent;\n }\n\n const translationResult = await intlayerAPI.ai.translateJSON({\n entryFileContent: chunkContent as unknown as JSON,\n presetOutputContent,\n dictionaryDescription:\n dictionaryToProcess.description ??\n metadata?.description ??\n '',\n entryLocale: task.sourceLocale,\n outputLocale: targetLocale,\n mode,\n aiOptions,\n });\n\n if (!translationResult.data?.fileContent) {\n throw new Error('No content result');\n }\n\n const { isIdentic } = verifyIdenticObjectFormat(\n translationResult.data.fileContent,\n chunkContent\n );\n if (!isIdentic) {\n throw new Error(\n 'Translation result does not match expected format'\n );\n }\n\n notifySuccess();\n return translationResult.data.fileContent;\n },\n {\n maxRetry: MAX_RETRY,\n delay: RETRY_DELAY,\n onError: ({ error, attempt, maxRetry }) => {\n const chunkPreset = createChunkPreset(\n chunk.index,\n chunk.total\n );\n appLogger(\n `${task.dictionaryPreset}${localePreset}${chunkPreset} ${colorize('Error filling:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)} - Attempt ${colorizeNumber(attempt + 1)} of ${colorizeNumber(maxRetry)}`,\n {\n level: 'error',\n }\n );\n\n followingErrors += 1;\n\n if (followingErrors >= MAX_FOLLOWING_ERRORS) {\n appLogger(`There is something wrong.`, {\n level: 'error',\n });\n process.exit(1); // 1 for error\n }\n },\n }\n )();\n };\n\n const wrapped = options?.onHandle\n ? options.onHandle(executeTranslation) // queued in global limiter\n : executeTranslation(); // no global limiter\n\n return wrapped.then((result) => ({ chunk, result }));\n });\n\n // Wait for all chunks for this locale in parallel (still capped by global limiter)\n const chunkResults = await Promise.all(chunkPromises);\n\n // Maintain order\n chunkResults\n .sort((chunkA, chunkB) => chunkA.chunk.index - chunkB.chunk.index)\n .forEach(({ result }) => {\n chunkResult.push(result);\n });\n\n // Merge partial JSON objects produced from each chunk into a single object\n const merged = mergeDictionaries(\n chunkResult.map((chunk) => ({\n ...dictionaryToProcess,\n content: chunk,\n }))\n );\n\n return [targetLocale, merged.content] as const;\n })\n );\n\n const translatedContent: Partial<Record<Locale, Dictionary['content']>> =\n Object.fromEntries(translatedContentResults);\n\n const baseDictionary = baseUnmergedDictionary.locale\n ? {\n ...baseUnmergedDictionary,\n key: baseUnmergedDictionary.key!,\n content: {},\n }\n : baseUnmergedDictionary;\n\n let dictionaryOutput: Dictionary = {\n ...getMultilingualDictionary(baseDictionary),\n locale: undefined, // Ensure the dictionary is multilingual\n ...metadata,\n };\n\n for (const targetLocale of task.targetLocales) {\n if (translatedContent[targetLocale]) {\n dictionaryOutput = insertContentInDictionary(\n dictionaryOutput,\n translatedContent[targetLocale],\n targetLocale\n );\n }\n }\n\n appLogger(\n `${task.dictionaryPreset} ${colorize('Translation completed successfully', ANSIColors.GREEN)} for ${colorizePath(basename(dictionaryOutput.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n if (baseUnmergedDictionary.locale) {\n const dictionaryFilePath = baseUnmergedDictionary\n .filePath!.split('.')\n .slice(0, -1);\n\n const contentIndex = dictionaryFilePath[dictionaryFilePath.length - 1];\n\n return JSON.parse(\n JSON.stringify({\n ...task,\n dictionaryOutput: {\n ...dictionaryOutput,\n fill: undefined,\n filled: true,\n },\n }).replaceAll(\n new RegExp(`\\\\.${contentIndex}\\\\.[a-zA-Z0-9]+`, 'g'),\n `.filled.${contentIndex}.json`\n )\n );\n }\n\n return {\n ...task,\n dictionaryOutput,\n };\n },\n {\n maxRetry: GROUP_MAX_RETRY,\n delay: RETRY_DELAY,\n onError: ({ error, attempt, maxRetry }) =>\n appLogger(\n `${task.dictionaryPreset} ${colorize('Error fill command:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)} - Attempt ${colorizeNumber(attempt + 1)} of ${colorizeNumber(maxRetry)}`,\n {\n level: 'error',\n }\n ),\n onMaxTryReached: ({ error }) =>\n appLogger(\n `${task.dictionaryPreset} ${colorize('Maximum number of retries reached:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)}`,\n {\n level: 'error',\n }\n ),\n }\n )();\n\n return result as TranslateDictionaryResult;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"translateDictionary.cjs","names":["baseUnmergedDictionary: Dictionary | undefined","metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined","targetLocaleDictionary: Dictionary","ANSIColors","chunkedJsonContent: JsonChunk[]","chunkResult: JsonChunk[]","translationResult: any","chunkPreset","translatedContent: Partial<Record<Locale, Dictionary['content']>>","dictionaryOutput: Dictionary"],"sources":["../../../src/fill/translateDictionary.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport type { AIConfig } from '@intlayer/ai';\nimport { type AIOptions, getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n chunkJSON,\n formatLocale,\n type JsonChunk,\n reconstructFromSingleChunk,\n reduceObjectFormat,\n verifyIdenticObjectFormat,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeNumber,\n colorizePath,\n getAppLogger,\n retryManager,\n} from '@intlayer/config';\nimport {\n getFilterMissingTranslationsDictionary,\n getMultilingualDictionary,\n getPerLocaleDictionary,\n insertContentInDictionary,\n mergeDictionaries,\n} from '@intlayer/core';\nimport type { Dictionary, IntlayerConfig, Locale } from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport type { AIClient } from '../utils/setupAI';\nimport type { TranslationTask } from './listTranslationsTasks';\n\ntype TranslateDictionaryResult = TranslationTask & {\n dictionaryOutput: Dictionary | null;\n};\n\ntype TranslateDictionaryOptions = {\n mode: 'complete' | 'review';\n aiOptions?: AIOptions;\n fillMetadata?: boolean;\n onHandle?: ReturnType<typeof import('@intlayer/chokidar').getGlobalLimiter>;\n onSuccess?: () => void;\n onError?: (error: unknown) => void;\n getAbortError?: () => Error | null;\n aiClient?: AIClient;\n aiConfig?: AIConfig;\n};\n\nconst hasMissingMetadata = (dictionary: Dictionary) =>\n !dictionary.description || !dictionary.title || !dictionary.tags;\n\nconst CHUNK_SIZE = 7000; // GPT-5 Mini safe input size\nconst GROUP_MAX_RETRY = 2;\nconst MAX_RETRY = 3;\nconst RETRY_DELAY = 1000 * 10; // 10 seconds\n\nconst MAX_FOLLOWING_ERRORS = 10; // 10 errors in a row, hard exit the process\nlet followingErrors = 0;\n\nexport const translateDictionary = async (\n task: TranslationTask,\n configuration: IntlayerConfig,\n options?: TranslateDictionaryOptions\n): Promise<TranslateDictionaryResult> => {\n const appLogger = getAppLogger(configuration);\n const intlayerAPI = getIntlayerAPIProxy(undefined, configuration);\n\n const { mode, aiOptions, fillMetadata, aiClient, aiConfig } = {\n mode: 'complete',\n fillMetadata: true,\n ...options,\n } as const;\n\n const notifySuccess = () => {\n followingErrors = 0;\n options?.onSuccess?.();\n };\n\n const result = await retryManager(\n async () => {\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n\n const baseUnmergedDictionary: Dictionary | undefined =\n unmergedDictionariesRecord[task.dictionaryKey].find(\n (dict) => dict.localId === task.dictionaryLocalId\n );\n\n if (!baseUnmergedDictionary) {\n appLogger(\n `${task.dictionaryPreset}Dictionary not found in unmergedDictionariesRecord. Skipping.`,\n {\n level: 'warn',\n }\n );\n return { ...task, dictionaryOutput: null };\n }\n\n let metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined;\n\n if (\n fillMetadata &&\n (hasMissingMetadata(baseUnmergedDictionary) || mode === 'review')\n ) {\n const defaultLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n configuration.internationalization.defaultLocale\n );\n\n appLogger(\n `${task.dictionaryPreset} Filling missing metadata for ${colorizePath(basename(baseUnmergedDictionary.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n const runAudit = async () => {\n if (aiClient && aiConfig) {\n const result = await aiClient.auditDictionaryMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiConfig,\n });\n\n return {\n data: result,\n };\n }\n\n return await intlayerAPI.ai.auditContentDeclarationMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiOptions,\n });\n };\n\n const metadataResult = options?.onHandle\n ? await options.onHandle(runAudit)\n : await runAudit();\n\n metadata = metadataResult.data?.fileContent;\n }\n\n const translatedContentResults = await Promise.all(\n task.targetLocales.map(async (targetLocale) => {\n /**\n * In complete mode, for large dictionaries, we want to filter all content that is already translated\n *\n * targetLocale: fr\n *\n * { test1: t({ ar: 'Hello', en: 'Hello', fr: 'Bonjour' } }) -> {}\n * { test2: t({ ar: 'Hello', en: 'Hello' }) } -> { test2: t({ ar: 'Hello', en: 'Hello' }) }\n *\n */\n // Reset to base dictionary for each locale to ensure we filter from the original\n let dictionaryToProcess = structuredClone(baseUnmergedDictionary);\n\n if (\n mode === 'complete' &&\n typeof baseUnmergedDictionary.locale !== 'string'\n ) {\n // Remove all nodes that don't have any content to translate\n dictionaryToProcess = getFilterMissingTranslationsDictionary(\n dictionaryToProcess,\n targetLocale\n );\n }\n\n dictionaryToProcess = getPerLocaleDictionary(\n dictionaryToProcess,\n task.sourceLocale\n );\n\n let targetLocaleDictionary: Dictionary;\n\n if (typeof baseUnmergedDictionary.locale === 'string') {\n targetLocaleDictionary = {\n key: baseUnmergedDictionary.key,\n content: {},\n filePath: baseUnmergedDictionary.filePath,\n };\n } else {\n targetLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n targetLocale\n );\n }\n\n const localePreset = colon(\n [\n colorize('[', ANSIColors.GREY_DARK),\n formatLocale(targetLocale),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: 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 chunkedJsonContent: JsonChunk[] = chunkJSON(\n dictionaryToProcess.content,\n CHUNK_SIZE\n );\n\n const nbOfChunks = chunkedJsonContent.length;\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset} Split into ${colorizeNumber(nbOfChunks)} chunks for translation`,\n {\n level: 'info',\n }\n );\n }\n\n const chunkResult: JsonChunk[] = [];\n\n // Process chunks in parallel (globally throttled) to allow concurrent translation\n const chunkPromises = chunkedJsonContent.map((chunk) => {\n const chunkPreset = createChunkPreset(chunk.index, chunk.total);\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset}${chunkPreset} Translating chunk`,\n {\n level: 'info',\n }\n );\n }\n\n // Reconstruct partial JSON content from this chunk's patches\n const chunkContent = reconstructFromSingleChunk(chunk);\n const presetOutputContent = reduceObjectFormat(\n targetLocaleDictionary.content,\n chunkContent\n ) as unknown as JSON;\n\n const executeTranslation = async () => {\n return await retryManager(\n async () => {\n 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 if (!isIdentic) {\n console.log(translationResult.fileContent);\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 merged = mergeDictionaries(\n chunkResult.map((chunk) => ({\n ...dictionaryToProcess,\n content: chunk,\n }))\n );\n\n return [targetLocale, merged.content] as const;\n })\n );\n\n const translatedContent: Partial<Record<Locale, Dictionary['content']>> =\n Object.fromEntries(translatedContentResults);\n\n const baseDictionary = baseUnmergedDictionary.locale\n ? {\n ...baseUnmergedDictionary,\n key: baseUnmergedDictionary.key!,\n content: {},\n }\n : baseUnmergedDictionary;\n\n let dictionaryOutput: Dictionary = {\n ...getMultilingualDictionary(baseDictionary),\n locale: undefined, // Ensure the dictionary is multilingual\n ...metadata,\n };\n\n for (const targetLocale of task.targetLocales) {\n if (translatedContent[targetLocale]) {\n dictionaryOutput = insertContentInDictionary(\n dictionaryOutput,\n translatedContent[targetLocale],\n targetLocale\n );\n }\n }\n\n appLogger(\n `${task.dictionaryPreset} ${colorize('Translation completed successfully', ANSIColors.GREEN)} for ${colorizePath(basename(dictionaryOutput.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n if (\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 fill command:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)} - Attempt ${colorizeNumber(attempt + 1)} of ${colorizeNumber(maxRetry)}`,\n {\n level: 'error',\n }\n ),\n onMaxTryReached: ({ error }) =>\n appLogger(\n `${task.dictionaryPreset} ${colorize('Maximum number of retries reached:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)}`,\n {\n level: 'error',\n }\n ),\n }\n )();\n\n return result as TranslateDictionaryResult;\n};\n"],"mappings":"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"}
|
|
@@ -14,15 +14,12 @@ const writeFill = async (contentDeclarationFile, outputLocales, parentLocales, c
|
|
|
14
14
|
appLogger("No file path found for dictionary", { level: "error" });
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
if (typeof fillOptions === "undefined" && typeof contentDeclarationFile.locale === "string") fillOptions = "./{{key}}.filled.content.json";
|
|
19
|
-
else fillOptions = true;
|
|
17
|
+
const fillOptions = contentDeclarationFile.fill ?? configuration.dictionary?.fill ?? true;
|
|
20
18
|
if (fillOptions === false) {
|
|
21
19
|
appLogger(`Auto fill is disabled for '${(0, __intlayer_config.colorizeKey)(fullDictionary.key)}'`, { level: "info" });
|
|
22
20
|
return;
|
|
23
21
|
}
|
|
24
|
-
const
|
|
25
|
-
const fillData = require_fill_formatFillData.formatFillData(fillOptions, localeList, filePath, fullDictionary.key, configuration);
|
|
22
|
+
const fillData = require_fill_formatFillData.formatFillData(fillOptions, (outputLocales ?? configuration.internationalization.locales).filter((locale) => !parentLocales?.includes(locale)), filePath, fullDictionary.key, configuration);
|
|
26
23
|
for await (const output of fillData) {
|
|
27
24
|
if (!output.filePath) {
|
|
28
25
|
appLogger(`No file path found for auto filled content declaration for '${(0, __intlayer_config.colorizeKey)(fullDictionary.key)}'`, { level: "error" });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeFill.cjs","names":["fillOptions: Fill | undefined","
|
|
1
|
+
{"version":3,"file":"writeFill.cjs","names":["fillOptions: Fill | undefined","fillData: FillData[]","formatFillData"],"sources":["../../../src/fill/writeFill.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport {\n formatLocale,\n formatPath,\n writeContentDeclaration,\n} from '@intlayer/chokidar';\nimport { colorizeKey, getAppLogger } from '@intlayer/config';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { Dictionary, Fill, IntlayerConfig, Locale } from '@intlayer/types';\nimport { type FillData, formatFillData } from './formatFillData';\n\nexport const writeFill = async (\n contentDeclarationFile: Dictionary,\n outputLocales: Locale[],\n parentLocales: Locale[],\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n const dictionaries = getDictionaries(configuration);\n\n const fullDictionary = dictionaries[contentDeclarationFile.key];\n\n const { filePath } = contentDeclarationFile;\n\n if (!filePath) {\n appLogger('No file path found for dictionary', {\n level: 'error',\n });\n return;\n }\n\n 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 localeList: Locale[] = (\n outputLocales ?? configuration.internationalization.locales\n ).filter((locale) => !parentLocales?.includes(locale));\n\n const fillData: FillData[] = formatFillData(\n fillOptions as Fill,\n localeList,\n filePath,\n fullDictionary.key,\n configuration\n );\n\n for await (const output of fillData) {\n if (!output.filePath) {\n appLogger(\n `No file path found for auto filled content declaration for '${colorizeKey(fullDictionary.key)}'`,\n {\n level: 'error',\n }\n );\n continue;\n }\n\n // biome-ignore lint/correctness/noUnusedVariables: Just filtering out the fill property\n const { fill, ...rest } = contentDeclarationFile;\n\n const relativeFilePath = relative(\n configuration.content.baseDir,\n output.filePath\n );\n\n // write file\n await writeContentDeclaration(\n {\n ...rest,\n filled: true,\n locale: output.isPerLocale ? output.localeList[0] : undefined,\n localId: `${contentDeclarationFile.key}::local::${relativeFilePath}`,\n filePath: relativeFilePath,\n },\n configuration,\n {\n localeList: output.localeList,\n }\n );\n\n if (output.isPerLocale) {\n const sourceLocale = output.localeList[0];\n\n appLogger(\n `Auto filled per-locale content declaration for '${colorizeKey(fullDictionary.key)}' written to ${formatPath(output.filePath)} for locale ${formatLocale(sourceLocale)}`,\n {\n level: 'info',\n }\n );\n } else {\n appLogger(\n `Auto filled content declaration for '${colorizeKey(fullDictionary.key)}' written to ${formatPath(output.filePath)}`,\n {\n level: 'info',\n }\n );\n }\n }\n};\n"],"mappings":";;;;;;;;AAWA,MAAa,YAAY,OACvB,wBACA,eACA,eACA,kBACG;CACH,MAAM,gDAAyB,cAAc;CAG7C,MAAM,oEAF+B,cAAc,CAEf,uBAAuB;CAE3D,MAAM,EAAE,aAAa;AAErB,KAAI,CAAC,UAAU;AACb,YAAU,qCAAqC,EAC7C,OAAO,SACR,CAAC;AACF;;CAGF,MAAMA,cACJ,uBAAuB,QAAQ,cAAc,YAAY,QAAQ;AAEnE,KAAK,gBAA4B,OAAO;AACtC,YACE,iEAA0C,eAAe,IAAI,CAAC,IAC9D,EACE,OAAO,QACR,CACF;AACD;;CAOF,MAAMC,WAAuBC,2CAC3B,cAJA,iBAAiB,cAAc,qBAAqB,SACpD,QAAQ,WAAW,CAAC,eAAe,SAAS,OAAO,CAAC,EAKpD,UACA,eAAe,KACf,cACD;AAED,YAAW,MAAM,UAAU,UAAU;AACnC,MAAI,CAAC,OAAO,UAAU;AACpB,aACE,kGAA2E,eAAe,IAAI,CAAC,IAC/F,EACE,OAAO,SACR,CACF;AACD;;EAIF,MAAM,EAAE,MAAM,GAAG,SAAS;EAE1B,MAAM,2CACJ,cAAc,QAAQ,SACtB,OAAO,SACR;AAGD,yDACE;GACE,GAAG;GACH,QAAQ;GACR,QAAQ,OAAO,cAAc,OAAO,WAAW,KAAK;GACpD,SAAS,GAAG,uBAAuB,IAAI,WAAW;GAClD,UAAU;GACX,EACD,eACA,EACE,YAAY,OAAO,YACpB,CACF;AAED,MAAI,OAAO,aAAa;GACtB,MAAM,eAAe,OAAO,WAAW;AAEvC,aACE,sFAA+D,eAAe,IAAI,CAAC,mDAA0B,OAAO,SAAS,CAAC,oDAA2B,aAAa,IACtK,EACE,OAAO,QACR,CACF;QAED,WACE,2EAAoD,eAAe,IAAI,CAAC,mDAA0B,OAAO,SAAS,IAClH,EACE,OAAO,QACR,CACF"}
|
|
@@ -16,17 +16,17 @@ const listContentDeclarationRows = (options) => {
|
|
|
16
16
|
const listContentDeclaration = (options) => {
|
|
17
17
|
const appLogger = (0, __intlayer_config.getAppLogger)((0, __intlayer_config.getConfiguration)(options?.configOptions), { config: { prefix: "" } });
|
|
18
18
|
const rows = listContentDeclarationRows(options);
|
|
19
|
-
const lines = rows.map((
|
|
20
|
-
(0, __intlayer_config.colon)(` - ${(0, __intlayer_config.colorizeKey)(
|
|
21
|
-
colSize: rows.map((
|
|
19
|
+
const lines = rows.map((row) => [
|
|
20
|
+
(0, __intlayer_config.colon)(` - ${(0, __intlayer_config.colorizeKey)(row.key)}`, {
|
|
21
|
+
colSize: rows.map((row$1) => row$1.key.length),
|
|
22
22
|
maxSize: 60
|
|
23
23
|
}),
|
|
24
24
|
" - ",
|
|
25
|
-
(0, __intlayer_chokidar.formatPath)(
|
|
25
|
+
(0, __intlayer_chokidar.formatPath)(row.path)
|
|
26
26
|
].join(""));
|
|
27
27
|
appLogger(`Content declaration files:`);
|
|
28
|
-
lines.forEach((
|
|
29
|
-
appLogger(
|
|
28
|
+
lines.forEach((line) => {
|
|
29
|
+
appLogger(line, { level: "info" });
|
|
30
30
|
});
|
|
31
31
|
appLogger(`Total content declaration files: ${(0, __intlayer_config.colorizeNumber)(rows.length)}`);
|
|
32
32
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listContentDeclaration.cjs","names":["
|
|
1
|
+
{"version":3,"file":"listContentDeclaration.cjs","names":["row"],"sources":["../../src/listContentDeclaration.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport { formatPath } from '@intlayer/chokidar';\nimport {\n colon,\n colorizeKey,\n colorizeNumber,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\n\ntype ListContentDeclarationOptions = {\n configOptions?: GetConfigurationOptions;\n};\n\nexport const listContentDeclarationRows = (\n options?: ListContentDeclarationOptions\n) => {\n const config = getConfiguration(options?.configOptions);\n\n const unmergedDictionariesRecord = getUnmergedDictionaries(config);\n\n const rows = Object.values(unmergedDictionariesRecord)\n .flat()\n .map((dictionary) => ({\n key: dictionary.key ?? '',\n path: relative(config.content.baseDir, dictionary.filePath ?? 'Remote'),\n }));\n return rows;\n};\n\nexport const listContentDeclaration = (\n options?: ListContentDeclarationOptions\n) => {\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config, {\n config: {\n prefix: '',\n },\n });\n\n const rows = listContentDeclarationRows(options);\n\n const lines = rows.map((row) =>\n [\n colon(` - ${colorizeKey(row.key)}`, {\n colSize: rows.map((row) => row.key.length),\n maxSize: 60,\n }),\n ' - ',\n formatPath(row.path),\n ].join('')\n );\n\n appLogger(`Content declaration files:`);\n\n lines.forEach((line) => {\n appLogger(line, {\n level: 'info',\n });\n });\n\n appLogger(`Total content declaration files: ${colorizeNumber(rows.length)}`);\n};\n"],"mappings":";;;;;;;AAgBA,MAAa,8BACX,YACG;CACH,MAAM,iDAA0B,SAAS,cAAc;CAEvD,MAAM,iGAAqD,OAAO;AAQlE,QANa,OAAO,OAAO,2BAA2B,CACnD,MAAM,CACN,KAAK,gBAAgB;EACpB,KAAK,WAAW,OAAO;EACvB,8BAAe,OAAO,QAAQ,SAAS,WAAW,YAAY,SAAS;EACxE,EAAE;;AAIP,MAAa,0BACX,YACG;CAEH,MAAM,wFAD0B,SAAS,cAAc,EAChB,EACrC,QAAQ,EACN,QAAQ,IACT,EACF,CAAC;CAEF,MAAM,OAAO,2BAA2B,QAAQ;CAEhD,MAAM,QAAQ,KAAK,KAAK,QACtB;+BACQ,yCAAkB,IAAI,IAAI,IAAI;GAClC,SAAS,KAAK,KAAK,UAAQA,MAAI,IAAI,OAAO;GAC1C,SAAS;GACV,CAAC;EACF;sCACW,IAAI,KAAK;EACrB,CAAC,KAAK,GAAG,CACX;AAED,WAAU,6BAA6B;AAEvC,OAAM,SAAS,SAAS;AACtB,YAAU,MAAM,EACd,OAAO,QACR,CAAC;GACF;AAEF,WAAU,0EAAmD,KAAK,OAAO,GAAG"}
|
|
@@ -7,7 +7,7 @@ const formatAutoFilledFilePath = (autoFillField, dictionaryKey, dictionaryFilePa
|
|
|
7
7
|
if (!dictionaryFilePath || typeof dictionaryFilePath !== "string") throw new Error("dictionaryFilePath must be a non-empty string");
|
|
8
8
|
if (!baseDir || typeof baseDir !== "string") throw new Error("baseDir must be a non-empty string");
|
|
9
9
|
const originalFileName = basename(dictionaryFilePath).split(".").slice(0, -2).join(".");
|
|
10
|
-
let result = autoFillField.replace(/\{\{key\}\}/g,
|
|
10
|
+
let result = autoFillField.replace(/\{\{key\}\}/g, dictionaryKey).replace(/\{\{fileName\}\}/g, originalFileName);
|
|
11
11
|
if (locale) result = result.replace(/\{\{locale\}\}/g, locale);
|
|
12
12
|
const absoluteDictionaryPath = isAbsolute(dictionaryFilePath) ? dictionaryFilePath : resolve(baseDir, dictionaryFilePath);
|
|
13
13
|
if (result.startsWith("./") || result.startsWith("../")) return resolve(dirname(absoluteDictionaryPath), result);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatAutoFilledFilePath.mjs","names":["result: string"],"sources":["../../../src/fill/formatAutoFilledFilePath.ts"],"sourcesContent":["import { basename, dirname, isAbsolute, normalize, resolve } from 'node:path';\nimport type { LocalesValues } from '@intlayer/types';\n\nexport const formatAutoFilledFilePath = (\n autoFillField: string,\n dictionaryKey: string,\n dictionaryFilePath: string,\n baseDir: string,\n locale?: LocalesValues\n): string => {\n // Validate inputs\n if (!autoFillField || typeof autoFillField !== 'string') {\n throw new Error('autoFillField must be a non-empty string');\n }\n if (!dictionaryKey || typeof dictionaryKey !== 'string') {\n throw new Error('dictionaryKey must be a non-empty string');\n }\n if (!dictionaryFilePath || typeof dictionaryFilePath !== 'string') {\n throw new Error('dictionaryFilePath must be a non-empty string');\n }\n if (!baseDir || typeof baseDir !== 'string') {\n throw new Error('baseDir must be a non-empty string');\n }\n\n // Extract the original filename without extensions (.content.ts -> dictionaryFieldEditor)\n const originalFileName = basename(dictionaryFilePath)\n .split('.')\n .slice(0, -2) // Remove last 2 extensions (.content.tsx)\n .join('.');\n\n // Replace placeholders in autoFillField\n let result: string = autoFillField\n .replace(/\\{\\{key\\}\\}/g,
|
|
1
|
+
{"version":3,"file":"formatAutoFilledFilePath.mjs","names":["result: string"],"sources":["../../../src/fill/formatAutoFilledFilePath.ts"],"sourcesContent":["import { basename, dirname, isAbsolute, normalize, resolve } from 'node:path';\nimport type { LocalesValues } from '@intlayer/types';\n\nexport const formatAutoFilledFilePath = (\n autoFillField: string,\n dictionaryKey: string,\n dictionaryFilePath: string,\n baseDir: string,\n locale?: LocalesValues\n): string => {\n // Validate inputs\n if (!autoFillField || typeof autoFillField !== 'string') {\n throw new Error('autoFillField must be a non-empty string');\n }\n if (!dictionaryKey || typeof dictionaryKey !== 'string') {\n throw new Error('dictionaryKey must be a non-empty string');\n }\n if (!dictionaryFilePath || typeof dictionaryFilePath !== 'string') {\n throw new Error('dictionaryFilePath must be a non-empty string');\n }\n if (!baseDir || typeof baseDir !== 'string') {\n throw new Error('baseDir must be a non-empty string');\n }\n\n // Extract the original filename without extensions (.content.ts -> dictionaryFieldEditor)\n const originalFileName = basename(dictionaryFilePath)\n .split('.')\n .slice(0, -2) // Remove last 2 extensions (.content.tsx)\n .join('.');\n\n // Replace placeholders in autoFillField\n let result: string = autoFillField\n .replace(/\\{\\{key\\}\\}/g, dictionaryKey) // Use original filename, not dictionaryKey\n .replace(/\\{\\{fileName\\}\\}/g, originalFileName);\n\n if (locale) {\n result = result.replace(/\\{\\{locale\\}\\}/g, locale);\n }\n\n // Normalize the dictionary file path - if it's relative, make it absolute relative to baseDir\n const absoluteDictionaryPath = isAbsolute(dictionaryFilePath)\n ? dictionaryFilePath\n : resolve(baseDir, dictionaryFilePath);\n\n // Handle relative paths (starting with ./ or ../)\n if (result.startsWith('./') || result.startsWith('../')) {\n const fileDir = dirname(absoluteDictionaryPath);\n const resolvedPath = resolve(fileDir, result);\n\n return resolvedPath;\n }\n\n // Handle absolute paths\n if (isAbsolute(result)) {\n const normalizedResult = normalize(result);\n const normalizedBaseDir = normalize(baseDir);\n\n // Check if it's relative to baseDir (starts with /)\n // but not a system path (like /usr/local)\n if (\n result.startsWith('/') &&\n !normalizedResult.startsWith(normalizedBaseDir)\n ) {\n // Try to resolve it relative to baseDir first\n const relativeToBase = resolve(baseDir, result.substring(1));\n\n // If the path doesn't exist in common system directories, treat as relative to baseDir\n if (\n !result.startsWith('/usr/') &&\n !result.startsWith('/etc/') &&\n !result.startsWith('/var/') &&\n !result.startsWith('/home/') &&\n !result.startsWith('/Users/')\n ) {\n return relativeToBase;\n }\n }\n\n // It's a true system absolute path\n return normalizedResult;\n }\n\n // Default case: treat as relative to baseDir\n return normalize(result);\n};\n"],"mappings":";;;AAGA,MAAa,4BACX,eACA,eACA,oBACA,SACA,WACW;AAEX,KAAI,CAAC,iBAAiB,OAAO,kBAAkB,SAC7C,OAAM,IAAI,MAAM,2CAA2C;AAE7D,KAAI,CAAC,iBAAiB,OAAO,kBAAkB,SAC7C,OAAM,IAAI,MAAM,2CAA2C;AAE7D,KAAI,CAAC,sBAAsB,OAAO,uBAAuB,SACvD,OAAM,IAAI,MAAM,gDAAgD;AAElE,KAAI,CAAC,WAAW,OAAO,YAAY,SACjC,OAAM,IAAI,MAAM,qCAAqC;CAIvD,MAAM,mBAAmB,SAAS,mBAAmB,CAClD,MAAM,IAAI,CACV,MAAM,GAAG,GAAG,CACZ,KAAK,IAAI;CAGZ,IAAIA,SAAiB,cAClB,QAAQ,gBAAgB,cAAc,CACtC,QAAQ,qBAAqB,iBAAiB;AAEjD,KAAI,OACF,UAAS,OAAO,QAAQ,mBAAmB,OAAO;CAIpD,MAAM,yBAAyB,WAAW,mBAAmB,GACzD,qBACA,QAAQ,SAAS,mBAAmB;AAGxC,KAAI,OAAO,WAAW,KAAK,IAAI,OAAO,WAAW,MAAM,CAIrD,QAFqB,QADL,QAAQ,uBAAuB,EACT,OAAO;AAM/C,KAAI,WAAW,OAAO,EAAE;EACtB,MAAM,mBAAmB,UAAU,OAAO;EAC1C,MAAM,oBAAoB,UAAU,QAAQ;AAI5C,MACE,OAAO,WAAW,IAAI,IACtB,CAAC,iBAAiB,WAAW,kBAAkB,EAC/C;GAEA,MAAM,iBAAiB,QAAQ,SAAS,OAAO,UAAU,EAAE,CAAC;AAG5D,OACE,CAAC,OAAO,WAAW,QAAQ,IAC3B,CAAC,OAAO,WAAW,QAAQ,IAC3B,CAAC,OAAO,WAAW,QAAQ,IAC3B,CAAC,OAAO,WAAW,SAAS,IAC5B,CAAC,OAAO,WAAW,UAAU,CAE7B,QAAO;;AAKX,SAAO;;AAIT,QAAO,UAAU,OAAO"}
|
|
@@ -25,6 +25,7 @@ const listTranslationsTasks = (localIds, outputLocales, mode, baseLocale, config
|
|
|
25
25
|
const dictionaryKey = targetUnmergedDictionary.key;
|
|
26
26
|
const dictionaryLocalId = targetUnmergedDictionary.localId;
|
|
27
27
|
const mainDictionaryToProcess = mergedDictionariesRecord[dictionaryKey];
|
|
28
|
+
if ((targetUnmergedDictionary.filled ?? false) === true) continue;
|
|
28
29
|
if ((targetUnmergedDictionary.fill ?? configuration.dictionary?.fill ?? false) === false) continue;
|
|
29
30
|
const sourceLocale = targetUnmergedDictionary.locale ?? baseLocale;
|
|
30
31
|
if (!mainDictionaryToProcess) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listTranslationsTasks.mjs","names":["translationTasks: TranslationTask[]","mainDictionaryToProcess: Dictionary","sourceLocale: Locale","outputLocalesList: Locale[]"],"sources":["../../../src/fill/listTranslationsTasks.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport { formatLocale } from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeKey,\n colorizePath,\n getAppLogger,\n} from '@intlayer/config';\nimport { getFilterTranslationsOnlyDictionary } from '@intlayer/core';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type {\n Dictionary,\n IntlayerConfig,\n LocalDictionaryId,\n Locale,\n} from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { listMissingTranslationsWithConfig } from '../test';\n\nexport type TranslationTask = {\n dictionaryKey: string;\n dictionaryLocalId: LocalDictionaryId;\n sourceLocale: Locale;\n targetLocales: Locale[];\n dictionaryPreset: string;\n dictionaryFilePath: string;\n};\n\nexport const listTranslationsTasks = (\n localIds: LocalDictionaryId[],\n outputLocales: Locale[],\n mode: 'complete' | 'review',\n baseLocale: Locale,\n configuration: IntlayerConfig\n): TranslationTask[] => {\n const appLogger = getAppLogger(configuration);\n\n const mergedDictionariesRecord = getDictionaries(configuration);\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n\n const allFlatDictionaries = Object.values(unmergedDictionariesRecord).flat();\n const dictionariesToProcess = allFlatDictionaries.filter((dictionary) =>\n localIds.includes(dictionary.localId!)\n );\n\n const { missingTranslations } =\n listMissingTranslationsWithConfig(configuration);\n\n const maxKeyLength = Math.max(\n ...dictionariesToProcess.map((dictionary) => dictionary.key.length)\n );\n\n const translationTasks: TranslationTask[] = [];\n\n for (const targetUnmergedDictionary of dictionariesToProcess) {\n const dictionaryPreset = colon(\n [\n ' - ',\n colorize('[', ANSIColors.GREY_DARK),\n colorizeKey(targetUnmergedDictionary.key),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: maxKeyLength + 6 }\n );\n\n const dictionaryKey = targetUnmergedDictionary.key;\n const dictionaryLocalId = targetUnmergedDictionary.localId!;\n const mainDictionaryToProcess: Dictionary =\n mergedDictionariesRecord[dictionaryKey];\n const dictionaryFill =\n targetUnmergedDictionary.fill ?? configuration.dictionary?.fill ?? false;\n\n if (dictionaryFill === false) continue;\n\n const sourceLocale: Locale = (targetUnmergedDictionary.locale ??\n baseLocale) as Locale;\n\n if (!mainDictionaryToProcess) {\n appLogger(\n `${dictionaryPreset} Dictionary not found in dictionariesRecord. Skipping.`,\n {\n level: 'warn',\n }\n );\n continue;\n }\n\n if (!targetUnmergedDictionary.filePath) {\n appLogger(`${dictionaryPreset} Dictionary has no file path. Skipping.`, {\n level: 'warn',\n });\n continue;\n }\n\n const sourceLocaleContent = getFilterTranslationsOnlyDictionary(\n mainDictionaryToProcess,\n sourceLocale\n );\n\n if (\n Object.keys(sourceLocaleContent as Record<string, unknown>).length === 0\n ) {\n appLogger(\n `${dictionaryPreset} No content found for dictionary in source locale ${formatLocale(sourceLocale)}. Skipping translation for this dictionary.`,\n {\n level: 'warn',\n }\n );\n continue;\n }\n\n /**\n * In 'complete' mode, filter only the missing locales to translate\n *\n * Skip the dictionary if there are no missing locales to translate\n */\n let outputLocalesList: Locale[] = outputLocales as Locale[];\n\n if (mode === 'complete') {\n outputLocalesList =\n missingTranslations\n .find(\n (missingTranslation) => missingTranslation.key === dictionaryKey\n )\n ?.locales.filter((locale) => outputLocales.includes(locale)) ?? [];\n }\n\n if (outputLocalesList.length === 0) {\n appLogger(\n `${dictionaryPreset} No locales to fill, Skipping ${colorizePath(basename(targetUnmergedDictionary.filePath))}`,\n {\n level: 'warn',\n }\n );\n continue;\n }\n\n translationTasks.push({\n dictionaryKey,\n dictionaryLocalId,\n sourceLocale,\n targetLocales: outputLocalesList,\n dictionaryPreset,\n dictionaryFilePath: targetUnmergedDictionary.filePath,\n });\n }\n\n // Return the list of tasks to execute\n
|
|
1
|
+
{"version":3,"file":"listTranslationsTasks.mjs","names":["translationTasks: TranslationTask[]","mainDictionaryToProcess: Dictionary","sourceLocale: Locale","outputLocalesList: Locale[]"],"sources":["../../../src/fill/listTranslationsTasks.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport { formatLocale } from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeKey,\n colorizePath,\n getAppLogger,\n} from '@intlayer/config';\nimport { getFilterTranslationsOnlyDictionary } from '@intlayer/core';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type {\n Dictionary,\n IntlayerConfig,\n LocalDictionaryId,\n Locale,\n} from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { listMissingTranslationsWithConfig } from '../test';\n\nexport type TranslationTask = {\n dictionaryKey: string;\n dictionaryLocalId: LocalDictionaryId;\n sourceLocale: Locale;\n targetLocales: Locale[];\n dictionaryPreset: string;\n dictionaryFilePath: string;\n};\n\nexport const listTranslationsTasks = (\n localIds: LocalDictionaryId[],\n outputLocales: Locale[],\n mode: 'complete' | 'review',\n baseLocale: Locale,\n configuration: IntlayerConfig\n): TranslationTask[] => {\n const appLogger = getAppLogger(configuration);\n\n const mergedDictionariesRecord = getDictionaries(configuration);\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n\n const allFlatDictionaries = Object.values(unmergedDictionariesRecord).flat();\n const dictionariesToProcess = allFlatDictionaries.filter((dictionary) =>\n localIds.includes(dictionary.localId!)\n );\n\n const { missingTranslations } =\n listMissingTranslationsWithConfig(configuration);\n\n const maxKeyLength = Math.max(\n ...dictionariesToProcess.map((dictionary) => dictionary.key.length)\n );\n\n const translationTasks: TranslationTask[] = [];\n\n for (const targetUnmergedDictionary of dictionariesToProcess) {\n const dictionaryPreset = colon(\n [\n ' - ',\n colorize('[', ANSIColors.GREY_DARK),\n colorizeKey(targetUnmergedDictionary.key),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: maxKeyLength + 6 }\n );\n\n const dictionaryKey = targetUnmergedDictionary.key;\n const dictionaryLocalId = targetUnmergedDictionary.localId!;\n const mainDictionaryToProcess: Dictionary =\n mergedDictionariesRecord[dictionaryKey];\n const dictionaryFilled = targetUnmergedDictionary.filled ?? false;\n\n if (dictionaryFilled === true) {\n continue;\n }\n\n const dictionaryFill =\n targetUnmergedDictionary.fill ?? configuration.dictionary?.fill ?? false;\n\n if (dictionaryFill === false) continue;\n\n const sourceLocale: Locale = (targetUnmergedDictionary.locale ??\n baseLocale) as Locale;\n\n if (!mainDictionaryToProcess) {\n appLogger(\n `${dictionaryPreset} Dictionary not found in dictionariesRecord. Skipping.`,\n {\n level: 'warn',\n }\n );\n continue;\n }\n\n if (!targetUnmergedDictionary.filePath) {\n appLogger(`${dictionaryPreset} Dictionary has no file path. Skipping.`, {\n level: 'warn',\n });\n continue;\n }\n\n const sourceLocaleContent = getFilterTranslationsOnlyDictionary(\n mainDictionaryToProcess,\n sourceLocale\n );\n\n if (\n Object.keys(sourceLocaleContent as Record<string, unknown>).length === 0\n ) {\n appLogger(\n `${dictionaryPreset} No content found for dictionary in source locale ${formatLocale(sourceLocale)}. Skipping translation for this dictionary.`,\n {\n level: 'warn',\n }\n );\n continue;\n }\n\n /**\n * In 'complete' mode, filter only the missing locales to translate\n *\n * Skip the dictionary if there are no missing locales to translate\n */\n let outputLocalesList: Locale[] = outputLocales as Locale[];\n\n if (mode === 'complete') {\n outputLocalesList =\n missingTranslations\n .find(\n (missingTranslation) => missingTranslation.key === dictionaryKey\n )\n ?.locales.filter((locale) => outputLocales.includes(locale)) ?? [];\n }\n\n if (outputLocalesList.length === 0) {\n appLogger(\n `${dictionaryPreset} No locales to fill, Skipping ${colorizePath(basename(targetUnmergedDictionary.filePath))}`,\n {\n level: 'warn',\n }\n );\n continue;\n }\n\n translationTasks.push({\n dictionaryKey,\n dictionaryLocalId,\n sourceLocale,\n targetLocales: outputLocalesList,\n dictionaryPreset,\n dictionaryFilePath: targetUnmergedDictionary.filePath,\n });\n }\n\n // Return the list of tasks to execute\n return translationTasks;\n};\n"],"mappings":";;;;;;;;;AA8BA,MAAa,yBACX,UACA,eACA,MACA,YACA,kBACsB;CACtB,MAAM,YAAY,aAAa,cAAc;CAE7C,MAAM,2BAA2B,gBAAgB,cAAc;CAC/D,MAAM,6BAA6B,wBAAwB,cAAc;CAGzE,MAAM,wBADsB,OAAO,OAAO,2BAA2B,CAAC,MAAM,CAC1B,QAAQ,eACxD,SAAS,SAAS,WAAW,QAAS,CACvC;CAED,MAAM,EAAE,wBACN,kCAAkC,cAAc;CAElD,MAAM,eAAe,KAAK,IACxB,GAAG,sBAAsB,KAAK,eAAe,WAAW,IAAI,OAAO,CACpE;CAED,MAAMA,mBAAsC,EAAE;AAE9C,MAAK,MAAM,4BAA4B,uBAAuB;EAC5D,MAAM,mBAAmB,MACvB;GACE;GACA,SAAS,KAAK,WAAW,UAAU;GACnC,YAAY,yBAAyB,IAAI;GACzC,SAAS,KAAK,WAAW,UAAU;GACpC,CAAC,KAAK,GAAG,EACV,EAAE,SAAS,eAAe,GAAG,CAC9B;EAED,MAAM,gBAAgB,yBAAyB;EAC/C,MAAM,oBAAoB,yBAAyB;EACnD,MAAMC,0BACJ,yBAAyB;AAG3B,OAFyB,yBAAyB,UAAU,WAEnC,KACvB;AAMF,OAFE,yBAAyB,QAAQ,cAAc,YAAY,QAAQ,WAE9C,MAAO;EAE9B,MAAMC,eAAwB,yBAAyB,UACrD;AAEF,MAAI,CAAC,yBAAyB;AAC5B,aACE,GAAG,iBAAiB,yDACpB,EACE,OAAO,QACR,CACF;AACD;;AAGF,MAAI,CAAC,yBAAyB,UAAU;AACtC,aAAU,GAAG,iBAAiB,0CAA0C,EACtE,OAAO,QACR,CAAC;AACF;;EAGF,MAAM,sBAAsB,oCAC1B,yBACA,aACD;AAED,MACE,OAAO,KAAK,oBAA+C,CAAC,WAAW,GACvE;AACA,aACE,GAAG,iBAAiB,oDAAoD,aAAa,aAAa,CAAC,8CACnG,EACE,OAAO,QACR,CACF;AACD;;;;;;;EAQF,IAAIC,oBAA8B;AAElC,MAAI,SAAS,WACX,qBACE,oBACG,MACE,uBAAuB,mBAAmB,QAAQ,cACpD,EACC,QAAQ,QAAQ,WAAW,cAAc,SAAS,OAAO,CAAC,IAAI,EAAE;AAGxE,MAAI,kBAAkB,WAAW,GAAG;AAClC,aACE,GAAG,iBAAiB,gCAAgC,aAAa,SAAS,yBAAyB,SAAS,CAAC,IAC7G,EACE,OAAO,QACR,CACF;AACD;;AAGF,mBAAiB,KAAK;GACpB;GACA;GACA;GACA,eAAe;GACf;GACA,oBAAoB,yBAAyB;GAC9C,CAAC;;AAIJ,QAAO"}
|
|
@@ -74,7 +74,7 @@ const translateDictionary = async (task, configuration, options) => {
|
|
|
74
74
|
colorize("[", ANSIColors.GREY_DARK),
|
|
75
75
|
formatLocale(targetLocale),
|
|
76
76
|
colorize("]", ANSIColors.GREY_DARK)
|
|
77
|
-
].join(""), { colSize:
|
|
77
|
+
].join(""), { colSize: 18 });
|
|
78
78
|
const createChunkPreset = (chunkIndex, totalChunks) => {
|
|
79
79
|
if (totalChunks <= 1) return "";
|
|
80
80
|
return colon([
|
|
@@ -96,36 +96,33 @@ const translateDictionary = async (task, configuration, options) => {
|
|
|
96
96
|
const presetOutputContent = reduceObjectFormat(targetLocaleDictionary.content, chunkContent);
|
|
97
97
|
const executeTranslation = async () => {
|
|
98
98
|
return await retryManager(async () => {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
entryFileContent: chunkContent,
|
|
102
|
-
presetOutputContent,
|
|
103
|
-
dictionaryDescription: dictionaryToProcess.description ?? metadata?.description ?? "",
|
|
104
|
-
entryLocale: task.sourceLocale,
|
|
105
|
-
outputLocale: targetLocale,
|
|
106
|
-
mode,
|
|
107
|
-
aiConfig
|
|
108
|
-
});
|
|
109
|
-
if (!translationResult$1) throw new Error("No content result");
|
|
110
|
-
const { isIdentic: isIdentic$1 } = verifyIdenticObjectFormat(translationResult$1.fileContent, chunkContent);
|
|
111
|
-
if (!isIdentic$1) throw new Error("Translation result does not match expected format");
|
|
112
|
-
notifySuccess();
|
|
113
|
-
return translationResult$1.fileContent;
|
|
114
|
-
}
|
|
115
|
-
const translationResult = await intlayerAPI.ai.translateJSON({
|
|
99
|
+
let translationResult;
|
|
100
|
+
if (aiClient && aiConfig) translationResult = await aiClient.translateJSON({
|
|
116
101
|
entryFileContent: chunkContent,
|
|
117
102
|
presetOutputContent,
|
|
118
103
|
dictionaryDescription: dictionaryToProcess.description ?? metadata?.description ?? "",
|
|
119
104
|
entryLocale: task.sourceLocale,
|
|
120
105
|
outputLocale: targetLocale,
|
|
121
106
|
mode,
|
|
122
|
-
|
|
107
|
+
aiConfig
|
|
123
108
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
109
|
+
else translationResult = await intlayerAPI.ai.translateJSON({
|
|
110
|
+
entryFileContent: chunkContent,
|
|
111
|
+
presetOutputContent,
|
|
112
|
+
dictionaryDescription: dictionaryToProcess.description ?? metadata?.description ?? "",
|
|
113
|
+
entryLocale: task.sourceLocale,
|
|
114
|
+
outputLocale: targetLocale,
|
|
115
|
+
mode,
|
|
116
|
+
aiOptions
|
|
117
|
+
}).then((result) => result.data);
|
|
118
|
+
if (!translationResult?.fileContent) throw new Error("No content result");
|
|
119
|
+
const { isIdentic } = verifyIdenticObjectFormat(translationResult.fileContent, chunkContent);
|
|
120
|
+
if (!isIdentic) {
|
|
121
|
+
console.log(translationResult.fileContent);
|
|
122
|
+
throw new Error("Translation result does not match expected format");
|
|
123
|
+
}
|
|
127
124
|
notifySuccess();
|
|
128
|
-
return translationResult.
|
|
125
|
+
return translationResult.fileContent;
|
|
129
126
|
}, {
|
|
130
127
|
maxRetry: MAX_RETRY,
|
|
131
128
|
delay: RETRY_DELAY,
|
|
@@ -165,7 +162,7 @@ const translateDictionary = async (task, configuration, options) => {
|
|
|
165
162
|
};
|
|
166
163
|
for (const targetLocale of task.targetLocales) if (translatedContent[targetLocale]) dictionaryOutput = insertContentInDictionary(dictionaryOutput, translatedContent[targetLocale], targetLocale);
|
|
167
164
|
appLogger(`${task.dictionaryPreset} ${colorize("Translation completed successfully", ANSIColors.GREEN)} for ${colorizePath(basename(dictionaryOutput.filePath))}`, { level: "info" });
|
|
168
|
-
if (baseUnmergedDictionary.locale) {
|
|
165
|
+
if (baseUnmergedDictionary.locale && (baseUnmergedDictionary.fill === true || baseUnmergedDictionary.fill === void 0) && baseUnmergedDictionary.location === "local") {
|
|
169
166
|
const dictionaryFilePath = baseUnmergedDictionary.filePath.split(".").slice(0, -1);
|
|
170
167
|
const contentIndex = dictionaryFilePath[dictionaryFilePath.length - 1];
|
|
171
168
|
return JSON.parse(JSON.stringify({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translateDictionary.mjs","names":["baseUnmergedDictionary: Dictionary | undefined","metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined","targetLocaleDictionary: Dictionary","chunkedJsonContent: JsonChunk[]","chunkResult: JsonChunk[]","translationResult","isIdentic","chunkPreset","translatedContent: Partial<Record<Locale, Dictionary['content']>>","dictionaryOutput: Dictionary"],"sources":["../../../src/fill/translateDictionary.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport type { AIConfig } from '@intlayer/ai';\nimport { type AIOptions, getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n chunkJSON,\n formatLocale,\n type JsonChunk,\n reconstructFromSingleChunk,\n reduceObjectFormat,\n verifyIdenticObjectFormat,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeNumber,\n colorizePath,\n getAppLogger,\n retryManager,\n} from '@intlayer/config';\nimport {\n getFilterMissingTranslationsDictionary,\n getMultilingualDictionary,\n getPerLocaleDictionary,\n insertContentInDictionary,\n mergeDictionaries,\n} from '@intlayer/core';\nimport type { Dictionary, IntlayerConfig, Locale } from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport type { AIClient } from '../utils/setupAI';\nimport type { TranslationTask } from './listTranslationsTasks';\n\ntype TranslateDictionaryResult = TranslationTask & {\n dictionaryOutput: Dictionary | null;\n};\n\ntype TranslateDictionaryOptions = {\n mode: 'complete' | 'review';\n aiOptions?: AIOptions;\n fillMetadata?: boolean;\n onHandle?: ReturnType<typeof import('@intlayer/chokidar').getGlobalLimiter>;\n onSuccess?: () => void;\n onError?: (error: unknown) => void;\n getAbortError?: () => Error | null;\n aiClient?: AIClient;\n aiConfig?: AIConfig;\n};\n\nconst hasMissingMetadata = (dictionary: Dictionary) =>\n !dictionary.description || !dictionary.title || !dictionary.tags;\n\nconst CHUNK_SIZE = 7000; // GPT-5 Mini safe input size\nconst GROUP_MAX_RETRY = 2;\nconst MAX_RETRY = 3;\nconst RETRY_DELAY = 1000 * 10; // 10 seconds\n\nconst MAX_FOLLOWING_ERRORS = 10; // 10 errors in a row, hard exit the process\nlet followingErrors = 0;\n\nexport const translateDictionary = async (\n task: TranslationTask,\n configuration: IntlayerConfig,\n options?: TranslateDictionaryOptions\n): Promise<TranslateDictionaryResult> => {\n const appLogger = getAppLogger(configuration);\n const intlayerAPI = getIntlayerAPIProxy(undefined, configuration);\n\n const { mode, aiOptions, fillMetadata, aiClient, aiConfig } = {\n mode: 'complete',\n fillMetadata: true,\n ...options,\n } as const;\n\n const notifySuccess = () => {\n followingErrors = 0;\n options?.onSuccess?.();\n };\n\n const result = await retryManager(\n async () => {\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n\n const baseUnmergedDictionary: Dictionary | undefined =\n unmergedDictionariesRecord[task.dictionaryKey].find(\n (dict) => dict.localId === task.dictionaryLocalId\n );\n\n if (!baseUnmergedDictionary) {\n appLogger(\n `${task.dictionaryPreset}Dictionary not found in unmergedDictionariesRecord. Skipping.`,\n {\n level: 'warn',\n }\n );\n return { ...task, dictionaryOutput: null };\n }\n\n let metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined;\n\n if (\n fillMetadata &&\n (hasMissingMetadata(baseUnmergedDictionary) || mode === 'review')\n ) {\n const defaultLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n configuration.internationalization.defaultLocale\n );\n\n appLogger(\n `${task.dictionaryPreset} Filling missing metadata for ${colorizePath(basename(baseUnmergedDictionary.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n const runAudit = async () => {\n if (aiClient && aiConfig) {\n const result = await aiClient.auditDictionaryMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiConfig,\n });\n\n return {\n data: result,\n };\n }\n\n return await intlayerAPI.ai.auditContentDeclarationMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiOptions,\n });\n };\n\n const metadataResult = options?.onHandle\n ? await options.onHandle(runAudit)\n : await runAudit();\n\n metadata = metadataResult.data?.fileContent;\n }\n\n const translatedContentResults = await Promise.all(\n task.targetLocales.map(async (targetLocale) => {\n /**\n * In complete mode, for large dictionaries, we want to filter all content that is already translated\n *\n * targetLocale: fr\n *\n * { test1: t({ ar: 'Hello', en: 'Hello', fr: 'Bonjour' } }) -> {}\n * { test2: t({ ar: 'Hello', en: 'Hello' }) } -> { test2: t({ ar: 'Hello', en: 'Hello' }) }\n *\n */\n // Reset to base dictionary for each locale to ensure we filter from the original\n let dictionaryToProcess = structuredClone(baseUnmergedDictionary);\n\n if (\n mode === 'complete' &&\n typeof baseUnmergedDictionary.locale !== 'string'\n ) {\n // Remove all nodes that don't have any content to translate\n dictionaryToProcess = getFilterMissingTranslationsDictionary(\n dictionaryToProcess,\n targetLocale\n );\n }\n\n dictionaryToProcess = getPerLocaleDictionary(\n dictionaryToProcess,\n task.sourceLocale\n );\n\n let targetLocaleDictionary: Dictionary;\n\n if (typeof baseUnmergedDictionary.locale === 'string') {\n targetLocaleDictionary = {\n key: baseUnmergedDictionary.key,\n content: {},\n filePath: baseUnmergedDictionary.filePath,\n };\n } else {\n targetLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n targetLocale\n );\n }\n\n const localePreset = colon(\n [\n colorize('[', ANSIColors.GREY_DARK),\n formatLocale(targetLocale),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: 10 }\n );\n\n const createChunkPreset = (\n chunkIndex: number,\n totalChunks: number\n ) => {\n if (totalChunks <= 1) return '';\n return colon(\n [\n colorize('[', ANSIColors.GREY_DARK),\n colorizeNumber(chunkIndex + 1),\n colorize(`/${totalChunks}`, ANSIColors.GREY_DARK),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: 5 }\n );\n };\n\n appLogger(\n `${task.dictionaryPreset}${localePreset} Preparing ${colorizePath(basename(targetLocaleDictionary.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n const chunkedJsonContent: JsonChunk[] = chunkJSON(\n dictionaryToProcess.content,\n CHUNK_SIZE\n );\n\n const nbOfChunks = chunkedJsonContent.length;\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset} Split into ${colorizeNumber(nbOfChunks)} chunks for translation`,\n {\n level: 'info',\n }\n );\n }\n\n const chunkResult: JsonChunk[] = [];\n\n // Process chunks in parallel (globally throttled) to allow concurrent translation\n const chunkPromises = chunkedJsonContent.map((chunk) => {\n const chunkPreset = createChunkPreset(chunk.index, chunk.total);\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset}${chunkPreset} Translating chunk`,\n {\n level: 'info',\n }\n );\n }\n\n // Reconstruct partial JSON content from this chunk's patches\n const chunkContent = reconstructFromSingleChunk(chunk);\n const presetOutputContent = reduceObjectFormat(\n targetLocaleDictionary.content,\n chunkContent\n ) as unknown as JSON;\n\n const executeTranslation = async () => {\n return await retryManager(\n async () => {\n if (aiClient && aiConfig) {\n const translationResult = await aiClient.translateJSON({\n entryFileContent: chunkContent as unknown as JSON,\n presetOutputContent,\n dictionaryDescription:\n dictionaryToProcess.description ??\n metadata?.description ??\n '',\n entryLocale: task.sourceLocale,\n outputLocale: targetLocale,\n mode,\n aiConfig,\n });\n\n if (!translationResult) {\n throw new Error('No content result');\n }\n\n const { isIdentic } = verifyIdenticObjectFormat(\n translationResult.fileContent,\n chunkContent\n );\n if (!isIdentic) {\n throw new Error(\n 'Translation result does not match expected format'\n );\n }\n\n notifySuccess();\n return translationResult.fileContent;\n }\n\n const translationResult = await intlayerAPI.ai.translateJSON({\n entryFileContent: chunkContent as unknown as JSON,\n presetOutputContent,\n dictionaryDescription:\n dictionaryToProcess.description ??\n metadata?.description ??\n '',\n entryLocale: task.sourceLocale,\n outputLocale: targetLocale,\n mode,\n aiOptions,\n });\n\n if (!translationResult.data?.fileContent) {\n throw new Error('No content result');\n }\n\n const { isIdentic } = verifyIdenticObjectFormat(\n translationResult.data.fileContent,\n chunkContent\n );\n if (!isIdentic) {\n throw new Error(\n 'Translation result does not match expected format'\n );\n }\n\n notifySuccess();\n return translationResult.data.fileContent;\n },\n {\n maxRetry: MAX_RETRY,\n delay: RETRY_DELAY,\n onError: ({ error, attempt, maxRetry }) => {\n const chunkPreset = createChunkPreset(\n chunk.index,\n chunk.total\n );\n appLogger(\n `${task.dictionaryPreset}${localePreset}${chunkPreset} ${colorize('Error filling:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)} - Attempt ${colorizeNumber(attempt + 1)} of ${colorizeNumber(maxRetry)}`,\n {\n level: 'error',\n }\n );\n\n followingErrors += 1;\n\n if (followingErrors >= MAX_FOLLOWING_ERRORS) {\n appLogger(`There is something wrong.`, {\n level: 'error',\n });\n process.exit(1); // 1 for error\n }\n },\n }\n )();\n };\n\n const wrapped = options?.onHandle\n ? options.onHandle(executeTranslation) // queued in global limiter\n : executeTranslation(); // no global limiter\n\n return wrapped.then((result) => ({ chunk, result }));\n });\n\n // Wait for all chunks for this locale in parallel (still capped by global limiter)\n const chunkResults = await Promise.all(chunkPromises);\n\n // Maintain order\n chunkResults\n .sort((chunkA, chunkB) => chunkA.chunk.index - chunkB.chunk.index)\n .forEach(({ result }) => {\n chunkResult.push(result);\n });\n\n // Merge partial JSON objects produced from each chunk into a single object\n const merged = mergeDictionaries(\n chunkResult.map((chunk) => ({\n ...dictionaryToProcess,\n content: chunk,\n }))\n );\n\n return [targetLocale, merged.content] as const;\n })\n );\n\n const translatedContent: Partial<Record<Locale, Dictionary['content']>> =\n Object.fromEntries(translatedContentResults);\n\n const baseDictionary = baseUnmergedDictionary.locale\n ? {\n ...baseUnmergedDictionary,\n key: baseUnmergedDictionary.key!,\n content: {},\n }\n : baseUnmergedDictionary;\n\n let dictionaryOutput: Dictionary = {\n ...getMultilingualDictionary(baseDictionary),\n locale: undefined, // Ensure the dictionary is multilingual\n ...metadata,\n };\n\n for (const targetLocale of task.targetLocales) {\n if (translatedContent[targetLocale]) {\n dictionaryOutput = insertContentInDictionary(\n dictionaryOutput,\n translatedContent[targetLocale],\n targetLocale\n );\n }\n }\n\n appLogger(\n `${task.dictionaryPreset} ${colorize('Translation completed successfully', ANSIColors.GREEN)} for ${colorizePath(basename(dictionaryOutput.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n if (baseUnmergedDictionary.locale) {\n const dictionaryFilePath = baseUnmergedDictionary\n .filePath!.split('.')\n .slice(0, -1);\n\n const contentIndex = dictionaryFilePath[dictionaryFilePath.length - 1];\n\n return JSON.parse(\n JSON.stringify({\n ...task,\n dictionaryOutput: {\n ...dictionaryOutput,\n fill: undefined,\n filled: true,\n },\n }).replaceAll(\n new RegExp(`\\\\.${contentIndex}\\\\.[a-zA-Z0-9]+`, 'g'),\n `.filled.${contentIndex}.json`\n )\n );\n }\n\n return {\n ...task,\n dictionaryOutput,\n };\n },\n {\n maxRetry: GROUP_MAX_RETRY,\n delay: RETRY_DELAY,\n onError: ({ error, attempt, maxRetry }) =>\n appLogger(\n `${task.dictionaryPreset} ${colorize('Error fill command:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)} - Attempt ${colorizeNumber(attempt + 1)} of ${colorizeNumber(maxRetry)}`,\n {\n level: 'error',\n }\n ),\n onMaxTryReached: ({ error }) =>\n appLogger(\n `${task.dictionaryPreset} ${colorize('Maximum number of retries reached:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)}`,\n {\n level: 'error',\n }\n ),\n }\n )();\n\n return result as TranslateDictionaryResult;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"translateDictionary.mjs","names":["baseUnmergedDictionary: Dictionary | undefined","metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined","targetLocaleDictionary: Dictionary","chunkedJsonContent: JsonChunk[]","chunkResult: JsonChunk[]","translationResult: any","chunkPreset","translatedContent: Partial<Record<Locale, Dictionary['content']>>","dictionaryOutput: Dictionary"],"sources":["../../../src/fill/translateDictionary.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport type { AIConfig } from '@intlayer/ai';\nimport { type AIOptions, getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n chunkJSON,\n formatLocale,\n type JsonChunk,\n reconstructFromSingleChunk,\n reduceObjectFormat,\n verifyIdenticObjectFormat,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeNumber,\n colorizePath,\n getAppLogger,\n retryManager,\n} from '@intlayer/config';\nimport {\n getFilterMissingTranslationsDictionary,\n getMultilingualDictionary,\n getPerLocaleDictionary,\n insertContentInDictionary,\n mergeDictionaries,\n} from '@intlayer/core';\nimport type { Dictionary, IntlayerConfig, Locale } from '@intlayer/types';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport type { AIClient } from '../utils/setupAI';\nimport type { TranslationTask } from './listTranslationsTasks';\n\ntype TranslateDictionaryResult = TranslationTask & {\n dictionaryOutput: Dictionary | null;\n};\n\ntype TranslateDictionaryOptions = {\n mode: 'complete' | 'review';\n aiOptions?: AIOptions;\n fillMetadata?: boolean;\n onHandle?: ReturnType<typeof import('@intlayer/chokidar').getGlobalLimiter>;\n onSuccess?: () => void;\n onError?: (error: unknown) => void;\n getAbortError?: () => Error | null;\n aiClient?: AIClient;\n aiConfig?: AIConfig;\n};\n\nconst hasMissingMetadata = (dictionary: Dictionary) =>\n !dictionary.description || !dictionary.title || !dictionary.tags;\n\nconst CHUNK_SIZE = 7000; // GPT-5 Mini safe input size\nconst GROUP_MAX_RETRY = 2;\nconst MAX_RETRY = 3;\nconst RETRY_DELAY = 1000 * 10; // 10 seconds\n\nconst MAX_FOLLOWING_ERRORS = 10; // 10 errors in a row, hard exit the process\nlet followingErrors = 0;\n\nexport const translateDictionary = async (\n task: TranslationTask,\n configuration: IntlayerConfig,\n options?: TranslateDictionaryOptions\n): Promise<TranslateDictionaryResult> => {\n const appLogger = getAppLogger(configuration);\n const intlayerAPI = getIntlayerAPIProxy(undefined, configuration);\n\n const { mode, aiOptions, fillMetadata, aiClient, aiConfig } = {\n mode: 'complete',\n fillMetadata: true,\n ...options,\n } as const;\n\n const notifySuccess = () => {\n followingErrors = 0;\n options?.onSuccess?.();\n };\n\n const result = await retryManager(\n async () => {\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n\n const baseUnmergedDictionary: Dictionary | undefined =\n unmergedDictionariesRecord[task.dictionaryKey].find(\n (dict) => dict.localId === task.dictionaryLocalId\n );\n\n if (!baseUnmergedDictionary) {\n appLogger(\n `${task.dictionaryPreset}Dictionary not found in unmergedDictionariesRecord. Skipping.`,\n {\n level: 'warn',\n }\n );\n return { ...task, dictionaryOutput: null };\n }\n\n let metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined;\n\n if (\n fillMetadata &&\n (hasMissingMetadata(baseUnmergedDictionary) || mode === 'review')\n ) {\n const defaultLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n configuration.internationalization.defaultLocale\n );\n\n appLogger(\n `${task.dictionaryPreset} Filling missing metadata for ${colorizePath(basename(baseUnmergedDictionary.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n const runAudit = async () => {\n if (aiClient && aiConfig) {\n const result = await aiClient.auditDictionaryMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiConfig,\n });\n\n return {\n data: result,\n };\n }\n\n return await intlayerAPI.ai.auditContentDeclarationMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiOptions,\n });\n };\n\n const metadataResult = options?.onHandle\n ? await options.onHandle(runAudit)\n : await runAudit();\n\n metadata = metadataResult.data?.fileContent;\n }\n\n const translatedContentResults = await Promise.all(\n task.targetLocales.map(async (targetLocale) => {\n /**\n * In complete mode, for large dictionaries, we want to filter all content that is already translated\n *\n * targetLocale: fr\n *\n * { test1: t({ ar: 'Hello', en: 'Hello', fr: 'Bonjour' } }) -> {}\n * { test2: t({ ar: 'Hello', en: 'Hello' }) } -> { test2: t({ ar: 'Hello', en: 'Hello' }) }\n *\n */\n // Reset to base dictionary for each locale to ensure we filter from the original\n let dictionaryToProcess = structuredClone(baseUnmergedDictionary);\n\n if (\n mode === 'complete' &&\n typeof baseUnmergedDictionary.locale !== 'string'\n ) {\n // Remove all nodes that don't have any content to translate\n dictionaryToProcess = getFilterMissingTranslationsDictionary(\n dictionaryToProcess,\n targetLocale\n );\n }\n\n dictionaryToProcess = getPerLocaleDictionary(\n dictionaryToProcess,\n task.sourceLocale\n );\n\n let targetLocaleDictionary: Dictionary;\n\n if (typeof baseUnmergedDictionary.locale === 'string') {\n targetLocaleDictionary = {\n key: baseUnmergedDictionary.key,\n content: {},\n filePath: baseUnmergedDictionary.filePath,\n };\n } else {\n targetLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n targetLocale\n );\n }\n\n const localePreset = colon(\n [\n colorize('[', ANSIColors.GREY_DARK),\n formatLocale(targetLocale),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: 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 chunkedJsonContent: JsonChunk[] = chunkJSON(\n dictionaryToProcess.content,\n CHUNK_SIZE\n );\n\n const nbOfChunks = chunkedJsonContent.length;\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset} Split into ${colorizeNumber(nbOfChunks)} chunks for translation`,\n {\n level: 'info',\n }\n );\n }\n\n const chunkResult: JsonChunk[] = [];\n\n // Process chunks in parallel (globally throttled) to allow concurrent translation\n const chunkPromises = chunkedJsonContent.map((chunk) => {\n const chunkPreset = createChunkPreset(chunk.index, chunk.total);\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset}${chunkPreset} Translating chunk`,\n {\n level: 'info',\n }\n );\n }\n\n // Reconstruct partial JSON content from this chunk's patches\n const chunkContent = reconstructFromSingleChunk(chunk);\n const presetOutputContent = reduceObjectFormat(\n targetLocaleDictionary.content,\n chunkContent\n ) as unknown as JSON;\n\n const executeTranslation = async () => {\n return await retryManager(\n async () => {\n 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 if (!isIdentic) {\n console.log(translationResult.fileContent);\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 merged = mergeDictionaries(\n chunkResult.map((chunk) => ({\n ...dictionaryToProcess,\n content: chunk,\n }))\n );\n\n return [targetLocale, merged.content] as const;\n })\n );\n\n const translatedContent: Partial<Record<Locale, Dictionary['content']>> =\n Object.fromEntries(translatedContentResults);\n\n const baseDictionary = baseUnmergedDictionary.locale\n ? {\n ...baseUnmergedDictionary,\n key: baseUnmergedDictionary.key!,\n content: {},\n }\n : baseUnmergedDictionary;\n\n let dictionaryOutput: Dictionary = {\n ...getMultilingualDictionary(baseDictionary),\n locale: undefined, // Ensure the dictionary is multilingual\n ...metadata,\n };\n\n for (const targetLocale of task.targetLocales) {\n if (translatedContent[targetLocale]) {\n dictionaryOutput = insertContentInDictionary(\n dictionaryOutput,\n translatedContent[targetLocale],\n targetLocale\n );\n }\n }\n\n appLogger(\n `${task.dictionaryPreset} ${colorize('Translation completed successfully', ANSIColors.GREEN)} for ${colorizePath(basename(dictionaryOutput.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n if (\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 fill command:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)} - Attempt ${colorizeNumber(attempt + 1)} of ${colorizeNumber(maxRetry)}`,\n {\n level: 'error',\n }\n ),\n onMaxTryReached: ({ error }) =>\n appLogger(\n `${task.dictionaryPreset} ${colorize('Maximum number of retries reached:', ANSIColors.RED)} ${colorize(typeof error === 'string' ? error : JSON.stringify(error), ANSIColors.GREY_DARK)}`,\n {\n level: 'error',\n }\n ),\n }\n )();\n\n return result as TranslateDictionaryResult;\n};\n"],"mappings":"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"}
|
|
@@ -13,15 +13,12 @@ const writeFill = async (contentDeclarationFile, outputLocales, parentLocales, c
|
|
|
13
13
|
appLogger("No file path found for dictionary", { level: "error" });
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
if (typeof fillOptions === "undefined" && typeof contentDeclarationFile.locale === "string") fillOptions = "./{{key}}.filled.content.json";
|
|
18
|
-
else fillOptions = true;
|
|
16
|
+
const fillOptions = contentDeclarationFile.fill ?? configuration.dictionary?.fill ?? true;
|
|
19
17
|
if (fillOptions === false) {
|
|
20
18
|
appLogger(`Auto fill is disabled for '${colorizeKey(fullDictionary.key)}'`, { level: "info" });
|
|
21
19
|
return;
|
|
22
20
|
}
|
|
23
|
-
const
|
|
24
|
-
const fillData = formatFillData(fillOptions, localeList, filePath, fullDictionary.key, configuration);
|
|
21
|
+
const fillData = formatFillData(fillOptions, (outputLocales ?? configuration.internationalization.locales).filter((locale) => !parentLocales?.includes(locale)), filePath, fullDictionary.key, configuration);
|
|
25
22
|
for await (const output of fillData) {
|
|
26
23
|
if (!output.filePath) {
|
|
27
24
|
appLogger(`No file path found for auto filled content declaration for '${colorizeKey(fullDictionary.key)}'`, { level: "error" });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeFill.mjs","names":["fillOptions: Fill | undefined","
|
|
1
|
+
{"version":3,"file":"writeFill.mjs","names":["fillOptions: Fill | undefined","fillData: FillData[]"],"sources":["../../../src/fill/writeFill.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport {\n formatLocale,\n formatPath,\n writeContentDeclaration,\n} from '@intlayer/chokidar';\nimport { colorizeKey, getAppLogger } from '@intlayer/config';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { Dictionary, Fill, IntlayerConfig, Locale } from '@intlayer/types';\nimport { type FillData, formatFillData } from './formatFillData';\n\nexport const writeFill = async (\n contentDeclarationFile: Dictionary,\n outputLocales: Locale[],\n parentLocales: Locale[],\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n const dictionaries = getDictionaries(configuration);\n\n const fullDictionary = dictionaries[contentDeclarationFile.key];\n\n const { filePath } = contentDeclarationFile;\n\n if (!filePath) {\n appLogger('No file path found for dictionary', {\n level: 'error',\n });\n return;\n }\n\n const 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 localeList: Locale[] = (\n outputLocales ?? configuration.internationalization.locales\n ).filter((locale) => !parentLocales?.includes(locale));\n\n const fillData: FillData[] = formatFillData(\n fillOptions as Fill,\n localeList,\n filePath,\n fullDictionary.key,\n configuration\n );\n\n for await (const output of fillData) {\n if (!output.filePath) {\n appLogger(\n `No file path found for auto filled content declaration for '${colorizeKey(fullDictionary.key)}'`,\n {\n level: 'error',\n }\n );\n continue;\n }\n\n // biome-ignore lint/correctness/noUnusedVariables: Just filtering out the fill property\n const { fill, ...rest } = contentDeclarationFile;\n\n const relativeFilePath = relative(\n configuration.content.baseDir,\n output.filePath\n );\n\n // write file\n await writeContentDeclaration(\n {\n ...rest,\n filled: true,\n locale: output.isPerLocale ? output.localeList[0] : undefined,\n localId: `${contentDeclarationFile.key}::local::${relativeFilePath}`,\n filePath: relativeFilePath,\n },\n configuration,\n {\n localeList: output.localeList,\n }\n );\n\n if (output.isPerLocale) {\n const sourceLocale = output.localeList[0];\n\n appLogger(\n `Auto filled per-locale content declaration for '${colorizeKey(fullDictionary.key)}' written to ${formatPath(output.filePath)} for locale ${formatLocale(sourceLocale)}`,\n {\n level: 'info',\n }\n );\n } else {\n appLogger(\n `Auto filled content declaration for '${colorizeKey(fullDictionary.key)}' written to ${formatPath(output.filePath)}`,\n {\n level: 'info',\n }\n );\n }\n }\n};\n"],"mappings":";;;;;;;AAWA,MAAa,YAAY,OACvB,wBACA,eACA,eACA,kBACG;CACH,MAAM,YAAY,aAAa,cAAc;CAG7C,MAAM,iBAFe,gBAAgB,cAAc,CAEf,uBAAuB;CAE3D,MAAM,EAAE,aAAa;AAErB,KAAI,CAAC,UAAU;AACb,YAAU,qCAAqC,EAC7C,OAAO,SACR,CAAC;AACF;;CAGF,MAAMA,cACJ,uBAAuB,QAAQ,cAAc,YAAY,QAAQ;AAEnE,KAAK,gBAA4B,OAAO;AACtC,YACE,8BAA8B,YAAY,eAAe,IAAI,CAAC,IAC9D,EACE,OAAO,QACR,CACF;AACD;;CAOF,MAAMC,WAAuB,eAC3B,cAJA,iBAAiB,cAAc,qBAAqB,SACpD,QAAQ,WAAW,CAAC,eAAe,SAAS,OAAO,CAAC,EAKpD,UACA,eAAe,KACf,cACD;AAED,YAAW,MAAM,UAAU,UAAU;AACnC,MAAI,CAAC,OAAO,UAAU;AACpB,aACE,+DAA+D,YAAY,eAAe,IAAI,CAAC,IAC/F,EACE,OAAO,SACR,CACF;AACD;;EAIF,MAAM,EAAE,MAAM,GAAG,SAAS;EAE1B,MAAM,mBAAmB,SACvB,cAAc,QAAQ,SACtB,OAAO,SACR;AAGD,QAAM,wBACJ;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,mDAAmD,YAAY,eAAe,IAAI,CAAC,eAAe,WAAW,OAAO,SAAS,CAAC,cAAc,aAAa,aAAa,IACtK,EACE,OAAO,QACR,CACF;QAED,WACE,wCAAwC,YAAY,eAAe,IAAI,CAAC,eAAe,WAAW,OAAO,SAAS,IAClH,EACE,OAAO,QACR,CACF"}
|
|
@@ -15,17 +15,17 @@ const listContentDeclarationRows = (options) => {
|
|
|
15
15
|
const listContentDeclaration = (options) => {
|
|
16
16
|
const appLogger = getAppLogger(getConfiguration(options?.configOptions), { config: { prefix: "" } });
|
|
17
17
|
const rows = listContentDeclarationRows(options);
|
|
18
|
-
const lines = rows.map((
|
|
19
|
-
colon(` - ${colorizeKey(
|
|
20
|
-
colSize: rows.map((
|
|
18
|
+
const lines = rows.map((row) => [
|
|
19
|
+
colon(` - ${colorizeKey(row.key)}`, {
|
|
20
|
+
colSize: rows.map((row$1) => row$1.key.length),
|
|
21
21
|
maxSize: 60
|
|
22
22
|
}),
|
|
23
23
|
" - ",
|
|
24
|
-
formatPath(
|
|
24
|
+
formatPath(row.path)
|
|
25
25
|
].join(""));
|
|
26
26
|
appLogger(`Content declaration files:`);
|
|
27
|
-
lines.forEach((
|
|
28
|
-
appLogger(
|
|
27
|
+
lines.forEach((line) => {
|
|
28
|
+
appLogger(line, { level: "info" });
|
|
29
29
|
});
|
|
30
30
|
appLogger(`Total content declaration files: ${colorizeNumber(rows.length)}`);
|
|
31
31
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listContentDeclaration.mjs","names":["
|
|
1
|
+
{"version":3,"file":"listContentDeclaration.mjs","names":["row"],"sources":["../../src/listContentDeclaration.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport { formatPath } from '@intlayer/chokidar';\nimport {\n colon,\n colorizeKey,\n colorizeNumber,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\n\ntype ListContentDeclarationOptions = {\n configOptions?: GetConfigurationOptions;\n};\n\nexport const listContentDeclarationRows = (\n options?: ListContentDeclarationOptions\n) => {\n const config = getConfiguration(options?.configOptions);\n\n const unmergedDictionariesRecord = getUnmergedDictionaries(config);\n\n const rows = Object.values(unmergedDictionariesRecord)\n .flat()\n .map((dictionary) => ({\n key: dictionary.key ?? '',\n path: relative(config.content.baseDir, dictionary.filePath ?? 'Remote'),\n }));\n return rows;\n};\n\nexport const listContentDeclaration = (\n options?: ListContentDeclarationOptions\n) => {\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config, {\n config: {\n prefix: '',\n },\n });\n\n const rows = listContentDeclarationRows(options);\n\n const lines = rows.map((row) =>\n [\n colon(` - ${colorizeKey(row.key)}`, {\n colSize: rows.map((row) => row.key.length),\n maxSize: 60,\n }),\n ' - ',\n formatPath(row.path),\n ].join('')\n );\n\n appLogger(`Content declaration files:`);\n\n lines.forEach((line) => {\n appLogger(line, {\n level: 'info',\n });\n });\n\n appLogger(`Total content declaration files: ${colorizeNumber(rows.length)}`);\n};\n"],"mappings":";;;;;;AAgBA,MAAa,8BACX,YACG;CACH,MAAM,SAAS,iBAAiB,SAAS,cAAc;CAEvD,MAAM,6BAA6B,wBAAwB,OAAO;AAQlE,QANa,OAAO,OAAO,2BAA2B,CACnD,MAAM,CACN,KAAK,gBAAgB;EACpB,KAAK,WAAW,OAAO;EACvB,MAAM,SAAS,OAAO,QAAQ,SAAS,WAAW,YAAY,SAAS;EACxE,EAAE;;AAIP,MAAa,0BACX,YACG;CAEH,MAAM,YAAY,aADH,iBAAiB,SAAS,cAAc,EAChB,EACrC,QAAQ,EACN,QAAQ,IACT,EACF,CAAC;CAEF,MAAM,OAAO,2BAA2B,QAAQ;CAEhD,MAAM,QAAQ,KAAK,KAAK,QACtB;EACE,MAAM,MAAM,YAAY,IAAI,IAAI,IAAI;GAClC,SAAS,KAAK,KAAK,UAAQA,MAAI,IAAI,OAAO;GAC1C,SAAS;GACV,CAAC;EACF;EACA,WAAW,IAAI,KAAK;EACrB,CAAC,KAAK,GAAG,CACX;AAED,WAAU,6BAA6B;AAEvC,OAAM,SAAS,SAAS;AACtB,YAAU,MAAM,EACd,OAAO,QACR,CAAC;GACF;AAEF,WAAU,oCAAoC,eAAe,KAAK,OAAO,GAAG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pushConfig.d.ts","names":[],"sources":["../../src/pushConfig.ts"],"sourcesContent":[],"mappings":";;;KAQK,WAAA;kBACa;AAJQ,CAAA;AAOb,cAAA,UAA8B,EAAA,CAAA,OAAW,
|
|
1
|
+
{"version":3,"file":"pushConfig.d.ts","names":[],"sources":["../../src/pushConfig.ts"],"sourcesContent":[],"mappings":";;;KAQK,WAAA;kBACa;AAJQ,CAAA;AAOb,cAAA,UAA8B,EAAA,CAAA,OAAW,CAAA,EAAX,WAAW,EAAA,GAAA,OAAA,CAAA,IAAA,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intlayer/cli",
|
|
3
|
-
"version": "7.3.0",
|
|
3
|
+
"version": "7.3.2-canary.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Provides uniform command-line interface scripts for Intlayer, used in packages like intlayer-cli and intlayer.",
|
|
6
6
|
"keywords": [
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"./package.json"
|
|
50
50
|
],
|
|
51
51
|
"scripts": {
|
|
52
|
+
"_prepublish": "cp -f ../../../README.md ./README.md",
|
|
52
53
|
"build": "tsdown --config tsdown.config.ts",
|
|
53
54
|
"build:ci": "tsdown --config tsdown.config.ts",
|
|
54
55
|
"clean": "rimraf ./dist .turbo",
|
|
@@ -57,7 +58,7 @@
|
|
|
57
58
|
"format:fix": "biome format --write .",
|
|
58
59
|
"lint": "biome lint .",
|
|
59
60
|
"lint:fix": "biome lint --write .",
|
|
60
|
-
"prepublish": "
|
|
61
|
+
"prepublish": "echo prepublish temporally disabled to avoid rewrite readme",
|
|
61
62
|
"publish": "bun publish || true",
|
|
62
63
|
"publish:canary": "bun publish --access public --tag canary || true",
|
|
63
64
|
"publish:latest": "bun publish --access public --tag latest || true",
|
|
@@ -67,19 +68,19 @@
|
|
|
67
68
|
},
|
|
68
69
|
"dependencies": {
|
|
69
70
|
"@clack/prompts": "^0.11.0",
|
|
70
|
-
"@intlayer/api": "7.3.0",
|
|
71
|
-
"@intlayer/chokidar": "7.3.0",
|
|
72
|
-
"@intlayer/config": "7.3.0",
|
|
73
|
-
"@intlayer/dictionaries-entry": "7.3.0",
|
|
74
|
-
"@intlayer/remote-dictionaries-entry": "7.3.0",
|
|
75
|
-
"@intlayer/types": "7.3.0",
|
|
76
|
-
"@intlayer/unmerged-dictionaries-entry": "7.3.0",
|
|
71
|
+
"@intlayer/api": "7.3.2-canary.0",
|
|
72
|
+
"@intlayer/chokidar": "7.3.2-canary.0",
|
|
73
|
+
"@intlayer/config": "7.3.2-canary.0",
|
|
74
|
+
"@intlayer/dictionaries-entry": "7.3.2-canary.0",
|
|
75
|
+
"@intlayer/remote-dictionaries-entry": "7.3.2-canary.0",
|
|
76
|
+
"@intlayer/types": "7.3.2-canary.0",
|
|
77
|
+
"@intlayer/unmerged-dictionaries-entry": "7.3.2-canary.0",
|
|
77
78
|
"commander": "14.0.1",
|
|
78
79
|
"eventsource": "3.0.7",
|
|
79
80
|
"fast-glob": "3.3.3"
|
|
80
81
|
},
|
|
81
82
|
"devDependencies": {
|
|
82
|
-
"@intlayer/core": "7.3.0",
|
|
83
|
+
"@intlayer/core": "7.3.2-canary.0",
|
|
83
84
|
"@types/node": "24.10.1",
|
|
84
85
|
"@utils/ts-config": "1.0.4",
|
|
85
86
|
"@utils/ts-config-types": "1.0.4",
|
|
@@ -90,7 +91,7 @@
|
|
|
90
91
|
"vitest": "4.0.13"
|
|
91
92
|
},
|
|
92
93
|
"peerDependencies": {
|
|
93
|
-
"@intlayer/ai": "7.3.0"
|
|
94
|
+
"@intlayer/ai": "7.3.2-canary.0"
|
|
94
95
|
},
|
|
95
96
|
"peerDependenciesMeta": {
|
|
96
97
|
"@intlayer/ai": {
|