UNPKG

@lucidcms/core

Version:

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

1 lines 6.35 kB
{"version":3,"file":"translate.mjs","names":[],"sources":["../../../src/libs/i18n/translate.ts"],"sourcesContent":["import type { Config } from \"../../types/config.js\";\nimport { createTranslationStore } from \"./store.js\";\nimport type {\n\tTranslatableCopy,\n\tTranslateCopy,\n\tTranslateCopyOptions,\n\tTranslationStore,\n\tTranslator,\n} from \"./types.js\";\n\nconst coreTranslationStore = createTranslationStore({\n\tdefaultLocale: \"en\",\n\tbundles: {},\n});\n\n/**\n * Resolves the interface locale for server/admin copy.\n *\n * Explicit locale values win first, then the browser `Accept-Language` header,\n * then the configured interface default locale.\n *\n * @example\n * ```ts\n * const locale = resolveInterfaceLocale({\n * config,\n * acceptLanguage: request.headers.get(\"accept-language\"),\n * });\n * ```\n */\nexport const resolveInterfaceLocale = (props: {\n\tconfig: Pick<Config, \"i18n\">;\n\tlocale?: string | null;\n\tacceptLanguage?: string | null;\n}) => {\n\tconst configuredLocales = props.config.i18n.locales.map(\n\t\t(locale) => locale.code,\n\t);\n\n\tif (props.locale && configuredLocales.includes(props.locale)) {\n\t\treturn props.locale;\n\t}\n\n\tfor (const acceptedLocale of parseAcceptLanguage(props.acceptLanguage)) {\n\t\tif (configuredLocales.includes(acceptedLocale)) return acceptedLocale;\n\t\tconst baseLocale = acceptedLocale.split(\"-\")[0];\n\t\tif (baseLocale && configuredLocales.includes(baseLocale)) return baseLocale;\n\t}\n\n\treturn props.config.i18n.defaultLocale;\n};\n\n/**\n * Resolves Lucid core translations without project or plugin overrides.\n *\n * Use this for build-time, CLI, or low-level code that does not have a processed\n * config available. For request, service, lifecycle, and plugin code that should\n * respect project translations, use `createTranslator`.\n *\n * @example\n * ```ts\n * import { translate } from \"@lucidcms/core\";\n *\n * throw new Error(\n * translate(\"server:core.config.duplicate.keys\", {\n * data: { builder: \"collections\" },\n * }),\n * );\n * ```\n */\nexport function translate(\n\tvalue: string | TranslatableCopy,\n\toptions?: TranslateCopyOptions,\n): string;\nexport function translate(\n\tvalue: undefined,\n\toptions?: TranslateCopyOptions,\n): undefined;\nexport function translate(\n\tvalue: string | TranslatableCopy | undefined,\n\toptions?: TranslateCopyOptions,\n): string | undefined;\nexport function translate(\n\tvalue: string | TranslatableCopy | undefined,\n\toptions?: TranslateCopyOptions,\n) {\n\treturn coreTranslationStore.copy(value, {\n\t\tlocale: options?.locale,\n\t\toptions: {\n\t\t\tdata: options?.data,\n\t\t\tdefaultMessage: options?.defaultMessage,\n\t\t},\n\t});\n}\n\n/**\n * Creates a translator bound to a translation store and locale.\n *\n * Use this at request, service, adapter lifecycle, cron, and job boundaries when\n * text should respect project and plugin translation overrides. The returned\n * translator resolves prefixed keys or any value created with `copy`.\n *\n * @example\n * ```ts\n * import { copy, createTranslator } from \"@lucidcms/core\";\n *\n * const translator = createTranslator({ store, locale: \"fr\" });\n *\n * translator(\"server:posts.not.found\", {\n * data: { id: 12 },\n * defaultMessage: \"Post {{id}} was not found.\",\n * });\n *\n * translator(copy(\"admin:collections.posts.name\", {\n * defaultMessage: \"Posts\",\n * }));\n * ```\n */\nexport const createTranslator = (props: {\n\tstore: TranslationStore;\n\tlocale: string;\n}): Translator => {\n\tconst translateBoundCopy = (\n\t\tlocale: string,\n\t\tvalue: string | TranslatableCopy | undefined,\n\t\toptions?: TranslateCopyOptions,\n\t) =>\n\t\tprops.store.copy(value, {\n\t\t\tlocale,\n\t\t\toptions,\n\t\t});\n\tconst boundTranslate = ((\n\t\tvalue: string | TranslatableCopy | undefined,\n\t\toptions?: TranslateCopyOptions,\n\t) => translateBoundCopy(props.locale, value, options)) as TranslateCopy;\n\tconst englishTranslate = ((\n\t\tvalue: string | TranslatableCopy | undefined,\n\t\toptions?: TranslateCopyOptions,\n\t) => translateBoundCopy(\"en\", value, options)) as TranslateCopy;\n\n\treturn Object.assign(boundTranslate, {\n\t\tlocale: props.locale,\n\t\tforLocale: (locale: string) =>\n\t\t\tcreateTranslator({\n\t\t\t\tstore: props.store,\n\t\t\t\tlocale,\n\t\t\t}),\n\t\tadminBundle: () => props.store.admin({ locale: props.locale }),\n\t\tenglish: englishTranslate,\n\t});\n};\n\n/**\n * Parses an `Accept-Language` header into locale codes ordered by quality.\n */\nconst parseAcceptLanguage = (header?: string | null) => {\n\tif (!header) return [];\n\n\treturn header\n\t\t.split(\",\")\n\t\t.map((part) => {\n\t\t\tconst [locale, quality] = part.trim().split(/\\s*;\\s*q=/);\n\t\t\treturn {\n\t\t\t\tlocale: locale?.trim(),\n\t\t\t\tquality: quality ? Number.parseFloat(quality) : 1,\n\t\t\t};\n\t\t})\n\t\t.filter(\n\t\t\t(item): item is { locale: string; quality: number } =>\n\t\t\t\tBoolean(item.locale) && Number.isFinite(item.quality),\n\t\t)\n\t\t.sort((left, right) => right.quality - left.quality)\n\t\t.map((item) => item.locale);\n};\n"],"mappings":"qDAUA,MAAM,EAAuB,EAAuB,CACnD,cAAe,KACf,QAAS,CAAC,CACX,CAAC,EAgBY,EAA0B,GAIjC,CACL,IAAM,EAAoB,EAAM,OAAO,KAAK,QAAQ,IAClD,GAAW,EAAO,IACpB,EAEA,GAAI,EAAM,QAAU,EAAkB,SAAS,EAAM,MAAM,EAC1D,OAAO,EAAM,OAGd,IAAK,IAAM,KAAkB,EAAoB,EAAM,cAAc,EAAG,CACvE,GAAI,EAAkB,SAAS,CAAc,EAAG,OAAO,EACvD,IAAM,EAAa,EAAe,MAAM,GAAG,CAAC,CAAC,GAC7C,GAAI,GAAc,EAAkB,SAAS,CAAU,EAAG,OAAO,CAClE,CAEA,OAAO,EAAM,OAAO,KAAK,aAC1B,EAgCA,SAAgB,EACf,EACA,EACC,CACD,OAAO,EAAqB,KAAK,EAAO,CACvC,OAAQ,GAAS,OACjB,QAAS,CACR,KAAM,GAAS,KACf,eAAgB,GAAS,cAC1B,CACD,CAAC,CACF,CAyBA,MAAa,EAAoB,GAGf,CACjB,IAAM,GACL,EACA,EACA,IAEA,EAAM,MAAM,KAAK,EAAO,CACvB,SACA,SACD,CAAC,EAUF,OAAO,OAAO,SARb,EACA,IACI,EAAmB,EAAM,OAAQ,EAAO,CAAO,GAMf,CACpC,OAAQ,EAAM,OACd,UAAY,GACX,EAAiB,CAChB,MAAO,EAAM,MACb,QACD,CAAC,EACF,gBAAmB,EAAM,MAAM,MAAM,CAAE,OAAQ,EAAM,MAAO,CAAC,EAC7D,UAZA,EACA,IACI,EAAmB,KAAM,EAAO,CAAO,EAW5C,CAAC,CACF,EAKM,EAAuB,GACvB,EAEE,EACL,MAAM,GAAG,CAAC,CACV,IAAK,GAAS,CACd,GAAM,CAAC,EAAQ,GAAW,EAAK,KAAK,CAAC,CAAC,MAAM,WAAW,EACvD,MAAO,CACN,OAAQ,GAAQ,KAAK,EACrB,QAAS,EAAU,OAAO,WAAW,CAAO,EAAI,CACjD,CACD,CAAC,CAAC,CACD,OACC,GACA,EAAQ,EAAK,QAAW,OAAO,SAAS,EAAK,OAAO,CACtD,CAAC,CACA,MAAM,EAAM,IAAU,EAAM,QAAU,EAAK,OAAO,CAAC,CACnD,IAAK,GAAS,EAAK,MAAM,EAhBP,CAAC"}