From 38d575bae59b284fe4095c08fae09dcde9794f72 Mon Sep 17 00:00:00 2001 From: martinandert Date: Fri, 27 Mar 2015 10:19:33 +0100 Subject: [PATCH] add fallback locale support --- index.js | 49 ++++++++++++++++++++++++++++++++++++++++------- spec.js | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index a981280..bae3ae9 100644 --- a/index.js +++ b/index.js @@ -19,13 +19,32 @@ function isFunction(val) { return typeof val === 'function' || Object.prototype.toString.call(val) === '[object Function]'; } +function isPlainObject(val) { + return Object.prototype.toString.call(val) === '[object Object]'; +} + function isSymbol(key) { return isString(key) && key[0] === ':'; } +function hasOwnProp(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); +} + +function getEntry(translations, keys) { + return keys.reduce(function(result, key) { + if (isPlainObject(result) && hasOwnProp(result, key)) { + return result[key]; + } else { + return null; + } + }, translations); +} + function Counterpart() { this._registry = { locale: 'en', + fallbackLocale: null, scope: null, translations: {}, interpolations: {}, @@ -54,6 +73,16 @@ Counterpart.prototype.setLocale = function(value) { return previous; }; +Counterpart.prototype.getFallbackLocale = function() { + return this._registry.fallbackLocale; +}; + +Counterpart.prototype.setFallbackLocale = function(value) { + var previous = this._registry.fallbackLocale; + this._registry.fallbackLocale = value; + return previous; +}; + Counterpart.prototype.getSeparator = function() { return this._registry.separator; }; @@ -105,20 +134,26 @@ Counterpart.prototype.translate = function(key, options) { var separator = options.separator || this._registry.separator; delete options.separator; + var fallbackLocale = options.fallbackLocale || this._registry.fallbackLocale; + delete options.fallbackLocale; + var keys = this._normalizeKeys(locale, scope, key, separator); - var entry = keys.reduce(function(result, key) { - if (Object.prototype.toString.call(result) === '[object Object]' && Object.prototype.hasOwnProperty.call(result, key)) { - return result[key]; - } else { - return null; - } - }, this._registry.translations); + var entry = getEntry(this._registry.translations, keys); if (entry === null && options.fallback) { entry = this._fallback(locale, scope, key, options.fallback, options); } + if (entry === null && fallbackLocale && locale !== fallbackLocale) { + var fallbackKeys = this._normalizeKeys(fallbackLocale, scope, key, separator); + entry = getEntry(this._registry.translations, fallbackKeys); + + if (entry) { + locale = fallbackLocale; + } + } + if (entry === null) { entry = 'missing translation: ' + keys.join(separator); } diff --git a/spec.js b/spec.js index 7f46017..d7010b4 100644 --- a/spec.js +++ b/spec.js @@ -275,6 +275,33 @@ describe('translate', function() { assert.matches(instance.translate('missing', { fallback: [':also_missing', ':foo.missed'] }), /missing translation/); }); }); + + describe('with a global `fallbackLocale` present', function() { + it('returns the entry of the fallback locale', function() { + instance.registerTranslations('de', { bar: { baz: 'bam' } }); + instance.registerTranslations('de', { hello: 'Hallo %(name)s!' }); + + assert.equal(instance.translate('baz', { locale: 'foo', scope: 'bar' }), 'missing translation: foo.bar.baz'); + assert.equal(instance.translate('hello', { locale: 'foo', name: 'Martin' }), 'missing translation: foo.hello'); + + var previousFallbackLocale = instance.setFallbackLocale('de'); + + assert.equal(instance.translate('baz', { locale: 'foo', scope: 'bar' }), 'bam'); + assert.equal(instance.translate('hello', { locale: 'foo', name: 'Martin' }), 'Hallo Martin!'); + + instance.setFallbackLocale(previousFallbackLocale); + }); + }); + + describe('with a `fallbackLocale` provided as option', function() { + it('returns the entry of the fallback locale', function() { + instance.registerTranslations('en', { bar: { baz: 'bam' } }); + instance.registerTranslations('en', { hello: 'Hello, %(name)s!' }); + + assert.equal(instance.translate('baz', { locale: 'foo', scope: 'bar', fallbackLocale: 'en' }), 'bam'); + assert.equal(instance.translate('hello', { locale: 'foo', fallbackLocale: 'en', name: 'Martin' }), 'Hello, Martin!'); + }); + }); }); }); @@ -345,6 +372,37 @@ describe('translate', function() { }); }); + describe('#getFallbackLocale', function() { + it('is a function', function() { + assert.isFunction(instance.getFallbackLocale); + }); + + it('returns the fallback locale stored in the registry', function() { + assert.equal(instance.getFallbackLocale(), instance._registry.fallbackLocale); + }); + + it('returns null by default', function() { + assert.strictEqual(instance.getFallbackLocale(), null); + }); + }); + + describe('#setFallbackLocale', function() { + it('is a function', function() { + assert.isFunction(instance.setFallbackLocale); + }); + + it('sets the fallback locale stored in the registry', function() { + instance.setFallbackLocale('foo'); + assert.equal(instance._registry.fallbackLocale, 'foo'); + }); + + it('returns the previous fallback locale that was stored in the registry', function() { + var current = instance.getFallbackLocale(); + var previous = instance.setFallbackLocale(current + 'x'); + assert.equal(previous, current); + }); + }); + describe('#withLocale', function() { it('is a function', function() { assert.isFunction(instance.withLocale);