@intlayer/sync-po-plugin 8.12.1 → 8.12.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"loadPO.cjs","names":["extractKeyAndLocaleFromPath","sourcePattern","parsePO"],"sources":["../../src/loadPO.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport { parseFilePathPattern } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary, LocalDictionaryId } from '@intlayer/types/dictionary';\nimport type {\n FilePathPattern,\n FilePathPatternContext,\n} from '@intlayer/types/filePathPattern';\nimport type { Plugin } from '@intlayer/types/plugin';\nimport fg from 'fast-glob';\nimport { extractKeyAndLocaleFromPath, parsePO } from './syncPO';\n\ntype FilePath = string;\n\ntype MessagesRecord = Record<Locale, Record<Dictionary['key'], FilePath>>;\n\nconst listMessages = async (\n source: FilePathPattern,\n configuration: IntlayerConfig\n): Promise<MessagesRecord> => {\n const { system, internationalization } = configuration;\n\n const { baseDir } = system;\n const { locales } = internationalization;\n\n const result: MessagesRecord = {} as MessagesRecord;\n\n for (const locale of locales) {\n const globPatternLocale = await parseFilePathPattern(source, {\n key: '**',\n locale,\n } as any as FilePathPatternContext);\n\n const maskPatternLocale = await parseFilePathPattern(source, {\n key: '{{__KEY__}}',\n locale,\n } as any as FilePathPatternContext);\n\n if (!globPatternLocale || !maskPatternLocale) {\n continue;\n }\n\n const normalizedGlobPattern = globPatternLocale.startsWith('./')\n ? globPatternLocale.slice(2)\n : globPatternLocale;\n\n const files = await fg(normalizedGlobPattern, {\n cwd: baseDir,\n });\n\n for (const file of files) {\n // extractKeyAndLocaleFromPath requires at least one named capture group\n // ({{__LOCALE__}} or {{__KEY__}}) in the mask to return a non-null result.\n // When the mask is fully concrete (e.g. `messages/en.po` — the source has\n // {{locale}} but no {{key}}), no groups exist and it returns null.\n // In that case, fall back directly to the loop locale and key = 'index'.\n const hasLocaleInMask = maskPatternLocale.includes('{{__LOCALE__}}');\n const hasKeyInMask = maskPatternLocale.includes('{{__KEY__}}');\n\n let key: string;\n let extractedLocale: Locale;\n\n if (hasLocaleInMask || hasKeyInMask) {\n const extraction = extractKeyAndLocaleFromPath(\n file,\n maskPatternLocale,\n locales,\n locale\n );\n\n if (!extraction) {\n continue;\n }\n\n key = extraction.key;\n extractedLocale = extraction.locale;\n } else {\n // Mask has no placeholders — attribute directly to the current loop locale.\n key = 'index';\n extractedLocale = locale;\n }\n\n const absolutePath = isAbsolute(file) ? file : resolve(baseDir, file);\n\n const usedLocale = extractedLocale as Locale;\n if (!result[usedLocale]) {\n result[usedLocale] = {};\n }\n\n result[usedLocale][key as Dictionary['key']] = absolutePath;\n }\n }\n\n // For the load plugin we only use actual discovered files; do not fabricate\n // missing locales or keys, since we don't write outputs.\n return result;\n};\n\ntype DictionariesMap = { path: string; locale: Locale; key: string }[];\n\nconst loadMessagePathMap = async (\n source: MessagesRecord | FilePathPattern,\n configuration: IntlayerConfig\n) => {\n const sourcePattern = source as FilePathPattern;\n const messages: MessagesRecord = await listMessages(\n sourcePattern,\n configuration\n );\n\n const entries = Object.entries(messages) as [\n Locale,\n Record<Dictionary['key'], FilePath>,\n ][];\n\n const dictionariesPathMap: DictionariesMap = entries.flatMap(\n ([locale, keysRecord]) =>\n Object.entries(keysRecord).map(([key, path]) => {\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(configuration.system.baseDir, path);\n\n return {\n path: absolutePath,\n locale,\n key,\n } as DictionariesMap[number];\n })\n );\n\n return dictionariesPathMap;\n};\n\ntype LoadPOPluginOptions = {\n /**\n * The source of the plugin.\n * Is a function to build the source from the key and locale.\n *\n * @example\n * ```ts\n * loadPO({\n * source: ({ key }) => `blog/${'**'}/${key}.i18n.po`,\n * })\n * ```\n */\n source: FilePathPattern;\n\n /**\n * Locale\n *\n * If not provided, the plugin will consider the default locale.\n *\n * @example\n * ```ts\n * loadPO({\n * source: ({ key }) => `blog/${'**'}/${key}.i18n.po`,\n * locale: Locales.ENGLISH,\n * })\n * ```\n */\n locale?: Locale;\n\n /**\n * Because Intlayer transforms PO files into Dictionary, we need to identify the plugin in the dictionary.\n * Used to identify the plugin in the dictionary.\n *\n * In the case you have multiple plugins, you can use this to identify the plugin in the dictionary.\n *\n * @example\n * ```ts\n * const config = {\n * plugins: [\n * loadPO({\n * source: ({ key }) => `./resources/${key}.po`,\n * location: 'plugin-i18next-po',\n * }),\n * ]\n * }\n * ```\n */\n location?: string;\n\n /**\n * The priority of the dictionaries created by the plugin.\n *\n * In the case of conflicts with remote dictionaries, or .content files, the dictionary with the highest priority will override the other dictionaries.\n *\n * Default is 0.\n */\n priority?: number;\n};\n\nexport const loadPO = (options: LoadPOPluginOptions): Plugin => {\n const { location, priority, locale } = {\n location: 'plugin',\n priority: 0,\n ...options,\n } as const;\n\n return {\n name: 'load-po',\n\n loadDictionaries: async ({ configuration }) => {\n const dictionariesMap: DictionariesMap = await loadMessagePathMap(\n options.source,\n configuration\n );\n\n const dictionaries: Dictionary[] = [];\n\n for (const { path, key, locale: entryLocale } of dictionariesMap) {\n let poContent: Record<string, string>;\n try {\n const fileContent = await readFile(path, 'utf-8');\n poContent = parsePO(fileContent);\n } catch {\n poContent = {};\n }\n\n const filePath = relative(configuration.system.baseDir, path);\n\n // Use the per-entry locale discovered from the file path. If a fixed\n // locale override was provided, use it only as a fallback.\n const entryUsedLocale = (locale ?? entryLocale) as Locale;\n\n const dictionary: Dictionary = {\n key,\n locale: entryUsedLocale,\n fill: filePath,\n format: 'po',\n localId: `${key}::${location}::${filePath}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled:\n entryUsedLocale !== configuration.internationalization.defaultLocale\n ? true\n : undefined,\n content: poContent,\n filePath,\n priority,\n };\n\n dictionaries.push(dictionary);\n }\n\n return dictionaries;\n },\n };\n};\n"],"mappings":";;;;;;;;;;AAkBA,MAAM,eAAe,OACnB,QACA,kBAC4B;CAC5B,MAAM,EAAE,QAAQ,yBAAyB;CAEzC,MAAM,EAAE,YAAY;CACpB,MAAM,EAAE,YAAY;CAEpB,MAAM,SAAyB,CAAC;CAEhC,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,oBAAoB,uDAA2B,QAAQ;GAC3D,KAAK;GACL;EACF,CAAkC;EAElC,MAAM,oBAAoB,uDAA2B,QAAQ;GAC3D,KAAK;GACL;EACF,CAAkC;EAElC,IAAI,CAAC,qBAAqB,CAAC,mBACzB;EAOF,MAAM,QAAQ,6BAJgB,kBAAkB,WAAW,IAAI,IAC3D,kBAAkB,MAAM,CAAC,IACzB,mBAE0C,EAC5C,KAAK,QACP,CAAC;EAED,KAAK,MAAM,QAAQ,OAAO;GAMxB,MAAM,kBAAkB,kBAAkB,SAAS,gBAAgB;GACnE,MAAM,eAAe,kBAAkB,SAAS,aAAa;GAE7D,IAAI;GACJ,IAAI;GAEJ,IAAI,mBAAmB,cAAc;IACnC,MAAM,aAAaA,2CACjB,MACA,mBACA,SACA,MACF;IAEA,IAAI,CAAC,YACH;IAGF,MAAM,WAAW;IACjB,kBAAkB,WAAW;GAC/B,OAAO;IAEL,MAAM;IACN,kBAAkB;GACpB;GAEA,MAAM,yCAA0B,IAAI,IAAI,8BAAe,SAAS,IAAI;GAEpE,MAAM,aAAa;GACnB,IAAI,CAAC,OAAO,aACV,OAAO,cAAc,CAAC;GAGxB,OAAO,YAAY,OAA4B;EACjD;CACF;CAIA,OAAO;AACT;AAIA,MAAM,qBAAqB,OACzB,QACA,kBACG;CAEH,MAAM,WAA2B,MAAM,aACrCC,QACA,aACF;CAsBA,OApBgB,OAAO,QAAQ,QAKoB,EAAE,SAClD,CAAC,QAAQ,gBACR,OAAO,QAAQ,UAAU,EAAE,KAAK,CAAC,KAAK,UAAU;EAK9C,OAAO;GACL,gCAL8B,IAAI,IAChC,8BACQ,cAAc,OAAO,SAAS,IAAI;GAI5C;GACA;EACF;CACF,CAAC,CAGoB;AAC3B;AA6DA,MAAa,UAAU,YAAyC;CAC9D,MAAM,EAAE,UAAU,UAAU,WAAW;EACrC,UAAU;EACV,UAAU;EACV,GAAG;CACL;CAEA,OAAO;EACL,MAAM;EAEN,kBAAkB,OAAO,EAAE,oBAAoB;GAC7C,MAAM,kBAAmC,MAAM,mBAC7C,QAAQ,QACR,aACF;GAEA,MAAM,eAA6B,CAAC;GAEpC,KAAK,MAAM,EAAE,MAAM,KAAK,QAAQ,iBAAiB,iBAAiB;IAChE,IAAI;IACJ,IAAI;KAEF,YAAYC,uBAAQ,qCADe,MAAM,OAAO,CACjB;IACjC,QAAQ;KACN,YAAY,CAAC;IACf;IAEA,MAAM,mCAAoB,cAAc,OAAO,SAAS,IAAI;IAI5D,MAAM,kBAAmB,UAAU;IAEnC,MAAM,aAAyB;KAC7B;KACA,QAAQ;KACR,MAAM;KACN,QAAQ;KACR,SAAS,GAAG,IAAI,IAAI,SAAS,IAAI;KACvB;KACV,QACE,oBAAoB,cAAc,qBAAqB,gBACnD,OACA;KACN,SAAS;KACT;KACA;IACF;IAEA,aAAa,KAAK,UAAU;GAC9B;GAEA,OAAO;EACT;CACF;AACF"}
1
+ {"version":3,"file":"loadPO.cjs","names":["extractKeyAndLocaleFromPath","sourcePattern","parsePO"],"sources":["../../src/loadPO.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport { parseFilePathPattern } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary, LocalDictionaryId } from '@intlayer/types/dictionary';\nimport type {\n FilePathPattern,\n FilePathPatternContext,\n} from '@intlayer/types/filePathPattern';\nimport type { Plugin } from '@intlayer/types/plugin';\nimport fg from 'fast-glob';\nimport { extractKeyAndLocaleFromPath, parsePO } from './syncPO';\n\ntype FilePath = string;\n\ntype MessagesRecord = Record<Locale, Record<Dictionary['key'], FilePath>>;\n\nconst listMessages = async (\n source: FilePathPattern,\n configuration: IntlayerConfig\n): Promise<MessagesRecord> => {\n const { system, internationalization } = configuration;\n\n const { baseDir } = system;\n const { locales } = internationalization;\n\n const result: MessagesRecord = {} as MessagesRecord;\n\n for (const locale of locales) {\n const globPatternLocale = await parseFilePathPattern(source, {\n key: '**',\n locale,\n } as any as FilePathPatternContext);\n\n const maskPatternLocale = await parseFilePathPattern(source, {\n key: '{{__KEY__}}',\n locale,\n } as any as FilePathPatternContext);\n\n if (!globPatternLocale || !maskPatternLocale) {\n continue;\n }\n\n const normalizedGlobPattern = globPatternLocale.startsWith('./')\n ? globPatternLocale.slice(2)\n : globPatternLocale;\n\n const files = await fg(normalizedGlobPattern, {\n cwd: baseDir,\n });\n\n for (const file of files) {\n // extractKeyAndLocaleFromPath requires at least one named capture group\n // ({{__LOCALE__}} or {{__KEY__}}) in the mask to return a non-null result.\n // When the mask is fully concrete (e.g. `messages/en.po` — the source has\n // {{locale}} but no {{key}}), no groups exist and it returns null.\n // In that case, fall back directly to the loop locale and key = 'index'.\n const hasLocaleInMask = maskPatternLocale.includes('{{__LOCALE__}}');\n const hasKeyInMask = maskPatternLocale.includes('{{__KEY__}}');\n\n let key: string;\n let extractedLocale: Locale;\n\n if (hasLocaleInMask || hasKeyInMask) {\n const extraction = extractKeyAndLocaleFromPath(\n file,\n maskPatternLocale,\n locales,\n locale\n );\n\n if (!extraction) {\n continue;\n }\n\n key = extraction.key;\n extractedLocale = extraction.locale;\n } else {\n // Mask has no placeholders — attribute directly to the current loop locale.\n key = 'index';\n extractedLocale = locale;\n }\n\n const absolutePath = isAbsolute(file) ? file : resolve(baseDir, file);\n\n const usedLocale = extractedLocale as Locale;\n if (!result[usedLocale]) {\n result[usedLocale] = {};\n }\n\n result[usedLocale][key as Dictionary['key']] = absolutePath;\n }\n }\n\n // For the load plugin we only use actual discovered files; do not fabricate\n // missing locales or keys, since we don't write outputs.\n return result;\n};\n\ntype DictionariesMap = { path: string; locale: Locale; key: string }[];\n\nconst loadMessagePathMap = async (\n source: MessagesRecord | FilePathPattern,\n configuration: IntlayerConfig\n) => {\n const sourcePattern = source as FilePathPattern;\n const messages: MessagesRecord = await listMessages(\n sourcePattern,\n configuration\n );\n\n const entries = Object.entries(messages) as [\n Locale,\n Record<Dictionary['key'], FilePath>,\n ][];\n\n const dictionariesPathMap: DictionariesMap = entries.flatMap(\n ([locale, keysRecord]) =>\n Object.entries(keysRecord).map(([key, path]) => {\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(configuration.system.baseDir, path);\n\n return {\n path: absolutePath,\n locale,\n key,\n } as DictionariesMap[number];\n })\n );\n\n return dictionariesPathMap;\n};\n\ntype LoadPOPluginOptions = {\n /**\n * The source of the plugin.\n * Is a function to build the source from the key and locale.\n *\n * @example\n * ```ts\n * loadPO({\n * source: ({ key }) => `blog/${'**'}/${key}.i18n.po`,\n * })\n * ```\n */\n source: FilePathPattern;\n\n /**\n * Locale\n *\n * If not provided, the plugin will consider the default locale.\n *\n * @example\n * ```ts\n * loadPO({\n * source: ({ key }) => `blog/${'**'}/${key}.i18n.po`,\n * locale: Locales.ENGLISH,\n * })\n * ```\n */\n locale?: Locale;\n\n /**\n * Because Intlayer transforms PO files into Dictionary, we need to identify the plugin in the dictionary.\n * Used to identify the plugin in the dictionary.\n *\n * In the case you have multiple plugins, you can use this to identify the plugin in the dictionary.\n *\n * @example\n * ```ts\n * const config = {\n * plugins: [\n * loadPO({\n * source: ({ key }) => `./resources/${key}.po`,\n * location: 'plugin-i18next-po',\n * }),\n * ]\n * }\n * ```\n */\n location?: string;\n\n /**\n * The priority of the dictionaries created by the plugin.\n *\n * In the case of conflicts with remote dictionaries, or .content files, the dictionary with the highest priority will override the other dictionaries.\n *\n * Default is 0.\n */\n priority?: number;\n};\n\nexport const loadPO = (options: LoadPOPluginOptions): Plugin => {\n const { location, priority, locale } = {\n location: 'plugin',\n priority: 0,\n ...options,\n } as const;\n\n return {\n name: 'load-po',\n\n loadDictionaries: async ({ configuration }) => {\n const dictionariesMap: DictionariesMap = await loadMessagePathMap(\n options.source,\n configuration\n );\n\n const dictionaries: Dictionary[] = [];\n\n for (const { path, key, locale: entryLocale } of dictionariesMap) {\n let poContent: Record<string, string>;\n try {\n const fileContent = await readFile(path, 'utf-8');\n poContent = parsePO(fileContent);\n } catch {\n poContent = {};\n }\n\n const filePath = relative(configuration.system.baseDir, path);\n\n // Use the per-entry locale discovered from the file path. If a fixed\n // locale override was provided, use it only as a fallback.\n const entryUsedLocale = (locale ?? entryLocale) as Locale;\n\n const dictionary: Dictionary = {\n key,\n locale: entryUsedLocale,\n fill: filePath,\n format: 'po',\n localId: `${key}::${location}::${filePath}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled:\n entryUsedLocale !== configuration.internationalization.defaultLocale\n ? true\n : undefined,\n content: poContent,\n filePath,\n priority,\n };\n\n dictionaries.push(dictionary);\n }\n\n return dictionaries;\n },\n };\n};\n"],"mappings":";;;;;;;;;;AAkBA,MAAM,eAAe,OACnB,QACA,kBAC4B;CAC5B,MAAM,EAAE,QAAQ,yBAAyB;CAEzC,MAAM,EAAE,YAAY;CACpB,MAAM,EAAE,YAAY;CAEpB,MAAM,SAAyB,CAAC;CAEhC,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,oBAAoB,uDAA2B,QAAQ;GAC3D,KAAK;GACL;EACF,CAAkC;EAElC,MAAM,oBAAoB,uDAA2B,QAAQ;GAC3D,KAAK;GACL;EACF,CAAkC;EAElC,IAAI,CAAC,qBAAqB,CAAC,mBACzB;EAOF,MAAM,QAAQ,6BAJgB,kBAAkB,WAAW,IAAI,IAC3D,kBAAkB,MAAM,CAAC,IACzB,mBAE0C,EAC5C,KAAK,QACP,CAAC;EAED,KAAK,MAAM,QAAQ,OAAO;GAMxB,MAAM,kBAAkB,kBAAkB,SAAS,gBAAgB;GACnE,MAAM,eAAe,kBAAkB,SAAS,aAAa;GAE7D,IAAI;GACJ,IAAI;GAEJ,IAAI,mBAAmB,cAAc;IACnC,MAAM,aAAaA,2CACjB,MACA,mBACA,SACA,MACF;IAEA,IAAI,CAAC,YACH;IAGF,MAAM,WAAW;IACjB,kBAAkB,WAAW;GAC/B,OAAO;IAEL,MAAM;IACN,kBAAkB;GACpB;GAEA,MAAM,yCAA0B,IAAI,IAAI,8BAAe,SAAS,IAAI;GAEpE,MAAM,aAAa;GACnB,IAAI,CAAC,OAAO,aACV,OAAO,cAAc,CAAC;GAGxB,OAAO,WAAW,CAAC,OAA4B;EACjD;CACF;CAIA,OAAO;AACT;AAIA,MAAM,qBAAqB,OACzB,QACA,kBACG;CAEH,MAAM,WAA2B,MAAM,aACrCC,QACA,aACF;CAsBA,OApBgB,OAAO,QAAQ,QAKoB,CAAC,CAAC,SAClD,CAAC,QAAQ,gBACR,OAAO,QAAQ,UAAU,CAAC,CAAC,KAAK,CAAC,KAAK,UAAU;EAK9C,OAAO;GACL,gCAL8B,IAAI,IAChC,8BACQ,cAAc,OAAO,SAAS,IAAI;GAI5C;GACA;EACF;CACF,CAAC,CAGoB;AAC3B;AA6DA,MAAa,UAAU,YAAyC;CAC9D,MAAM,EAAE,UAAU,UAAU,WAAW;EACrC,UAAU;EACV,UAAU;EACV,GAAG;CACL;CAEA,OAAO;EACL,MAAM;EAEN,kBAAkB,OAAO,EAAE,oBAAoB;GAC7C,MAAM,kBAAmC,MAAM,mBAC7C,QAAQ,QACR,aACF;GAEA,MAAM,eAA6B,CAAC;GAEpC,KAAK,MAAM,EAAE,MAAM,KAAK,QAAQ,iBAAiB,iBAAiB;IAChE,IAAI;IACJ,IAAI;KAEF,YAAYC,uBAAQ,qCADe,MAAM,OAAO,CACjB;IACjC,QAAQ;KACN,YAAY,CAAC;IACf;IAEA,MAAM,mCAAoB,cAAc,OAAO,SAAS,IAAI;IAI5D,MAAM,kBAAmB,UAAU;IAEnC,MAAM,aAAyB;KAC7B;KACA,QAAQ;KACR,MAAM;KACN,QAAQ;KACR,SAAS,GAAG,IAAI,IAAI,SAAS,IAAI;KACvB;KACV,QACE,oBAAoB,cAAc,qBAAqB,gBACnD,OACA;KACN,SAAS;KACT;KACA;IACF;IAEA,aAAa,KAAK,UAAU;GAC9B;GAEA,OAAO;EACT;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"syncPO.cjs","names":[],"sources":["../../src/syncPO.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, relative, resolve } from 'node:path';\nimport { parseFilePathPattern } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type {\n Dictionary,\n DictionaryLocation,\n LocalDictionaryId,\n} from '@intlayer/types/dictionary';\nimport type {\n FilePathPattern,\n FilePathPatternContext,\n} from '@intlayer/types/filePathPattern';\nimport type { Plugin } from '@intlayer/types/plugin';\nimport fg from 'fast-glob';\n\ntype FilePath = string;\n\ntype MessagesRecord = Record<Locale, Record<Dictionary['key'], FilePath>>;\n\nconst escapeRegex = (str: string) => str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nexport const extractKeyAndLocaleFromPath = (\n filePath: string,\n maskPattern: string,\n locales: Locale[],\n defaultLocale: Locale\n): { key: string; locale: Locale } | null => {\n const keyPlaceholder = '{{__KEY__}}';\n const localePlaceholder = '{{__LOCALE__}}';\n\n // fast-glob strips leading \"./\" from returned paths; normalize both sides\n const normalize = (path: string) =>\n path.startsWith('./') ? path.slice(2) : path;\n\n const normalizedFilePath = normalize(filePath);\n const normalizedMask = normalize(maskPattern);\n\n const localesAlternation = locales.join('|');\n\n // Escape special regex chars, then convert glob wildcards to regex equivalents.\n // Must replace ** before * to avoid double-replacing.\n let regexStr = `^${escapeRegex(normalizedMask)}$`;\n regexStr = regexStr.replace(/\\\\\\*\\\\\\*/g, '.*'); // ** → match any path segments\n regexStr = regexStr.replace(/\\\\\\*/g, '[^/]*'); // * → match within a single segment\n\n regexStr = regexStr.replace(\n escapeRegex(localePlaceholder),\n `(?<locale>${localesAlternation})`\n );\n\n if (normalizedMask.includes(keyPlaceholder)) {\n regexStr = regexStr.replace(escapeRegex(keyPlaceholder), '(?<key>.+)');\n }\n\n const maskRegex = new RegExp(regexStr);\n const match = maskRegex.exec(normalizedFilePath);\n\n if (!match?.groups) {\n return null;\n }\n\n let locale = match.groups.locale as Locale | undefined;\n let key = (match.groups.key as string | undefined) ?? 'index';\n\n if (typeof key === 'undefined') {\n key = 'index';\n }\n\n if (typeof locale === 'undefined') {\n locale = defaultLocale;\n }\n\n return { key, locale };\n};\n\n// ─── PO format utilities ────────────────────────────────────────────────────\n\nconst unescapePO = (str: string): string =>\n str\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\r/g, '\\r')\n .replace(/\\\\\"/g, '\"')\n .replace(/\\\\\\\\/g, '\\\\');\n\nconst escapePO = (str: string): string =>\n str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\t/g, '\\\\t')\n .replace(/\\r/g, '\\\\r');\n\n/**\n * Parse a PO file string into a flat msgid → msgstr record.\n * Skips the PO header (msgid \"\"), comment lines, and plural/context keywords.\n */\nexport const parsePO = (fileContent: string): Record<string, string> => {\n const result: Record<string, string> = {};\n const lines = fileContent\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .split('\\n');\n\n let msgid = '';\n let msgstr = '';\n let currentField: 'msgid' | 'msgstr' | null = null;\n\n const finalize = () => {\n if (msgid !== '') {\n result[msgid] = msgstr;\n }\n msgid = '';\n msgstr = '';\n currentField = null;\n };\n\n for (const line of lines) {\n // Skip all comment types (#, #., #:, #,, #|)\n if (line.startsWith('#')) continue;\n\n if (line.trim() === '') {\n finalize();\n continue;\n }\n\n const msgidMatch = line.match(/^msgid\\s+\"((?:[^\"\\\\]|\\\\.)*)\"$/);\n if (msgidMatch) {\n // Starting a new entry — finalize the previous one first\n finalize();\n msgid = unescapePO(msgidMatch[1]);\n currentField = 'msgid';\n continue;\n }\n\n const msgstrMatch = line.match(/^msgstr\\s+\"((?:[^\"\\\\]|\\\\.)*)\"$/);\n if (msgstrMatch) {\n msgstr = unescapePO(msgstrMatch[1]);\n currentField = 'msgstr';\n continue;\n }\n\n // Continuation line: `\"...\"` appends to the current keyword's value\n const contMatch = line.match(/^\"((?:[^\"\\\\]|\\\\.)*)\"$/);\n if (contMatch) {\n if (currentField === 'msgid') {\n msgid += unescapePO(contMatch[1]);\n } else if (currentField === 'msgstr') {\n msgstr += unescapePO(contMatch[1]);\n }\n continue;\n }\n\n // Other keywords (msgid_plural, msgstr[n], msgctxt) — not supported; reset field\n currentField = null;\n }\n\n // Finalize the last entry in the file (no trailing blank line needed)\n finalize();\n\n return result;\n};\n\n/**\n * Serialize a flat key → value record to PO file format.\n * Non-string values are silently skipped.\n */\nexport const serializePO = (\n content: Record<string, unknown>,\n locale?: string\n): string => {\n const lines: string[] = [];\n\n // PO header entry\n lines.push('msgid \"\"');\n lines.push('msgstr \"\"');\n lines.push('\"Content-Type: text/plain; charset=UTF-8\\\\n\"');\n lines.push('\"Content-Transfer-Encoding: 8bit\\\\n\"');\n if (locale) {\n lines.push(`\"Language: ${locale}\\\\n\"`);\n }\n lines.push('\"MIME-Version: 1.0\\\\n\"');\n lines.push('');\n\n for (const [msgid, msgstr] of Object.entries(content)) {\n if (typeof msgstr !== 'string') continue;\n\n lines.push(`msgid \"${escapePO(msgid)}\"`);\n lines.push(`msgstr \"${escapePO(msgstr)}\"`);\n lines.push('');\n }\n\n return `${lines.join('\\n')}\\n`;\n};\n\n// ─── File-discovery helpers (format-agnostic) ────────────────────────────────\n\nconst listMessages = async (\n source: FilePathPattern,\n configuration: IntlayerConfig\n): Promise<MessagesRecord> => {\n const { system, internationalization } = configuration;\n\n const { baseDir } = system;\n const { locales } = internationalization;\n\n const result: MessagesRecord = {} as MessagesRecord;\n\n for (const locale of locales) {\n const globPatternLocale = await parseFilePathPattern(source, {\n key: '**',\n locale,\n } as any as FilePathPatternContext);\n\n const maskPatternLocale = await parseFilePathPattern(source, {\n key: '{{__KEY__}}',\n locale,\n } as any as FilePathPatternContext);\n\n if (!globPatternLocale || !maskPatternLocale) {\n continue;\n }\n\n const normalizedGlobPattern = globPatternLocale.startsWith('./')\n ? globPatternLocale.slice(2)\n : globPatternLocale;\n\n const files = await fg(normalizedGlobPattern, {\n cwd: baseDir,\n });\n\n for (const file of files) {\n const extraction = extractKeyAndLocaleFromPath(\n file,\n maskPatternLocale,\n locales,\n locale\n );\n\n if (!extraction) {\n continue;\n }\n\n const { key, locale: extractedLocale } = extraction;\n\n // Generate what the path SHOULD be for this key/locale using the current builder\n const expectedPath = await parseFilePathPattern(source, {\n key,\n locale: extractedLocale,\n } as any as FilePathPatternContext);\n\n // Resolve both to absolute paths to ensure safe comparison\n const absoluteFoundPath = isAbsolute(file)\n ? file\n : resolve(baseDir, file);\n const absoluteExpectedPath = isAbsolute(expectedPath)\n ? expectedPath\n : resolve(baseDir, expectedPath);\n\n // If the file found doesn't exactly match the file expected, it belongs to another plugin/structure\n if (absoluteFoundPath !== absoluteExpectedPath) {\n continue;\n }\n\n const usedLocale = extractedLocale as Locale;\n if (!result[usedLocale]) {\n result[usedLocale] = {};\n }\n\n result[usedLocale][key as Dictionary['key']] = absoluteFoundPath;\n }\n }\n\n // Ensure all declared locales are present even if the file doesn't exist yet\n const maskWithKey = await parseFilePathPattern(source, {\n key: '{{__KEY__}}',\n locale: locales[0],\n } as any as FilePathPatternContext);\n\n const hasKeyInMask = maskWithKey.includes('{{__KEY__}}');\n const discoveredKeys = new Set<string>();\n\n for (const locale of Object.keys(result)) {\n for (const key of Object.keys(result[locale as Locale] ?? {})) {\n discoveredKeys.add(key);\n }\n }\n\n if (!hasKeyInMask) {\n discoveredKeys.add('index');\n }\n\n const keysToEnsure =\n discoveredKeys.size > 0 ? Array.from(discoveredKeys) : [];\n\n for (const locale of locales) {\n if (!result[locale]) {\n result[locale] = {} as Record<Dictionary['key'], FilePath>;\n }\n\n for (const key of keysToEnsure) {\n if (!result[locale][key as Dictionary['key']]) {\n const builtPath = await parseFilePathPattern(source, {\n key,\n locale,\n } as any as FilePathPatternContext);\n const absoluteBuiltPath = isAbsolute(builtPath)\n ? builtPath\n : resolve(baseDir, builtPath);\n\n result[locale][key as Dictionary['key']] = absoluteBuiltPath;\n }\n }\n }\n\n return result;\n};\n\ntype DictionariesMap = { path: string; locale: Locale; key: string }[];\n\nconst loadMessagePathMap = async (\n source: MessagesRecord | FilePathPattern,\n configuration: IntlayerConfig\n) => {\n const messages: MessagesRecord = await listMessages(\n source as FilePathPattern,\n configuration\n );\n\n const dictionariesPathMap: DictionariesMap = Object.entries(messages).flatMap(\n ([locale, keysRecord]) =>\n Object.entries(keysRecord).map(([key, path]) => {\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(configuration.system.baseDir, path);\n\n return {\n path: absolutePath,\n locale,\n key,\n } as DictionariesMap[number];\n })\n );\n\n return dictionariesPathMap;\n};\n\n// ─── Plugin ──────────────────────────────────────────────────────────────────\n\ntype SyncPOPluginOptions = {\n /**\n * The source of the plugin.\n * Is a function to build the source from the key and locale.\n *\n * ```ts\n * syncPO({\n * source: ({ key, locale }) => `./messages/${locale}/${key}.po`\n * })\n * ```\n */\n source: FilePathPattern;\n\n /**\n * Because Intlayer transforms PO files into Dictionary, we need to identify the plugin in the dictionary.\n * Used to identify the plugin in the dictionary.\n *\n * In the case you have multiple plugins, you can use this to identify the plugin in the dictionary.\n *\n * ```ts\n * // Example usage:\n * const config = {\n * plugins: [\n * syncPO({\n * source: ({ key, locale }) => `./resources/${locale}/${key}.po`,\n * location: 'plugin-i18next-po',\n * }),\n * ]\n * }\n * ```\n */\n location?: DictionaryLocation | (string & {});\n\n /**\n * The priority of the dictionaries created by the plugin.\n *\n * In the case of conflicts with remote dictionaries, or .content files, the dictionary with the highest priority will override the other dictionaries.\n *\n * Default is 0.\n */\n priority?: number;\n};\n\nexport const syncPO = async (options: SyncPOPluginOptions): Promise<Plugin> => {\n // Generate a unique default location based on the source pattern.\n const patternMarker = await parseFilePathPattern(options.source, {\n key: '{{key}}',\n locale: '{{locale}}',\n } as any as FilePathPatternContext);\n const defaultLocation = `sync-po::${patternMarker}`;\n\n const { location, priority } = {\n location: defaultLocation,\n priority: 0,\n ...options,\n };\n\n return {\n name: 'sync-po',\n\n loadDictionaries: async ({ configuration }) => {\n const dictionariesMap: DictionariesMap = await loadMessagePathMap(\n options.source,\n configuration\n );\n\n let fill: string = await parseFilePathPattern(options.source, {\n key: '{{key}}',\n locale: '{{locale}}',\n } as any as FilePathPatternContext);\n\n if (fill) {\n fill = relative(\n configuration.system.baseDir,\n resolve(configuration.system.baseDir, fill)\n );\n }\n\n const dictionaries: Dictionary[] = [];\n\n for (const { locale, path, key } of dictionariesMap) {\n let poContent: Record<string, string>;\n try {\n const fileContent = await readFile(path, 'utf-8');\n poContent = parsePO(fileContent);\n } catch {\n poContent = {};\n }\n\n const filePath = relative(configuration.system.baseDir, path);\n\n const dictionary: Dictionary = {\n key,\n locale,\n fill,\n format: 'po',\n localId: `${key}::${location}::${filePath}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled:\n locale !== configuration.internationalization.defaultLocale\n ? true\n : undefined,\n content: poContent,\n filePath,\n priority,\n };\n\n dictionaries.push(dictionary);\n }\n\n return dictionaries;\n },\n\n formatOutput: async ({ dictionary, configuration }) => {\n // Lazy import intlayer modules to avoid circular dependencies\n const { formatDictionaryOutput } = await import(\n '@intlayer/chokidar/build'\n );\n\n if (!dictionary.filePath || !dictionary.locale) return dictionary;\n\n const builderPath = await parseFilePathPattern(options.source, {\n key: dictionary.key,\n locale: dictionary.locale,\n } as FilePathPatternContext);\n\n // Verification to ensure we are formatting the correct file\n if (\n resolve(configuration.system.baseDir, builderPath) !==\n resolve(configuration.system.baseDir, dictionary.filePath)\n ) {\n return dictionary;\n }\n\n const formattedOutput = formatDictionaryOutput(dictionary, 'po');\n\n return formattedOutput.content;\n },\n\n afterBuild: async ({ dictionaries, configuration }) => {\n // Lazy import intlayer modules to avoid circular dependencies\n const { getPerLocaleDictionary } = await import('@intlayer/core/plugins');\n const { parallelize } = await import('@intlayer/chokidar/utils');\n const { formatDictionaryOutput } = await import(\n '@intlayer/chokidar/build'\n );\n\n const { locales } = configuration.internationalization;\n\n type RecordList = {\n key: string;\n dictionary: Dictionary;\n locale: Locale;\n };\n\n const recordList: RecordList[] = Object.entries(\n dictionaries.mergedDictionaries\n ).flatMap(([key, dictionary]) =>\n locales.map((locale) => ({\n key,\n dictionary: dictionary.dictionary as Dictionary,\n locale,\n }))\n );\n\n await parallelize(recordList, async ({ key, dictionary, locale }) => {\n // Only process dictionaries that belong to THIS plugin instance.\n if (dictionary.location !== location) {\n return;\n }\n\n const builderPath = await parseFilePathPattern(options.source, {\n key,\n locale,\n } as any as FilePathPatternContext);\n\n const localizedDictionary = getPerLocaleDictionary(dictionary, locale);\n\n const formattedOutput = formatDictionaryOutput(\n localizedDictionary,\n 'po'\n );\n\n const content = JSON.parse(\n JSON.stringify(formattedOutput.content)\n ) as Record<string, unknown>;\n\n if (\n typeof content === 'undefined' ||\n (typeof content === 'object' &&\n Object.keys(content as Record<string, unknown>).length === 0)\n ) {\n return;\n }\n\n await mkdir(dirname(builderPath), { recursive: true });\n\n const poContent = serializePO(content, locale);\n\n await writeFile(builderPath, poContent, 'utf-8');\n });\n },\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"syncPO.cjs","names":[],"sources":["../../src/syncPO.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, relative, resolve } from 'node:path';\nimport { parseFilePathPattern } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type {\n Dictionary,\n DictionaryLocation,\n LocalDictionaryId,\n} from '@intlayer/types/dictionary';\nimport type {\n FilePathPattern,\n FilePathPatternContext,\n} from '@intlayer/types/filePathPattern';\nimport type { Plugin } from '@intlayer/types/plugin';\nimport fg from 'fast-glob';\n\ntype FilePath = string;\n\ntype MessagesRecord = Record<Locale, Record<Dictionary['key'], FilePath>>;\n\nconst escapeRegex = (str: string) => str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nexport const extractKeyAndLocaleFromPath = (\n filePath: string,\n maskPattern: string,\n locales: Locale[],\n defaultLocale: Locale\n): { key: string; locale: Locale } | null => {\n const keyPlaceholder = '{{__KEY__}}';\n const localePlaceholder = '{{__LOCALE__}}';\n\n // fast-glob strips leading \"./\" from returned paths; normalize both sides\n const normalize = (path: string) =>\n path.startsWith('./') ? path.slice(2) : path;\n\n const normalizedFilePath = normalize(filePath);\n const normalizedMask = normalize(maskPattern);\n\n const localesAlternation = locales.join('|');\n\n // Escape special regex chars, then convert glob wildcards to regex equivalents.\n // Must replace ** before * to avoid double-replacing.\n let regexStr = `^${escapeRegex(normalizedMask)}$`;\n regexStr = regexStr.replace(/\\\\\\*\\\\\\*/g, '.*'); // ** → match any path segments\n regexStr = regexStr.replace(/\\\\\\*/g, '[^/]*'); // * → match within a single segment\n\n regexStr = regexStr.replace(\n escapeRegex(localePlaceholder),\n `(?<locale>${localesAlternation})`\n );\n\n if (normalizedMask.includes(keyPlaceholder)) {\n regexStr = regexStr.replace(escapeRegex(keyPlaceholder), '(?<key>.+)');\n }\n\n const maskRegex = new RegExp(regexStr);\n const match = maskRegex.exec(normalizedFilePath);\n\n if (!match?.groups) {\n return null;\n }\n\n let locale = match.groups.locale as Locale | undefined;\n let key = (match.groups.key as string | undefined) ?? 'index';\n\n if (typeof key === 'undefined') {\n key = 'index';\n }\n\n if (typeof locale === 'undefined') {\n locale = defaultLocale;\n }\n\n return { key, locale };\n};\n\n// ─── PO format utilities ────────────────────────────────────────────────────\n\nconst unescapePO = (str: string): string =>\n str\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\r/g, '\\r')\n .replace(/\\\\\"/g, '\"')\n .replace(/\\\\\\\\/g, '\\\\');\n\nconst escapePO = (str: string): string =>\n str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\t/g, '\\\\t')\n .replace(/\\r/g, '\\\\r');\n\n/**\n * Parse a PO file string into a flat msgid → msgstr record.\n * Skips the PO header (msgid \"\"), comment lines, and plural/context keywords.\n */\nexport const parsePO = (fileContent: string): Record<string, string> => {\n const result: Record<string, string> = {};\n const lines = fileContent\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .split('\\n');\n\n let msgid = '';\n let msgstr = '';\n let currentField: 'msgid' | 'msgstr' | null = null;\n\n const finalize = () => {\n if (msgid !== '') {\n result[msgid] = msgstr;\n }\n msgid = '';\n msgstr = '';\n currentField = null;\n };\n\n for (const line of lines) {\n // Skip all comment types (#, #., #:, #,, #|)\n if (line.startsWith('#')) continue;\n\n if (line.trim() === '') {\n finalize();\n continue;\n }\n\n const msgidMatch = line.match(/^msgid\\s+\"((?:[^\"\\\\]|\\\\.)*)\"$/);\n if (msgidMatch) {\n // Starting a new entry — finalize the previous one first\n finalize();\n msgid = unescapePO(msgidMatch[1]);\n currentField = 'msgid';\n continue;\n }\n\n const msgstrMatch = line.match(/^msgstr\\s+\"((?:[^\"\\\\]|\\\\.)*)\"$/);\n if (msgstrMatch) {\n msgstr = unescapePO(msgstrMatch[1]);\n currentField = 'msgstr';\n continue;\n }\n\n // Continuation line: `\"...\"` appends to the current keyword's value\n const contMatch = line.match(/^\"((?:[^\"\\\\]|\\\\.)*)\"$/);\n if (contMatch) {\n if (currentField === 'msgid') {\n msgid += unescapePO(contMatch[1]);\n } else if (currentField === 'msgstr') {\n msgstr += unescapePO(contMatch[1]);\n }\n continue;\n }\n\n // Other keywords (msgid_plural, msgstr[n], msgctxt) — not supported; reset field\n currentField = null;\n }\n\n // Finalize the last entry in the file (no trailing blank line needed)\n finalize();\n\n return result;\n};\n\n/**\n * Serialize a flat key → value record to PO file format.\n * Non-string values are silently skipped.\n */\nexport const serializePO = (\n content: Record<string, unknown>,\n locale?: string\n): string => {\n const lines: string[] = [];\n\n // PO header entry\n lines.push('msgid \"\"');\n lines.push('msgstr \"\"');\n lines.push('\"Content-Type: text/plain; charset=UTF-8\\\\n\"');\n lines.push('\"Content-Transfer-Encoding: 8bit\\\\n\"');\n if (locale) {\n lines.push(`\"Language: ${locale}\\\\n\"`);\n }\n lines.push('\"MIME-Version: 1.0\\\\n\"');\n lines.push('');\n\n for (const [msgid, msgstr] of Object.entries(content)) {\n if (typeof msgstr !== 'string') continue;\n\n lines.push(`msgid \"${escapePO(msgid)}\"`);\n lines.push(`msgstr \"${escapePO(msgstr)}\"`);\n lines.push('');\n }\n\n return `${lines.join('\\n')}\\n`;\n};\n\n// ─── File-discovery helpers (format-agnostic) ────────────────────────────────\n\nconst listMessages = async (\n source: FilePathPattern,\n configuration: IntlayerConfig\n): Promise<MessagesRecord> => {\n const { system, internationalization } = configuration;\n\n const { baseDir } = system;\n const { locales } = internationalization;\n\n const result: MessagesRecord = {} as MessagesRecord;\n\n for (const locale of locales) {\n const globPatternLocale = await parseFilePathPattern(source, {\n key: '**',\n locale,\n } as any as FilePathPatternContext);\n\n const maskPatternLocale = await parseFilePathPattern(source, {\n key: '{{__KEY__}}',\n locale,\n } as any as FilePathPatternContext);\n\n if (!globPatternLocale || !maskPatternLocale) {\n continue;\n }\n\n const normalizedGlobPattern = globPatternLocale.startsWith('./')\n ? globPatternLocale.slice(2)\n : globPatternLocale;\n\n const files = await fg(normalizedGlobPattern, {\n cwd: baseDir,\n });\n\n for (const file of files) {\n const extraction = extractKeyAndLocaleFromPath(\n file,\n maskPatternLocale,\n locales,\n locale\n );\n\n if (!extraction) {\n continue;\n }\n\n const { key, locale: extractedLocale } = extraction;\n\n // Generate what the path SHOULD be for this key/locale using the current builder\n const expectedPath = await parseFilePathPattern(source, {\n key,\n locale: extractedLocale,\n } as any as FilePathPatternContext);\n\n // Resolve both to absolute paths to ensure safe comparison\n const absoluteFoundPath = isAbsolute(file)\n ? file\n : resolve(baseDir, file);\n const absoluteExpectedPath = isAbsolute(expectedPath)\n ? expectedPath\n : resolve(baseDir, expectedPath);\n\n // If the file found doesn't exactly match the file expected, it belongs to another plugin/structure\n if (absoluteFoundPath !== absoluteExpectedPath) {\n continue;\n }\n\n const usedLocale = extractedLocale as Locale;\n if (!result[usedLocale]) {\n result[usedLocale] = {};\n }\n\n result[usedLocale][key as Dictionary['key']] = absoluteFoundPath;\n }\n }\n\n // Ensure all declared locales are present even if the file doesn't exist yet\n const maskWithKey = await parseFilePathPattern(source, {\n key: '{{__KEY__}}',\n locale: locales[0],\n } as any as FilePathPatternContext);\n\n const hasKeyInMask = maskWithKey.includes('{{__KEY__}}');\n const discoveredKeys = new Set<string>();\n\n for (const locale of Object.keys(result)) {\n for (const key of Object.keys(result[locale as Locale] ?? {})) {\n discoveredKeys.add(key);\n }\n }\n\n if (!hasKeyInMask) {\n discoveredKeys.add('index');\n }\n\n const keysToEnsure =\n discoveredKeys.size > 0 ? Array.from(discoveredKeys) : [];\n\n for (const locale of locales) {\n if (!result[locale]) {\n result[locale] = {} as Record<Dictionary['key'], FilePath>;\n }\n\n for (const key of keysToEnsure) {\n if (!result[locale][key as Dictionary['key']]) {\n const builtPath = await parseFilePathPattern(source, {\n key,\n locale,\n } as any as FilePathPatternContext);\n const absoluteBuiltPath = isAbsolute(builtPath)\n ? builtPath\n : resolve(baseDir, builtPath);\n\n result[locale][key as Dictionary['key']] = absoluteBuiltPath;\n }\n }\n }\n\n return result;\n};\n\ntype DictionariesMap = { path: string; locale: Locale; key: string }[];\n\nconst loadMessagePathMap = async (\n source: MessagesRecord | FilePathPattern,\n configuration: IntlayerConfig\n) => {\n const messages: MessagesRecord = await listMessages(\n source as FilePathPattern,\n configuration\n );\n\n const dictionariesPathMap: DictionariesMap = Object.entries(messages).flatMap(\n ([locale, keysRecord]) =>\n Object.entries(keysRecord).map(([key, path]) => {\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(configuration.system.baseDir, path);\n\n return {\n path: absolutePath,\n locale,\n key,\n } as DictionariesMap[number];\n })\n );\n\n return dictionariesPathMap;\n};\n\n// ─── Plugin ──────────────────────────────────────────────────────────────────\n\ntype SyncPOPluginOptions = {\n /**\n * The source of the plugin.\n * Is a function to build the source from the key and locale.\n *\n * ```ts\n * syncPO({\n * source: ({ key, locale }) => `./messages/${locale}/${key}.po`\n * })\n * ```\n */\n source: FilePathPattern;\n\n /**\n * Because Intlayer transforms PO files into Dictionary, we need to identify the plugin in the dictionary.\n * Used to identify the plugin in the dictionary.\n *\n * In the case you have multiple plugins, you can use this to identify the plugin in the dictionary.\n *\n * ```ts\n * // Example usage:\n * const config = {\n * plugins: [\n * syncPO({\n * source: ({ key, locale }) => `./resources/${locale}/${key}.po`,\n * location: 'plugin-i18next-po',\n * }),\n * ]\n * }\n * ```\n */\n location?: DictionaryLocation | (string & {});\n\n /**\n * The priority of the dictionaries created by the plugin.\n *\n * In the case of conflicts with remote dictionaries, or .content files, the dictionary with the highest priority will override the other dictionaries.\n *\n * Default is 0.\n */\n priority?: number;\n};\n\nexport const syncPO = async (options: SyncPOPluginOptions): Promise<Plugin> => {\n // Generate a unique default location based on the source pattern.\n const patternMarker = await parseFilePathPattern(options.source, {\n key: '{{key}}',\n locale: '{{locale}}',\n } as any as FilePathPatternContext);\n const defaultLocation = `sync-po::${patternMarker}`;\n\n const { location, priority } = {\n location: defaultLocation,\n priority: 0,\n ...options,\n };\n\n return {\n name: 'sync-po',\n\n loadDictionaries: async ({ configuration }) => {\n const dictionariesMap: DictionariesMap = await loadMessagePathMap(\n options.source,\n configuration\n );\n\n let fill: string = await parseFilePathPattern(options.source, {\n key: '{{key}}',\n locale: '{{locale}}',\n } as any as FilePathPatternContext);\n\n if (fill) {\n fill = relative(\n configuration.system.baseDir,\n resolve(configuration.system.baseDir, fill)\n );\n }\n\n const dictionaries: Dictionary[] = [];\n\n for (const { locale, path, key } of dictionariesMap) {\n let poContent: Record<string, string>;\n try {\n const fileContent = await readFile(path, 'utf-8');\n poContent = parsePO(fileContent);\n } catch {\n poContent = {};\n }\n\n const filePath = relative(configuration.system.baseDir, path);\n\n const dictionary: Dictionary = {\n key,\n locale,\n fill,\n format: 'po',\n localId: `${key}::${location}::${filePath}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled:\n locale !== configuration.internationalization.defaultLocale\n ? true\n : undefined,\n content: poContent,\n filePath,\n priority,\n };\n\n dictionaries.push(dictionary);\n }\n\n return dictionaries;\n },\n\n formatOutput: async ({ dictionary, configuration }) => {\n // Lazy import intlayer modules to avoid circular dependencies\n const { formatDictionaryOutput } = await import(\n '@intlayer/chokidar/build'\n );\n\n if (!dictionary.filePath || !dictionary.locale) return dictionary;\n\n const builderPath = await parseFilePathPattern(options.source, {\n key: dictionary.key,\n locale: dictionary.locale,\n } as FilePathPatternContext);\n\n // Verification to ensure we are formatting the correct file\n if (\n resolve(configuration.system.baseDir, builderPath) !==\n resolve(configuration.system.baseDir, dictionary.filePath)\n ) {\n return dictionary;\n }\n\n const formattedOutput = formatDictionaryOutput(dictionary, 'po');\n\n return formattedOutput.content;\n },\n\n afterBuild: async ({ dictionaries, configuration }) => {\n // Lazy import intlayer modules to avoid circular dependencies\n const { getPerLocaleDictionary } = await import('@intlayer/core/plugins');\n const { parallelize } = await import('@intlayer/chokidar/utils');\n const { formatDictionaryOutput } = await import(\n '@intlayer/chokidar/build'\n );\n\n const { locales } = configuration.internationalization;\n\n type RecordList = {\n key: string;\n dictionary: Dictionary;\n locale: Locale;\n };\n\n const recordList: RecordList[] = Object.entries(\n dictionaries.mergedDictionaries\n ).flatMap(([key, dictionary]) =>\n locales.map((locale) => ({\n key,\n dictionary: dictionary.dictionary as Dictionary,\n locale,\n }))\n );\n\n await parallelize(recordList, async ({ key, dictionary, locale }) => {\n // Only process dictionaries that belong to THIS plugin instance.\n if (dictionary.location !== location) {\n return;\n }\n\n const builderPath = await parseFilePathPattern(options.source, {\n key,\n locale,\n } as any as FilePathPatternContext);\n\n const localizedDictionary = getPerLocaleDictionary(dictionary, locale);\n\n const formattedOutput = formatDictionaryOutput(\n localizedDictionary,\n 'po'\n );\n\n const content = JSON.parse(\n JSON.stringify(formattedOutput.content)\n ) as Record<string, unknown>;\n\n if (\n typeof content === 'undefined' ||\n (typeof content === 'object' &&\n Object.keys(content as Record<string, unknown>).length === 0)\n ) {\n return;\n }\n\n await mkdir(dirname(builderPath), { recursive: true });\n\n const poContent = serializePO(content, locale);\n\n await writeFile(builderPath, poContent, 'utf-8');\n });\n },\n };\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"loadPO.mjs","names":["sourcePattern"],"sources":["../../src/loadPO.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport { parseFilePathPattern } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary, LocalDictionaryId } from '@intlayer/types/dictionary';\nimport type {\n FilePathPattern,\n FilePathPatternContext,\n} from '@intlayer/types/filePathPattern';\nimport type { Plugin } from '@intlayer/types/plugin';\nimport fg from 'fast-glob';\nimport { extractKeyAndLocaleFromPath, parsePO } from './syncPO';\n\ntype FilePath = string;\n\ntype MessagesRecord = Record<Locale, Record<Dictionary['key'], FilePath>>;\n\nconst listMessages = async (\n source: FilePathPattern,\n configuration: IntlayerConfig\n): Promise<MessagesRecord> => {\n const { system, internationalization } = configuration;\n\n const { baseDir } = system;\n const { locales } = internationalization;\n\n const result: MessagesRecord = {} as MessagesRecord;\n\n for (const locale of locales) {\n const globPatternLocale = await parseFilePathPattern(source, {\n key: '**',\n locale,\n } as any as FilePathPatternContext);\n\n const maskPatternLocale = await parseFilePathPattern(source, {\n key: '{{__KEY__}}',\n locale,\n } as any as FilePathPatternContext);\n\n if (!globPatternLocale || !maskPatternLocale) {\n continue;\n }\n\n const normalizedGlobPattern = globPatternLocale.startsWith('./')\n ? globPatternLocale.slice(2)\n : globPatternLocale;\n\n const files = await fg(normalizedGlobPattern, {\n cwd: baseDir,\n });\n\n for (const file of files) {\n // extractKeyAndLocaleFromPath requires at least one named capture group\n // ({{__LOCALE__}} or {{__KEY__}}) in the mask to return a non-null result.\n // When the mask is fully concrete (e.g. `messages/en.po` — the source has\n // {{locale}} but no {{key}}), no groups exist and it returns null.\n // In that case, fall back directly to the loop locale and key = 'index'.\n const hasLocaleInMask = maskPatternLocale.includes('{{__LOCALE__}}');\n const hasKeyInMask = maskPatternLocale.includes('{{__KEY__}}');\n\n let key: string;\n let extractedLocale: Locale;\n\n if (hasLocaleInMask || hasKeyInMask) {\n const extraction = extractKeyAndLocaleFromPath(\n file,\n maskPatternLocale,\n locales,\n locale\n );\n\n if (!extraction) {\n continue;\n }\n\n key = extraction.key;\n extractedLocale = extraction.locale;\n } else {\n // Mask has no placeholders — attribute directly to the current loop locale.\n key = 'index';\n extractedLocale = locale;\n }\n\n const absolutePath = isAbsolute(file) ? file : resolve(baseDir, file);\n\n const usedLocale = extractedLocale as Locale;\n if (!result[usedLocale]) {\n result[usedLocale] = {};\n }\n\n result[usedLocale][key as Dictionary['key']] = absolutePath;\n }\n }\n\n // For the load plugin we only use actual discovered files; do not fabricate\n // missing locales or keys, since we don't write outputs.\n return result;\n};\n\ntype DictionariesMap = { path: string; locale: Locale; key: string }[];\n\nconst loadMessagePathMap = async (\n source: MessagesRecord | FilePathPattern,\n configuration: IntlayerConfig\n) => {\n const sourcePattern = source as FilePathPattern;\n const messages: MessagesRecord = await listMessages(\n sourcePattern,\n configuration\n );\n\n const entries = Object.entries(messages) as [\n Locale,\n Record<Dictionary['key'], FilePath>,\n ][];\n\n const dictionariesPathMap: DictionariesMap = entries.flatMap(\n ([locale, keysRecord]) =>\n Object.entries(keysRecord).map(([key, path]) => {\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(configuration.system.baseDir, path);\n\n return {\n path: absolutePath,\n locale,\n key,\n } as DictionariesMap[number];\n })\n );\n\n return dictionariesPathMap;\n};\n\ntype LoadPOPluginOptions = {\n /**\n * The source of the plugin.\n * Is a function to build the source from the key and locale.\n *\n * @example\n * ```ts\n * loadPO({\n * source: ({ key }) => `blog/${'**'}/${key}.i18n.po`,\n * })\n * ```\n */\n source: FilePathPattern;\n\n /**\n * Locale\n *\n * If not provided, the plugin will consider the default locale.\n *\n * @example\n * ```ts\n * loadPO({\n * source: ({ key }) => `blog/${'**'}/${key}.i18n.po`,\n * locale: Locales.ENGLISH,\n * })\n * ```\n */\n locale?: Locale;\n\n /**\n * Because Intlayer transforms PO files into Dictionary, we need to identify the plugin in the dictionary.\n * Used to identify the plugin in the dictionary.\n *\n * In the case you have multiple plugins, you can use this to identify the plugin in the dictionary.\n *\n * @example\n * ```ts\n * const config = {\n * plugins: [\n * loadPO({\n * source: ({ key }) => `./resources/${key}.po`,\n * location: 'plugin-i18next-po',\n * }),\n * ]\n * }\n * ```\n */\n location?: string;\n\n /**\n * The priority of the dictionaries created by the plugin.\n *\n * In the case of conflicts with remote dictionaries, or .content files, the dictionary with the highest priority will override the other dictionaries.\n *\n * Default is 0.\n */\n priority?: number;\n};\n\nexport const loadPO = (options: LoadPOPluginOptions): Plugin => {\n const { location, priority, locale } = {\n location: 'plugin',\n priority: 0,\n ...options,\n } as const;\n\n return {\n name: 'load-po',\n\n loadDictionaries: async ({ configuration }) => {\n const dictionariesMap: DictionariesMap = await loadMessagePathMap(\n options.source,\n configuration\n );\n\n const dictionaries: Dictionary[] = [];\n\n for (const { path, key, locale: entryLocale } of dictionariesMap) {\n let poContent: Record<string, string>;\n try {\n const fileContent = await readFile(path, 'utf-8');\n poContent = parsePO(fileContent);\n } catch {\n poContent = {};\n }\n\n const filePath = relative(configuration.system.baseDir, path);\n\n // Use the per-entry locale discovered from the file path. If a fixed\n // locale override was provided, use it only as a fallback.\n const entryUsedLocale = (locale ?? entryLocale) as Locale;\n\n const dictionary: Dictionary = {\n key,\n locale: entryUsedLocale,\n fill: filePath,\n format: 'po',\n localId: `${key}::${location}::${filePath}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled:\n entryUsedLocale !== configuration.internationalization.defaultLocale\n ? true\n : undefined,\n content: poContent,\n filePath,\n priority,\n };\n\n dictionaries.push(dictionary);\n }\n\n return dictionaries;\n },\n };\n};\n"],"mappings":";;;;;;;AAkBA,MAAM,eAAe,OACnB,QACA,kBAC4B;CAC5B,MAAM,EAAE,QAAQ,yBAAyB;CAEzC,MAAM,EAAE,YAAY;CACpB,MAAM,EAAE,YAAY;CAEpB,MAAM,SAAyB,CAAC;CAEhC,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,oBAAoB,MAAM,qBAAqB,QAAQ;GAC3D,KAAK;GACL;EACF,CAAkC;EAElC,MAAM,oBAAoB,MAAM,qBAAqB,QAAQ;GAC3D,KAAK;GACL;EACF,CAAkC;EAElC,IAAI,CAAC,qBAAqB,CAAC,mBACzB;EAOF,MAAM,QAAQ,MAAM,GAJU,kBAAkB,WAAW,IAAI,IAC3D,kBAAkB,MAAM,CAAC,IACzB,mBAE0C,EAC5C,KAAK,QACP,CAAC;EAED,KAAK,MAAM,QAAQ,OAAO;GAMxB,MAAM,kBAAkB,kBAAkB,SAAS,gBAAgB;GACnE,MAAM,eAAe,kBAAkB,SAAS,aAAa;GAE7D,IAAI;GACJ,IAAI;GAEJ,IAAI,mBAAmB,cAAc;IACnC,MAAM,aAAa,4BACjB,MACA,mBACA,SACA,MACF;IAEA,IAAI,CAAC,YACH;IAGF,MAAM,WAAW;IACjB,kBAAkB,WAAW;GAC/B,OAAO;IAEL,MAAM;IACN,kBAAkB;GACpB;GAEA,MAAM,eAAe,WAAW,IAAI,IAAI,OAAO,QAAQ,SAAS,IAAI;GAEpE,MAAM,aAAa;GACnB,IAAI,CAAC,OAAO,aACV,OAAO,cAAc,CAAC;GAGxB,OAAO,YAAY,OAA4B;EACjD;CACF;CAIA,OAAO;AACT;AAIA,MAAM,qBAAqB,OACzB,QACA,kBACG;CAEH,MAAM,WAA2B,MAAM,aACrCA,QACA,aACF;CAsBA,OApBgB,OAAO,QAAQ,QAKoB,EAAE,SAClD,CAAC,QAAQ,gBACR,OAAO,QAAQ,UAAU,EAAE,KAAK,CAAC,KAAK,UAAU;EAK9C,OAAO;GACL,MALmB,WAAW,IAAI,IAChC,OACA,QAAQ,cAAc,OAAO,SAAS,IAAI;GAI5C;GACA;EACF;CACF,CAAC,CAGoB;AAC3B;AA6DA,MAAa,UAAU,YAAyC;CAC9D,MAAM,EAAE,UAAU,UAAU,WAAW;EACrC,UAAU;EACV,UAAU;EACV,GAAG;CACL;CAEA,OAAO;EACL,MAAM;EAEN,kBAAkB,OAAO,EAAE,oBAAoB;GAC7C,MAAM,kBAAmC,MAAM,mBAC7C,QAAQ,QACR,aACF;GAEA,MAAM,eAA6B,CAAC;GAEpC,KAAK,MAAM,EAAE,MAAM,KAAK,QAAQ,iBAAiB,iBAAiB;IAChE,IAAI;IACJ,IAAI;KAEF,YAAY,QAAQ,MADM,SAAS,MAAM,OAAO,CACjB;IACjC,QAAQ;KACN,YAAY,CAAC;IACf;IAEA,MAAM,WAAW,SAAS,cAAc,OAAO,SAAS,IAAI;IAI5D,MAAM,kBAAmB,UAAU;IAEnC,MAAM,aAAyB;KAC7B;KACA,QAAQ;KACR,MAAM;KACN,QAAQ;KACR,SAAS,GAAG,IAAI,IAAI,SAAS,IAAI;KACvB;KACV,QACE,oBAAoB,cAAc,qBAAqB,gBACnD,OACA;KACN,SAAS;KACT;KACA;IACF;IAEA,aAAa,KAAK,UAAU;GAC9B;GAEA,OAAO;EACT;CACF;AACF"}
1
+ {"version":3,"file":"loadPO.mjs","names":["sourcePattern"],"sources":["../../src/loadPO.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport { parseFilePathPattern } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary, LocalDictionaryId } from '@intlayer/types/dictionary';\nimport type {\n FilePathPattern,\n FilePathPatternContext,\n} from '@intlayer/types/filePathPattern';\nimport type { Plugin } from '@intlayer/types/plugin';\nimport fg from 'fast-glob';\nimport { extractKeyAndLocaleFromPath, parsePO } from './syncPO';\n\ntype FilePath = string;\n\ntype MessagesRecord = Record<Locale, Record<Dictionary['key'], FilePath>>;\n\nconst listMessages = async (\n source: FilePathPattern,\n configuration: IntlayerConfig\n): Promise<MessagesRecord> => {\n const { system, internationalization } = configuration;\n\n const { baseDir } = system;\n const { locales } = internationalization;\n\n const result: MessagesRecord = {} as MessagesRecord;\n\n for (const locale of locales) {\n const globPatternLocale = await parseFilePathPattern(source, {\n key: '**',\n locale,\n } as any as FilePathPatternContext);\n\n const maskPatternLocale = await parseFilePathPattern(source, {\n key: '{{__KEY__}}',\n locale,\n } as any as FilePathPatternContext);\n\n if (!globPatternLocale || !maskPatternLocale) {\n continue;\n }\n\n const normalizedGlobPattern = globPatternLocale.startsWith('./')\n ? globPatternLocale.slice(2)\n : globPatternLocale;\n\n const files = await fg(normalizedGlobPattern, {\n cwd: baseDir,\n });\n\n for (const file of files) {\n // extractKeyAndLocaleFromPath requires at least one named capture group\n // ({{__LOCALE__}} or {{__KEY__}}) in the mask to return a non-null result.\n // When the mask is fully concrete (e.g. `messages/en.po` — the source has\n // {{locale}} but no {{key}}), no groups exist and it returns null.\n // In that case, fall back directly to the loop locale and key = 'index'.\n const hasLocaleInMask = maskPatternLocale.includes('{{__LOCALE__}}');\n const hasKeyInMask = maskPatternLocale.includes('{{__KEY__}}');\n\n let key: string;\n let extractedLocale: Locale;\n\n if (hasLocaleInMask || hasKeyInMask) {\n const extraction = extractKeyAndLocaleFromPath(\n file,\n maskPatternLocale,\n locales,\n locale\n );\n\n if (!extraction) {\n continue;\n }\n\n key = extraction.key;\n extractedLocale = extraction.locale;\n } else {\n // Mask has no placeholders — attribute directly to the current loop locale.\n key = 'index';\n extractedLocale = locale;\n }\n\n const absolutePath = isAbsolute(file) ? file : resolve(baseDir, file);\n\n const usedLocale = extractedLocale as Locale;\n if (!result[usedLocale]) {\n result[usedLocale] = {};\n }\n\n result[usedLocale][key as Dictionary['key']] = absolutePath;\n }\n }\n\n // For the load plugin we only use actual discovered files; do not fabricate\n // missing locales or keys, since we don't write outputs.\n return result;\n};\n\ntype DictionariesMap = { path: string; locale: Locale; key: string }[];\n\nconst loadMessagePathMap = async (\n source: MessagesRecord | FilePathPattern,\n configuration: IntlayerConfig\n) => {\n const sourcePattern = source as FilePathPattern;\n const messages: MessagesRecord = await listMessages(\n sourcePattern,\n configuration\n );\n\n const entries = Object.entries(messages) as [\n Locale,\n Record<Dictionary['key'], FilePath>,\n ][];\n\n const dictionariesPathMap: DictionariesMap = entries.flatMap(\n ([locale, keysRecord]) =>\n Object.entries(keysRecord).map(([key, path]) => {\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(configuration.system.baseDir, path);\n\n return {\n path: absolutePath,\n locale,\n key,\n } as DictionariesMap[number];\n })\n );\n\n return dictionariesPathMap;\n};\n\ntype LoadPOPluginOptions = {\n /**\n * The source of the plugin.\n * Is a function to build the source from the key and locale.\n *\n * @example\n * ```ts\n * loadPO({\n * source: ({ key }) => `blog/${'**'}/${key}.i18n.po`,\n * })\n * ```\n */\n source: FilePathPattern;\n\n /**\n * Locale\n *\n * If not provided, the plugin will consider the default locale.\n *\n * @example\n * ```ts\n * loadPO({\n * source: ({ key }) => `blog/${'**'}/${key}.i18n.po`,\n * locale: Locales.ENGLISH,\n * })\n * ```\n */\n locale?: Locale;\n\n /**\n * Because Intlayer transforms PO files into Dictionary, we need to identify the plugin in the dictionary.\n * Used to identify the plugin in the dictionary.\n *\n * In the case you have multiple plugins, you can use this to identify the plugin in the dictionary.\n *\n * @example\n * ```ts\n * const config = {\n * plugins: [\n * loadPO({\n * source: ({ key }) => `./resources/${key}.po`,\n * location: 'plugin-i18next-po',\n * }),\n * ]\n * }\n * ```\n */\n location?: string;\n\n /**\n * The priority of the dictionaries created by the plugin.\n *\n * In the case of conflicts with remote dictionaries, or .content files, the dictionary with the highest priority will override the other dictionaries.\n *\n * Default is 0.\n */\n priority?: number;\n};\n\nexport const loadPO = (options: LoadPOPluginOptions): Plugin => {\n const { location, priority, locale } = {\n location: 'plugin',\n priority: 0,\n ...options,\n } as const;\n\n return {\n name: 'load-po',\n\n loadDictionaries: async ({ configuration }) => {\n const dictionariesMap: DictionariesMap = await loadMessagePathMap(\n options.source,\n configuration\n );\n\n const dictionaries: Dictionary[] = [];\n\n for (const { path, key, locale: entryLocale } of dictionariesMap) {\n let poContent: Record<string, string>;\n try {\n const fileContent = await readFile(path, 'utf-8');\n poContent = parsePO(fileContent);\n } catch {\n poContent = {};\n }\n\n const filePath = relative(configuration.system.baseDir, path);\n\n // Use the per-entry locale discovered from the file path. If a fixed\n // locale override was provided, use it only as a fallback.\n const entryUsedLocale = (locale ?? entryLocale) as Locale;\n\n const dictionary: Dictionary = {\n key,\n locale: entryUsedLocale,\n fill: filePath,\n format: 'po',\n localId: `${key}::${location}::${filePath}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled:\n entryUsedLocale !== configuration.internationalization.defaultLocale\n ? true\n : undefined,\n content: poContent,\n filePath,\n priority,\n };\n\n dictionaries.push(dictionary);\n }\n\n return dictionaries;\n },\n };\n};\n"],"mappings":";;;;;;;AAkBA,MAAM,eAAe,OACnB,QACA,kBAC4B;CAC5B,MAAM,EAAE,QAAQ,yBAAyB;CAEzC,MAAM,EAAE,YAAY;CACpB,MAAM,EAAE,YAAY;CAEpB,MAAM,SAAyB,CAAC;CAEhC,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,oBAAoB,MAAM,qBAAqB,QAAQ;GAC3D,KAAK;GACL;EACF,CAAkC;EAElC,MAAM,oBAAoB,MAAM,qBAAqB,QAAQ;GAC3D,KAAK;GACL;EACF,CAAkC;EAElC,IAAI,CAAC,qBAAqB,CAAC,mBACzB;EAOF,MAAM,QAAQ,MAAM,GAJU,kBAAkB,WAAW,IAAI,IAC3D,kBAAkB,MAAM,CAAC,IACzB,mBAE0C,EAC5C,KAAK,QACP,CAAC;EAED,KAAK,MAAM,QAAQ,OAAO;GAMxB,MAAM,kBAAkB,kBAAkB,SAAS,gBAAgB;GACnE,MAAM,eAAe,kBAAkB,SAAS,aAAa;GAE7D,IAAI;GACJ,IAAI;GAEJ,IAAI,mBAAmB,cAAc;IACnC,MAAM,aAAa,4BACjB,MACA,mBACA,SACA,MACF;IAEA,IAAI,CAAC,YACH;IAGF,MAAM,WAAW;IACjB,kBAAkB,WAAW;GAC/B,OAAO;IAEL,MAAM;IACN,kBAAkB;GACpB;GAEA,MAAM,eAAe,WAAW,IAAI,IAAI,OAAO,QAAQ,SAAS,IAAI;GAEpE,MAAM,aAAa;GACnB,IAAI,CAAC,OAAO,aACV,OAAO,cAAc,CAAC;GAGxB,OAAO,WAAW,CAAC,OAA4B;EACjD;CACF;CAIA,OAAO;AACT;AAIA,MAAM,qBAAqB,OACzB,QACA,kBACG;CAEH,MAAM,WAA2B,MAAM,aACrCA,QACA,aACF;CAsBA,OApBgB,OAAO,QAAQ,QAKoB,CAAC,CAAC,SAClD,CAAC,QAAQ,gBACR,OAAO,QAAQ,UAAU,CAAC,CAAC,KAAK,CAAC,KAAK,UAAU;EAK9C,OAAO;GACL,MALmB,WAAW,IAAI,IAChC,OACA,QAAQ,cAAc,OAAO,SAAS,IAAI;GAI5C;GACA;EACF;CACF,CAAC,CAGoB;AAC3B;AA6DA,MAAa,UAAU,YAAyC;CAC9D,MAAM,EAAE,UAAU,UAAU,WAAW;EACrC,UAAU;EACV,UAAU;EACV,GAAG;CACL;CAEA,OAAO;EACL,MAAM;EAEN,kBAAkB,OAAO,EAAE,oBAAoB;GAC7C,MAAM,kBAAmC,MAAM,mBAC7C,QAAQ,QACR,aACF;GAEA,MAAM,eAA6B,CAAC;GAEpC,KAAK,MAAM,EAAE,MAAM,KAAK,QAAQ,iBAAiB,iBAAiB;IAChE,IAAI;IACJ,IAAI;KAEF,YAAY,QAAQ,MADM,SAAS,MAAM,OAAO,CACjB;IACjC,QAAQ;KACN,YAAY,CAAC;IACf;IAEA,MAAM,WAAW,SAAS,cAAc,OAAO,SAAS,IAAI;IAI5D,MAAM,kBAAmB,UAAU;IAEnC,MAAM,aAAyB;KAC7B;KACA,QAAQ;KACR,MAAM;KACN,QAAQ;KACR,SAAS,GAAG,IAAI,IAAI,SAAS,IAAI;KACvB;KACV,QACE,oBAAoB,cAAc,qBAAqB,gBACnD,OACA;KACN,SAAS;KACT;KACA;IACF;IAEA,aAAa,KAAK,UAAU;GAC9B;GAEA,OAAO;EACT;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"syncPO.mjs","names":[],"sources":["../../src/syncPO.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, relative, resolve } from 'node:path';\nimport { parseFilePathPattern } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type {\n Dictionary,\n DictionaryLocation,\n LocalDictionaryId,\n} from '@intlayer/types/dictionary';\nimport type {\n FilePathPattern,\n FilePathPatternContext,\n} from '@intlayer/types/filePathPattern';\nimport type { Plugin } from '@intlayer/types/plugin';\nimport fg from 'fast-glob';\n\ntype FilePath = string;\n\ntype MessagesRecord = Record<Locale, Record<Dictionary['key'], FilePath>>;\n\nconst escapeRegex = (str: string) => str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nexport const extractKeyAndLocaleFromPath = (\n filePath: string,\n maskPattern: string,\n locales: Locale[],\n defaultLocale: Locale\n): { key: string; locale: Locale } | null => {\n const keyPlaceholder = '{{__KEY__}}';\n const localePlaceholder = '{{__LOCALE__}}';\n\n // fast-glob strips leading \"./\" from returned paths; normalize both sides\n const normalize = (path: string) =>\n path.startsWith('./') ? path.slice(2) : path;\n\n const normalizedFilePath = normalize(filePath);\n const normalizedMask = normalize(maskPattern);\n\n const localesAlternation = locales.join('|');\n\n // Escape special regex chars, then convert glob wildcards to regex equivalents.\n // Must replace ** before * to avoid double-replacing.\n let regexStr = `^${escapeRegex(normalizedMask)}$`;\n regexStr = regexStr.replace(/\\\\\\*\\\\\\*/g, '.*'); // ** → match any path segments\n regexStr = regexStr.replace(/\\\\\\*/g, '[^/]*'); // * → match within a single segment\n\n regexStr = regexStr.replace(\n escapeRegex(localePlaceholder),\n `(?<locale>${localesAlternation})`\n );\n\n if (normalizedMask.includes(keyPlaceholder)) {\n regexStr = regexStr.replace(escapeRegex(keyPlaceholder), '(?<key>.+)');\n }\n\n const maskRegex = new RegExp(regexStr);\n const match = maskRegex.exec(normalizedFilePath);\n\n if (!match?.groups) {\n return null;\n }\n\n let locale = match.groups.locale as Locale | undefined;\n let key = (match.groups.key as string | undefined) ?? 'index';\n\n if (typeof key === 'undefined') {\n key = 'index';\n }\n\n if (typeof locale === 'undefined') {\n locale = defaultLocale;\n }\n\n return { key, locale };\n};\n\n// ─── PO format utilities ────────────────────────────────────────────────────\n\nconst unescapePO = (str: string): string =>\n str\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\r/g, '\\r')\n .replace(/\\\\\"/g, '\"')\n .replace(/\\\\\\\\/g, '\\\\');\n\nconst escapePO = (str: string): string =>\n str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\t/g, '\\\\t')\n .replace(/\\r/g, '\\\\r');\n\n/**\n * Parse a PO file string into a flat msgid → msgstr record.\n * Skips the PO header (msgid \"\"), comment lines, and plural/context keywords.\n */\nexport const parsePO = (fileContent: string): Record<string, string> => {\n const result: Record<string, string> = {};\n const lines = fileContent\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .split('\\n');\n\n let msgid = '';\n let msgstr = '';\n let currentField: 'msgid' | 'msgstr' | null = null;\n\n const finalize = () => {\n if (msgid !== '') {\n result[msgid] = msgstr;\n }\n msgid = '';\n msgstr = '';\n currentField = null;\n };\n\n for (const line of lines) {\n // Skip all comment types (#, #., #:, #,, #|)\n if (line.startsWith('#')) continue;\n\n if (line.trim() === '') {\n finalize();\n continue;\n }\n\n const msgidMatch = line.match(/^msgid\\s+\"((?:[^\"\\\\]|\\\\.)*)\"$/);\n if (msgidMatch) {\n // Starting a new entry — finalize the previous one first\n finalize();\n msgid = unescapePO(msgidMatch[1]);\n currentField = 'msgid';\n continue;\n }\n\n const msgstrMatch = line.match(/^msgstr\\s+\"((?:[^\"\\\\]|\\\\.)*)\"$/);\n if (msgstrMatch) {\n msgstr = unescapePO(msgstrMatch[1]);\n currentField = 'msgstr';\n continue;\n }\n\n // Continuation line: `\"...\"` appends to the current keyword's value\n const contMatch = line.match(/^\"((?:[^\"\\\\]|\\\\.)*)\"$/);\n if (contMatch) {\n if (currentField === 'msgid') {\n msgid += unescapePO(contMatch[1]);\n } else if (currentField === 'msgstr') {\n msgstr += unescapePO(contMatch[1]);\n }\n continue;\n }\n\n // Other keywords (msgid_plural, msgstr[n], msgctxt) — not supported; reset field\n currentField = null;\n }\n\n // Finalize the last entry in the file (no trailing blank line needed)\n finalize();\n\n return result;\n};\n\n/**\n * Serialize a flat key → value record to PO file format.\n * Non-string values are silently skipped.\n */\nexport const serializePO = (\n content: Record<string, unknown>,\n locale?: string\n): string => {\n const lines: string[] = [];\n\n // PO header entry\n lines.push('msgid \"\"');\n lines.push('msgstr \"\"');\n lines.push('\"Content-Type: text/plain; charset=UTF-8\\\\n\"');\n lines.push('\"Content-Transfer-Encoding: 8bit\\\\n\"');\n if (locale) {\n lines.push(`\"Language: ${locale}\\\\n\"`);\n }\n lines.push('\"MIME-Version: 1.0\\\\n\"');\n lines.push('');\n\n for (const [msgid, msgstr] of Object.entries(content)) {\n if (typeof msgstr !== 'string') continue;\n\n lines.push(`msgid \"${escapePO(msgid)}\"`);\n lines.push(`msgstr \"${escapePO(msgstr)}\"`);\n lines.push('');\n }\n\n return `${lines.join('\\n')}\\n`;\n};\n\n// ─── File-discovery helpers (format-agnostic) ────────────────────────────────\n\nconst listMessages = async (\n source: FilePathPattern,\n configuration: IntlayerConfig\n): Promise<MessagesRecord> => {\n const { system, internationalization } = configuration;\n\n const { baseDir } = system;\n const { locales } = internationalization;\n\n const result: MessagesRecord = {} as MessagesRecord;\n\n for (const locale of locales) {\n const globPatternLocale = await parseFilePathPattern(source, {\n key: '**',\n locale,\n } as any as FilePathPatternContext);\n\n const maskPatternLocale = await parseFilePathPattern(source, {\n key: '{{__KEY__}}',\n locale,\n } as any as FilePathPatternContext);\n\n if (!globPatternLocale || !maskPatternLocale) {\n continue;\n }\n\n const normalizedGlobPattern = globPatternLocale.startsWith('./')\n ? globPatternLocale.slice(2)\n : globPatternLocale;\n\n const files = await fg(normalizedGlobPattern, {\n cwd: baseDir,\n });\n\n for (const file of files) {\n const extraction = extractKeyAndLocaleFromPath(\n file,\n maskPatternLocale,\n locales,\n locale\n );\n\n if (!extraction) {\n continue;\n }\n\n const { key, locale: extractedLocale } = extraction;\n\n // Generate what the path SHOULD be for this key/locale using the current builder\n const expectedPath = await parseFilePathPattern(source, {\n key,\n locale: extractedLocale,\n } as any as FilePathPatternContext);\n\n // Resolve both to absolute paths to ensure safe comparison\n const absoluteFoundPath = isAbsolute(file)\n ? file\n : resolve(baseDir, file);\n const absoluteExpectedPath = isAbsolute(expectedPath)\n ? expectedPath\n : resolve(baseDir, expectedPath);\n\n // If the file found doesn't exactly match the file expected, it belongs to another plugin/structure\n if (absoluteFoundPath !== absoluteExpectedPath) {\n continue;\n }\n\n const usedLocale = extractedLocale as Locale;\n if (!result[usedLocale]) {\n result[usedLocale] = {};\n }\n\n result[usedLocale][key as Dictionary['key']] = absoluteFoundPath;\n }\n }\n\n // Ensure all declared locales are present even if the file doesn't exist yet\n const maskWithKey = await parseFilePathPattern(source, {\n key: '{{__KEY__}}',\n locale: locales[0],\n } as any as FilePathPatternContext);\n\n const hasKeyInMask = maskWithKey.includes('{{__KEY__}}');\n const discoveredKeys = new Set<string>();\n\n for (const locale of Object.keys(result)) {\n for (const key of Object.keys(result[locale as Locale] ?? {})) {\n discoveredKeys.add(key);\n }\n }\n\n if (!hasKeyInMask) {\n discoveredKeys.add('index');\n }\n\n const keysToEnsure =\n discoveredKeys.size > 0 ? Array.from(discoveredKeys) : [];\n\n for (const locale of locales) {\n if (!result[locale]) {\n result[locale] = {} as Record<Dictionary['key'], FilePath>;\n }\n\n for (const key of keysToEnsure) {\n if (!result[locale][key as Dictionary['key']]) {\n const builtPath = await parseFilePathPattern(source, {\n key,\n locale,\n } as any as FilePathPatternContext);\n const absoluteBuiltPath = isAbsolute(builtPath)\n ? builtPath\n : resolve(baseDir, builtPath);\n\n result[locale][key as Dictionary['key']] = absoluteBuiltPath;\n }\n }\n }\n\n return result;\n};\n\ntype DictionariesMap = { path: string; locale: Locale; key: string }[];\n\nconst loadMessagePathMap = async (\n source: MessagesRecord | FilePathPattern,\n configuration: IntlayerConfig\n) => {\n const messages: MessagesRecord = await listMessages(\n source as FilePathPattern,\n configuration\n );\n\n const dictionariesPathMap: DictionariesMap = Object.entries(messages).flatMap(\n ([locale, keysRecord]) =>\n Object.entries(keysRecord).map(([key, path]) => {\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(configuration.system.baseDir, path);\n\n return {\n path: absolutePath,\n locale,\n key,\n } as DictionariesMap[number];\n })\n );\n\n return dictionariesPathMap;\n};\n\n// ─── Plugin ──────────────────────────────────────────────────────────────────\n\ntype SyncPOPluginOptions = {\n /**\n * The source of the plugin.\n * Is a function to build the source from the key and locale.\n *\n * ```ts\n * syncPO({\n * source: ({ key, locale }) => `./messages/${locale}/${key}.po`\n * })\n * ```\n */\n source: FilePathPattern;\n\n /**\n * Because Intlayer transforms PO files into Dictionary, we need to identify the plugin in the dictionary.\n * Used to identify the plugin in the dictionary.\n *\n * In the case you have multiple plugins, you can use this to identify the plugin in the dictionary.\n *\n * ```ts\n * // Example usage:\n * const config = {\n * plugins: [\n * syncPO({\n * source: ({ key, locale }) => `./resources/${locale}/${key}.po`,\n * location: 'plugin-i18next-po',\n * }),\n * ]\n * }\n * ```\n */\n location?: DictionaryLocation | (string & {});\n\n /**\n * The priority of the dictionaries created by the plugin.\n *\n * In the case of conflicts with remote dictionaries, or .content files, the dictionary with the highest priority will override the other dictionaries.\n *\n * Default is 0.\n */\n priority?: number;\n};\n\nexport const syncPO = async (options: SyncPOPluginOptions): Promise<Plugin> => {\n // Generate a unique default location based on the source pattern.\n const patternMarker = await parseFilePathPattern(options.source, {\n key: '{{key}}',\n locale: '{{locale}}',\n } as any as FilePathPatternContext);\n const defaultLocation = `sync-po::${patternMarker}`;\n\n const { location, priority } = {\n location: defaultLocation,\n priority: 0,\n ...options,\n };\n\n return {\n name: 'sync-po',\n\n loadDictionaries: async ({ configuration }) => {\n const dictionariesMap: DictionariesMap = await loadMessagePathMap(\n options.source,\n configuration\n );\n\n let fill: string = await parseFilePathPattern(options.source, {\n key: '{{key}}',\n locale: '{{locale}}',\n } as any as FilePathPatternContext);\n\n if (fill) {\n fill = relative(\n configuration.system.baseDir,\n resolve(configuration.system.baseDir, fill)\n );\n }\n\n const dictionaries: Dictionary[] = [];\n\n for (const { locale, path, key } of dictionariesMap) {\n let poContent: Record<string, string>;\n try {\n const fileContent = await readFile(path, 'utf-8');\n poContent = parsePO(fileContent);\n } catch {\n poContent = {};\n }\n\n const filePath = relative(configuration.system.baseDir, path);\n\n const dictionary: Dictionary = {\n key,\n locale,\n fill,\n format: 'po',\n localId: `${key}::${location}::${filePath}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled:\n locale !== configuration.internationalization.defaultLocale\n ? true\n : undefined,\n content: poContent,\n filePath,\n priority,\n };\n\n dictionaries.push(dictionary);\n }\n\n return dictionaries;\n },\n\n formatOutput: async ({ dictionary, configuration }) => {\n // Lazy import intlayer modules to avoid circular dependencies\n const { formatDictionaryOutput } = await import(\n '@intlayer/chokidar/build'\n );\n\n if (!dictionary.filePath || !dictionary.locale) return dictionary;\n\n const builderPath = await parseFilePathPattern(options.source, {\n key: dictionary.key,\n locale: dictionary.locale,\n } as FilePathPatternContext);\n\n // Verification to ensure we are formatting the correct file\n if (\n resolve(configuration.system.baseDir, builderPath) !==\n resolve(configuration.system.baseDir, dictionary.filePath)\n ) {\n return dictionary;\n }\n\n const formattedOutput = formatDictionaryOutput(dictionary, 'po');\n\n return formattedOutput.content;\n },\n\n afterBuild: async ({ dictionaries, configuration }) => {\n // Lazy import intlayer modules to avoid circular dependencies\n const { getPerLocaleDictionary } = await import('@intlayer/core/plugins');\n const { parallelize } = await import('@intlayer/chokidar/utils');\n const { formatDictionaryOutput } = await import(\n '@intlayer/chokidar/build'\n );\n\n const { locales } = configuration.internationalization;\n\n type RecordList = {\n key: string;\n dictionary: Dictionary;\n locale: Locale;\n };\n\n const recordList: RecordList[] = Object.entries(\n dictionaries.mergedDictionaries\n ).flatMap(([key, dictionary]) =>\n locales.map((locale) => ({\n key,\n dictionary: dictionary.dictionary as Dictionary,\n locale,\n }))\n );\n\n await parallelize(recordList, async ({ key, dictionary, locale }) => {\n // Only process dictionaries that belong to THIS plugin instance.\n if (dictionary.location !== location) {\n return;\n }\n\n const builderPath = await parseFilePathPattern(options.source, {\n key,\n locale,\n } as any as FilePathPatternContext);\n\n const localizedDictionary = getPerLocaleDictionary(dictionary, locale);\n\n const formattedOutput = formatDictionaryOutput(\n localizedDictionary,\n 'po'\n );\n\n const content = JSON.parse(\n JSON.stringify(formattedOutput.content)\n ) as Record<string, unknown>;\n\n if (\n typeof content === 'undefined' ||\n (typeof content === 'object' &&\n Object.keys(content as Record<string, unknown>).length === 0)\n ) {\n return;\n }\n\n await mkdir(dirname(builderPath), { recursive: true });\n\n const poContent = serializePO(content, locale);\n\n await writeFile(builderPath, poContent, 'utf-8');\n });\n },\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"syncPO.mjs","names":[],"sources":["../../src/syncPO.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, relative, resolve } from 'node:path';\nimport { parseFilePathPattern } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type {\n Dictionary,\n DictionaryLocation,\n LocalDictionaryId,\n} from '@intlayer/types/dictionary';\nimport type {\n FilePathPattern,\n FilePathPatternContext,\n} from '@intlayer/types/filePathPattern';\nimport type { Plugin } from '@intlayer/types/plugin';\nimport fg from 'fast-glob';\n\ntype FilePath = string;\n\ntype MessagesRecord = Record<Locale, Record<Dictionary['key'], FilePath>>;\n\nconst escapeRegex = (str: string) => str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nexport const extractKeyAndLocaleFromPath = (\n filePath: string,\n maskPattern: string,\n locales: Locale[],\n defaultLocale: Locale\n): { key: string; locale: Locale } | null => {\n const keyPlaceholder = '{{__KEY__}}';\n const localePlaceholder = '{{__LOCALE__}}';\n\n // fast-glob strips leading \"./\" from returned paths; normalize both sides\n const normalize = (path: string) =>\n path.startsWith('./') ? path.slice(2) : path;\n\n const normalizedFilePath = normalize(filePath);\n const normalizedMask = normalize(maskPattern);\n\n const localesAlternation = locales.join('|');\n\n // Escape special regex chars, then convert glob wildcards to regex equivalents.\n // Must replace ** before * to avoid double-replacing.\n let regexStr = `^${escapeRegex(normalizedMask)}$`;\n regexStr = regexStr.replace(/\\\\\\*\\\\\\*/g, '.*'); // ** → match any path segments\n regexStr = regexStr.replace(/\\\\\\*/g, '[^/]*'); // * → match within a single segment\n\n regexStr = regexStr.replace(\n escapeRegex(localePlaceholder),\n `(?<locale>${localesAlternation})`\n );\n\n if (normalizedMask.includes(keyPlaceholder)) {\n regexStr = regexStr.replace(escapeRegex(keyPlaceholder), '(?<key>.+)');\n }\n\n const maskRegex = new RegExp(regexStr);\n const match = maskRegex.exec(normalizedFilePath);\n\n if (!match?.groups) {\n return null;\n }\n\n let locale = match.groups.locale as Locale | undefined;\n let key = (match.groups.key as string | undefined) ?? 'index';\n\n if (typeof key === 'undefined') {\n key = 'index';\n }\n\n if (typeof locale === 'undefined') {\n locale = defaultLocale;\n }\n\n return { key, locale };\n};\n\n// ─── PO format utilities ────────────────────────────────────────────────────\n\nconst unescapePO = (str: string): string =>\n str\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\r/g, '\\r')\n .replace(/\\\\\"/g, '\"')\n .replace(/\\\\\\\\/g, '\\\\');\n\nconst escapePO = (str: string): string =>\n str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\t/g, '\\\\t')\n .replace(/\\r/g, '\\\\r');\n\n/**\n * Parse a PO file string into a flat msgid → msgstr record.\n * Skips the PO header (msgid \"\"), comment lines, and plural/context keywords.\n */\nexport const parsePO = (fileContent: string): Record<string, string> => {\n const result: Record<string, string> = {};\n const lines = fileContent\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .split('\\n');\n\n let msgid = '';\n let msgstr = '';\n let currentField: 'msgid' | 'msgstr' | null = null;\n\n const finalize = () => {\n if (msgid !== '') {\n result[msgid] = msgstr;\n }\n msgid = '';\n msgstr = '';\n currentField = null;\n };\n\n for (const line of lines) {\n // Skip all comment types (#, #., #:, #,, #|)\n if (line.startsWith('#')) continue;\n\n if (line.trim() === '') {\n finalize();\n continue;\n }\n\n const msgidMatch = line.match(/^msgid\\s+\"((?:[^\"\\\\]|\\\\.)*)\"$/);\n if (msgidMatch) {\n // Starting a new entry — finalize the previous one first\n finalize();\n msgid = unescapePO(msgidMatch[1]);\n currentField = 'msgid';\n continue;\n }\n\n const msgstrMatch = line.match(/^msgstr\\s+\"((?:[^\"\\\\]|\\\\.)*)\"$/);\n if (msgstrMatch) {\n msgstr = unescapePO(msgstrMatch[1]);\n currentField = 'msgstr';\n continue;\n }\n\n // Continuation line: `\"...\"` appends to the current keyword's value\n const contMatch = line.match(/^\"((?:[^\"\\\\]|\\\\.)*)\"$/);\n if (contMatch) {\n if (currentField === 'msgid') {\n msgid += unescapePO(contMatch[1]);\n } else if (currentField === 'msgstr') {\n msgstr += unescapePO(contMatch[1]);\n }\n continue;\n }\n\n // Other keywords (msgid_plural, msgstr[n], msgctxt) — not supported; reset field\n currentField = null;\n }\n\n // Finalize the last entry in the file (no trailing blank line needed)\n finalize();\n\n return result;\n};\n\n/**\n * Serialize a flat key → value record to PO file format.\n * Non-string values are silently skipped.\n */\nexport const serializePO = (\n content: Record<string, unknown>,\n locale?: string\n): string => {\n const lines: string[] = [];\n\n // PO header entry\n lines.push('msgid \"\"');\n lines.push('msgstr \"\"');\n lines.push('\"Content-Type: text/plain; charset=UTF-8\\\\n\"');\n lines.push('\"Content-Transfer-Encoding: 8bit\\\\n\"');\n if (locale) {\n lines.push(`\"Language: ${locale}\\\\n\"`);\n }\n lines.push('\"MIME-Version: 1.0\\\\n\"');\n lines.push('');\n\n for (const [msgid, msgstr] of Object.entries(content)) {\n if (typeof msgstr !== 'string') continue;\n\n lines.push(`msgid \"${escapePO(msgid)}\"`);\n lines.push(`msgstr \"${escapePO(msgstr)}\"`);\n lines.push('');\n }\n\n return `${lines.join('\\n')}\\n`;\n};\n\n// ─── File-discovery helpers (format-agnostic) ────────────────────────────────\n\nconst listMessages = async (\n source: FilePathPattern,\n configuration: IntlayerConfig\n): Promise<MessagesRecord> => {\n const { system, internationalization } = configuration;\n\n const { baseDir } = system;\n const { locales } = internationalization;\n\n const result: MessagesRecord = {} as MessagesRecord;\n\n for (const locale of locales) {\n const globPatternLocale = await parseFilePathPattern(source, {\n key: '**',\n locale,\n } as any as FilePathPatternContext);\n\n const maskPatternLocale = await parseFilePathPattern(source, {\n key: '{{__KEY__}}',\n locale,\n } as any as FilePathPatternContext);\n\n if (!globPatternLocale || !maskPatternLocale) {\n continue;\n }\n\n const normalizedGlobPattern = globPatternLocale.startsWith('./')\n ? globPatternLocale.slice(2)\n : globPatternLocale;\n\n const files = await fg(normalizedGlobPattern, {\n cwd: baseDir,\n });\n\n for (const file of files) {\n const extraction = extractKeyAndLocaleFromPath(\n file,\n maskPatternLocale,\n locales,\n locale\n );\n\n if (!extraction) {\n continue;\n }\n\n const { key, locale: extractedLocale } = extraction;\n\n // Generate what the path SHOULD be for this key/locale using the current builder\n const expectedPath = await parseFilePathPattern(source, {\n key,\n locale: extractedLocale,\n } as any as FilePathPatternContext);\n\n // Resolve both to absolute paths to ensure safe comparison\n const absoluteFoundPath = isAbsolute(file)\n ? file\n : resolve(baseDir, file);\n const absoluteExpectedPath = isAbsolute(expectedPath)\n ? expectedPath\n : resolve(baseDir, expectedPath);\n\n // If the file found doesn't exactly match the file expected, it belongs to another plugin/structure\n if (absoluteFoundPath !== absoluteExpectedPath) {\n continue;\n }\n\n const usedLocale = extractedLocale as Locale;\n if (!result[usedLocale]) {\n result[usedLocale] = {};\n }\n\n result[usedLocale][key as Dictionary['key']] = absoluteFoundPath;\n }\n }\n\n // Ensure all declared locales are present even if the file doesn't exist yet\n const maskWithKey = await parseFilePathPattern(source, {\n key: '{{__KEY__}}',\n locale: locales[0],\n } as any as FilePathPatternContext);\n\n const hasKeyInMask = maskWithKey.includes('{{__KEY__}}');\n const discoveredKeys = new Set<string>();\n\n for (const locale of Object.keys(result)) {\n for (const key of Object.keys(result[locale as Locale] ?? {})) {\n discoveredKeys.add(key);\n }\n }\n\n if (!hasKeyInMask) {\n discoveredKeys.add('index');\n }\n\n const keysToEnsure =\n discoveredKeys.size > 0 ? Array.from(discoveredKeys) : [];\n\n for (const locale of locales) {\n if (!result[locale]) {\n result[locale] = {} as Record<Dictionary['key'], FilePath>;\n }\n\n for (const key of keysToEnsure) {\n if (!result[locale][key as Dictionary['key']]) {\n const builtPath = await parseFilePathPattern(source, {\n key,\n locale,\n } as any as FilePathPatternContext);\n const absoluteBuiltPath = isAbsolute(builtPath)\n ? builtPath\n : resolve(baseDir, builtPath);\n\n result[locale][key as Dictionary['key']] = absoluteBuiltPath;\n }\n }\n }\n\n return result;\n};\n\ntype DictionariesMap = { path: string; locale: Locale; key: string }[];\n\nconst loadMessagePathMap = async (\n source: MessagesRecord | FilePathPattern,\n configuration: IntlayerConfig\n) => {\n const messages: MessagesRecord = await listMessages(\n source as FilePathPattern,\n configuration\n );\n\n const dictionariesPathMap: DictionariesMap = Object.entries(messages).flatMap(\n ([locale, keysRecord]) =>\n Object.entries(keysRecord).map(([key, path]) => {\n const absolutePath = isAbsolute(path)\n ? path\n : resolve(configuration.system.baseDir, path);\n\n return {\n path: absolutePath,\n locale,\n key,\n } as DictionariesMap[number];\n })\n );\n\n return dictionariesPathMap;\n};\n\n// ─── Plugin ──────────────────────────────────────────────────────────────────\n\ntype SyncPOPluginOptions = {\n /**\n * The source of the plugin.\n * Is a function to build the source from the key and locale.\n *\n * ```ts\n * syncPO({\n * source: ({ key, locale }) => `./messages/${locale}/${key}.po`\n * })\n * ```\n */\n source: FilePathPattern;\n\n /**\n * Because Intlayer transforms PO files into Dictionary, we need to identify the plugin in the dictionary.\n * Used to identify the plugin in the dictionary.\n *\n * In the case you have multiple plugins, you can use this to identify the plugin in the dictionary.\n *\n * ```ts\n * // Example usage:\n * const config = {\n * plugins: [\n * syncPO({\n * source: ({ key, locale }) => `./resources/${locale}/${key}.po`,\n * location: 'plugin-i18next-po',\n * }),\n * ]\n * }\n * ```\n */\n location?: DictionaryLocation | (string & {});\n\n /**\n * The priority of the dictionaries created by the plugin.\n *\n * In the case of conflicts with remote dictionaries, or .content files, the dictionary with the highest priority will override the other dictionaries.\n *\n * Default is 0.\n */\n priority?: number;\n};\n\nexport const syncPO = async (options: SyncPOPluginOptions): Promise<Plugin> => {\n // Generate a unique default location based on the source pattern.\n const patternMarker = await parseFilePathPattern(options.source, {\n key: '{{key}}',\n locale: '{{locale}}',\n } as any as FilePathPatternContext);\n const defaultLocation = `sync-po::${patternMarker}`;\n\n const { location, priority } = {\n location: defaultLocation,\n priority: 0,\n ...options,\n };\n\n return {\n name: 'sync-po',\n\n loadDictionaries: async ({ configuration }) => {\n const dictionariesMap: DictionariesMap = await loadMessagePathMap(\n options.source,\n configuration\n );\n\n let fill: string = await parseFilePathPattern(options.source, {\n key: '{{key}}',\n locale: '{{locale}}',\n } as any as FilePathPatternContext);\n\n if (fill) {\n fill = relative(\n configuration.system.baseDir,\n resolve(configuration.system.baseDir, fill)\n );\n }\n\n const dictionaries: Dictionary[] = [];\n\n for (const { locale, path, key } of dictionariesMap) {\n let poContent: Record<string, string>;\n try {\n const fileContent = await readFile(path, 'utf-8');\n poContent = parsePO(fileContent);\n } catch {\n poContent = {};\n }\n\n const filePath = relative(configuration.system.baseDir, path);\n\n const dictionary: Dictionary = {\n key,\n locale,\n fill,\n format: 'po',\n localId: `${key}::${location}::${filePath}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled:\n locale !== configuration.internationalization.defaultLocale\n ? true\n : undefined,\n content: poContent,\n filePath,\n priority,\n };\n\n dictionaries.push(dictionary);\n }\n\n return dictionaries;\n },\n\n formatOutput: async ({ dictionary, configuration }) => {\n // Lazy import intlayer modules to avoid circular dependencies\n const { formatDictionaryOutput } = await import(\n '@intlayer/chokidar/build'\n );\n\n if (!dictionary.filePath || !dictionary.locale) return dictionary;\n\n const builderPath = await parseFilePathPattern(options.source, {\n key: dictionary.key,\n locale: dictionary.locale,\n } as FilePathPatternContext);\n\n // Verification to ensure we are formatting the correct file\n if (\n resolve(configuration.system.baseDir, builderPath) !==\n resolve(configuration.system.baseDir, dictionary.filePath)\n ) {\n return dictionary;\n }\n\n const formattedOutput = formatDictionaryOutput(dictionary, 'po');\n\n return formattedOutput.content;\n },\n\n afterBuild: async ({ dictionaries, configuration }) => {\n // Lazy import intlayer modules to avoid circular dependencies\n const { getPerLocaleDictionary } = await import('@intlayer/core/plugins');\n const { parallelize } = await import('@intlayer/chokidar/utils');\n const { formatDictionaryOutput } = await import(\n '@intlayer/chokidar/build'\n );\n\n const { locales } = configuration.internationalization;\n\n type RecordList = {\n key: string;\n dictionary: Dictionary;\n locale: Locale;\n };\n\n const recordList: RecordList[] = Object.entries(\n dictionaries.mergedDictionaries\n ).flatMap(([key, dictionary]) =>\n locales.map((locale) => ({\n key,\n dictionary: dictionary.dictionary as Dictionary,\n locale,\n }))\n );\n\n await parallelize(recordList, async ({ key, dictionary, locale }) => {\n // Only process dictionaries that belong to THIS plugin instance.\n if (dictionary.location !== location) {\n return;\n }\n\n const builderPath = await parseFilePathPattern(options.source, {\n key,\n locale,\n } as any as FilePathPatternContext);\n\n const localizedDictionary = getPerLocaleDictionary(dictionary, locale);\n\n const formattedOutput = formatDictionaryOutput(\n localizedDictionary,\n 'po'\n );\n\n const content = JSON.parse(\n JSON.stringify(formattedOutput.content)\n ) as Record<string, unknown>;\n\n if (\n typeof content === 'undefined' ||\n (typeof content === 'object' &&\n Object.keys(content as Record<string, unknown>).length === 0)\n ) {\n return;\n }\n\n await mkdir(dirname(builderPath), { recursive: true });\n\n const poContent = serializePO(content, locale);\n\n await writeFile(builderPath, poContent, 'utf-8');\n });\n },\n };\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intlayer/sync-po-plugin",
3
- "version": "8.12.1",
3
+ "version": "8.12.2",
4
4
  "private": false,
5
5
  "description": "A plugin for Intlayer that syncs Portable Object (.po) files to dictionaries.",
6
6
  "keywords": [
@@ -72,10 +72,10 @@
72
72
  "typecheck": "tsc --noEmit --project tsconfig.types.json"
73
73
  },
74
74
  "dependencies": {
75
- "@intlayer/chokidar": "8.12.1",
76
- "@intlayer/config": "8.12.1",
77
- "@intlayer/core": "8.12.1",
78
- "@intlayer/types": "8.12.1",
75
+ "@intlayer/chokidar": "8.12.2",
76
+ "@intlayer/config": "8.12.2",
77
+ "@intlayer/core": "8.12.2",
78
+ "@intlayer/types": "8.12.2",
79
79
  "fast-glob": "3.3.3"
80
80
  },
81
81
  "devDependencies": {
@@ -84,7 +84,7 @@
84
84
  "@utils/ts-config-types": "1.0.4",
85
85
  "@utils/tsdown-config": "1.0.4",
86
86
  "rimraf": "6.1.3",
87
- "tsdown": "0.22.1",
87
+ "tsdown": "0.22.2",
88
88
  "typescript": "6.0.3",
89
89
  "vitest": "4.1.8"
90
90
  },