Skip to content

Commit

Permalink
feat(l10n): ResourceBundle
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjamin Strauß committed Jan 24, 2018
1 parent 9208f11 commit fed6d09
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 0 deletions.
187 changes: 187 additions & 0 deletions src/l10n/resource_bundle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
goog.module('clulib.l10n.ResourceBundle');

const {httpGetJson} = goog.require('clulib.net.http_request');
const {objectToMap} = goog.require('clulib.collections');
const {cacheAsyncValue} = goog.require('clulib.functions');

/**
* A resource bundle containing localized messages.
*
* Loads json localization files via pattern `baseUrl/locale/id.json`
*/
class ResourceBundle {
/**
* @param {string} id
* @param {string} locale
* @param {string} baseUrl
*/
constructor (id, locale, baseUrl) {
/**
* @type {string}
* @private
*/
this.id_ = id;

/**
* @type {string}
* @private
*/
this.locale_ = locale;

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

/**
* @type {boolean}
* @private
*/
this.loaded_ = false;

/**
* @type {Map<string, *>}
* @private
*/
this.data_ = null;

/**
* @type {function():Promise<clulib.net.http_request.HttpResult>}
* @private
*/
this.loadJson_ = cacheAsyncValue(() => {
const url = `${this.baseUrl_}/${this.locale_}/${this.id_}.json`;
return httpGetJson(url).promise;
});
}

/**
* @returns {string}
*/
get id () {
return this.id_;
}

/**
* @returns {string}
*/
get locale () {
return this.locale_;
}

/**
* @returns {boolean}
*/
get loaded () {
return this.loaded_;
}

// eslint-disable-next-line valid-jsdoc
/**
* Loads the json file via pattern `baseUrl/locale/id.json`
*
* @returns {Promise<void>}
*/
async load () {
if (this.loaded_)
return;

const result = await this.loadJson_();
this.data_ = objectToMap(result.response);
this.loaded_ = true;
}

/**
* Checks if the bundle contains a localization key.
*
* @param {string} key
* @returns {boolean}
*/
contains (key) {
if (this.data_ == null)
throw new Error(
`ResourceBundle '${this.id_}' with locale '${this.locale_} has not yet been loaded.`
);

return this.data_.has(key);
}

/**
* @param {string} key
* @returns {*}
* @private
*/
getValue_ (key) {
if (!this.contains(key))
throw new Error(
`Provided key '${key}' could not be found in ResourceBundle '${this.id_}' with locale '${this.locale_}'.`
);

return this.data_.get(key);
}

/**
* Gets an object from the json localization file.
*
* @param {string} key
* @returns {Object}
*/
getObject (key) {
return /** @type {Object} */ (this.getValue_(key));
}

/**
* Gets an array from the json localization file.
*
* @param {string} key
* @returns {Array}
*/
getArray (key) {
return /** @type {Array} */ (this.getValue_(key));
}

/**
* Gets a boolean from the json localization file.
*
* @param {string} key
* @returns {boolean}
*/
getBoolean (key) {
return /** @type {boolean} */ (this.getValue_(key));
}

/**
* Gets a number from the json localization file.
*
* @param {string} key
* @returns {number}
*/
getNumber (key) {
return /** @type {number} */ (this.getValue_(key));
}

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

if (replaceObject != null) {
for (const [key, value] of Object.entries(replaceObject)) {
str = str.replace(new RegExp(`{${key}}`, 'g'), value);
}
}

return str;
}
}

exports = ResourceBundle;
16 changes: 16 additions & 0 deletions test-assets/l10n/de-DE/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}."
}
31 changes: 31 additions & 0 deletions test/l10n/resource_bundle_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
goog.module('test.clulib.l10n.ResourceBundle');

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

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

exports = function () {
describe('clulib.l10n.ResourceBundle', () => {
it('should load resource files', async () => {
const bundle = new ResourceBundle('test', 'de-DE', base);

expect(bundle.id).toBe('test');
expect(bundle.locale).toBe('de-DE');
expect(bundle.loaded).toBe(false);

await bundle.load();

expect(bundle.loaded).toBe(true);
expect(bundle.contains('string-placeholder')).toBe(true);
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-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 @@ -6,6 +6,7 @@ const cmMain = goog.require('test.clulib.cm');
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 httpRequestMain = goog.require('test.clulib.net.http_request');
const mathMain = goog.require('test.clulib.math');

Expand All @@ -15,5 +16,6 @@ cmMain();
collectionsMain();
domMain();
functionsMain();
resourceBundleMain();
httpRequestMain();
mathMain();

0 comments on commit fed6d09

Please sign in to comment.