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
JavaScript
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;
});