next-intl
Version:
Internationalization (i18n) for Next.js
213 lines (198 loc) • 7.54 kB
JavaScript
var fs = require('fs');
var path = require('path');
var module$1 = require('module');
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
function formatMessage(message) {
return `\n[next-intl] ${message}\n`;
}
function throwError(message) {
throw new Error(formatMessage(message));
}
function warn(message) {
console.warn(formatMessage(message));
}
/**
* Wrapper around `fs.watch` that provides a workaround
* for https://github.com/nodejs/node/issues/5039.
*/
function watchFile(filepath, callback) {
const directory = path.dirname(filepath);
const filename = path.basename(filepath);
return fs.watch(directory, {
persistent: false,
recursive: false
}, (event, changedFilename) => {
if (changedFilename === filename) {
callback();
}
});
}
function runOnce(fn) {
if (process.env._NEXT_INTL_COMPILE_MESSAGES === '1') {
return;
}
process.env._NEXT_INTL_COMPILE_MESSAGES = '1';
fn();
}
function createMessagesDeclaration(messagesPaths) {
// Next.js can call the Next.js config multiple
// times - ensure we only run once.
runOnce(() => {
for (const messagesPath of messagesPaths) {
const fullPath = path.resolve(messagesPath);
if (!fs.existsSync(fullPath)) {
throwError(`\`createMessagesDeclaration\` points to a non-existent file: ${fullPath}`);
}
if (!fullPath.endsWith('.json')) {
throwError(`\`createMessagesDeclaration\` needs to point to a JSON file. Received: ${fullPath}`);
}
// Keep this as a runtime check and don't replace
// this with a constant during the build process
const env = process.env['NODE_ENV'.trim()];
compileDeclaration(messagesPath);
if (env === 'development') {
startWatching(messagesPath);
}
}
});
}
function startWatching(messagesPath) {
const watcher = watchFile(messagesPath, () => {
compileDeclaration(messagesPath, true);
});
process.on('exit', () => {
watcher.close();
});
}
function compileDeclaration(messagesPath, async = false) {
const declarationPath = messagesPath.replace(/\.json$/, '.d.json.ts');
function createDeclaration(content) {
return `// This file is auto-generated by next-intl, do not edit directly.
// See: https://next-intl.dev/docs/workflows/typescript#messages-arguments
declare const messages: ${content.trim()};
export default messages;`;
}
if (async) {
return fs.promises.readFile(messagesPath, 'utf-8').then(content => fs.promises.writeFile(declarationPath, createDeclaration(content)));
}
const content = fs.readFileSync(messagesPath, 'utf-8');
fs.writeFileSync(declarationPath, createDeclaration(content));
}
// eslint-disable-next-line import/order
const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('plugin.cjs', document.baseURI).href)));
const pkg = require$1('next/package.json');
function compareVersions(version1, version2) {
const v1Parts = version1.split('.').map(Number);
const v2Parts = version2.split('.').map(Number);
for (let i = 0; i < 3; i++) {
const v1 = v1Parts[i] || 0;
const v2 = v2Parts[i] || 0;
if (v1 > v2) return 1;
if (v1 < v2) return -1;
}
return 0;
}
const hasStableTurboConfig = compareVersions(pkg.version, '15.3.0') >= 0;
function withExtensions(localPath) {
return [`${localPath}.ts`, `${localPath}.tsx`, `${localPath}.js`, `${localPath}.jsx`];
}
function resolveI18nPath(providedPath, cwd) {
function resolvePath(pathname) {
const parts = [];
if (cwd) parts.push(cwd);
parts.push(pathname);
return path.resolve(...parts);
}
function pathExists(pathname) {
return fs.existsSync(resolvePath(pathname));
}
if (providedPath) {
if (!pathExists(providedPath)) {
throwError(`Could not find i18n config at ${providedPath}, please provide a valid path.`);
}
return providedPath;
} else {
for (const candidate of [...withExtensions('./i18n/request'), ...withExtensions('./src/i18n/request')]) {
if (pathExists(candidate)) {
return candidate;
}
}
throwError(`Could not locate request configuration module.\n\nThis path is supported by default: ./(src/)i18n/request.{js,jsx,ts,tsx}\n\nAlternatively, you can specify a custom location in your Next.js config:\n\nconst withNextIntl = createNextIntlPlugin(
Alternatively, you can specify a custom location in your Next.js config:
const withNextIntl = createNextIntlPlugin(
'./path/to/i18n/request.tsx'
);`);
}
}
function getNextConfig(pluginConfig, nextConfig) {
const useTurbo = process.env.TURBOPACK != null;
const nextIntlConfig = {};
// Assign alias for `next-intl/config`
if (useTurbo) {
if (pluginConfig.requestConfig?.startsWith('/')) {
throwError("Turbopack support for next-intl currently does not support absolute paths, please provide a relative one (e.g. './src/i18n/config.ts').\n\nFound: " + pluginConfig.requestConfig);
}
const resolveAlias = {
// Turbo aliases don't work with absolute
// paths (see error handling above)
'next-intl/config': resolveI18nPath(pluginConfig.requestConfig)
};
if (hasStableTurboConfig && !nextConfig?.experimental?.turbo) {
nextIntlConfig.turbopack = {
...nextConfig?.turbopack,
resolveAlias: {
...nextConfig?.turbopack?.resolveAlias,
...resolveAlias
}
};
} else {
nextIntlConfig.experimental = {
...nextConfig?.experimental,
turbo: {
...nextConfig?.experimental?.turbo,
resolveAlias: {
...nextConfig?.experimental?.turbo?.resolveAlias,
...resolveAlias
}
}
};
}
} else {
nextIntlConfig.webpack = function webpack(...[config, options]) {
// Webpack requires absolute paths
config.resolve.alias['next-intl/config'] = path.resolve(config.context, resolveI18nPath(pluginConfig.requestConfig, config.context));
if (typeof nextConfig?.webpack === 'function') {
return nextConfig.webpack(config, options);
}
return config;
};
}
// Forward config
if (nextConfig?.trailingSlash) {
nextIntlConfig.env = {
...nextConfig.env,
_next_intl_trailing_slash: 'true'
};
}
return Object.assign({}, nextConfig, nextIntlConfig);
}
function initPlugin(pluginConfig, nextConfig) {
if (nextConfig?.i18n != null) {
warn("\n[next-intl] An `i18n` property was found in your Next.js config. This likely causes conflicts and should therefore be removed if you use the App Router.\n\nIf you're in progress of migrating from the Pages Router, you can refer to this example: https://next-intl.dev/examples#app-router-migration\n");
}
const messagesPathOrPaths = pluginConfig.experimental?.createMessagesDeclaration;
if (messagesPathOrPaths) {
createMessagesDeclaration(typeof messagesPathOrPaths === 'string' ? [messagesPathOrPaths] : messagesPathOrPaths);
}
return getNextConfig(pluginConfig, nextConfig);
}
function createNextIntlPlugin(i18nPathOrConfig = {}) {
const config = typeof i18nPathOrConfig === 'string' ? {
requestConfig: i18nPathOrConfig
} : i18nPathOrConfig;
return function withNextIntl(nextConfig) {
return initPlugin(config, nextConfig);
};
}
module.exports = createNextIntlPlugin;
;