@intlayer/sync-json-plugin 8.1.2 → 8.6.10

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":"syncJSON.cjs","names":["fg"],"sources":["../../src/syncJSON.ts"],"sourcesContent":["import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path';\nimport { getProjectRequire } from '@intlayer/config';\nimport type {\n Dictionary,\n DictionaryFormat,\n DictionaryLocation,\n IntlayerConfig,\n LocalDictionaryId,\n Locale,\n LocalesValues,\n Plugin,\n} from '@intlayer/types';\nimport fg from 'fast-glob';\n\ntype JSONContent = Record<string, any>;\n\ntype Builder = ({\n key,\n locale,\n}: {\n key: string;\n locale: LocalesValues | (string & {});\n}) => 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 const escapedMask = escapeRegex(maskPattern);\n const localesAlternation = locales.join('|');\n\n // Build a regex from the mask to capture locale (and key if present)\n let regexStr = `^${escapedMask}$`;\n\n regexStr = regexStr.replace(\n escapeRegex(localePlaceholder),\n `(?<locale>${localesAlternation})`\n );\n\n if (maskPattern.includes(keyPlaceholder)) {\n // FIX: Allow key to match multiple directory levels (e.g. \"nested/file\" or \"folder/index\")\n // Previous value: '(?<key>[^/]+)'\n regexStr = regexStr.replace(escapeRegex(keyPlaceholder), '(?<key>.+)');\n }\n\n const maskRegex = new RegExp(regexStr);\n const match = maskRegex.exec(filePath);\n\n if (!match || !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 {\n key,\n locale,\n };\n};\n\nconst listMessages = (\n builder: Builder,\n configuration: IntlayerConfig\n): MessagesRecord => {\n const { content, internationalization } = configuration;\n\n const baseDir = content.baseDir;\n const locales = internationalization.locales;\n const defaultLocale = internationalization.defaultLocale;\n\n const localePattern = `{${locales.map((locale) => locale).join(',')}}`;\n\n // FIX: Use '**' instead of '*' to allow recursive globbing for the key\n const globPattern = builder({ key: '**', locale: localePattern });\n const maskPattern = builder({ key: '{{__KEY__}}', locale: '{{__LOCALE__}}' });\n\n const files = fg.sync(globPattern, {\n cwd: baseDir,\n });\n\n const result: MessagesRecord = {} as MessagesRecord;\n\n for (const file of files) {\n const extraction = extractKeyAndLocaleFromPath(\n file,\n maskPattern,\n locales,\n defaultLocale\n );\n\n // If extraction failed (regex mismatch), skip this file\n if (!extraction) {\n continue;\n }\n\n const { key, locale } = extraction;\n\n // Generate what the path SHOULD be for this key/locale using the current builder\n const expectedPath = builder({ key, locale });\n\n // Resolve both to absolute paths to ensure safe comparison\n const absoluteFoundPath = isAbsolute(file) ? file : 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 if (!result[locale as Locale]) {\n result[locale as Locale] = {};\n }\n\n result[locale as Locale][key as Dictionary['key']] = absoluteFoundPath;\n }\n\n // Ensure all declared locales are present even if the file doesn't exist yet\n const hasKeyInMask = maskPattern.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 = builder({ key, locale });\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 FilePath = string;\n\ntype DictionariesMap = { path: string; locale: Locale; key: string }[];\n\nconst loadMessagePathMap = (\n source: MessagesRecord | Builder,\n configuration: IntlayerConfig\n) => {\n const messages: MessagesRecord = listMessages(\n source as Builder,\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.content.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 SyncJSONPluginOptions = {\n /**\n * The source of the plugin.\n * Is a function to build the source from the key and locale.\n *\n * ```ts\n * syncJSON({\n * source: ({ key, locale }) => `./messages/${locale}/${key}.json`\n * })\n * ```\n */\n source: Builder;\n\n /**\n * Because Intlayer transform the JSON 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 * syncJSON({\n * source: ({ key, locale }) => `./resources/${locale}/${key}.json`,\n * location: 'plugin-i18next',\n * }),\n * syncJSON({\n * source: ({ key, locale }) => `./messages/${locale}/${key}.json`,\n * location: 'plugin-next-intl',\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 -1. (.content file priority is 0)\n *\n */\n priority?: number;\n\n /**\n * The format of the dictionaries created by the plugin.\n *\n * Default: 'intlayer'\n *\n * The format of the dictionaries created by the plugin.\n */\n format?: DictionaryFormat;\n};\n\nexport const syncJSON = (options: SyncJSONPluginOptions): Plugin => {\n // Generate a unique default location based on the source pattern.\n // This ensures that if you have multiple plugins, they don't share the same 'plugin' ID.\n const patternMarker = options.source({\n key: '{{key}}',\n locale: '{{locale}}',\n });\n const defaultLocation = `sync-json::${patternMarker}`;\n\n const { location, priority, format } = {\n location: defaultLocation,\n priority: 0,\n ...options,\n };\n\n return {\n name: 'sync-json',\n\n loadDictionaries: async ({ configuration }) => {\n const dictionariesMap: DictionariesMap = loadMessagePathMap(\n options.source,\n configuration\n );\n\n let fill: string = options.source({\n key: '{{key}}',\n locale: '{{locale}}',\n });\n\n if (fill && !isAbsolute(fill)) {\n fill = join(configuration.content.baseDir, fill);\n }\n\n const dictionaries: Dictionary[] = [];\n\n for (const { locale, path, key } of dictionariesMap) {\n const requireFunction =\n configuration.build?.require ?? getProjectRequire();\n let json: JSONContent = {};\n try {\n json = requireFunction(path as string);\n } catch {\n json = {};\n }\n\n const filePath = relative(configuration.content.baseDir, path);\n\n const dictionary: Dictionary = {\n key,\n locale,\n fill,\n format,\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: json,\n filePath,\n priority,\n };\n\n dictionaries.push(dictionary);\n }\n\n return dictionaries;\n },\n\n formatOutput: async ({ dictionary }) => {\n // Lazy import intlayer modules to avoid circular dependencies\n const { formatDictionaryOutput } = await import('@intlayer/chokidar');\n\n if (!dictionary.filePath || !dictionary.locale) return dictionary;\n\n const builderPath = options.source({\n key: dictionary.key,\n locale: dictionary.locale,\n });\n\n // Verification to ensure we are formatting the correct file\n if (resolve(builderPath) !== resolve(dictionary.filePath)) {\n return dictionary;\n }\n\n const dictionaryWithFormat = {\n ...dictionary,\n format,\n };\n\n const formattedOutput = formatDictionaryOutput(\n dictionaryWithFormat as Dictionary\n );\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');\n const { parallelize, formatDictionaryOutput } = await import(\n '@intlayer/chokidar'\n );\n\n const locales = configuration.internationalization.locales;\n\n type RecordList = {\n key: string;\n dictionary: Dictionary;\n locale: Locale;\n };\n\n // We get all dictionaries, but we need to filter them\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 = options.source({\n key,\n locale,\n });\n\n const localizedDictionary = getPerLocaleDictionary(dictionary, locale);\n\n const dictionaryWithFormat = {\n ...localizedDictionary,\n format,\n };\n\n const formattedOutput = formatDictionaryOutput(dictionaryWithFormat);\n\n const content = JSON.parse(JSON.stringify(formattedOutput.content));\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 stringContent = JSON.stringify(content, null, 2);\n\n await writeFile(builderPath, `${stringContent}\\n`, 'utf-8');\n });\n },\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"syncJSON.cjs","names":[],"sources":["../../src/syncJSON.ts"],"sourcesContent":["import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path';\nimport { loadExternalFile } from '@intlayer/config/file';\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 DictionaryFormat,\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 JSONContent = Record<string, any>;\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 {\n key,\n locale,\n };\n};\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\ntype SyncJSONPluginOptions = {\n /**\n * The source of the plugin.\n * Is a function to build the source from the key and locale.\n *\n * ```ts\n * syncJSON({\n * source: ({ key, locale }) => `./messages/${locale}/${key}.json`\n * })\n * ```\n */\n source: FilePathPattern;\n\n /**\n * Because Intlayer transform the JSON 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 * syncJSON({\n * source: ({ key, locale }) => `./resources/${locale}/${key}.json`,\n * location: 'plugin-i18next',\n * }),\n * syncJSON({\n * source: ({ key, locale }) => `./messages/${locale}/${key}.json`,\n * location: 'plugin-next-intl',\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 -1. (.content file priority is 0)\n *\n */\n priority?: number;\n\n /**\n * The format of the dictionaries created by the plugin.\n *\n * Default: 'intlayer'\n *\n * The format of the dictionaries created by the plugin.\n */\n format?: DictionaryFormat;\n};\n\nexport const syncJSON = async (\n options: SyncJSONPluginOptions\n): Promise<Plugin> => {\n // Generate a unique default location based on the source pattern.\n // This ensures that if you have multiple plugins, they don't share the same 'plugin' ID.\n const patternMarker = await parseFilePathPattern(options.source, {\n key: '{{key}}',\n locale: '{{locale}}',\n } as any as FilePathPatternContext);\n const defaultLocation = `sync-json::${patternMarker}`;\n\n const { location, priority, format } = {\n location: defaultLocation,\n priority: 0,\n ...options,\n };\n\n return {\n name: 'sync-json',\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 && !isAbsolute(fill)) {\n fill = join(configuration.system.baseDir, fill);\n }\n\n const dictionaries: Dictionary[] = [];\n\n for (const { locale, path, key } of dictionariesMap) {\n // loadExternalFile swallows errors and returns undefined for missing files;\n // the try/catch does not help here — use ?? {} to guarantee a plain object.\n const json: JSONContent =\n (await loadExternalFile(path, { logError: false })) ?? {};\n\n const filePath = relative(configuration.system.baseDir, path);\n\n const dictionary: Dictionary = {\n key,\n locale,\n fill,\n format,\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: json,\n filePath,\n priority,\n };\n\n dictionaries.push(dictionary);\n }\n\n return dictionaries;\n },\n\n formatOutput: async ({ dictionary }) => {\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 (resolve(builderPath) !== resolve(dictionary.filePath)) {\n return dictionary;\n }\n\n const formattedOutput = formatDictionaryOutput(dictionary, format);\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 // We get all dictionaries, but we need to filter them\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 format\n );\n\n const content = JSON.parse(JSON.stringify(formattedOutput.content));\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 stringContent = JSON.stringify(content, null, 2);\n\n await writeFile(builderPath, `${stringContent}\\n`, 'utf-8');\n });\n },\n };\n};\n"],"mappings":"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"}
@@ -1,42 +1,47 @@
1
1
  import { extractKeyAndLocaleFromPath } from "./syncJSON.mjs";
2
2
  import { isAbsolute, join, relative, resolve } from "node:path";
3
- import { getProjectRequire } from "@intlayer/config";
3
+ import { loadExternalFile } from "@intlayer/config/file";
4
+ import { parseFilePathPattern } from "@intlayer/config/utils";
4
5
  import fg from "fast-glob";
5
6
 
6
7
  //#region src/loadJSON.ts
7
- const listMessages = (builder, configuration, selectedLocale) => {
8
- const { content, internationalization } = configuration;
9
- const baseDir = content.baseDir;
10
- const locales = internationalization.locales;
11
- const globPattern = builder({
12
- key: "*",
13
- locale: `{${locales.map((locale) => locale).join(",")}}`
14
- });
15
- const maskPattern = builder({
16
- key: "{{__KEY__}}",
17
- locale: "{{__LOCALE__}}"
18
- });
19
- const files = fg.sync(globPattern, { cwd: baseDir });
8
+ const listMessages = async (source, configuration, selectedLocale) => {
9
+ const { system, internationalization } = configuration;
10
+ const { baseDir } = system;
11
+ const { locales } = internationalization;
20
12
  const result = {};
21
- for (const file of files) {
22
- const extraction = extractKeyAndLocaleFromPath(file, maskPattern, locales, selectedLocale);
23
- if (!extraction) continue;
24
- const { key, locale } = extraction;
25
- const absolutePath = isAbsolute(file) ? file : resolve(baseDir, file);
26
- if (!result[locale]) result[locale] = {};
27
- result[locale][key] = absolutePath;
13
+ for (const locale of locales) {
14
+ const globPatternLocale = await parseFilePathPattern(source, {
15
+ key: "*",
16
+ locale
17
+ });
18
+ const maskPatternLocale = await parseFilePathPattern(source, {
19
+ key: "{{__KEY__}}",
20
+ locale
21
+ });
22
+ if (!globPatternLocale || !maskPatternLocale) continue;
23
+ const files = await fg(globPatternLocale.startsWith("./") ? globPatternLocale.slice(2) : globPatternLocale, { cwd: baseDir });
24
+ for (const file of files) {
25
+ const extraction = extractKeyAndLocaleFromPath(file, maskPatternLocale, locales, selectedLocale);
26
+ if (!extraction) continue;
27
+ const { key, locale: extractedLocale } = extraction;
28
+ const absolutePath = isAbsolute(file) ? file : resolve(baseDir, file);
29
+ const usedLocale = extractedLocale;
30
+ if (!result[usedLocale]) result[usedLocale] = {};
31
+ result[usedLocale][key] = absolutePath;
32
+ }
28
33
  }
29
34
  return result;
30
35
  };
31
- const loadMessagePathMap = (source, configuration, selectedLocale) => {
32
- const builder = source;
33
- const messages = listMessages(builder, configuration, selectedLocale);
34
- return (builder({
36
+ const loadMessagePathMap = async (source, configuration, selectedLocale) => {
37
+ const sourcePattern = source;
38
+ const messages = await listMessages(sourcePattern, configuration, selectedLocale);
39
+ return ((await parseFilePathPattern(sourcePattern, {
35
40
  key: "{{__KEY__}}",
36
41
  locale: "{{__LOCALE__}}"
37
- }).includes("{{__LOCALE__}}") && selectedLocale ? Object.entries(messages).filter(([locale]) => locale === selectedLocale) : Object.entries(messages)).flatMap(([locale, keysRecord]) => Object.entries(keysRecord).map(([key, path]) => {
42
+ })).includes("{{__LOCALE__}}") && selectedLocale ? Object.entries(messages).filter(([locale]) => locale === selectedLocale) : Object.entries(messages)).flatMap(([locale, keysRecord]) => Object.entries(keysRecord).map(([key, path]) => {
38
43
  return {
39
- path: isAbsolute(path) ? path : resolve(configuration.content.baseDir, path),
44
+ path: isAbsolute(path) ? path : resolve(configuration.system.baseDir, path),
40
45
  locale,
41
46
  key
42
47
  };
@@ -52,19 +57,13 @@ const loadJSON = (options) => {
52
57
  name: "load-json",
53
58
  loadDictionaries: async ({ configuration }) => {
54
59
  const usedLocale = locale ?? configuration.internationalization.defaultLocale;
55
- const dictionariesMap = loadMessagePathMap(options.source, configuration, usedLocale);
56
- let filePath = options.source({ key: "{{key}}" });
57
- if (filePath && !isAbsolute(filePath)) filePath = join(configuration.content.baseDir, filePath);
60
+ const dictionariesMap = await loadMessagePathMap(options.source, configuration, usedLocale);
61
+ let filePath = await parseFilePathPattern(options.source, { key: "{{key}}" });
62
+ if (filePath && !isAbsolute(filePath)) filePath = join(configuration.system.baseDir, filePath);
58
63
  const dictionaries = [];
59
64
  for (const { path, key } of dictionariesMap) {
60
- const requireFunction = configuration.build?.require ?? getProjectRequire();
61
- let json = {};
62
- try {
63
- json = requireFunction(path);
64
- } catch {
65
- json = {};
66
- }
67
- const filePath = relative(configuration.content.baseDir, path);
65
+ const json = await loadExternalFile(path, { logError: false }) ?? {};
66
+ const filePath = relative(configuration.system.baseDir, path);
68
67
  const dictionary = {
69
68
  key,
70
69
  locale: usedLocale,
@@ -1 +1 @@
1
- {"version":3,"file":"loadJSON.mjs","names":[],"sources":["../../src/loadJSON.ts"],"sourcesContent":["import { isAbsolute, join, relative, resolve } from 'node:path';\nimport { getProjectRequire } from '@intlayer/config';\nimport type {\n Dictionary,\n DictionaryFormat,\n IntlayerConfig,\n LocalDictionaryId,\n Locale,\n Plugin,\n} from '@intlayer/types';\nimport fg from 'fast-glob';\nimport { extractKeyAndLocaleFromPath } from './syncJSON';\n\ntype JSONContent = Record<string, any>;\n\ntype Builder = ({\n key,\n locale,\n}: {\n key: string;\n locale?: Locale | (string & {});\n}) => string;\n\ntype MessagesRecord = Record<Locale, Record<Dictionary['key'], FilePath>>;\n\nconst listMessages = (\n builder: Builder,\n configuration: IntlayerConfig,\n selectedLocale: Locale\n): MessagesRecord => {\n const { content, internationalization } = configuration;\n\n const baseDir = content.baseDir;\n const locales = internationalization.locales;\n\n const localePattern = `{${locales.map((locale) => locale).join(',')}}`;\n\n const globPattern = builder({ key: '*', locale: localePattern });\n const maskPattern = builder({ key: '{{__KEY__}}', locale: '{{__LOCALE__}}' });\n\n const files = fg.sync(globPattern, {\n cwd: baseDir,\n });\n\n const result: MessagesRecord = {} as MessagesRecord;\n\n for (const file of files) {\n const extraction = extractKeyAndLocaleFromPath(\n file,\n maskPattern,\n locales,\n selectedLocale\n );\n\n if (!extraction) {\n continue;\n }\n\n const { key, locale } = extraction;\n\n const absolutePath = isAbsolute(file) ? file : resolve(baseDir, file);\n\n if (!result[locale as Locale]) {\n result[locale as Locale] = {};\n }\n\n result[locale as Locale][key as Dictionary['key']] = absolutePath;\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 FilePath = string;\n\ntype DictionariesMap = { path: string; locale: Locale; key: string }[];\n\nconst loadMessagePathMap = (\n source: MessagesRecord | Builder,\n configuration: IntlayerConfig,\n selectedLocale: Locale\n) => {\n const builder = source as Builder;\n const messages: MessagesRecord = listMessages(\n builder,\n configuration,\n selectedLocale\n );\n\n const maskPattern = builder({\n key: '{{__KEY__}}',\n locale: '{{__LOCALE__}}',\n });\n const hasLocaleInMask = maskPattern.includes('{{__LOCALE__}}');\n\n const entries = (\n hasLocaleInMask && selectedLocale\n ? Object.entries(messages).filter(([locale]) => locale === selectedLocale)\n : Object.entries(messages)\n ) as [Locale, Record<Dictionary['key'], FilePath>][];\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.content.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 LoadJSONPluginOptions = {\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 * loadJSON({\n * source: ({ key }) => `blog/${'**'}/${key}.i18n.json`,\n * })\n * ```\n */\n source: Builder;\n\n /**\n * Locale\n *\n * If not provided, the plugin will consider the default locale.\n *\n * @example\n * ```ts\n * loadJSON({\n * source: ({ key }) => `blog/${'**'}/${key}.i18n.json`,\n * locale: Locales.ENGLISH,\n * })\n * ```\n */\n locale?: Locale;\n\n /**\n * Because Intlayer transform the JSON 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 * loadJSON({\n * source: ({ key }) => `./resources/${key}.json`,\n * location: 'plugin-i18next',\n * }),\n * loadJSON({\n * source: ({ key }) => `./messages/${key}.json`,\n * location: 'plugin-next-intl',\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 -1. (.content file priority is 0)\n *\n */\n priority?: number;\n\n /**\n * The format of the dictionary content.\n *\n * @example\n * ```ts\n * loadJSON({\n * format: 'icu',\n * })\n * ```\n */\n format?: DictionaryFormat;\n};\n\nexport const loadJSON = (options: LoadJSONPluginOptions): Plugin => {\n const { location, priority, locale, format } = {\n location: 'plugin',\n priority: 0,\n ...options,\n } as const;\n\n return {\n name: 'load-json',\n\n loadDictionaries: async ({ configuration }) => {\n const usedLocale = (locale ??\n configuration.internationalization.defaultLocale) as Locale;\n\n const dictionariesMap: DictionariesMap = loadMessagePathMap(\n options.source,\n configuration,\n usedLocale\n );\n\n let filePath: string = options.source({\n key: '{{key}}',\n });\n\n if (filePath && !isAbsolute(filePath)) {\n filePath = join(configuration.content.baseDir, filePath);\n }\n\n const dictionaries: Dictionary[] = [];\n\n for (const { path, key } of dictionariesMap) {\n const requireFunction =\n configuration.build?.require ?? getProjectRequire();\n let json: JSONContent = {};\n try {\n json = requireFunction(path as string);\n } catch {\n // File does not exist yet; default to empty content so it can be filled later\n json = {};\n }\n\n const filePath = relative(configuration.content.baseDir, path);\n\n const dictionary: Dictionary = {\n key,\n locale: usedLocale,\n fill: filePath,\n format,\n localId: `${key}::${location}::${filePath}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled:\n usedLocale !== configuration.internationalization.defaultLocale\n ? true\n : undefined,\n content: json,\n filePath,\n priority,\n };\n\n dictionaries.push(dictionary);\n }\n\n return dictionaries;\n },\n };\n};\n"],"mappings":";;;;;;AAyBA,MAAM,gBACJ,SACA,eACA,mBACmB;CACnB,MAAM,EAAE,SAAS,yBAAyB;CAE1C,MAAM,UAAU,QAAQ;CACxB,MAAM,UAAU,qBAAqB;CAIrC,MAAM,cAAc,QAAQ;EAAE,KAAK;EAAK,QAFlB,IAAI,QAAQ,KAAK,WAAW,OAAO,CAAC,KAAK,IAAI,CAAC;EAEL,CAAC;CAChE,MAAM,cAAc,QAAQ;EAAE,KAAK;EAAe,QAAQ;EAAkB,CAAC;CAE7E,MAAM,QAAQ,GAAG,KAAK,aAAa,EACjC,KAAK,SACN,CAAC;CAEF,MAAM,SAAyB,EAAE;AAEjC,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,aAAa,4BACjB,MACA,aACA,SACA,eACD;AAED,MAAI,CAAC,WACH;EAGF,MAAM,EAAE,KAAK,WAAW;EAExB,MAAM,eAAe,WAAW,KAAK,GAAG,OAAO,QAAQ,SAAS,KAAK;AAErE,MAAI,CAAC,OAAO,QACV,QAAO,UAAoB,EAAE;AAG/B,SAAO,QAAkB,OAA4B;;AAKvD,QAAO;;AAOT,MAAM,sBACJ,QACA,eACA,mBACG;CACH,MAAM,UAAU;CAChB,MAAM,WAA2B,aAC/B,SACA,eACA,eACD;AA6BD,SA3BoB,QAAQ;EAC1B,KAAK;EACL,QAAQ;EACT,CAAC,CACkC,SAAS,iBAAiB,IAGzC,iBACf,OAAO,QAAQ,SAAS,CAAC,QAAQ,CAAC,YAAY,WAAW,eAAe,GACxE,OAAO,QAAQ,SAAS,EAGuB,SAClD,CAAC,QAAQ,gBACR,OAAO,QAAQ,WAAW,CAAC,KAAK,CAAC,KAAK,UAAU;AAK9C,SAAO;GACL,MALmB,WAAW,KAAK,GACjC,OACA,QAAQ,cAAc,QAAQ,SAAS,KAAK;GAI9C;GACA;GACD;GACD,CACL;;AAiFH,MAAa,YAAY,YAA2C;CAClE,MAAM,EAAE,UAAU,UAAU,QAAQ,WAAW;EAC7C,UAAU;EACV,UAAU;EACV,GAAG;EACJ;AAED,QAAO;EACL,MAAM;EAEN,kBAAkB,OAAO,EAAE,oBAAoB;GAC7C,MAAM,aAAc,UAClB,cAAc,qBAAqB;GAErC,MAAM,kBAAmC,mBACvC,QAAQ,QACR,eACA,WACD;GAED,IAAI,WAAmB,QAAQ,OAAO,EACpC,KAAK,WACN,CAAC;AAEF,OAAI,YAAY,CAAC,WAAW,SAAS,CACnC,YAAW,KAAK,cAAc,QAAQ,SAAS,SAAS;GAG1D,MAAM,eAA6B,EAAE;AAErC,QAAK,MAAM,EAAE,MAAM,SAAS,iBAAiB;IAC3C,MAAM,kBACJ,cAAc,OAAO,WAAW,mBAAmB;IACrD,IAAI,OAAoB,EAAE;AAC1B,QAAI;AACF,YAAO,gBAAgB,KAAe;YAChC;AAEN,YAAO,EAAE;;IAGX,MAAM,WAAW,SAAS,cAAc,QAAQ,SAAS,KAAK;IAE9D,MAAM,aAAyB;KAC7B;KACA,QAAQ;KACR,MAAM;KACN;KACA,SAAS,GAAG,IAAI,IAAI,SAAS,IAAI;KACvB;KACV,QACE,eAAe,cAAc,qBAAqB,gBAC9C,OACA;KACN,SAAS;KACT;KACA;KACD;AAED,iBAAa,KAAK,WAAW;;AAG/B,UAAO;;EAEV"}
1
+ {"version":3,"file":"loadJSON.mjs","names":[],"sources":["../../src/loadJSON.ts"],"sourcesContent":["import { isAbsolute, join, relative, resolve } from 'node:path';\nimport { loadExternalFile } from '@intlayer/config/file';\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 DictionaryFormat,\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';\nimport { extractKeyAndLocaleFromPath } from './syncJSON';\n\ntype JSONContent = Record<string, any>;\n\ntype FilePath = string;\n\ntype MessagesRecord = Record<Locale, Record<Dictionary['key'], FilePath>>;\n\nconst listMessages = async (\n source: FilePathPattern,\n configuration: IntlayerConfig,\n selectedLocale: Locale\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 selectedLocale\n );\n\n if (!extraction) {\n continue;\n }\n\n const { key, locale: extractedLocale } = extraction;\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 selectedLocale: Locale\n) => {\n const sourcePattern = source as FilePathPattern;\n const messages: MessagesRecord = await listMessages(\n sourcePattern,\n configuration,\n selectedLocale\n );\n\n const maskPattern = await parseFilePathPattern(sourcePattern, {\n key: '{{__KEY__}}',\n locale: '{{__LOCALE__}}',\n } as any as FilePathPatternContext);\n const hasLocaleInMask = maskPattern.includes('{{__LOCALE__}}');\n\n const entries = (\n hasLocaleInMask && selectedLocale\n ? Object.entries(messages).filter(([locale]) => locale === selectedLocale)\n : Object.entries(messages)\n ) as [Locale, Record<Dictionary['key'], FilePath>][];\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 LoadJSONPluginOptions = {\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 * loadJSON({\n * source: ({ key }) => `blog/${'**'}/${key}.i18n.json`,\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 * loadJSON({\n * source: ({ key }) => `blog/${'**'}/${key}.i18n.json`,\n * locale: Locales.ENGLISH,\n * })\n * ```\n */\n locale?: Locale;\n\n /**\n * Because Intlayer transform the JSON 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 * loadJSON({\n * source: ({ key }) => `./resources/${key}.json`,\n * location: 'plugin-i18next',\n * }),\n * loadJSON({\n * source: ({ key }) => `./messages/${key}.json`,\n * location: 'plugin-next-intl',\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 -1. (.content file priority is 0)\n *\n */\n priority?: number;\n\n /**\n * The format of the dictionary content.\n *\n * @example\n * ```ts\n * loadJSON({\n * format: 'icu',\n * })\n * ```\n */\n format?: DictionaryFormat;\n};\n\nexport const loadJSON = (options: LoadJSONPluginOptions): Plugin => {\n const { location, priority, locale, format } = {\n location: 'plugin',\n priority: 0,\n ...options,\n } as const;\n\n return {\n name: 'load-json',\n\n loadDictionaries: async ({ configuration }) => {\n const usedLocale = (locale ??\n configuration.internationalization.defaultLocale) as Locale;\n\n const dictionariesMap: DictionariesMap = await loadMessagePathMap(\n options.source,\n configuration,\n usedLocale\n );\n\n let filePath: string = await parseFilePathPattern(options.source, {\n key: '{{key}}',\n } as any as FilePathPatternContext);\n\n if (filePath && !isAbsolute(filePath)) {\n filePath = join(configuration.system.baseDir, filePath);\n }\n\n const dictionaries: Dictionary[] = [];\n\n for (const { path, key } of dictionariesMap) {\n // loadExternalFile swallows errors and returns undefined for missing files;\n // use ?? {} to guarantee a plain object regardless.\n const json: JSONContent =\n (await loadExternalFile(path, { logError: false })) ?? {};\n\n const filePath = relative(configuration.system.baseDir, path);\n\n const dictionary: Dictionary = {\n key,\n locale: usedLocale,\n fill: filePath,\n format,\n localId: `${key}::${location}::${filePath}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled:\n usedLocale !== configuration.internationalization.defaultLocale\n ? true\n : undefined,\n content: json,\n filePath,\n priority,\n };\n\n dictionaries.push(dictionary);\n }\n\n return dictionaries;\n },\n };\n};\n"],"mappings":";;;;;;;AAwBA,MAAM,eAAe,OACnB,QACA,eACA,mBAC4B;CAC5B,MAAM,EAAE,QAAQ,yBAAyB;CAEzC,MAAM,EAAE,YAAY;CACpB,MAAM,EAAE,YAAY;CAEpB,MAAM,SAAyB,EAAE;AAEjC,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,oBAAoB,MAAM,qBAAqB,QAAQ;GAC3D,KAAK;GACL;GACD,CAAkC;EAEnC,MAAM,oBAAoB,MAAM,qBAAqB,QAAQ;GAC3D,KAAK;GACL;GACD,CAAkC;AAEnC,MAAI,CAAC,qBAAqB,CAAC,kBACzB;EAOF,MAAM,QAAQ,MAAM,GAJU,kBAAkB,WAAW,KAAK,GAC5D,kBAAkB,MAAM,EAAE,GAC1B,mBAE0C,EAC5C,KAAK,SACN,CAAC;AAEF,OAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,aAAa,4BACjB,MACA,mBACA,SACA,eACD;AAED,OAAI,CAAC,WACH;GAGF,MAAM,EAAE,KAAK,QAAQ,oBAAoB;GAEzC,MAAM,eAAe,WAAW,KAAK,GAAG,OAAO,QAAQ,SAAS,KAAK;GAErE,MAAM,aAAa;AACnB,OAAI,CAAC,OAAO,YACV,QAAO,cAAc,EAAE;AAGzB,UAAO,YAAY,OAA4B;;;AAMnD,QAAO;;AAKT,MAAM,qBAAqB,OACzB,QACA,eACA,mBACG;CACH,MAAM,gBAAgB;CACtB,MAAM,WAA2B,MAAM,aACrC,eACA,eACA,eACD;AA6BD,UA3BoB,MAAM,qBAAqB,eAAe;EAC5D,KAAK;EACL,QAAQ;EACT,CAAkC,EACC,SAAS,iBAAiB,IAGzC,iBACf,OAAO,QAAQ,SAAS,CAAC,QAAQ,CAAC,YAAY,WAAW,eAAe,GACxE,OAAO,QAAQ,SAAS,EAGuB,SAClD,CAAC,QAAQ,gBACR,OAAO,QAAQ,WAAW,CAAC,KAAK,CAAC,KAAK,UAAU;AAK9C,SAAO;GACL,MALmB,WAAW,KAAK,GACjC,OACA,QAAQ,cAAc,OAAO,SAAS,KAAK;GAI7C;GACA;GACD;GACD,CACL;;AAiFH,MAAa,YAAY,YAA2C;CAClE,MAAM,EAAE,UAAU,UAAU,QAAQ,WAAW;EAC7C,UAAU;EACV,UAAU;EACV,GAAG;EACJ;AAED,QAAO;EACL,MAAM;EAEN,kBAAkB,OAAO,EAAE,oBAAoB;GAC7C,MAAM,aAAc,UAClB,cAAc,qBAAqB;GAErC,MAAM,kBAAmC,MAAM,mBAC7C,QAAQ,QACR,eACA,WACD;GAED,IAAI,WAAmB,MAAM,qBAAqB,QAAQ,QAAQ,EAChE,KAAK,WACN,CAAkC;AAEnC,OAAI,YAAY,CAAC,WAAW,SAAS,CACnC,YAAW,KAAK,cAAc,OAAO,SAAS,SAAS;GAGzD,MAAM,eAA6B,EAAE;AAErC,QAAK,MAAM,EAAE,MAAM,SAAS,iBAAiB;IAG3C,MAAM,OACH,MAAM,iBAAiB,MAAM,EAAE,UAAU,OAAO,CAAC,IAAK,EAAE;IAE3D,MAAM,WAAW,SAAS,cAAc,OAAO,SAAS,KAAK;IAE7D,MAAM,aAAyB;KAC7B;KACA,QAAQ;KACR,MAAM;KACN;KACA,SAAS,GAAG,IAAI,IAAI,SAAS,IAAI;KACvB;KACV,QACE,eAAe,cAAc,qBAAqB,gBAC9C,OACA;KACN,SAAS;KACT;KACA;KACD;AAED,iBAAa,KAAK,WAAW;;AAG/B,UAAO;;EAEV"}
@@ -1,5 +1,6 @@
1
1
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
2
- import { getProjectRequire } from "@intlayer/config";
2
+ import { loadExternalFile } from "@intlayer/config/file";
3
+ import { parseFilePathPattern } from "@intlayer/config/utils";
3
4
  import fg from "fast-glob";
4
5
  import { mkdir, writeFile } from "node:fs/promises";
5
6
 
@@ -8,13 +9,17 @@ const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8
9
  const extractKeyAndLocaleFromPath = (filePath, maskPattern, locales, defaultLocale) => {
9
10
  const keyPlaceholder = "{{__KEY__}}";
10
11
  const localePlaceholder = "{{__LOCALE__}}";
11
- const escapedMask = escapeRegex(maskPattern);
12
+ const normalize = (path) => path.startsWith("./") ? path.slice(2) : path;
13
+ const normalizedFilePath = normalize(filePath);
14
+ const normalizedMask = normalize(maskPattern);
12
15
  const localesAlternation = locales.join("|");
13
- let regexStr = `^${escapedMask}$`;
16
+ let regexStr = `^${escapeRegex(normalizedMask)}$`;
17
+ regexStr = regexStr.replace(/\\\*\\\*/g, ".*");
18
+ regexStr = regexStr.replace(/\\\*/g, "[^/]*");
14
19
  regexStr = regexStr.replace(escapeRegex(localePlaceholder), `(?<locale>${localesAlternation})`);
15
- if (maskPattern.includes(keyPlaceholder)) regexStr = regexStr.replace(escapeRegex(keyPlaceholder), "(?<key>.+)");
16
- const match = new RegExp(regexStr).exec(filePath);
17
- if (!match || !match.groups) return null;
20
+ if (normalizedMask.includes(keyPlaceholder)) regexStr = regexStr.replace(escapeRegex(keyPlaceholder), "(?<key>.+)");
21
+ const match = new RegExp(regexStr).exec(normalizedFilePath);
22
+ if (!match?.groups) return null;
18
23
  let locale = match.groups.locale;
19
24
  let key = match.groups.key ?? "index";
20
25
  if (typeof key === "undefined") key = "index";
@@ -24,35 +29,41 @@ const extractKeyAndLocaleFromPath = (filePath, maskPattern, locales, defaultLoca
24
29
  locale
25
30
  };
26
31
  };
27
- const listMessages = (builder, configuration) => {
28
- const { content, internationalization } = configuration;
29
- const baseDir = content.baseDir;
30
- const locales = internationalization.locales;
31
- const defaultLocale = internationalization.defaultLocale;
32
- const globPattern = builder({
33
- key: "**",
34
- locale: `{${locales.map((locale) => locale).join(",")}}`
35
- });
36
- const maskPattern = builder({
37
- key: "{{__KEY__}}",
38
- locale: "{{__LOCALE__}}"
39
- });
40
- const files = fg.sync(globPattern, { cwd: baseDir });
32
+ const listMessages = async (source, configuration) => {
33
+ const { system, internationalization } = configuration;
34
+ const { baseDir } = system;
35
+ const { locales } = internationalization;
41
36
  const result = {};
42
- for (const file of files) {
43
- const extraction = extractKeyAndLocaleFromPath(file, maskPattern, locales, defaultLocale);
44
- if (!extraction) continue;
45
- const { key, locale } = extraction;
46
- const expectedPath = builder({
47
- key,
37
+ for (const locale of locales) {
38
+ const globPatternLocale = await parseFilePathPattern(source, {
39
+ key: "**",
48
40
  locale
49
41
  });
50
- const absoluteFoundPath = isAbsolute(file) ? file : resolve(baseDir, file);
51
- if (absoluteFoundPath !== (isAbsolute(expectedPath) ? expectedPath : resolve(baseDir, expectedPath))) continue;
52
- if (!result[locale]) result[locale] = {};
53
- result[locale][key] = absoluteFoundPath;
42
+ const maskPatternLocale = await parseFilePathPattern(source, {
43
+ key: "{{__KEY__}}",
44
+ locale
45
+ });
46
+ if (!globPatternLocale || !maskPatternLocale) continue;
47
+ const files = await fg(globPatternLocale.startsWith("./") ? globPatternLocale.slice(2) : globPatternLocale, { cwd: baseDir });
48
+ for (const file of files) {
49
+ const extraction = extractKeyAndLocaleFromPath(file, maskPatternLocale, locales, locale);
50
+ if (!extraction) continue;
51
+ const { key, locale: extractedLocale } = extraction;
52
+ const expectedPath = await parseFilePathPattern(source, {
53
+ key,
54
+ locale: extractedLocale
55
+ });
56
+ const absoluteFoundPath = isAbsolute(file) ? file : resolve(baseDir, file);
57
+ if (absoluteFoundPath !== (isAbsolute(expectedPath) ? expectedPath : resolve(baseDir, expectedPath))) continue;
58
+ const usedLocale = extractedLocale;
59
+ if (!result[usedLocale]) result[usedLocale] = {};
60
+ result[usedLocale][key] = absoluteFoundPath;
61
+ }
54
62
  }
55
- const hasKeyInMask = maskPattern.includes("{{__KEY__}}");
63
+ const hasKeyInMask = (await parseFilePathPattern(source, {
64
+ key: "{{__KEY__}}",
65
+ locale: locales[0]
66
+ })).includes("{{__KEY__}}");
56
67
  const discoveredKeys = /* @__PURE__ */ new Set();
57
68
  for (const locale of Object.keys(result)) for (const key of Object.keys(result[locale] ?? {})) discoveredKeys.add(key);
58
69
  if (!hasKeyInMask) discoveredKeys.add("index");
@@ -60,7 +71,7 @@ const listMessages = (builder, configuration) => {
60
71
  for (const locale of locales) {
61
72
  if (!result[locale]) result[locale] = {};
62
73
  for (const key of keysToEnsure) if (!result[locale][key]) {
63
- const builtPath = builder({
74
+ const builtPath = await parseFilePathPattern(source, {
64
75
  key,
65
76
  locale
66
77
  });
@@ -70,19 +81,19 @@ const listMessages = (builder, configuration) => {
70
81
  }
71
82
  return result;
72
83
  };
73
- const loadMessagePathMap = (source, configuration) => {
74
- const messages = listMessages(source, configuration);
84
+ const loadMessagePathMap = async (source, configuration) => {
85
+ const messages = await listMessages(source, configuration);
75
86
  return Object.entries(messages).flatMap(([locale, keysRecord]) => Object.entries(keysRecord).map(([key, path]) => {
76
87
  return {
77
- path: isAbsolute(path) ? path : resolve(configuration.content.baseDir, path),
88
+ path: isAbsolute(path) ? path : resolve(configuration.system.baseDir, path),
78
89
  locale,
79
90
  key
80
91
  };
81
92
  }));
82
93
  };
83
- const syncJSON = (options) => {
94
+ const syncJSON = async (options) => {
84
95
  const { location, priority, format } = {
85
- location: `sync-json::${options.source({
96
+ location: `sync-json::${await parseFilePathPattern(options.source, {
86
97
  key: "{{key}}",
87
98
  locale: "{{locale}}"
88
99
  })}`,
@@ -92,22 +103,16 @@ const syncJSON = (options) => {
92
103
  return {
93
104
  name: "sync-json",
94
105
  loadDictionaries: async ({ configuration }) => {
95
- const dictionariesMap = loadMessagePathMap(options.source, configuration);
96
- let fill = options.source({
106
+ const dictionariesMap = await loadMessagePathMap(options.source, configuration);
107
+ let fill = await parseFilePathPattern(options.source, {
97
108
  key: "{{key}}",
98
109
  locale: "{{locale}}"
99
110
  });
100
- if (fill && !isAbsolute(fill)) fill = join(configuration.content.baseDir, fill);
111
+ if (fill && !isAbsolute(fill)) fill = join(configuration.system.baseDir, fill);
101
112
  const dictionaries = [];
102
113
  for (const { locale, path, key } of dictionariesMap) {
103
- const requireFunction = configuration.build?.require ?? getProjectRequire();
104
- let json = {};
105
- try {
106
- json = requireFunction(path);
107
- } catch {
108
- json = {};
109
- }
110
- const filePath = relative(configuration.content.baseDir, path);
114
+ const json = await loadExternalFile(path, { logError: false }) ?? {};
115
+ const filePath = relative(configuration.system.baseDir, path);
111
116
  const dictionary = {
112
117
  key,
113
118
  locale,
@@ -125,35 +130,30 @@ const syncJSON = (options) => {
125
130
  return dictionaries;
126
131
  },
127
132
  formatOutput: async ({ dictionary }) => {
128
- const { formatDictionaryOutput } = await import("@intlayer/chokidar");
133
+ const { formatDictionaryOutput } = await import("@intlayer/chokidar/build");
129
134
  if (!dictionary.filePath || !dictionary.locale) return dictionary;
130
- if (resolve(options.source({
135
+ if (resolve(await parseFilePathPattern(options.source, {
131
136
  key: dictionary.key,
132
137
  locale: dictionary.locale
133
138
  })) !== resolve(dictionary.filePath)) return dictionary;
134
- return formatDictionaryOutput({
135
- ...dictionary,
136
- format
137
- }).content;
139
+ return formatDictionaryOutput(dictionary, format).content;
138
140
  },
139
141
  afterBuild: async ({ dictionaries, configuration }) => {
140
- const { getPerLocaleDictionary } = await import("@intlayer/core");
141
- const { parallelize, formatDictionaryOutput } = await import("@intlayer/chokidar");
142
- const locales = configuration.internationalization.locales;
142
+ const { getPerLocaleDictionary } = await import("@intlayer/core/plugins");
143
+ const { parallelize } = await import("@intlayer/chokidar/utils");
144
+ const { formatDictionaryOutput } = await import("@intlayer/chokidar/build");
145
+ const { locales } = configuration.internationalization;
143
146
  await parallelize(Object.entries(dictionaries.mergedDictionaries).flatMap(([key, dictionary]) => locales.map((locale) => ({
144
147
  key,
145
148
  dictionary: dictionary.dictionary,
146
149
  locale
147
150
  }))), async ({ key, dictionary, locale }) => {
148
151
  if (dictionary.location !== location) return;
149
- const builderPath = options.source({
152
+ const builderPath = await parseFilePathPattern(options.source, {
150
153
  key,
151
154
  locale
152
155
  });
153
- const formattedOutput = formatDictionaryOutput({
154
- ...getPerLocaleDictionary(dictionary, locale),
155
- format
156
- });
156
+ const formattedOutput = formatDictionaryOutput(getPerLocaleDictionary(dictionary, locale), format);
157
157
  const content = JSON.parse(JSON.stringify(formattedOutput.content));
158
158
  if (typeof content === "undefined" || typeof content === "object" && Object.keys(content).length === 0) return;
159
159
  await mkdir(dirname(builderPath), { recursive: true });
@@ -1 +1 @@
1
- {"version":3,"file":"syncJSON.mjs","names":[],"sources":["../../src/syncJSON.ts"],"sourcesContent":["import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path';\nimport { getProjectRequire } from '@intlayer/config';\nimport type {\n Dictionary,\n DictionaryFormat,\n DictionaryLocation,\n IntlayerConfig,\n LocalDictionaryId,\n Locale,\n LocalesValues,\n Plugin,\n} from '@intlayer/types';\nimport fg from 'fast-glob';\n\ntype JSONContent = Record<string, any>;\n\ntype Builder = ({\n key,\n locale,\n}: {\n key: string;\n locale: LocalesValues | (string & {});\n}) => 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 const escapedMask = escapeRegex(maskPattern);\n const localesAlternation = locales.join('|');\n\n // Build a regex from the mask to capture locale (and key if present)\n let regexStr = `^${escapedMask}$`;\n\n regexStr = regexStr.replace(\n escapeRegex(localePlaceholder),\n `(?<locale>${localesAlternation})`\n );\n\n if (maskPattern.includes(keyPlaceholder)) {\n // FIX: Allow key to match multiple directory levels (e.g. \"nested/file\" or \"folder/index\")\n // Previous value: '(?<key>[^/]+)'\n regexStr = regexStr.replace(escapeRegex(keyPlaceholder), '(?<key>.+)');\n }\n\n const maskRegex = new RegExp(regexStr);\n const match = maskRegex.exec(filePath);\n\n if (!match || !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 {\n key,\n locale,\n };\n};\n\nconst listMessages = (\n builder: Builder,\n configuration: IntlayerConfig\n): MessagesRecord => {\n const { content, internationalization } = configuration;\n\n const baseDir = content.baseDir;\n const locales = internationalization.locales;\n const defaultLocale = internationalization.defaultLocale;\n\n const localePattern = `{${locales.map((locale) => locale).join(',')}}`;\n\n // FIX: Use '**' instead of '*' to allow recursive globbing for the key\n const globPattern = builder({ key: '**', locale: localePattern });\n const maskPattern = builder({ key: '{{__KEY__}}', locale: '{{__LOCALE__}}' });\n\n const files = fg.sync(globPattern, {\n cwd: baseDir,\n });\n\n const result: MessagesRecord = {} as MessagesRecord;\n\n for (const file of files) {\n const extraction = extractKeyAndLocaleFromPath(\n file,\n maskPattern,\n locales,\n defaultLocale\n );\n\n // If extraction failed (regex mismatch), skip this file\n if (!extraction) {\n continue;\n }\n\n const { key, locale } = extraction;\n\n // Generate what the path SHOULD be for this key/locale using the current builder\n const expectedPath = builder({ key, locale });\n\n // Resolve both to absolute paths to ensure safe comparison\n const absoluteFoundPath = isAbsolute(file) ? file : 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 if (!result[locale as Locale]) {\n result[locale as Locale] = {};\n }\n\n result[locale as Locale][key as Dictionary['key']] = absoluteFoundPath;\n }\n\n // Ensure all declared locales are present even if the file doesn't exist yet\n const hasKeyInMask = maskPattern.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 = builder({ key, locale });\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 FilePath = string;\n\ntype DictionariesMap = { path: string; locale: Locale; key: string }[];\n\nconst loadMessagePathMap = (\n source: MessagesRecord | Builder,\n configuration: IntlayerConfig\n) => {\n const messages: MessagesRecord = listMessages(\n source as Builder,\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.content.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 SyncJSONPluginOptions = {\n /**\n * The source of the plugin.\n * Is a function to build the source from the key and locale.\n *\n * ```ts\n * syncJSON({\n * source: ({ key, locale }) => `./messages/${locale}/${key}.json`\n * })\n * ```\n */\n source: Builder;\n\n /**\n * Because Intlayer transform the JSON 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 * syncJSON({\n * source: ({ key, locale }) => `./resources/${locale}/${key}.json`,\n * location: 'plugin-i18next',\n * }),\n * syncJSON({\n * source: ({ key, locale }) => `./messages/${locale}/${key}.json`,\n * location: 'plugin-next-intl',\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 -1. (.content file priority is 0)\n *\n */\n priority?: number;\n\n /**\n * The format of the dictionaries created by the plugin.\n *\n * Default: 'intlayer'\n *\n * The format of the dictionaries created by the plugin.\n */\n format?: DictionaryFormat;\n};\n\nexport const syncJSON = (options: SyncJSONPluginOptions): Plugin => {\n // Generate a unique default location based on the source pattern.\n // This ensures that if you have multiple plugins, they don't share the same 'plugin' ID.\n const patternMarker = options.source({\n key: '{{key}}',\n locale: '{{locale}}',\n });\n const defaultLocation = `sync-json::${patternMarker}`;\n\n const { location, priority, format } = {\n location: defaultLocation,\n priority: 0,\n ...options,\n };\n\n return {\n name: 'sync-json',\n\n loadDictionaries: async ({ configuration }) => {\n const dictionariesMap: DictionariesMap = loadMessagePathMap(\n options.source,\n configuration\n );\n\n let fill: string = options.source({\n key: '{{key}}',\n locale: '{{locale}}',\n });\n\n if (fill && !isAbsolute(fill)) {\n fill = join(configuration.content.baseDir, fill);\n }\n\n const dictionaries: Dictionary[] = [];\n\n for (const { locale, path, key } of dictionariesMap) {\n const requireFunction =\n configuration.build?.require ?? getProjectRequire();\n let json: JSONContent = {};\n try {\n json = requireFunction(path as string);\n } catch {\n json = {};\n }\n\n const filePath = relative(configuration.content.baseDir, path);\n\n const dictionary: Dictionary = {\n key,\n locale,\n fill,\n format,\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: json,\n filePath,\n priority,\n };\n\n dictionaries.push(dictionary);\n }\n\n return dictionaries;\n },\n\n formatOutput: async ({ dictionary }) => {\n // Lazy import intlayer modules to avoid circular dependencies\n const { formatDictionaryOutput } = await import('@intlayer/chokidar');\n\n if (!dictionary.filePath || !dictionary.locale) return dictionary;\n\n const builderPath = options.source({\n key: dictionary.key,\n locale: dictionary.locale,\n });\n\n // Verification to ensure we are formatting the correct file\n if (resolve(builderPath) !== resolve(dictionary.filePath)) {\n return dictionary;\n }\n\n const dictionaryWithFormat = {\n ...dictionary,\n format,\n };\n\n const formattedOutput = formatDictionaryOutput(\n dictionaryWithFormat as Dictionary\n );\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');\n const { parallelize, formatDictionaryOutput } = await import(\n '@intlayer/chokidar'\n );\n\n const locales = configuration.internationalization.locales;\n\n type RecordList = {\n key: string;\n dictionary: Dictionary;\n locale: Locale;\n };\n\n // We get all dictionaries, but we need to filter them\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 = options.source({\n key,\n locale,\n });\n\n const localizedDictionary = getPerLocaleDictionary(dictionary, locale);\n\n const dictionaryWithFormat = {\n ...localizedDictionary,\n format,\n };\n\n const formattedOutput = formatDictionaryOutput(dictionaryWithFormat);\n\n const content = JSON.parse(JSON.stringify(formattedOutput.content));\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 stringContent = JSON.stringify(content, null, 2);\n\n await writeFile(builderPath, `${stringContent}\\n`, 'utf-8');\n });\n },\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"syncJSON.mjs","names":[],"sources":["../../src/syncJSON.ts"],"sourcesContent":["import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path';\nimport { loadExternalFile } from '@intlayer/config/file';\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 DictionaryFormat,\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 JSONContent = Record<string, any>;\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 {\n key,\n locale,\n };\n};\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\ntype SyncJSONPluginOptions = {\n /**\n * The source of the plugin.\n * Is a function to build the source from the key and locale.\n *\n * ```ts\n * syncJSON({\n * source: ({ key, locale }) => `./messages/${locale}/${key}.json`\n * })\n * ```\n */\n source: FilePathPattern;\n\n /**\n * Because Intlayer transform the JSON 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 * syncJSON({\n * source: ({ key, locale }) => `./resources/${locale}/${key}.json`,\n * location: 'plugin-i18next',\n * }),\n * syncJSON({\n * source: ({ key, locale }) => `./messages/${locale}/${key}.json`,\n * location: 'plugin-next-intl',\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 -1. (.content file priority is 0)\n *\n */\n priority?: number;\n\n /**\n * The format of the dictionaries created by the plugin.\n *\n * Default: 'intlayer'\n *\n * The format of the dictionaries created by the plugin.\n */\n format?: DictionaryFormat;\n};\n\nexport const syncJSON = async (\n options: SyncJSONPluginOptions\n): Promise<Plugin> => {\n // Generate a unique default location based on the source pattern.\n // This ensures that if you have multiple plugins, they don't share the same 'plugin' ID.\n const patternMarker = await parseFilePathPattern(options.source, {\n key: '{{key}}',\n locale: '{{locale}}',\n } as any as FilePathPatternContext);\n const defaultLocation = `sync-json::${patternMarker}`;\n\n const { location, priority, format } = {\n location: defaultLocation,\n priority: 0,\n ...options,\n };\n\n return {\n name: 'sync-json',\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 && !isAbsolute(fill)) {\n fill = join(configuration.system.baseDir, fill);\n }\n\n const dictionaries: Dictionary[] = [];\n\n for (const { locale, path, key } of dictionariesMap) {\n // loadExternalFile swallows errors and returns undefined for missing files;\n // the try/catch does not help here — use ?? {} to guarantee a plain object.\n const json: JSONContent =\n (await loadExternalFile(path, { logError: false })) ?? {};\n\n const filePath = relative(configuration.system.baseDir, path);\n\n const dictionary: Dictionary = {\n key,\n locale,\n fill,\n format,\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: json,\n filePath,\n priority,\n };\n\n dictionaries.push(dictionary);\n }\n\n return dictionaries;\n },\n\n formatOutput: async ({ dictionary }) => {\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 (resolve(builderPath) !== resolve(dictionary.filePath)) {\n return dictionary;\n }\n\n const formattedOutput = formatDictionaryOutput(dictionary, format);\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 // We get all dictionaries, but we need to filter them\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 format\n );\n\n const content = JSON.parse(JSON.stringify(formattedOutput.content));\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 stringContent = JSON.stringify(content, null, 2);\n\n await writeFile(builderPath, `${stringContent}\\n`, 'utf-8');\n });\n },\n };\n};\n"],"mappings":";;;;;;;AAyBA,MAAM,eAAe,QAAgB,IAAI,QAAQ,uBAAuB,OAAO;AAE/E,MAAa,+BACX,UACA,aACA,SACA,kBAC2C;CAC3C,MAAM,iBAAiB;CACvB,MAAM,oBAAoB;CAG1B,MAAM,aAAa,SACjB,KAAK,WAAW,KAAK,GAAG,KAAK,MAAM,EAAE,GAAG;CAE1C,MAAM,qBAAqB,UAAU,SAAS;CAC9C,MAAM,iBAAiB,UAAU,YAAY;CAE7C,MAAM,qBAAqB,QAAQ,KAAK,IAAI;CAI5C,IAAI,WAAW,IAAI,YAAY,eAAe,CAAC;AAC/C,YAAW,SAAS,QAAQ,aAAa,KAAK;AAC9C,YAAW,SAAS,QAAQ,SAAS,QAAQ;AAE7C,YAAW,SAAS,QAClB,YAAY,kBAAkB,EAC9B,aAAa,mBAAmB,GACjC;AAED,KAAI,eAAe,SAAS,eAAe,CACzC,YAAW,SAAS,QAAQ,YAAY,eAAe,EAAE,aAAa;CAIxE,MAAM,QADY,IAAI,OAAO,SAAS,CACd,KAAK,mBAAmB;AAEhD,KAAI,CAAC,OAAO,OACV,QAAO;CAGT,IAAI,SAAS,MAAM,OAAO;CAC1B,IAAI,MAAO,MAAM,OAAO,OAA8B;AAEtD,KAAI,OAAO,QAAQ,YACjB,OAAM;AAGR,KAAI,OAAO,WAAW,YACpB,UAAS;AAGX,QAAO;EACL;EACA;EACD;;AAGH,MAAM,eAAe,OACnB,QACA,kBAC4B;CAC5B,MAAM,EAAE,QAAQ,yBAAyB;CAEzC,MAAM,EAAE,YAAY;CACpB,MAAM,EAAE,YAAY;CAEpB,MAAM,SAAyB,EAAE;AAEjC,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,oBAAoB,MAAM,qBAAqB,QAAQ;GAC3D,KAAK;GACL;GACD,CAAkC;EAEnC,MAAM,oBAAoB,MAAM,qBAAqB,QAAQ;GAC3D,KAAK;GACL;GACD,CAAkC;AAEnC,MAAI,CAAC,qBAAqB,CAAC,kBACzB;EAOF,MAAM,QAAQ,MAAM,GAJU,kBAAkB,WAAW,KAAK,GAC5D,kBAAkB,MAAM,EAAE,GAC1B,mBAE0C,EAC5C,KAAK,SACN,CAAC;AAEF,OAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,aAAa,4BACjB,MACA,mBACA,SACA,OACD;AAED,OAAI,CAAC,WACH;GAGF,MAAM,EAAE,KAAK,QAAQ,oBAAoB;GAGzC,MAAM,eAAe,MAAM,qBAAqB,QAAQ;IACtD;IACA,QAAQ;IACT,CAAkC;GAGnC,MAAM,oBAAoB,WAAW,KAAK,GACtC,OACA,QAAQ,SAAS,KAAK;AAM1B,OAAI,uBALyB,WAAW,aAAa,GACjD,eACA,QAAQ,SAAS,aAAa,EAIhC;GAGF,MAAM,aAAa;AACnB,OAAI,CAAC,OAAO,YACV,QAAO,cAAc,EAAE;AAGzB,UAAO,YAAY,OAA4B;;;CAUnD,MAAM,gBALc,MAAM,qBAAqB,QAAQ;EACrD,KAAK;EACL,QAAQ,QAAQ;EACjB,CAAkC,EAEF,SAAS,cAAc;CACxD,MAAM,iCAAiB,IAAI,KAAa;AAExC,MAAK,MAAM,UAAU,OAAO,KAAK,OAAO,CACtC,MAAK,MAAM,OAAO,OAAO,KAAK,OAAO,WAAqB,EAAE,CAAC,CAC3D,gBAAe,IAAI,IAAI;AAI3B,KAAI,CAAC,aACH,gBAAe,IAAI,QAAQ;CAG7B,MAAM,eACJ,eAAe,OAAO,IAAI,MAAM,KAAK,eAAe,GAAG,EAAE;AAE3D,MAAK,MAAM,UAAU,SAAS;AAC5B,MAAI,CAAC,OAAO,QACV,QAAO,UAAU,EAAE;AAGrB,OAAK,MAAM,OAAO,aAChB,KAAI,CAAC,OAAO,QAAQ,MAA2B;GAC7C,MAAM,YAAY,MAAM,qBAAqB,QAAQ;IACnD;IACA;IACD,CAAkC;GACnC,MAAM,oBAAoB,WAAW,UAAU,GAC3C,YACA,QAAQ,SAAS,UAAU;AAE/B,UAAO,QAAQ,OAA4B;;;AAKjD,QAAO;;AAKT,MAAM,qBAAqB,OACzB,QACA,kBACG;CACH,MAAM,WAA2B,MAAM,aACrC,QACA,cACD;AAiBD,QAf6C,OAAO,QAAQ,SAAS,CAAC,SACnE,CAAC,QAAQ,gBACR,OAAO,QAAQ,WAAW,CAAC,KAAK,CAAC,KAAK,UAAU;AAK9C,SAAO;GACL,MALmB,WAAW,KAAK,GACjC,OACA,QAAQ,cAAc,OAAO,SAAS,KAAK;GAI7C;GACA;GACD;GACD,CACL;;AA8DH,MAAa,WAAW,OACtB,YACoB;CASpB,MAAM,EAAE,UAAU,UAAU,WAAW;EACrC,UAHsB,cAJF,MAAM,qBAAqB,QAAQ,QAAQ;GAC/D,KAAK;GACL,QAAQ;GACT,CAAkC;EAKjC,UAAU;EACV,GAAG;EACJ;AAED,QAAO;EACL,MAAM;EAEN,kBAAkB,OAAO,EAAE,oBAAoB;GAC7C,MAAM,kBAAmC,MAAM,mBAC7C,QAAQ,QACR,cACD;GAED,IAAI,OAAe,MAAM,qBAAqB,QAAQ,QAAQ;IAC5D,KAAK;IACL,QAAQ;IACT,CAAkC;AAEnC,OAAI,QAAQ,CAAC,WAAW,KAAK,CAC3B,QAAO,KAAK,cAAc,OAAO,SAAS,KAAK;GAGjD,MAAM,eAA6B,EAAE;AAErC,QAAK,MAAM,EAAE,QAAQ,MAAM,SAAS,iBAAiB;IAGnD,MAAM,OACH,MAAM,iBAAiB,MAAM,EAAE,UAAU,OAAO,CAAC,IAAK,EAAE;IAE3D,MAAM,WAAW,SAAS,cAAc,OAAO,SAAS,KAAK;IAE7D,MAAM,aAAyB;KAC7B;KACA;KACA;KACA;KACA,SAAS,GAAG,IAAI,IAAI,SAAS,IAAI;KACvB;KACV,QACE,WAAW,cAAc,qBAAqB,gBAC1C,OACA;KACN,SAAS;KACT;KACA;KACD;AAED,iBAAa,KAAK,WAAW;;AAG/B,UAAO;;EAGT,cAAc,OAAO,EAAE,iBAAiB;GAEtC,MAAM,EAAE,2BAA2B,MAAM,OACvC;AAGF,OAAI,CAAC,WAAW,YAAY,CAAC,WAAW,OAAQ,QAAO;AAQvD,OAAI,QANgB,MAAM,qBAAqB,QAAQ,QAAQ;IAC7D,KAAK,WAAW;IAChB,QAAQ,WAAW;IACpB,CAA2B,CAGJ,KAAK,QAAQ,WAAW,SAAS,CACvD,QAAO;AAKT,UAFwB,uBAAuB,YAAY,OAAO,CAE3C;;EAGzB,YAAY,OAAO,EAAE,cAAc,oBAAoB;GAErD,MAAM,EAAE,2BAA2B,MAAM,OAAO;GAChD,MAAM,EAAE,gBAAgB,MAAM,OAAO;GACrC,MAAM,EAAE,2BAA2B,MAAM,OACvC;GAGF,MAAM,EAAE,YAAY,cAAc;AAmBlC,SAAM,YAV2B,OAAO,QACtC,aAAa,mBACd,CAAC,SAAS,CAAC,KAAK,gBACf,QAAQ,KAAK,YAAY;IACvB;IACA,YAAY,WAAW;IACvB;IACD,EAAE,CACJ,EAE6B,OAAO,EAAE,KAAK,YAAY,aAAa;AAEnE,QAAI,WAAW,aAAa,SAC1B;IAGF,MAAM,cAAc,MAAM,qBAAqB,QAAQ,QAAQ;KAC7D;KACA;KACD,CAAkC;IAInC,MAAM,kBAAkB,uBAFI,uBAAuB,YAAY,OAAO,EAIpE,OACD;IAED,MAAM,UAAU,KAAK,MAAM,KAAK,UAAU,gBAAgB,QAAQ,CAAC;AAEnE,QACE,OAAO,YAAY,eAClB,OAAO,YAAY,YAClB,OAAO,KAAK,QAAmC,CAAC,WAAW,EAE7D;AAGF,UAAM,MAAM,QAAQ,YAAY,EAAE,EAAE,WAAW,MAAM,CAAC;AAItD,UAAM,UAAU,aAAa,GAFP,KAAK,UAAU,SAAS,MAAM,EAAE,CAER,KAAK,QAAQ;KAC3D;;EAEL"}
@@ -1,13 +1,9 @@
1
- import { DictionaryFormat, Locale, Plugin } from "@intlayer/types";
1
+ import { Locale } from "@intlayer/types/allLocales";
2
+ import { DictionaryFormat } from "@intlayer/types/dictionary";
3
+ import { FilePathPattern } from "@intlayer/types/filePathPattern";
4
+ import { Plugin } from "@intlayer/types/plugin";
2
5
 
3
6
  //#region src/loadJSON.d.ts
4
- type Builder = ({
5
- key,
6
- locale
7
- }: {
8
- key: string;
9
- locale?: Locale | (string & {});
10
- }) => string;
11
7
  type LoadJSONPluginOptions = {
12
8
  /**
13
9
  * The source of the plugin.
@@ -20,7 +16,7 @@ type LoadJSONPluginOptions = {
20
16
  * })
21
17
  * ```
22
18
  */
23
- source: Builder;
19
+ source: FilePathPattern;
24
20
  /**
25
21
  * Locale
26
22
  *
@@ -1 +1 @@
1
- {"version":3,"file":"loadJSON.d.ts","names":[],"sources":["../../src/loadJSON.ts"],"mappings":";;;KAeK,OAAA;EACH,GAAA;EACA;AAAA;EAEA,GAAA;EACA,MAAA,GAAS,MAAA;AAAA;AAAA,KAoGN,qBAAA;EAxGH;;;;;;;;;;;EAoHA,MAAA,EAAQ,OAAA;EAhHO;;AAAA;;;;;;;;;;;EA+Hf,MAAA,GAAS,MAAA;EAwBT;;;;;;AAyBF;;;;;;;;;;;;;;;;EAzBE,QAAA;;;;;;;;;EAUA,QAAA;;;;;;;;;;;EAYA,MAAA,GAAS,gBAAA;AAAA;AAAA,cAGE,QAAA,GAAY,OAAA,EAAS,qBAAA,KAAwB,MAAA"}
1
+ {"version":3,"file":"loadJSON.d.ts","names":[],"sources":["../../src/loadJSON.ts"],"mappings":";;;;;;KAqIK,qBAAA;;AAvHgD;;;;;;;;;;EAmInD,MAAA,EAAQ,eAAA;EAeC;;;;;;;AAiDX;;;;;;EAjDE,MAAA,GAAS,MAAA;EA6GV;;;;;;;;;;;;;;;;;;;;;;EArFC,QAAA;;;;;;;;;EAUA,QAAA;;;;;;;;;;;EAYA,MAAA,GAAS,gBAAA;AAAA;AAAA,cAGE,QAAA,GAAY,OAAA,EAAS,qBAAA,KAAwB,MAAA"}
@@ -1,13 +1,9 @@
1
- import { DictionaryFormat, DictionaryLocation, Locale, LocalesValues, Plugin } from "@intlayer/types";
1
+ import { Locale } from "@intlayer/types/allLocales";
2
+ import { DictionaryFormat, DictionaryLocation } from "@intlayer/types/dictionary";
3
+ import { FilePathPattern } from "@intlayer/types/filePathPattern";
4
+ import { Plugin } from "@intlayer/types/plugin";
2
5
 
3
6
  //#region src/syncJSON.d.ts
4
- type Builder = ({
5
- key,
6
- locale
7
- }: {
8
- key: string;
9
- locale: LocalesValues | (string & {});
10
- }) => string;
11
7
  declare const extractKeyAndLocaleFromPath: (filePath: string, maskPattern: string, locales: Locale[], defaultLocale: Locale) => {
12
8
  key: string;
13
9
  locale: Locale;
@@ -23,7 +19,7 @@ type SyncJSONPluginOptions = {
23
19
  * })
24
20
  * ```
25
21
  */
26
- source: Builder;
22
+ source: FilePathPattern;
27
23
  /**
28
24
  * Because Intlayer transform the JSON files into Dictionary, we need to identify the plugin in the dictionary.
29
25
  * Used to identify the plugin in the dictionary.
@@ -65,7 +61,7 @@ type SyncJSONPluginOptions = {
65
61
  */
66
62
  format?: DictionaryFormat;
67
63
  };
68
- declare const syncJSON: (options: SyncJSONPluginOptions) => Plugin;
64
+ declare const syncJSON: (options: SyncJSONPluginOptions) => Promise<Plugin>;
69
65
  //#endregion
70
66
  export { extractKeyAndLocaleFromPath, syncJSON };
71
67
  //# sourceMappingURL=syncJSON.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"syncJSON.d.ts","names":[],"sources":["../../src/syncJSON.ts"],"mappings":";;;KAiBK,OAAA;EACH,GAAA;EACA;AAAA;EAEA,GAAA;EACA,MAAA,EAAQ,aAAA;AAAA;AAAA,cAOG,2BAAA,GACX,QAAA,UACA,WAAA,UACA,OAAA,EAAS,MAAA,IACT,aAAA,EAAe,MAAA;EACZ,GAAA;EAAa,MAAA,EAAQ,MAAA;AAAA;AAAA,KA2KrB,qBAAA;EAvLkB;;;;;;;;;;EAkMrB,MAAA,EAAQ,OAAA;EA3IT;;;;;;;;;;;;;;;;;;AAAC;;;;EAmKA,QAAA,GAAW,kBAAA;EAmBF;;;;;;;;EATT,QAAA;EASS;;;AAGX;;;;EAHE,MAAA,GAAS,gBAAA;AAAA;AAAA,cAGE,QAAA,GAAY,OAAA,EAAS,qBAAA,KAAwB,MAAA"}
1
+ {"version":3,"file":"syncJSON.d.ts","names":[],"sources":["../../src/syncJSON.ts"],"mappings":";;;;;;cA2Ba,2BAAA,GACX,QAAA,UACA,WAAA,UACA,OAAA,EAAS,MAAA,IACT,aAAA,EAAe,MAAA;EACZ,GAAA;EAAa,MAAA,EAAQ,MAAA;AAAA;AAAA,KA0MrB,qBAAA;EA5MM;;;;;;;;;;EAuNT,MAAA,EAAQ,eAAA;EArNL;;;;;AAkDH;;;;;;;;;;;;;;;;;EA2LA,QAAA,GAAW,kBAAA;EA+KZ;;;;;;;;EArKC,QAAA;EAcC;;;;;;;EALD,MAAA,GAAS,gBAAA;AAAA;AAAA,cAGE,QAAA,GACX,OAAA,EAAS,qBAAA,KACR,OAAA,CAAQ,MAAA"}