mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Moved theme i18n into theme service + refactor (#11674)
- The existing common.i18n library contained code for core and theme translations - There is some shared logic and some theme-specific logic, and the theme-specific logic has dependencies we don't want in lib/common - This refactor introduces an I18n base class that does all the main shared logic, with no dependencies on other parts of the codebase - ThemeI18n then extends this logic, and replaces the functions it needs to handle differently and adds it's dependencies on config and settingsCache - The class has several methods broken down into smaller pieces to make it easier to extend only the necessary parts - The class also encapsulates all of its logic, without external functions or variables - The function loadThemeTranslations becomes the 'init()' function overridden in themeI18n.
This commit is contained in:
parent
7e4de9b0c1
commit
5e2c62e328
9 changed files with 279 additions and 230 deletions
|
@ -3,7 +3,7 @@
|
|||
//
|
||||
// Formats a date using moment-timezone.js. Formats published_at by default but will also take a date as a parameter
|
||||
|
||||
const {SafeString, i18n} = require('./proxy');
|
||||
const {SafeString, themeI18n} = require('./proxy');
|
||||
const moment = require('moment-timezone');
|
||||
|
||||
module.exports = function (date, options) {
|
||||
|
@ -35,7 +35,7 @@ module.exports = function (date, options) {
|
|||
// Documentation: http://momentjs.com/docs/#/i18n/
|
||||
// Locales: https://github.com/moment/moment/tree/develop/locale
|
||||
const dateMoment = moment(date);
|
||||
dateMoment.locale(i18n.locale());
|
||||
dateMoment.locale(themeI18n.locale());
|
||||
|
||||
if (timeago) {
|
||||
date = timezone ? dateMoment.tz(timezone).from(timeNow) : dateMoment.fromNow();
|
||||
|
|
|
@ -12,10 +12,8 @@
|
|||
// Language tags in HTML and XML
|
||||
// https://www.w3.org/International/articles/language-tags/
|
||||
|
||||
var proxy = require('./proxy'),
|
||||
i18n = proxy.i18n,
|
||||
SafeString = proxy.SafeString;
|
||||
const {SafeString, themeI18n} = require('./proxy');
|
||||
|
||||
module.exports = function lang() {
|
||||
return new SafeString(i18n.locale());
|
||||
return new SafeString(themeI18n.locale());
|
||||
};
|
||||
|
|
|
@ -28,6 +28,9 @@ module.exports = {
|
|||
i18n: require('../../server/lib/common/i18n'),
|
||||
logging: require('../../server/lib/common/logging'),
|
||||
|
||||
// Theme i18n is separate to common i18n
|
||||
themeI18n: require('../services/themes/i18n'),
|
||||
|
||||
// This is used to detect if "isPost" is true in prevNext.
|
||||
checks: require('../../server/data/schema').checks,
|
||||
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
// because often other helpers need that (t) returns a string to be able to work as subexpression; e.g.:
|
||||
// {{tags prefix=(t " on ")}}
|
||||
|
||||
var proxy = require('./proxy'),
|
||||
i18n = proxy.i18n;
|
||||
const {themeI18n} = require('./proxy');
|
||||
|
||||
module.exports = function t(text, options) {
|
||||
var bindings = {},
|
||||
|
@ -21,6 +20,6 @@ module.exports = function t(text, options) {
|
|||
bindings[prop] = options.hash[prop];
|
||||
}
|
||||
}
|
||||
bindings.isThemeString = true;
|
||||
return i18n.t(text, bindings);
|
||||
|
||||
return themeI18n.t(text, bindings);
|
||||
};
|
||||
|
|
113
core/frontend/services/themes/i18n.js
Normal file
113
core/frontend/services/themes/i18n.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
const {i18n, events, logging, errors} = require('../../../server/lib/common');
|
||||
const settingsCache = require('../../../server/services/settings/cache');
|
||||
const config = require('../../../server/config');
|
||||
|
||||
const jp = require('jsonpath');
|
||||
|
||||
const isNil = require('lodash/isNil');
|
||||
|
||||
class ThemeI18n extends i18n.I18n {
|
||||
constructor(locale) {
|
||||
super(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup i18n support for themes:
|
||||
* - Load correct language file into memory
|
||||
*/
|
||||
init() {
|
||||
// This function is called during theme initialization, and when switching language or theme.
|
||||
const currentLocale = this._loadLocale();
|
||||
const activeTheme = settingsCache.get('active_theme');
|
||||
|
||||
// Reading file for current locale and active theme and keeping its content in memory
|
||||
if (activeTheme) {
|
||||
// Reading translation file for theme .hbs templates.
|
||||
// Compatibility with both old themes and i18n-capable themes.
|
||||
// Preventing missing files.
|
||||
this._strings = this._tryGetLocale(activeTheme, currentLocale);
|
||||
|
||||
if (!this._strings && currentLocale !== this.defaultLocale()) {
|
||||
logging.warn(`Falling back to locales/${this.defaultLocale()}.json.`);
|
||||
this._strings = this._tryGetLocale(activeTheme, this.defaultLocale());
|
||||
}
|
||||
}
|
||||
|
||||
if (isNil(this._strings)) {
|
||||
// even if empty, themeStrings must be an object for jp.value
|
||||
this._strings = {};
|
||||
}
|
||||
|
||||
this._initializeIntl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to load a local file and parse the contents
|
||||
*
|
||||
* @param {String} activeTheme
|
||||
* @param {String} locale
|
||||
*/
|
||||
_tryGetLocale(activeTheme, locale) {
|
||||
try {
|
||||
return this._readStringsFile(config.getContentPath('themes'), activeTheme, 'locales', `${locale}.json`);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
if (locale !== this.defaultLocale()) {
|
||||
logging.warn(`Theme's file locales/${locale}.json not found.`);
|
||||
}
|
||||
} else if (err instanceof SyntaxError) {
|
||||
logging.error(new errors.IncorrectUsageError({
|
||||
err,
|
||||
message: `Unable to parse locales/${locale}.json. Please check that it is valid JSON.`
|
||||
}));
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the current locale out of the settings cache
|
||||
*/
|
||||
_loadLocale() {
|
||||
this._locale = settingsCache.get('default_locale');
|
||||
return this._locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the lookup with JSON path
|
||||
*
|
||||
* @param {String} msgPath
|
||||
*/
|
||||
_getCandidateString(msgPath) {
|
||||
// Both jsonpath's dot-notation and bracket-notation start with '$'
|
||||
// E.g.: $.store.book.title or $['store']['book']['title']
|
||||
// The {{t}} translation helper passes the default English text
|
||||
// The full Unicode jsonpath with '$' is built here
|
||||
// jp.stringify and jp.value are jsonpath methods
|
||||
// Info: https://www.npmjs.com/package/jsonpath
|
||||
let path = jp.stringify(['$', msgPath]);
|
||||
return jp.value(this._strings, path) || msgPath;
|
||||
}
|
||||
}
|
||||
|
||||
let themeI18n = new ThemeI18n();
|
||||
|
||||
// /**
|
||||
// * When active theme changes, we reload theme translations
|
||||
// * We listen on the service event, because of the following known case:
|
||||
// * 1. you override a theme, which is already active
|
||||
// * 2. The data has not changed, no event is triggered.
|
||||
// */
|
||||
events.on('services.themes.activated', function () {
|
||||
themeI18n.init();
|
||||
});
|
||||
|
||||
/**
|
||||
* When locale changes, we reload theme translations
|
||||
*/
|
||||
events.on('settings.default_locale.edited', function () {
|
||||
themeI18n.init();
|
||||
});
|
||||
|
||||
module.exports = themeI18n;
|
|
@ -5,6 +5,7 @@ const themeLoader = require('./loader');
|
|||
const active = require('./active');
|
||||
const activate = require('./activate');
|
||||
const validate = require('./validate');
|
||||
const i18n = require('./i18n');
|
||||
const list = require('./list');
|
||||
const settingsCache = require('../../../server/services/settings/cache');
|
||||
const engineDefaults = require('./engines/defaults');
|
||||
|
@ -15,6 +16,8 @@ module.exports = {
|
|||
init: function initThemes() {
|
||||
var activeThemeName = settingsCache.get('active_theme');
|
||||
|
||||
i18n.init();
|
||||
|
||||
debug('init themes', activeThemeName);
|
||||
|
||||
// Register a listener for server-start to load all themes
|
||||
|
|
|
@ -1,53 +1,39 @@
|
|||
const supportedLocales = ['en'],
|
||||
chalk = require('chalk'),
|
||||
fs = require('fs-extra'),
|
||||
MessageFormat = require('intl-messageformat'),
|
||||
jp = require('jsonpath'),
|
||||
isString = require('lodash/isString'),
|
||||
isObject = require('lodash/isObject'),
|
||||
isEqual = require('lodash/isEqual'),
|
||||
merge = require('lodash/merge'),
|
||||
path = require('path'),
|
||||
config = require('../../config'),
|
||||
errors = require('./errors'),
|
||||
events = require('./events'),
|
||||
logging = require('./logging'),
|
||||
settingsCache = require('../../services/settings/cache'),
|
||||
_private = {};
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const MessageFormat = require('intl-messageformat');
|
||||
const jp = require('jsonpath');
|
||||
const isString = require('lodash/isString');
|
||||
const isObject = require('lodash/isObject');
|
||||
const isEqual = require('lodash/isEqual');
|
||||
const isNil = require('lodash/isNil');
|
||||
const merge = require('lodash/merge');
|
||||
const get = require('lodash/get');
|
||||
const {errors, logging} = require('./');
|
||||
|
||||
// currentLocale, dynamically based on overall settings (key = "default_locale") in the settings db table
|
||||
// (during Ghost's initialization, settings available inside i18n functions below; see core/server/index.js)
|
||||
//
|
||||
// E.g.: en = English (default), es = Spanish, en-US = American English, etc.
|
||||
// Standard:
|
||||
// Language tags in HTML and XML
|
||||
// https://www.w3.org/International/articles/language-tags/
|
||||
//
|
||||
// The corresponding translation files should be at content/themes/mytheme/locales/es.json, etc.
|
||||
let currentLocale,
|
||||
activeTheme,
|
||||
coreStrings,
|
||||
themeStrings,
|
||||
I18n;
|
||||
class I18n {
|
||||
constructor(locale) {
|
||||
this._locale = locale || this.defaultLocale();
|
||||
this._strings = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* When active theme changes, we reload theme translations
|
||||
* We listen on the service event, because of the following known case:
|
||||
* 1. you override a theme, which is already active
|
||||
* 2. The data has not changed, no event is triggered.
|
||||
*/
|
||||
events.on('services.themes.activated', function () {
|
||||
I18n.loadThemeTranslations();
|
||||
});
|
||||
/**
|
||||
* English is our default locale
|
||||
*/
|
||||
defaultLocale() {
|
||||
return 'en';
|
||||
}
|
||||
|
||||
/**
|
||||
* When locale changes, we reload theme translations
|
||||
*/
|
||||
events.on('settings.default_locale.edited', function () {
|
||||
I18n.loadThemeTranslations();
|
||||
});
|
||||
supportedLocales() {
|
||||
return [this.defaultLocale()];
|
||||
}
|
||||
|
||||
I18n = {
|
||||
/**
|
||||
* Exporting the current locale (e.g. "en") to make it available for other files as well,
|
||||
* such as core/frontend/helpers/date.js and core/frontend/helpers/lang.js
|
||||
*/
|
||||
locale() {
|
||||
return this._locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to find and compile the given data context with a proper string resource.
|
||||
|
@ -56,15 +42,10 @@ I18n = {
|
|||
* @param {object} [bindings]
|
||||
* @returns {string}
|
||||
*/
|
||||
t: function t(path, bindings) {
|
||||
let string, isTheme, msg;
|
||||
t(path, bindings) {
|
||||
let string, msg;
|
||||
|
||||
currentLocale = I18n.locale();
|
||||
if (bindings !== undefined) {
|
||||
isTheme = bindings.isThemeString;
|
||||
delete bindings.isThemeString;
|
||||
}
|
||||
string = I18n.findString(path, {isThemeString: isTheme});
|
||||
string = this._findString(path);
|
||||
|
||||
// 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
|
||||
|
@ -72,36 +53,55 @@ I18n = {
|
|||
if (Array.isArray(string)) {
|
||||
msg = [];
|
||||
string.forEach(function (s) {
|
||||
let m = new MessageFormat(s, currentLocale);
|
||||
|
||||
try {
|
||||
m.format(bindings);
|
||||
} catch (err) {
|
||||
logging.error(err.message);
|
||||
|
||||
// fallback
|
||||
m = new MessageFormat(coreStrings.errors.errors.anErrorOccurred, currentLocale);
|
||||
m = msg.format();
|
||||
}
|
||||
|
||||
msg.push(m);
|
||||
msg.push(this._formatMessage(s, bindings));
|
||||
});
|
||||
} else {
|
||||
msg = new MessageFormat(string, currentLocale);
|
||||
|
||||
try {
|
||||
msg = msg.format(bindings);
|
||||
} catch (err) {
|
||||
logging.error(err.message);
|
||||
|
||||
// fallback
|
||||
msg = new MessageFormat(coreStrings.errors.errors.anErrorOccurred, currentLocale);
|
||||
msg = msg.format();
|
||||
}
|
||||
msg = this._formatMessage(string, bindings);
|
||||
}
|
||||
|
||||
return msg;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup i18n support:
|
||||
* - Load proper language file into memory
|
||||
*/
|
||||
init() {
|
||||
// This function is called during Ghost's initialization.
|
||||
// Reading translation file for messages from core .json files and keeping its content in memory
|
||||
// The English file is always loaded, until back-end translations are enabled in future versions.
|
||||
try {
|
||||
this._strings = this._readStringsFile(__dirname, '..', '..', 'translations', `${this.defaultLocale()}.json`);
|
||||
} catch (err) {
|
||||
this._strings = null;
|
||||
throw err;
|
||||
}
|
||||
|
||||
this._initializeIntl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a key exists in the loaded strings
|
||||
* @param {String} msgPath
|
||||
*/
|
||||
doesTranslationKeyExist(msgPath) {
|
||||
const translation = this._findString(msgPath, {log: false});
|
||||
return translation !== this._fallbackError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the lookup with JSON path
|
||||
*
|
||||
* @param {String} msgPath
|
||||
*/
|
||||
_getCandidateString(msgPath) {
|
||||
// Backend messages use dot-notation, and the '$.' prefix is added here
|
||||
// While bracket-notation allows any Unicode characters in keys for themes,
|
||||
// dot-notation allows only word characters in keys for backend messages
|
||||
// (that is \w or [A-Za-z0-9_] in RegExp)
|
||||
let path = `$.${msgPath}`;
|
||||
return jp.value(this._strings, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse JSON file for matching locale, returns string giving path.
|
||||
|
@ -109,42 +109,22 @@ I18n = {
|
|||
* @param {string} msgPath Path with in the JSON language file to desired string (ie: "errors.init.jsNotBuilt")
|
||||
* @returns {string}
|
||||
*/
|
||||
findString: function findString(msgPath, opts) {
|
||||
_findString(msgPath, opts) {
|
||||
const options = merge({log: true}, opts || {});
|
||||
let candidateString, matchingString, path;
|
||||
let candidateString, matchingString;
|
||||
|
||||
// no path? no string
|
||||
if (msgPath.length === 0 || !isString(msgPath)) {
|
||||
chalk.yellow('i18n.t() - received an empty path.');
|
||||
logging.warn('i18n.t() - received an empty path.');
|
||||
return '';
|
||||
}
|
||||
|
||||
// If not in memory, load translations for core
|
||||
if (coreStrings === undefined) {
|
||||
I18n.init();
|
||||
if (isNil(this._strings)) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
if (options.isThemeString) {
|
||||
// If not in memory, load translations for theme
|
||||
if (themeStrings === undefined) {
|
||||
I18n.loadThemeTranslations();
|
||||
}
|
||||
// Both jsonpath's dot-notation and bracket-notation start with '$'
|
||||
// E.g.: $.store.book.title or $['store']['book']['title']
|
||||
// The {{t}} translation helper passes the default English text
|
||||
// The full Unicode jsonpath with '$' is built here
|
||||
// jp.stringify and jp.value are jsonpath methods
|
||||
// Info: https://www.npmjs.com/package/jsonpath
|
||||
path = jp.stringify(['$', msgPath]);
|
||||
candidateString = jp.value(themeStrings, path) || msgPath;
|
||||
} else {
|
||||
// Backend messages use dot-notation, and the '$.' prefix is added here
|
||||
// While bracket-notation allows any Unicode characters in keys for themes,
|
||||
// dot-notation allows only word characters in keys for backend messages
|
||||
// (that is \w or [A-Za-z0-9_] in RegExp)
|
||||
path = `$.${msgPath}`;
|
||||
candidateString = jp.value(coreStrings, path);
|
||||
}
|
||||
candidateString = this._getCandidateString(msgPath);
|
||||
|
||||
matchingString = candidateString || {};
|
||||
|
||||
|
@ -155,126 +135,78 @@ I18n = {
|
|||
}));
|
||||
}
|
||||
|
||||
matchingString = coreStrings.errors.errors.anErrorOccurred;
|
||||
matchingString = this._fallbackError();
|
||||
}
|
||||
|
||||
return matchingString;
|
||||
},
|
||||
|
||||
doesTranslationKeyExist: function doesTranslationKeyExist(msgPath) {
|
||||
const translation = I18n.findString(msgPath, {log: false});
|
||||
return translation !== coreStrings.errors.errors.anErrorOccurred;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup i18n support:
|
||||
* - Load proper language file into memory
|
||||
* Resolve filepath, read file, and attempt a parse
|
||||
* Error handling to be done by consumer
|
||||
*
|
||||
* @param {...String} pathParts
|
||||
*/
|
||||
init: function init() {
|
||||
// This function is called during Ghost's initialization.
|
||||
// Reading translation file for messages from core .js files and keeping its content in memory
|
||||
// The English file is always loaded, until back-end translations are enabled in future versions.
|
||||
// Before that, see previous tasks on issue #6526 (error codes or identifiers, error message
|
||||
// translation at the point of display...)
|
||||
coreStrings = fs.readFileSync(path.join(__dirname, '..', '..', 'translations', 'en.json'));
|
||||
_readStringsFile(...pathParts) {
|
||||
const content = fs.readFileSync(path.join(...pathParts));
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the string using the correct locale and applying any bindings
|
||||
* @param {String} string
|
||||
* @param {Object} bindings
|
||||
*/
|
||||
_formatMessage(string, bindings) {
|
||||
let currentLocale = this.locale();
|
||||
let msg = new MessageFormat(string, currentLocale);
|
||||
|
||||
// if translation file is not valid, you will see an error
|
||||
try {
|
||||
coreStrings = JSON.parse(coreStrings);
|
||||
msg = msg.format(bindings);
|
||||
} catch (err) {
|
||||
coreStrings = undefined;
|
||||
throw err;
|
||||
logging.error(err.message);
|
||||
|
||||
// fallback
|
||||
msg = new MessageFormat(this._fallbackError(), currentLocale);
|
||||
msg = msg.format();
|
||||
}
|
||||
|
||||
_private.initializeIntl();
|
||||
},
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup i18n support for themes:
|
||||
* - Load proper language file into memory
|
||||
* [Private] Setup i18n support:
|
||||
* - Polyfill node.js if it does not have Intl support or support for a particular locale
|
||||
*/
|
||||
loadThemeTranslations() {
|
||||
// This function is called during theme initialization, and when switching language or theme.
|
||||
currentLocale = I18n.locale();
|
||||
activeTheme = settingsCache.get('active_theme');
|
||||
themeStrings = undefined;
|
||||
_initializeIntl() {
|
||||
let hasBuiltInLocaleData, IntlPolyfill;
|
||||
|
||||
const _tryGetLocale = (locale) => {
|
||||
try {
|
||||
const readBuffer = fs.readFileSync(
|
||||
path.join(config.getContentPath('themes'), activeTheme, 'locales', locale + '.json')
|
||||
);
|
||||
return JSON.parse(readBuffer);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
if (locale !== 'en') {
|
||||
logging.warn(`Theme's file locales/${locale}.json not found.`);
|
||||
}
|
||||
} else if (err instanceof SyntaxError) {
|
||||
logging.error(new errors.IncorrectUsageError({
|
||||
err,
|
||||
message: `Unable to parse locales/${locale}.json. Please check that it is valid JSON.`
|
||||
}));
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Reading file for current locale and active theme and keeping its content in memory
|
||||
if (activeTheme) {
|
||||
// Reading translation file for theme .hbs templates.
|
||||
// Compatibility with both old themes and i18n-capable themes.
|
||||
// Preventing missing files.
|
||||
themeStrings = _tryGetLocale(currentLocale);
|
||||
|
||||
if (!themeStrings && currentLocale !== 'en') {
|
||||
logging.warn('Falling back to locales/en.json.');
|
||||
themeStrings = _tryGetLocale('en');
|
||||
if (global.Intl) {
|
||||
// Determine if the built-in `Intl` has the locale data we need.
|
||||
hasBuiltInLocaleData = this.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');
|
||||
}
|
||||
|
||||
if (themeStrings === undefined) {
|
||||
// even if empty, themeStrings must be an object for jp.value
|
||||
themeStrings = {};
|
||||
}
|
||||
|
||||
_private.initializeIntl();
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporting the current locale (e.g. "en") to make it available for other files as well,
|
||||
* such as core/frontend/helpers/date.js and core/frontend/helpers/lang.js
|
||||
* A really basic error for if everything goes wrong
|
||||
*/
|
||||
locale: function locale() {
|
||||
return settingsCache.get('default_locale');
|
||||
_fallbackError() {
|
||||
return get(this._strings, 'errors.errors.anErrorOccurred', 'An error occurred');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup i18n support:
|
||||
* - Polyfill node.js if it does not have Intl support or support for a particular locale
|
||||
*/
|
||||
_private.initializeIntl = function initializeIntl() {
|
||||
let hasBuiltInLocaleData, IntlPolyfill;
|
||||
|
||||
if (global.Intl) {
|
||||
// Determine if the built-in `Intl` has the locale data we need.
|
||||
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;
|
||||
module.exports = new I18n();
|
||||
module.exports.I18n = I18n;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const should = require('should'),
|
||||
settingsCache = require('../../../server/services/settings/cache'),
|
||||
helpers = require('../../../frontend/helpers'),
|
||||
proxy = require('../../../frontend/helpers/proxy');
|
||||
const should = require('should');
|
||||
const settingsCache = require('../../../server/services/settings/cache');
|
||||
const helpers = require('../../../frontend/helpers');
|
||||
const proxy = require('../../../frontend/helpers/proxy');
|
||||
|
||||
describe('{{lang}} helper', function () {
|
||||
beforeEach(function () {
|
||||
|
@ -13,7 +13,7 @@ describe('{{lang}} helper', function () {
|
|||
});
|
||||
|
||||
it('returns correct language tag', function () {
|
||||
let expected = proxy.i18n.locale(),
|
||||
let expected = proxy.themeI18n.locale(),
|
||||
rendered = helpers.lang.call();
|
||||
|
||||
should.exist(rendered);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
const should = require('should'),
|
||||
path = require('path'),
|
||||
settingsCache = require('../../../server/services/settings/cache'),
|
||||
helpers = require('../../../frontend/helpers'),
|
||||
common = require('../../../server/lib/common'),
|
||||
configUtils = require('../../utils/configUtils');
|
||||
const should = require('should');
|
||||
const path = require('path');
|
||||
const settingsCache = require('../../../server/services/settings/cache');
|
||||
const helpers = require('../../../frontend/helpers');
|
||||
const themeI18n = require('../../../frontend/services/themes/i18n');
|
||||
const configUtils = require('../../utils/configUtils');
|
||||
|
||||
describe('{{t}} helper', function () {
|
||||
beforeEach(function () {
|
||||
|
@ -18,6 +18,7 @@ describe('{{t}} helper', function () {
|
|||
|
||||
it('theme translation is DE', function () {
|
||||
settingsCache.set('default_locale', {value: 'de'});
|
||||
themeI18n.init();
|
||||
|
||||
let rendered = helpers.t.call({}, 'Top left Button', {
|
||||
hash: {}
|
||||
|
@ -28,7 +29,7 @@ describe('{{t}} helper', function () {
|
|||
|
||||
it('theme translation is EN', function () {
|
||||
settingsCache.set('default_locale', {value: 'en'});
|
||||
common.i18n.loadThemeTranslations();
|
||||
themeI18n.init();
|
||||
|
||||
let rendered = helpers.t.call({}, 'Top left Button', {
|
||||
hash: {}
|
||||
|
@ -39,7 +40,7 @@ describe('{{t}} helper', function () {
|
|||
|
||||
it('[fallback] no theme translation file found for FR', function () {
|
||||
settingsCache.set('default_locale', {value: 'fr'});
|
||||
common.i18n.loadThemeTranslations();
|
||||
themeI18n.init();
|
||||
|
||||
let rendered = helpers.t.call({}, 'Top left Button', {
|
||||
hash: {}
|
||||
|
@ -51,7 +52,7 @@ describe('{{t}} helper', function () {
|
|||
it('[fallback] no theme files at all, use key as translation', function () {
|
||||
settingsCache.set('active_theme', {value: 'casper-1.4'});
|
||||
settingsCache.set('default_locale', {value: 'de'});
|
||||
common.i18n.loadThemeTranslations();
|
||||
themeI18n.init();
|
||||
|
||||
let rendered = helpers.t.call({}, 'Top left Button', {
|
||||
hash: {}
|
||||
|
|
Loading…
Add table
Reference in a new issue