Ghost/core/server/lib/common/i18n.js
2017-12-13 20:57:11 +01:00

154 lines
5.1 KiB
JavaScript

/* global Intl */
var supportedLocales = ['en'],
_ = require('lodash'),
fs = require('fs-extra'),
path = require('path'),
chalk = require('chalk'),
MessageFormat = require('intl-messageformat'),
logging = require('./logging'),
errors = require('./errors'),
// TODO: fetch this dynamically based on overall blog settings (`key = "default_locale"`) in the `settings` table
currentLocale = 'en',
blos,
I18n;
I18n = {
/**
* Helper method to find and compile the given data context with a proper string resource.
*
* @param {string} path Path with in the JSON language file to desired string (ie: "errors.init.jsNotBuilt")
* @param {object} [bindings]
* @returns {string}
*/
t: function t(path, bindings) {
var string = I18n.findString(path),
msg;
// If the path returns an array (as in the case with anything that has multiple paragraphs such as emails), then
// loop through them and return an array of translated/formatted strings. Otherwise, just return the normal
// translated/formatted string.
if (_.isArray(string)) {
msg = [];
string.forEach(function (s) {
var m = new MessageFormat(s, currentLocale);
try {
m.format(bindings);
} catch (err) {
logging.error(err.message);
// fallback
m = new MessageFormat(blos.errors.errors.anErrorOccurred, currentLocale);
m = msg.format();
}
msg.push(m);
});
} else {
msg = new MessageFormat(string, currentLocale);
try {
msg = msg.format(bindings);
} catch (err) {
logging.error(err.message);
// fallback
msg = new MessageFormat(blos.errors.errors.anErrorOccurred, currentLocale);
msg = msg.format();
}
}
return msg;
},
/**
* Parse JSON file for matching locale, returns string giving path.
*
* @param {string} msgPath Path with in the JSON language file to desired string (ie: "errors.init.jsNotBuilt")
* @returns {string}
*/
findString: function findString(msgPath, opts) {
var options = _.merge({log: true}, opts || {}),
matchingString, path;
// no path? no string
if (_.isEmpty(msgPath) || !_.isString(msgPath)) {
chalk.yellow('i18n:t() - received an empty path.');
return '';
}
if (blos === undefined) {
I18n.init();
}
matchingString = blos;
path = msgPath.split('.');
path.forEach(function (key) {
// reassign matching object, or set to an empty string if there is no match
matchingString = matchingString[key] || {};
});
if (_.isObject(matchingString) || _.isEqual(matchingString, {})) {
if (options.log) {
logging.error(new errors.IncorrectUsageError({
message: `i18n error: path "${msgPath}" was not found`
}));
}
matchingString = blos.errors.errors.anErrorOccurred;
}
return matchingString;
},
doesTranslationKeyExist: function doesTranslationKeyExist(msgPath) {
var translation = I18n.findString(msgPath, {log: false});
return translation !== blos.errors.errors.anErrorOccurred;
},
/**
* Setup i18n support:
* - Load proper language file in to memory
* - Polyfill node.js if it does not have Intl support or support for a particular locale
*/
init: function init() {
// read file for current locale and keep its content in memory
blos = fs.readFileSync(path.join(__dirname, '..', '..', 'translations', currentLocale + '.json'));
// if translation file is not valid, you will see an error
try {
blos = JSON.parse(blos);
} catch (err) {
blos = undefined;
throw err;
}
if (global.Intl) {
// Determine if the built-in `Intl` has the locale data we need.
var hasBuiltInLocaleData,
IntlPolyfill;
hasBuiltInLocaleData = supportedLocales.every(function (locale) {
return Intl.NumberFormat.supportedLocalesOf(locale)[0] === locale &&
Intl.DateTimeFormat.supportedLocalesOf(locale)[0] === locale;
});
if (!hasBuiltInLocaleData) {
// `Intl` exists, but it doesn't have the data we need, so load the
// polyfill and replace the constructors with need with the polyfill's.
IntlPolyfill = require('intl');
Intl.NumberFormat = IntlPolyfill.NumberFormat;
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
}
} else {
// No `Intl`, so use and load the polyfill.
global.Intl = require('intl');
}
}
};
module.exports = I18n;