UNPKG

@lucidcms/core

Version:

The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.

1 lines 5.45 kB
{"version":3,"file":"load-project-translations.mjs","names":[],"sources":["../../../src/libs/i18n/load-project-translations.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport z from \"zod\";\nimport { LucidError } from \"../../utils/errors/index.js\";\nimport {\n\tpathExists,\n\tresolveSourcePath,\n} from \"../../utils/helpers/resolve-source-path.js\";\nimport {\n\tmergeTranslationBundles,\n\tnormalizeTranslationBundles,\n} from \"./translations.js\";\nimport type { TranslationBundles, TranslationSource } from \"./types.js\";\n\nconst translationFileSchema = z.record(z.string(), z.string());\nconst translationFileNameRegex = /^(.+)\\.(admin|server)\\.json$/;\n\nconst readTranslationFile = async (\n\tfilePath: string,\n): Promise<TranslationBundles> => {\n\tconst entryName = path.basename(filePath);\n\tconst match = entryName.match(translationFileNameRegex);\n\tif (!match) {\n\t\tthrow new LucidError({\n\t\t\tmessage: `Invalid translation file name \"${entryName}\". Expected \"<locale>.admin.json\" or \"<locale>.server.json\".`,\n\t\t\tdata: { filePath },\n\t\t});\n\t}\n\n\tconst [, locale, scope] = match as [string, string, \"admin\" | \"server\"];\n\tlet parsed: Record<string, string>;\n\ttry {\n\t\tparsed = translationFileSchema.parse(\n\t\t\tJSON.parse(await fs.readFile(filePath, \"utf-8\")),\n\t\t);\n\t} catch (error) {\n\t\tthrow new LucidError({\n\t\t\tmessage: `Invalid translation file \"${entryName}\". Expected a flat JSON object of string keys and string values.`,\n\t\t\tdata: {\n\t\t\t\tfilePath,\n\t\t\t\terror: error instanceof Error ? error.message : error,\n\t\t\t},\n\t\t});\n\t}\n\n\treturn {\n\t\t[locale]: {\n\t\t\tadmin: scope === \"admin\" ? parsed : {},\n\t\t\tserver: scope === \"server\" ? parsed : {},\n\t\t},\n\t};\n};\n\nconst loadTranslationSource = async (\n\tsourcePath: string,\n\toptions?: {\n\t\toptional?: boolean;\n\t},\n): Promise<TranslationBundles> => {\n\tif (!(await pathExists(sourcePath))) {\n\t\tif (options?.optional) return {};\n\t\tthrow new LucidError({\n\t\t\tmessage: `Translation source \"${sourcePath}\" does not exist.`,\n\t\t});\n\t}\n\n\tconst stats = await fs.stat(sourcePath);\n\tif (stats.isFile()) {\n\t\treturn readTranslationFile(sourcePath);\n\t}\n\tif (!stats.isDirectory()) {\n\t\tthrow new LucidError({\n\t\t\tmessage: `Translation source \"${sourcePath}\" must be a file or directory.`,\n\t\t});\n\t}\n\n\tconst entries = await fs.readdir(sourcePath, { withFileTypes: true });\n\tconst bundles = await Promise.all(\n\t\tentries\n\t\t\t.filter((entry) => entry.isFile() && entry.name.endsWith(\".json\"))\n\t\t\t.map((entry) => readTranslationFile(path.join(sourcePath, entry.name))),\n\t);\n\n\treturn mergeTranslationBundles(...bundles);\n};\n\n/**\n * Loads configured translation files and the optional project `translations/`\n * directory, then returns one merged bundle in runtime precedence order. Core\n * translations are added by the translation store, not this loader.\n */\nexport const loadTranslationSources = async (props: {\n\tprojectRoot?: string;\n\tsources?: TranslationSource[];\n\tincludeProjectDirectory?: boolean;\n\tprojectDirectory?: string;\n}): Promise<TranslationBundles> => {\n\tconst configuredBundles = await Promise.all(\n\t\t(props.sources ?? []).map(async (source) =>\n\t\t\tloadTranslationSource(\n\t\t\t\tawait resolveSourcePath(source, {\n\t\t\t\t\tprojectRoot: props.projectRoot,\n\t\t\t\t\tlabel: \"Translation source\",\n\t\t\t\t}),\n\t\t\t),\n\t\t),\n\t);\n\n\tconst projectBundles =\n\t\tprops.includeProjectDirectory === false || !props.projectRoot\n\t\t\t? {}\n\t\t\t: await loadTranslationSource(\n\t\t\t\t\tpath.join(\n\t\t\t\t\t\tprops.projectRoot,\n\t\t\t\t\t\tprops.projectDirectory ?? \"translations\",\n\t\t\t\t\t),\n\t\t\t\t\t{ optional: true },\n\t\t\t\t);\n\n\treturn normalizeTranslationBundles(\n\t\tmergeTranslationBundles(...configuredBundles, projectBundles),\n\t);\n};\n"],"mappings":"4TAcA,MAAM,EAAwB,EAAE,OAAO,EAAE,OAAO,EAAG,EAAE,OAAO,CAAC,EACvD,EAA2B,+BAE3B,EAAsB,KAC3B,IACiC,CACjC,IAAM,EAAY,EAAK,SAAS,CAAQ,EAClC,EAAQ,EAAU,MAAM,CAAwB,EACtD,GAAI,CAAC,EACJ,MAAM,IAAI,EAAW,CACpB,QAAS,kCAAkC,EAAU,8DACrD,KAAM,CAAE,UAAS,CAClB,CAAC,EAGF,GAAM,EAAG,EAAQ,GAAS,EACtB,EACJ,GAAI,CACH,EAAS,EAAsB,MAC9B,KAAK,MAAM,MAAM,EAAG,SAAS,EAAU,OAAO,CAAC,CAChD,CACD,OAAS,EAAO,CACf,MAAM,IAAI,EAAW,CACpB,QAAS,6BAA6B,EAAU,kEAChD,KAAM,CACL,WACA,MAAO,aAAiB,MAAQ,EAAM,QAAU,CACjD,CACD,CAAC,CACF,CAEA,MAAO,EACL,GAAS,CACT,MAAO,IAAU,QAAU,EAAS,CAAC,EACrC,OAAQ,IAAU,SAAW,EAAS,CAAC,CACxC,CACD,CACD,EAEM,EAAwB,MAC7B,EACA,IAGiC,CACjC,GAAI,CAAE,MAAM,EAAW,CAAU,EAAI,CACpC,GAAI,GAAS,SAAU,MAAO,CAAC,EAC/B,MAAM,IAAI,EAAW,CACpB,QAAS,uBAAuB,EAAW,kBAC5C,CAAC,CACF,CAEA,IAAM,EAAQ,MAAM,EAAG,KAAK,CAAU,EACtC,GAAI,EAAM,OAAO,EAChB,OAAO,EAAoB,CAAU,EAEtC,GAAI,CAAC,EAAM,YAAY,EACtB,MAAM,IAAI,EAAW,CACpB,QAAS,uBAAuB,EAAW,+BAC5C,CAAC,EAGF,IAAM,EAAU,MAAM,EAAG,QAAQ,EAAY,CAAE,cAAe,EAAK,CAAC,EAOpE,OAAO,EAAwB,GAAG,MANZ,QAAQ,IAC7B,EACE,OAAQ,GAAU,EAAM,OAAO,GAAK,EAAM,KAAK,SAAS,OAAO,CAAC,CAAC,CACjE,IAAK,GAAU,EAAoB,EAAK,KAAK,EAAY,EAAM,IAAI,CAAC,CAAC,CACxE,CAEyC,CAC1C,EAOa,EAAyB,KAAO,IAKV,CAClC,IAAM,EAAoB,MAAM,QAAQ,KACtC,EAAM,SAAW,CAAC,EAAA,CAAG,IAAI,KAAO,IAChC,EACC,MAAM,EAAkB,EAAQ,CAC/B,YAAa,EAAM,YACnB,MAAO,oBACR,CAAC,CACF,CACD,CACD,EAEM,EACL,EAAM,0BAA4B,IAAS,CAAC,EAAM,YAC/C,CAAC,EACD,MAAM,EACN,EAAK,KACJ,EAAM,YACN,EAAM,kBAAoB,cAC3B,EACA,CAAE,SAAU,EAAK,CAClB,EAEH,OAAO,EACN,EAAwB,GAAG,EAAmB,CAAc,CAC7D,CACD"}