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.

115 lines (114 loc) 4.5 kB
import { existsSync, readdirSync, readFileSync } from "node:fs"; import path from "node:path"; import { SERVER_CC_KEY, STORAGE_CC_KEY } from "@i18n-micro/hmr/cache-keys"; import { handleTranslationWatchChange, parseTranslationWatchRelativePath } from "@i18n-micro/hmr/watcher"; import { CacheControl } from "@i18n-micro/utils/cache-control"; import { watch } from "chokidar"; import { defineNitroPlugin } from "nitropack/runtime"; import { getI18nPrivateConfig } from "#i18n-internal/config"; import { getI18nConfig } from "#i18n-internal/strategy"; function getCacheByKey(key) { const g = globalThis; const cc = g[key]; if (cc && typeof cc === "object" && "keys" in cc && "set" in cc) { return cc; } return null; } function getServerCache() { return getCacheByKey(SERVER_CC_KEY); } function getOrCreateServerCache() { const existing = getServerCache(); if (existing) { return existing; } const g = globalThis; const created = new CacheControl(); g[SERVER_CC_KEY] = created; return created; } function getStorageCache() { return getCacheByKey(STORAGE_CC_KEY); } function readJsonSafe(filePath) { try { if (existsSync(filePath)) { return JSON.parse(readFileSync(filePath, "utf-8")); } } catch { } return {}; } let watcherInstance = null; export default defineNitroPlugin((nitroApp) => { if (watcherInstance) { return; } const i18nConfig = getI18nPrivateConfig(); if (process.env.NODE_ENV !== "development") { return; } const log = (...args) => i18nConfig.debug && console.log("[i18n-hmr]", ...args); const warn = (...args) => i18nConfig.debug && console.warn("[i18n-hmr]", ...args); const rawLocales = i18nConfig.locales; const configuredLocales = new Set( (Array.isArray(rawLocales) ? rawLocales : []).map((l) => l.code).filter((code) => typeof code === "string" && code.length > 0) ); const routesLocaleLinks = i18nConfig.routesLocaleLinks || {}; const translationsRoot = path.resolve(i18nConfig.rootDir, i18nConfig.translationDir); log(`Watching for translation changes in: ${translationsRoot}`); const invalidateAndRefresh = async (filePath, event) => { const relativePath = path.relative(translationsRoot, filePath).replace(/\\/g, "/"); const runtimeConfig = getI18nConfig(); try { const result = await handleTranslationWatchChange({ relativePath, configuredLocales, listPageNames: () => { const pagesDir = path.join(translationsRoot, "pages"); if (!existsSync(pagesDir)) { return []; } return readdirSync(pagesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name); }, serverCache: getOrCreateServerCache(), storageCache: getStorageCache(), routesLocaleLinks, mergeInput: { translationPayloadMode: runtimeConfig.translationPayloadMode, locales: runtimeConfig.locales, fallbackLocale: runtimeConfig.fallbackLocale, disablePageLocales: runtimeConfig.disablePageLocales, readLocaleFile: (relativeFilePath) => readJsonSafe(path.join(translationsRoot, relativeFilePath)) } }); if (result === "root") { const parsed = parseTranslationWatchRelativePath(relativePath); if (parsed.type === "root") { log(`Re-merged ALL pages for locale '${parsed.locale}'`); } } else if (result === "page") { log(`Re-merged page cache for '${relativePath}'`); } else if (result === "ignored") { const parsed = parseTranslationWatchRelativePath(relativePath); if (parsed.type === "root" && !configuredLocales.has(parsed.locale)) { warn(`Detected ${event} for '${relativePath}', but locale '${parsed.locale}' is not in i18n.locales. Update config and restart dev server.`); } } } catch (e) { warn("Failed to refresh server cache for", filePath, e); } }; const watcher = watch(translationsRoot, { persistent: true, ignoreInitial: true, depth: 5 }); watcher.on("add", (filePath) => invalidateAndRefresh(filePath, "add")); watcher.on("change", (filePath) => invalidateAndRefresh(filePath, "change")); watcher.on("unlink", (filePath) => invalidateAndRefresh(filePath, "unlink")); watcherInstance = watcher; nitroApp.hooks.hook("close", async () => { if (watcherInstance) { await watcherInstance.close(); watcherInstance = null; } }); });