next-i18next
Version:
The easiest way to translate your NextJs apps.
225 lines (223 loc) • 9.51 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let i18next = require("i18next");
let i18next_resources_to_backend = require("i18next-resources-to-backend");
i18next_resources_to_backend = __toESM(i18next_resources_to_backend);
let react = require("react");
let next_headers = require("next/headers");
//#region src/appRouter/config.ts
function normalizeConfig(userConfig) {
const supportedLngs = userConfig.supportedLngs ?? userConfig.i18n?.locales?.filter((l) => l !== "default") ?? ["en"];
const fallbackLng = userConfig.fallbackLng ?? userConfig.i18n?.defaultLocale ?? supportedLngs[0];
if (!fallbackLng) throw new Error("next-i18next: fallbackLng (or i18n.defaultLocale) is required");
if (supportedLngs.length === 0) throw new Error("next-i18next: supportedLngs (or i18n.locales) must contain at least one language");
const defaultNS = userConfig.defaultNS ?? "common";
return {
supportedLngs,
fallbackLng,
defaultNS,
ns: userConfig.ns ?? [defaultNS],
localeInPath: userConfig.localeInPath ?? true,
hideDefaultLocale: userConfig.hideDefaultLocale ?? false,
localePath: userConfig.localePath ?? "/locales",
localeStructure: userConfig.localeStructure ?? "{{lng}}/{{ns}}",
localeExtension: userConfig.localeExtension ?? "json",
cookieName: userConfig.cookieName ?? "i18next",
headerName: userConfig.headerName ?? "x-i18next-current-language",
cookieMaxAge: userConfig.cookieMaxAge ?? 365 * 24 * 60 * 60,
ignoredPaths: userConfig.ignoredPaths ?? [
"/api",
"/_next",
"/static"
],
basePath: userConfig.basePath,
resources: userConfig.resources,
resourceLoader: userConfig.resourceLoader,
use: userConfig.use ?? [],
i18nextOptions: userConfig.i18nextOptions ?? {},
nonExplicitSupportedLngs: userConfig.nonExplicitSupportedLngs ?? false,
i18n: userConfig.i18n,
serializeConfig: userConfig.serializeConfig,
reloadOnPrerender: userConfig.reloadOnPrerender
};
}
//#endregion
//#region src/appRouter/server.ts
let _config = null;
let _sharedInstance = null;
let _sharedInstancePromise = null;
function getConfig() {
if (!_config) throw new Error("next-i18next: Server module not initialized. Call initServerI18next(config) in your root layout.");
return _config;
}
/**
* Initialize the server-side i18next configuration.
* Call this once in your root layout or a shared setup file.
*/
function initServerI18next(userConfig) {
_config = normalizeConfig(userConfig);
}
function hasCustomBackend(plugins) {
return plugins.some((b) => b.type === "backend");
}
function createResourceBackend(config) {
if (config.resourceLoader) return (0, i18next_resources_to_backend.default)(config.resourceLoader);
return (0, i18next_resources_to_backend.default)(async (language, namespace) => {
const filePath = `${config.localePath}/${config.localeStructure.replace("{{lng}}", language).replace("{{ns}}", namespace)}.${config.localeExtension}`;
if (typeof process !== "undefined" && process.versions?.node) try {
const fs = await import("fs/promises");
const resolved = (await import("path")).resolve(process.cwd(), `public${filePath}`);
const content = await fs.readFile(resolved, "utf-8");
return JSON.parse(content);
} catch {
throw new Error(`next-i18next: Could not read locale file "public${filePath}". On serverless platforms (Vercel, AWS Lambda, etc.), files in public/ are served via CDN but are NOT available on the filesystem at runtime. Use the \`resourceLoader\` option with dynamic imports instead:
resourceLoader: (language, namespace) =>
import(\`./public/locales/\${language}/\${namespace}.json\`)
`);
}
throw new Error(`next-i18next: Cannot load locale file "${filePath}" in Edge Runtime. Provide pre-bundled \`resources\`, a custom \`resourceLoader\`, or use a custom backend (e.g. i18next-http-backend) via the \`use\` option.`);
});
}
/**
* Get or create the shared i18next instance.
* The instance is created once and reused across all requests.
* All languages are preloaded so that getFixedT(lng) works for any supported language.
* Additional namespaces are loaded on demand and cached in the instance store.
*/
async function getSharedInstance(config) {
if (_sharedInstance?.isInitialized) return _sharedInstance;
if (_sharedInstancePromise) return _sharedInstancePromise;
_sharedInstancePromise = (async () => {
const i18nInstance = (0, i18next.createInstance)();
const partialBundled = config.i18nextOptions?.partialBundledLanguages;
if ((!config.resources || partialBundled) && !hasCustomBackend(config.use)) i18nInstance.use(createResourceBackend(config));
config.use.forEach((plugin) => i18nInstance.use(plugin));
await i18nInstance.init({
lng: config.fallbackLng,
ns: config.ns,
defaultNS: config.defaultNS,
fallbackLng: config.fallbackLng,
supportedLngs: config.supportedLngs,
nonExplicitSupportedLngs: config.nonExplicitSupportedLngs,
fallbackNS: config.defaultNS,
preload: config.supportedLngs,
interpolation: { escapeValue: false },
...config.resources ? { resources: config.resources } : {},
...config.i18nextOptions
});
_sharedInstance = i18nInstance;
return i18nInstance;
})();
return _sharedInstancePromise;
}
const reloadResourcesForRender = (0, react.cache)(async (i18n, lng) => {
const ns = i18n.options.ns ?? [];
await i18n.reloadResources([lng], ns);
});
const detectLanguage = (0, react.cache)(async (config) => {
const fromHeader = (await (0, next_headers.headers)()).get(config.headerName);
if (fromHeader) return fromHeader;
const cookieValue = (await (0, next_headers.cookies)()).get(config.cookieName)?.value;
if (cookieValue) {
if (config.supportedLngs.includes(cookieValue)) return cookieValue;
if (config.nonExplicitSupportedLngs) {
const prefix = cookieValue.toLowerCase().split("-")[0];
const match = config.supportedLngs.find((l) => l.toLowerCase() === prefix || l.toLowerCase().split("-")[0] === prefix);
if (match) return match;
}
}
return config.fallbackLng;
});
/**
* Get a translation function for use in Server Components, layouts, and generateMetadata.
*
* The underlying i18next instance is a **module-level singleton** that persists across
* requests. This means custom backends (i18next-http-backend, i18next-locize-backend, etc.)
* only fetch translations once (or according to their own reloadInterval), not on every request.
*
* @example
* ```tsx
* import { getT } from 'next-i18next/server'
*
* export default async function Page() {
* const { t, i18n } = await getT('home')
* return <h1>{t('heading')}</h1>
* }
* ```
*/
async function getT(ns, options = {}) {
const config = getConfig();
const lng = options.lng || await detectLanguage(config);
const i18nInstance = await getSharedInstance(config);
if (config.reloadOnPrerender && process.env.NODE_ENV !== "production") await reloadResourcesForRender(i18nInstance, lng);
const missingNs = (ns ? Array.isArray(ns) ? ns : [ns] : config.ns).filter((n) => !i18nInstance.hasLoadedNamespace(n));
if (missingNs.length > 0) await i18nInstance.loadNamespaces(missingNs);
const resolvedNs = ns ? Array.isArray(ns) ? ns[0] : ns : config.defaultNS;
return {
t: i18nInstance.getFixedT(lng, resolvedNs, options.keyPrefix),
i18n: i18nInstance,
lng
};
}
/**
* Extract loaded resources from the server i18next instance for passing to I18nProvider.
*
* @example
* ```tsx
* const { i18n } = await getT()
* const resources = getResources(i18n, ['common', 'footer'])
* return <I18nProvider language={i18n.language} resources={resources}>{children}</I18nProvider>
* ```
*/
function getResources(i18n, namespaces) {
const resources = {};
const store = i18n.store?.data || {};
const nsFilter = namespaces ? new Set(namespaces) : null;
for (const lng of Object.keys(store)) {
resources[lng] = {};
for (const ns of Object.keys(store[lng])) if (!nsFilter || nsFilter.has(ns)) resources[lng][ns] = store[lng][ns];
}
return resources;
}
/**
* Helper for generateStaticParams — returns params for all supported languages.
*
* @example
* ```tsx
* import { generateI18nStaticParams } from 'next-i18next/server'
*
* export async function generateStaticParams() {
* return generateI18nStaticParams()
* }
* ```
*/
function generateI18nStaticParams() {
return getConfig().supportedLngs.map((lng) => ({ lng }));
}
//#endregion
exports.generateI18nStaticParams = generateI18nStaticParams;
exports.getResources = getResources;
exports.getT = getT;
exports.initServerI18next = initServerI18next;
//# sourceMappingURL=server.cjs.map