mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
Improved i18n with unified getCandidateString fn
- the core i18n library and theme i18n library have slightly different methods of getting a candidate string - both of them use forms of jsonpath, meaning they both require jsonpath as a dependency - to try to get to a point of being able to rip more things out of ghost, we want to have less dependencies - so instead of overloading the method, we pass in a stringMode as an argument - eventually we might not need an overloaded class for themeI18n at all, which would simplify the codebase
This commit is contained in:
parent
470f2a8728
commit
d8318654a9
3 changed files with 86 additions and 27 deletions
|
@ -3,13 +3,14 @@ const i18n = require('../../../../shared/i18n');
|
|||
const logging = require('../../../../shared/logging');
|
||||
const settingsCache = require('../../../../server/services/settings/cache');
|
||||
const config = require('../../../../shared/config');
|
||||
const jp = require('jsonpath');
|
||||
|
||||
const isNil = require('lodash/isNil');
|
||||
|
||||
class ThemeI18n extends i18n.I18n {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
// We don't care what gets passed in, themes use fulltext mode
|
||||
this._stringMode = 'fulltext';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,22 +76,6 @@ class ThemeI18n extends i18n.I18n {
|
|||
this._locale = settingsCache.get('lang');
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ThemeI18n;
|
||||
|
|
|
@ -14,6 +14,7 @@ const logging = require('../logging');
|
|||
class I18n {
|
||||
constructor(options = {}) {
|
||||
this._locale = options.locale || this.defaultLocale();
|
||||
this._stringMode = options.stringMode || 'dot';
|
||||
this._strings = null;
|
||||
}
|
||||
|
||||
|
@ -69,17 +70,23 @@ class I18n {
|
|||
* - Load proper language file into memory
|
||||
*/
|
||||
init() {
|
||||
// This function is called during Ghost's initialization.
|
||||
this._strings = this._loadStrings();
|
||||
|
||||
this._initializeIntl();
|
||||
}
|
||||
|
||||
_loadStrings() {
|
||||
let strings;
|
||||
// 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`);
|
||||
strings = this._readStringsFile(__dirname, 'translations', `${this.defaultLocale()}.json`);
|
||||
} catch (err) {
|
||||
this._strings = null;
|
||||
strings = null;
|
||||
throw err;
|
||||
}
|
||||
|
||||
this._initializeIntl();
|
||||
return strings;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,17 +99,29 @@ class I18n {
|
|||
}
|
||||
|
||||
/**
|
||||
* Do the lookup with JSON path
|
||||
* Do the lookup within the JSON file using jsonpath
|
||||
*
|
||||
* @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)
|
||||
// Our default string mode is "dot" for dot-notation, e.g. $.something.like.this used in the backend
|
||||
// Both jsonpath's dot-notation and bracket-notation start with '$' E.g.: $.store.book.title or $['store']['book']['title']
|
||||
// While bracket-notation allows any Unicode characters in keys (i.e. for themes / fulltext mode) E.g. $['Read more']
|
||||
// dot-notation allows only word characters in keys for backend messages (that is \w or [A-Za-z0-9_] in RegExp)
|
||||
let jsonPath = `$.${msgPath}`;
|
||||
return jp.value(this._strings, jsonPath);
|
||||
let fallback = null;
|
||||
|
||||
if (this._stringMode === 'fulltext') {
|
||||
jsonPath = jp.stringify(['$', msgPath]);
|
||||
// In fulltext mode we can use the passed string as a fallback
|
||||
fallback = msgPath;
|
||||
}
|
||||
|
||||
try {
|
||||
return jp.value(this._strings, jsonPath) || fallback;
|
||||
} catch (error) {
|
||||
throw new errors.IncorrectUsageError({message: `i18n.t() called with an invalid path: ${msgPath}`});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const should = require('should');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const I18n = require('../../../core/shared/i18n').I18n;
|
||||
|
||||
|
@ -7,4 +8,58 @@ describe('I18n Class Behaviour', function () {
|
|||
const i18n = new I18n();
|
||||
i18n.locale().should.eql('en');
|
||||
});
|
||||
|
||||
describe('dot notation (default behaviour)', function () {
|
||||
const fakeStrings = {
|
||||
test: {string: {path: 'I am correct'}}
|
||||
};
|
||||
let i18n;
|
||||
|
||||
beforeEach(function initBasicI18n() {
|
||||
i18n = new I18n();
|
||||
sinon.stub(i18n, '_loadStrings').returns(fakeStrings);
|
||||
i18n.init();
|
||||
});
|
||||
|
||||
it('correctly loads strings', function () {
|
||||
i18n._strings.should.eql(fakeStrings);
|
||||
});
|
||||
|
||||
it('correctly uses dot notation', function () {
|
||||
i18n.t('test.string.path').should.eql('I am correct');
|
||||
});
|
||||
|
||||
it('uses fallback correctly', function () {
|
||||
i18n.t('unknown.string').should.eql('An error occurred');
|
||||
});
|
||||
|
||||
it('errors for invalid strings', function () {
|
||||
should(function () {
|
||||
i18n.t('unknown string');
|
||||
}).throw('i18n.t() called with an invalid path: unknown string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fulltext notation (theme behaviour)', function () {
|
||||
const fakeStrings = {'Full text': 'I am correct'};
|
||||
let i18n;
|
||||
|
||||
beforeEach(function initFulltextI18n() {
|
||||
i18n = new I18n({stringMode: 'fulltext'});
|
||||
sinon.stub(i18n, '_loadStrings').returns(fakeStrings);
|
||||
i18n.init();
|
||||
});
|
||||
|
||||
it('correctly loads strings', function () {
|
||||
i18n._strings.should.eql(fakeStrings);
|
||||
});
|
||||
|
||||
it('correctly uses fulltext with bracket notation', function () {
|
||||
i18n.t('Full text').should.eql('I am correct');
|
||||
});
|
||||
|
||||
it('uses fallback correctly', function () {
|
||||
i18n.t('unknown string').should.eql('unknown string');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue