Skip to content

Commit

Permalink
feat(l10n): ResourceManager
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjamin Strauß committed Jan 24, 2018
1 parent e7d3ca4 commit 8b82ffd
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 16 deletions.
9 changes: 1 addition & 8 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,7 @@ module.exports = config => {
files: [
'./bin/test.min.js',
{
pattern: 'test-assets/json/*.json',
watched: false,
included: false,
served: true,
nocache: false
},
{
pattern: 'test-assets/l10n/de-DE/*.json',
pattern: 'test-assets/**/*.*',
watched: false,
included: false,
served: true,
Expand Down
178 changes: 178 additions & 0 deletions src/l10n/resource_manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
goog.module('clulib.l10n.ResourceManager');

const ResourceBundle = goog.require('clulib.l10n.ResourceBundle');

/**
* Manages multiple ResourceBundles for multiple locales.
*
* Loads json localization files via pattern `baseUrl/locale/id.json`.
*/
class ResourceManager {
/**
* @param {Array<string>} bundleIds
* @param {Array<string>} locales
* @param {string} baseUrl
*/
constructor (bundleIds, locales, baseUrl) {
/**
* @type {Array<string>}
* @private
*/
this.bundleIds_ = bundleIds;

/**
* @type {Array<string>}
* @private
*/
this.locales_ = locales;

/**
* @type {string}
* @private
*/
this.baseUrl_ = baseUrl;

/**
* @type {?string}
* @private
*/
this.currentLocale_ = null;

/**
* @type {Map<string, Map<string, ResourceBundle>>}
* @private
*/
this.bundles_ = new Map();

locales.forEach(locale => {
const localeMap = new Map();

bundleIds.forEach(bundleId => {
localeMap.set(bundleId, new ResourceBundle(bundleId, locale, baseUrl));
});

this.bundles_.set(locale, localeMap);
});
}

/**
* @returns {?string}
*/
get currentLocale () {
return this.currentLocale_;
}

/**
* Checks if the locale is valid for this ResourceManager
*
* @param {string} locale
* @returns {boolean}
*/
isLocaleValid (locale) {
return this.locales_.includes(locale);
}

// eslint-disable-next-line valid-jsdoc
/**
* Changes the current locale, loads the resource files if necessary.
*
* @param {string} locale
* @returns {Promise<void>}
*/
async changeLocale (locale) {
if (!this.isLocaleValid(locale))
throw new Error(`Locale '${locale}' is not registered with this ResourceManager.`);

const bundleLoaders = Array.from(this.bundles_.get(locale).values())
.map(bundle => bundle.load());

await Promise.all(bundleLoaders);

this.currentLocale_ = locale;
}

/**
* Gets a specific bundle for the current locale.
*
* @param {string} bundleId
* @returns {ResourceBundle}
*/
getBundle (bundleId) {
if (this.currentLocale_ == null)
throw new Error('No locale set!');

return this.bundles_.get(/** @type {!string} */ (this.currentLocale_)).get(bundleId);
}

/**
* Checks if a bundle contains a localization key.
*
* @param {string} bundleId
* @param {string} key
* @returns {boolean}
*/
contains (bundleId, key) {
return this.getBundle(bundleId).contains(key);
}

/**
* Gets an object from the json localization file of the current bundle.
*
* @param {string} bundleId
* @param {string} key
* @returns {Object}
*/
getObject (bundleId, key) {
return this.getBundle(bundleId).getObject(key);
}

/**
* Gets an array from the json localization file of the current bundle.
*
* @param {string} bundleId
* @param {string} key
* @returns {Array}
*/
getArray (bundleId, key) {
return this.getBundle(bundleId).getArray(key);
}

/**
* Gets a boolean from the json localization file of the current bundle.
*
* @param {string} bundleId
* @param {string} key
* @returns {boolean}
*/
getBoolean (bundleId, key) {
return this.getBundle(bundleId).getBoolean(key);
}

/**
* Gets a number from the json localization file of the current bundle.
*
* @param {string} bundleId
* @param {string} key
* @returns {number}
*/
getNumber (bundleId, key) {
return this.getBundle(bundleId).getNumber(key);
}

/**
* Gets a string from the json localization file of the current bundle.
*
* Takes an optional `replaceObject` which will replace placeholder keys in the string.
* An object with key `foo` will replace all placeholders `{foo}`.
*
* @param {string} bundleId
* @param {string} key
* @param {Object=} replaceObject
* @returns {string}
*/
getString (bundleId, key, replaceObject = null) {
return this.getBundle(bundleId).getString(key, replaceObject);
}
}

exports = ResourceManager;
10 changes: 5 additions & 5 deletions test-assets/l10n/de-DE/test.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"string": "Hello, world!",
"string": "Hallo, Welt!",
"array": [
0,
1,
2
],
"object": {
"one": 1,
"two": 2,
"three": 3
"eins": 1,
"zwei": 2,
"drei": 3
},
"number": 10,
"boolean": false,
"string-placeholder": "Hello {to}, my name is {from}."
"string-placeholder": "Hallo {to}, mein name ist {from}."
}
16 changes: 16 additions & 0 deletions test-assets/l10n/en-GB/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"string": "Hello, world!",
"array": [
0,
1,
2
],
"object": {
"one": 1,
"two": 2,
"three": 3
},
"number": 10,
"boolean": false,
"string-placeholder": "Hello {to}, my name is {from}."
}
6 changes: 3 additions & 3 deletions test/l10n/resource_bundle_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ exports = function () {
expect(bundle.getArray('array')).toEqual([0, 1, 2]);
expect(bundle.getBoolean('boolean')).toBe(false);
expect(bundle.getNumber('number')).toBe(10);
expect(bundle.getString('string')).toBe('Hello, world!');
expect(bundle.getObject('object')).toEqual({'one': 1, 'two': 2, 'three': 3});
expect(bundle.getString('string')).toBe('Hallo, Welt!');
expect(bundle.getObject('object')).toEqual({'eins': 1, 'zwei': 2, 'drei': 3});

expect(bundle.getString('string-placeholder', {'from': 'Bob', 'to': 'Max'}))
.toBe('Hello Max, my name is Bob.');
.toBe('Hallo Max, mein name ist Bob.');
});
});
};
42 changes: 42 additions & 0 deletions test/l10n/resource_manager_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
goog.module('test.clulib.l10n.ResourceManager');

const ResourceManager = goog.require('clulib.l10n.ResourceManager');
const env = goog.require('testing.environment');

const base = `${env.basePath}/test-assets/l10n`;

exports = function () {
describe('clulib.l10n.ResourceManager', () => {
it('should load resource files in different languages', async () => {
const manager = new ResourceManager(['test'], ['de-DE', 'en-GB'], base);

expect(manager.currentLocale).toBe(null);

await manager.changeLocale('de-DE');

expect(manager.contains('test', 'string-placeholder')).toBe(true);

expect(manager.getArray('test', 'array')).toEqual([0, 1, 2]);
expect(manager.getBoolean('test', 'boolean')).toBe(false);
expect(manager.getNumber('test', 'number')).toBe(10);
expect(manager.getString('test', 'string')).toBe('Hallo, Welt!');
expect(manager.getObject('test', 'object')).toEqual({'eins': 1, 'zwei': 2, 'drei': 3});

expect(manager.getString('test', 'string-placeholder', {'from': 'Bob', 'to': 'Max'}))
.toBe('Hallo Max, mein name ist Bob.');

await manager.changeLocale('en-GB');

expect(manager.contains('test', 'string-placeholder')).toBe(true);

expect(manager.getArray('test', 'array')).toEqual([0, 1, 2]);
expect(manager.getBoolean('test', 'boolean')).toBe(false);
expect(manager.getNumber('test', 'number')).toBe(10);
expect(manager.getString('test', 'string')).toBe('Hello, world!');
expect(manager.getObject('test', 'object')).toEqual({'one': 1, 'two': 2, 'three': 3});

expect(manager.getString('test', 'string-placeholder', {'from': 'Bob', 'to': 'Max'}))
.toBe('Hello Max, my name is Bob.');
});
});
};
2 changes: 2 additions & 0 deletions test/test_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const collectionsMain = goog.require('test.clulib.collections');
const domMain = goog.require('test.clulib.dom');
const functionsMain = goog.require('test.clulib.functions');
const resourceBundleMain = goog.require('test.clulib.l10n.ResourceBundle');
const resourceManagerMain = goog.require('test.clulib.l10n.ResourceManager');
const httpRequestMain = goog.require('test.clulib.net.http_request');
const mathMain = goog.require('test.clulib.math');

Expand All @@ -17,5 +18,6 @@ collectionsMain();
domMain();
functionsMain();
resourceBundleMain();
resourceManagerMain();
httpRequestMain();
mathMain();

0 comments on commit 8b82ffd

Please sign in to comment.