Files
mygit/ghost/i18n/lib/i18n.js
T
DuckQ1u 93d1b7c3d3
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
first commit
2026-04-22 19:51:20 +07:00

156 lines
5.0 KiB
JavaScript

const i18next = require('i18next');
const path = require('path');
const fs = require('fs');
const debug = require('@tryghost/debug')('i18n');
// Locale data loaded from JSON (single source of truth)
const LOCALE_DATA = require('./locale-data.json');
// Export just the locale codes for backward compatibility
const SUPPORTED_LOCALES = LOCALE_DATA.map(locale => locale.code);
function generateResources(locales, ns) {
return locales.reduce((acc, locale) => {
let res;
// add an extra fallback - this handles the case where we have a partial set of translations for some reason
// by falling back to the english translations
try {
res = require(`../locales/${locale}/${ns}.json`);
} catch (err) {
res = require(`../locales/en/${ns}.json`);
}
// Note: due some random thing in TypeScript, 'requiring' a JSON file with a space in a key name, only adds it to the default export
// If changing this behaviour, please also check the comments and signup-form apps in another language (mainly sentences with a space in them)
acc[locale] = {
[ns]: {...res, ...(res.default && typeof res.default === 'object' ? res.default : {})}
};
return acc;
}, {});
}
function generateThemeResources(lng, themeLocalesPath) {
if (!themeLocalesPath) {
return {
[lng]: {
theme: {}
}
};
}
// Get available theme locales by scanning the directory
let availableLocales = [];
try {
const files = fs.readdirSync(themeLocalesPath);
availableLocales = files
.filter(file => file.endsWith('.json'))
.map(file => file.replace('.json', ''));
} catch (err) {
// If we can't read the directory, fall back to just trying the requested locale and English
availableLocales = [lng, 'en'];
}
// Always include the requested locale and English as fallbacks
const locales = [...new Set([lng, ...availableLocales, 'en'])];
return locales.reduce((acc, locale) => {
let res;
let needsFallback = false;
// Try to load the locale file, fallback to English
const localePath = path.join(themeLocalesPath, `${locale}.json`);
if (fs.existsSync(localePath)) {
try {
// Delete from require cache to ensure fresh reads for theme files
delete require.cache[require.resolve(localePath)];
res = require(localePath);
} catch (err) {
debug(`Error loading theme locale file: ${locale}`);
needsFallback = true;
}
} else {
needsFallback = true;
}
if (needsFallback) {
// Fallback to English if it's not the locale we're already trying
if (locale !== 'en') {
try {
const enPath = path.join(themeLocalesPath, 'en.json');
if (fs.existsSync(enPath)) {
// Delete from require cache to ensure fresh reads for theme files
delete require.cache[require.resolve(enPath)];
res = require(enPath);
} else {
res = {};
}
} catch (enErr) {
res = {};
}
} else {
debug(`Theme en.json file not found`);
res = {};
}
}
// Handle the same default export issue as other namespaces
acc[locale] = {
theme: {...res, ...(res.default && typeof res.default === 'object' ? res.default : {})}
};
return acc;
}, {});
}
/**
* @param {string} [lng]
* @param {'ghost'|'portal'|'test'|'signup-form'|'comments'|'search'|'theme'} ns
*/
module.exports = (lng = 'en', ns = 'portal', options = {}) => {
const i18nextInstance = i18next.createInstance();
const interpolation = {
prefix: '{',
suffix: '}'
};
if (ns === 'theme') {
interpolation.escapeValue = false;
}
let resources;
if (ns !== 'theme') {
resources = generateResources(SUPPORTED_LOCALES, ns);
} else {
debug(`generateThemeResources: ${lng}, ${options.themePath}`);
resources = generateThemeResources(lng, options.themePath);
}
i18nextInstance.init({
lng,
// allow keys to be phrases having `:`, `.`
nsSeparator: false,
keySeparator: false,
// if the value is an empty string, return the key
returnEmptyString: false,
// do not load a fallback
fallbackLng: {
no: ['nb', 'en'],
default: ['en']
},
ns: ns,
defaultNS: ns,
// separators
interpolation,
resources
});
return i18nextInstance;
};
module.exports.SUPPORTED_LOCALES = SUPPORTED_LOCALES;
module.exports.LOCALE_DATA = LOCALE_DATA;
module.exports.generateResources = generateResources;