UNPKG

nuxt-i18n-micro

Version:

Nuxt I18n Micro is a lightweight, high-performance internationalization module for Nuxt, designed to handle multi-language support with minimal overhead, fast build times, and efficient runtime performance.

89 lines (88 loc) 3.66 kB
import { resolve } from "node:path"; import { readFile } from "node:fs/promises"; import { defineEventHandler, setResponseHeader } from "h3"; import { useRuntimeConfig, createError, useStorage } from "#imports"; let storageInit = false; function deepMerge(target, source) { const output = { ...target }; for (const key in source) { if (key === "__proto__" || key === "constructor") continue; const src = source[key]; const dst = output[key]; if (src && typeof src === "object" && !Array.isArray(src) && dst && typeof dst === "object" && !Array.isArray(dst)) { output[key] = deepMerge(dst, src); } else { output[key] = src; } } return output; } async function readTranslationFile(filePath, debug) { try { const content = await readFile(filePath, "utf-8"); return JSON.parse(content); } catch (e) { if (debug && e?.code !== "ENOENT") { console.error(`[i18n] Error loading locale file: ${filePath}`, e); } return null; } } export default defineEventHandler(async (event) => { setResponseHeader(event, "Content-Type", "application/json"); const { page, locale } = event.context.params; const config = useRuntimeConfig(); const { rootDirs, debug, translationDir, fallbackLocale, routesLocaleLinks } = config.i18nConfig; const { locales } = config.public.i18nConfig; if (locales && !locales.map((l) => l.code).includes(locale)) { throw createError({ statusCode: 404 }); } let fileLookupPage = page; if (routesLocaleLinks && page && routesLocaleLinks[page]) { fileLookupPage = routesLocaleLinks[page] || page; if (debug) { console.log(`[i18n] Route link found: '${page}' -> '${fileLookupPage}'. Using linked translations.`); } } const serverStorage = useStorage("assets:server"); const cacheKey = `_locales:merged:${page}:${locale}`; if (!storageInit) { if (debug) console.log("[nuxt-i18n-micro] clear storage cache"); await Promise.all((await serverStorage.getKeys("_locales")).map((key) => serverStorage.removeItem(key))); storageInit = true; } const cachedMerged = await serverStorage.getItem(cacheKey); if (cachedMerged) { return cachedMerged; } const getPathsFor = (targetLocale, targetPage) => rootDirs.map((dir) => resolve(dir, translationDir, targetPage === "general" ? `${targetLocale}.json` : `pages/${targetPage}/${targetLocale}.json`)); let finalTranslations = {}; const currentLocaleConfig = locales?.find((l) => l.code === locale) ?? null; const loadAndMerge = async (targetLocale) => { let globalTranslations = {}; let pageTranslations = {}; for (const p of getPathsFor(targetLocale, "general")) { const content = await readTranslationFile(p, debug); if (content) globalTranslations = deepMerge(globalTranslations, content); } if (page !== "general") { for (const p of getPathsFor(targetLocale, fileLookupPage)) { const content = await readTranslationFile(p, debug); if (content) pageTranslations = deepMerge(pageTranslations, content); } } return deepMerge(globalTranslations, pageTranslations); }; const fallbackLocalesList = [ fallbackLocale, currentLocaleConfig?.fallbackLocale ].filter((l) => !!l && l !== locale); for (const fb of [...new Set(fallbackLocalesList)]) { const fbTranslations = await loadAndMerge(fb); finalTranslations = deepMerge(finalTranslations, fbTranslations); } const mainTranslations = await loadAndMerge(locale); finalTranslations = deepMerge(finalTranslations, mainTranslations); await serverStorage.setItem(cacheKey, finalTranslations); return finalTranslations; });