next-i18next
Version:
The easiest way to translate your NextJs apps.
212 lines (206 loc) • 8.36 kB
JavaScript
import { defaultConfig } from './defaultConfig';
import { getFallbackForLng, unique } from '../utils';
const deepMergeObjects = ['backend', 'detection'];
export const createConfig = userConfig => {
if (typeof userConfig?.lng !== 'string') {
throw new Error('config.lng was not passed into createConfig');
}
//
// Initial merge of default and user-provided config
//
const {
i18n: userI18n,
...userConfigStripped
} = userConfig;
const {
i18n: defaultI18n,
...defaultConfigStripped
} = defaultConfig;
const combinedConfig = {
...defaultConfigStripped,
...userConfigStripped,
...defaultI18n,
...userI18n
};
const {
defaultNS,
lng,
localeExtension,
localePath,
nonExplicitSupportedLngs
} = combinedConfig;
const locales = combinedConfig.locales.filter(l => l !== 'default');
/**
* Skips translation file resolution while in cimode
* https://github.com/i18next/next-i18next/pull/851#discussion_r503113620
*/
if (lng === 'cimode') {
return combinedConfig;
}
if (typeof combinedConfig.fallbackLng === 'undefined') {
combinedConfig.fallbackLng = combinedConfig.defaultLocale;
if (combinedConfig.fallbackLng === 'default') [combinedConfig.fallbackLng] = locales;
}
const userPrefix = userConfig?.interpolation?.prefix;
const userSuffix = userConfig?.interpolation?.suffix;
const prefix = userPrefix ?? '{{';
const suffix = userSuffix ?? '}}';
if (typeof userConfig?.localeStructure !== 'string' && (userPrefix || userSuffix)) {
combinedConfig.localeStructure = `${prefix}lng${suffix}/${prefix}ns${suffix}`;
}
const {
fallbackLng,
localeStructure
} = combinedConfig;
if (nonExplicitSupportedLngs) {
const createFallbackObject = (acc, l) => {
const [locale] = l.split('-');
acc[l] = [locale];
return acc;
};
if (typeof fallbackLng === 'string') {
combinedConfig.fallbackLng = combinedConfig.locales.filter(l => l.includes('-')).reduce(createFallbackObject, {
default: [fallbackLng]
});
} else if (Array.isArray(fallbackLng)) {
combinedConfig.fallbackLng = combinedConfig.locales.filter(l => l.includes('-')).reduce(createFallbackObject, {
default: fallbackLng
});
} else if (typeof fallbackLng === 'object') {
combinedConfig.fallbackLng = Object.entries(combinedConfig.fallbackLng).reduce((acc, [l, f]) => {
acc[l] = l.includes('-') ? unique([l.split('-')[0], ...f]) : f;
return acc;
}, fallbackLng);
} else if (typeof fallbackLng === 'function') {
throw new Error('If nonExplicitSupportedLngs is true, no functions are allowed for fallbackLng');
}
}
const hasCustomBackend = userConfig?.use?.some(b => b.type === 'backend');
if (!process.browser && typeof window === 'undefined') {
combinedConfig.preload = locales;
if (!hasCustomBackend) {
const fs = require('fs');
const path = require('path');
//
// Validate defaultNS
// https://github.com/i18next/next-i18next/issues/358
//
if (typeof defaultNS === 'string' && typeof lng !== 'undefined') {
if (typeof localePath === 'string') {
const defaultLocaleStructure = localeStructure.replace(`${prefix}lng${suffix}`, lng).replace(`${prefix}ns${suffix}`, defaultNS);
const defaultFile = `/${defaultLocaleStructure}.${localeExtension}`;
const defaultNSPath = path.join(localePath, defaultFile);
const defaultNSExists = fs.existsSync(defaultNSPath);
const fallback = getFallbackForLng(lng, combinedConfig.fallbackLng);
const defaultFallbackNSExists = fallback.some(f => {
const fallbackFile = defaultFile.replace(lng, f);
const defaultNSPath = path.join(localePath, fallbackFile);
return fs.existsSync(defaultNSPath);
});
if (!defaultNSExists && !defaultFallbackNSExists && process.env.NODE_ENV !== 'production') {
throw new Error(`Default namespace not found at ${defaultNSPath}`);
}
} else if (typeof localePath === 'function') {
const defaultNSPath = localePath(lng, defaultNS, false);
const defaultNSExists = fs.existsSync(defaultNSPath);
const fallback = getFallbackForLng(lng, combinedConfig.fallbackLng);
const defaultFallbackNSExists = fallback.some(f => {
const defaultNSPath = localePath(f, defaultNS, false);
return fs.existsSync(defaultNSPath);
});
if (!defaultNSExists && !defaultFallbackNSExists && process.env.NODE_ENV !== 'production') {
throw new Error(`Default namespace not found at ${defaultNSPath}`);
}
}
}
//
// Set server side backend
//
if (typeof localePath === 'string') {
combinedConfig.backend = {
addPath: path.resolve(process.cwd(), `${localePath}/${localeStructure}.missing.${localeExtension}`),
loadPath: path.resolve(process.cwd(), `${localePath}/${localeStructure}.${localeExtension}`)
};
} else if (typeof localePath === 'function') {
combinedConfig.backend = {
addPath: (locale, namespace) => localePath(locale, namespace, true),
loadPath: (locale, namespace) => localePath(locale, namespace, false)
};
} else if (localePath) {
throw new Error(`Unsupported localePath type: ${typeof localePath}`);
}
//
// Set server side preload (namespaces)
//
if (!combinedConfig.ns && typeof lng !== 'undefined') {
if (typeof localePath === 'function') {
throw new Error('Must provide all namespaces in ns option if using a function as localePath');
}
const getNamespaces = locales => {
const getLocaleNamespaces = p => {
let ret = [];
if (!fs.existsSync(p)) return ret;
fs.readdirSync(p).map(file => {
const joinedP = path.join(p, file);
if (fs.statSync(joinedP).isDirectory()) {
const subRet = getLocaleNamespaces(joinedP).map(n => `${file}/${n}`);
ret = ret.concat(subRet);
return;
}
ret.push(file.replace(`.${localeExtension}`, ''));
});
return ret;
};
let namespacesByLocale;
const r = combinedConfig.resources;
if (!localePath && r) {
namespacesByLocale = locales.map(locale => Object.keys(r[locale]));
} else {
namespacesByLocale = locales.map(locale => getLocaleNamespaces(path.resolve(process.cwd(), `${localePath}/${locale}`)));
}
const allNamespaces = [];
for (const localNamespaces of namespacesByLocale) {
allNamespaces.push(...localNamespaces);
}
return unique(allNamespaces);
};
if (localeStructure.indexOf(`${prefix}lng${suffix}`) > localeStructure.indexOf(`${prefix}ns${suffix}`)) {
throw new Error('Must provide all namespaces in ns option if using a localeStructure that is not namespace-listable like lng/ns');
}
combinedConfig.ns = getNamespaces(unique([lng, ...getFallbackForLng(lng, combinedConfig.fallbackLng)]));
}
}
} else {
//
// Set client side backend, if there is no custom backend
//
if (!hasCustomBackend) {
if (typeof localePath === 'string') {
combinedConfig.backend = {
addPath: `${localePath}/${localeStructure}.missing.${localeExtension}`,
loadPath: `${localePath}/${localeStructure}.${localeExtension}`
};
} else if (typeof localePath === 'function') {
combinedConfig.backend = {
addPath: (locale, namespace) => localePath(locale, namespace, true),
loadPath: (locale, namespace) => localePath(locale, namespace, false)
};
}
}
if (typeof combinedConfig.ns !== 'string' && !Array.isArray(combinedConfig.ns)) {
combinedConfig.ns = [defaultNS];
}
}
//
// Deep merge with overwrite - goes last
//
deepMergeObjects.forEach(obj => {
if (userConfig[obj]) {
combinedConfig[obj] = {
...combinedConfig[obj],
...userConfig[obj]
};
}
});
return combinedConfig;
};