From a479841e54920e12f1722a469dcecfd6827f6085 Mon Sep 17 00:00:00 2001 From: Omri Lotan <516342+omrilotan@users.noreply.github.com> Date: Mon, 9 Dec 2019 13:23:22 +0200 Subject: [PATCH] Support a list of keys (#36) --- index.js | 37 +++++++++++++++++++++++------- package.json | 2 +- readme.md | 10 ++++++++ tests/index.js | 19 ++++++++++++++++ tests/missing-key.js | 54 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 6f51b76..48aeb1d 100644 --- a/index.js +++ b/index.js @@ -38,8 +38,8 @@ class I18n { * @param {String} key * @return {String} A default string for a missing key */ - static getDefault(key = '') { - return key.split('.').pop().replace(/_/g, ' '); + static getDefault(key) { + return (key || '').split('.').pop().replace(/_/g, ' '); } /** @@ -78,14 +78,17 @@ class I18n { */ translate(key, data) { - // Collect scopes - const scopes = [data || {}, this].map(({$scope}) => $scope).filter(Boolean); + const keys = Array.isArray(key) ? key : [ key ]; // Create key alternatives with prefixes - const alternatives = scopes.map((scope) => [scope, key].join('.')); + const alternatives = [].concat( + ...keys.map( + (key) => this.alternatives(key, data) + ) + ); // Find the first match - let result = this.find(...alternatives, key); + let result = this.find(...alternatives); // Handle one,other translation structure result = getOneOther(result, data); @@ -101,11 +104,29 @@ class I18n { } break; default: - this[MISSING].forEach((fn) => fn(key, this.$scope, this.translations)); - return I18n.getDefault(key); + this[MISSING].forEach((fn) => fn(`${key}`, this.$scope, this.translations)); + return I18n.getDefault(...keys); } } + /** + * Create key alternatives with prefixes according to instance scopes + * @param {string} key + * @param {object} data Object optionally containing '$scope' parameter + * @return {string[]} + */ + alternatives(key, data) { + return [data || {}, this].map( + ({$scope}) => $scope + ).filter( + Boolean + ).map( + (scope) => [scope, key].join('.') + ).concat( + key + ); + } + /** * find * @param {...[String]} alternatives Different alternatives of strings to find diff --git a/package.json b/package.json index 6b4dd67..54d1aad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fiverr/i18n", - "version": "1.5.8", + "version": "1.6.0", "description": "Translation helper", "keywords": [ "i18n", diff --git a/readme.md b/readme.md index b7285fb..3c2e416 100644 --- a/readme.md +++ b/readme.md @@ -24,6 +24,16 @@ const i18n = new I18n({ }); ``` +### Translate +```js +i18n.t('my.key'); // I'm a sentence +``` + +### Find alternatives +```js +i18n.t(['my.missing.key', 'my.key']); // I'm a sentence +``` + ### Add more translations after instantiation ```js i18n.add({yet: {another: {key: 'I\'m here, too!'}}}); diff --git a/tests/index.js b/tests/index.js index 2d655f0..88d9ca7 100644 --- a/tests/index.js +++ b/tests/index.js @@ -57,6 +57,13 @@ describe('I18n', () => { expect(i18n.translate('root.user.name')).to.equal('Martin'); }); + it('i18n uses keys list to find an existing key', () => { + expect(i18n.translate([ + 'root_user_name', + 'root.user.name' + ])).to.equal('Martin'); + }); + it('translate function is bound to instance', () => { const translate = i18n.translate; @@ -118,6 +125,18 @@ describe('I18n', () => { })).to.equal('I am in a different scope'); }); + it('keys arrays performs lookup in scope', () => { + expect(i18n.translate( + [ + 'i.am.not.found', + 'i.am.in.scope' + ], + { + $scope: 'another_controller_name.action_name' + } + )).to.equal('I am in a different scope'); + }); + it('prefers contextual string to non contextual (found in scope)', () => { const more = { i: { diff --git a/tests/missing-key.js b/tests/missing-key.js index 9a2d4e2..cdfd34a 100644 --- a/tests/missing-key.js +++ b/tests/missing-key.js @@ -4,9 +4,11 @@ const I18n = require('../'); describe('missing keys report', () => { it('reports missing keys', () => { + let reported = false; const i18n = new I18n({ $scope: 'some.scope', missing: (key, scope, translations) => { + reported = true; expect(scope).to.equal('some.scope'); expect(key).to.equal('a.missing.key'); expect(translations).to.be.an('object'); @@ -15,6 +17,58 @@ describe('missing keys report', () => { }); i18n.translate('a.missing.key'); + expect(reported).to.be.true; + }); + + it('reports missing key for undefined', () => { + let reported = false; + const i18n = new I18n({ + $scope: 'some.scope', + missing: (key, scope, translations) => { + reported = true; + expect(scope).to.equal('some.scope'); + expect(key).to.equal('undefined'); + expect(translations).to.be.an('object'); + expect(translations).to.be.empty; + } + }); + + i18n.translate(); + expect(reported).to.be.true; + }); + + it('reports missing keys for arrays', () => { + let reported = false; + const i18n = new I18n({ + $scope: 'some.scope', + missing: (key, scope, translations) => { + reported = true; + expect(scope).to.equal('some.scope'); + expect(key).to.equal('a.missing.key,another.missing.key'); + expect(translations).to.be.an('object'); + expect(translations).to.be.empty; + } + }); + + i18n.translate(['a.missing.key', 'another.missing.key']); + expect(reported).to.be.true; + }); + + it('does not report missing key when one of the keys was found', () => { + let reported = false; + const i18n = new I18n({ + $scope: 'some.scope', + missing: () => { + reported = true; + }, + translations: { + fallback: 'falling back' + } + }); + + const translation = i18n.translate(['a.missing.key', 'fallback']); + expect(reported).to.be.false; + expect(translation).to.equal('falling back'); }); it('reports missing keys using "onmiss"', () => {