diff --git a/.travis.yml b/.travis.yml index 0ef616f7..5c9e61a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,12 @@ language: node_js node_js: - 0.10 - - 0.11 - 0.12 - 4 - 5 + - 6 + - 7 + - 8 - iojs branches: only: diff --git a/Makefile b/Makefile index b470f232..62ea1b50 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ test: mocha cover: - istanbul cover ./node_modules/mocha/bin/_mocha + istanbul cover _mocha -- --recursive hint: jshint --verbose . diff --git a/README.md b/README.md index 984c8948..7ce3f444 100644 --- a/README.md +++ b/README.md @@ -137,8 +137,8 @@ i18n.configure({ // setup some locales - other locales default to en silently locales:['en', 'de'], - // fall back from Dutch to German - fallbacks:{'nl': 'de'}, + // fall back from Dutch to German and from any localized German (de-at, de-li etc.) to German + fallbacks:{'nl': 'de', 'de-*': 'de'}, // you may alter a site wide default locale defaultLocale: 'de', diff --git a/appveyor.yml b/appveyor.yml index 1fc97dc0..b85f3a19 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,6 +4,9 @@ environment: - nodejs_version: "0.12" - nodejs_version: "4" - nodejs_version: "5" + - nodejs_version: "6" + - nodejs_version: "7" + - nodejs_version: "8" - nodejs_version: "iojs" branches: only: diff --git a/i18n.js b/i18n.js index 0870cac9..1f84b7ce 100644 --- a/i18n.js +++ b/i18n.js @@ -402,8 +402,8 @@ module.exports = (function() { } // consider a fallback - if (!locales[targetLocale] && fallbacks[targetLocale]) { - targetLocale = fallbacks[targetLocale]; + if (!locales[targetLocale]) { + targetLocale = getFallback(targetLocale, fallbacks) || targetLocale; } // now set locale on object @@ -498,8 +498,8 @@ module.exports = (function() { return locales; } - if (!locales[targetLocale] && fallbacks[targetLocale]) { - targetLocale = fallbacks[targetLocale]; + if (!locales[targetLocale]) { + targetLocale = getFallback(targetLocale, fallbacks) || targetLocale; } if (locales[targetLocale]) { @@ -688,8 +688,9 @@ module.exports = (function() { region = lr[1]; // Check if we have a configured fallback set for this language. - if (fallbacks && fallbacks[lang]) { - fallback = fallbacks[lang]; + var fallbackLang = getFallback(lang, fallbacks); + if (fallbackLang) { + fallback = fallbackLang; // Fallbacks for languages should be inserted // where the original, unsupported language existed. var acceptedLanguageIndex = acceptedLanguages.indexOf(lang); @@ -701,8 +702,9 @@ module.exports = (function() { } // Check if we have a configured fallback set for the parent language of the locale. - if (fallbacks && fallbacks[parentLang]) { - fallback = fallbacks[parentLang]; + var fallbackParentLang = getFallback(parentLang, fallbacks); + if (fallbackParentLang) { + fallback = fallbackParentLang; // Fallbacks for a parent language should be inserted // to the end of the list, so they're only picked // if there is no better match. @@ -842,8 +844,8 @@ module.exports = (function() { locale = defaultLocale; } - if (!locales[locale] && fallbacks[locale]) { - locale = fallbacks[locale]; + if (!locales[locale]) { + locale = getFallback(locale, fallbacks) || locale; } // attempt to read when defined as valid locale @@ -1165,6 +1167,18 @@ module.exports = (function() { return filepath; }; + /** + * Get locales with wildcard support + */ + var getFallback = function(targetLocale, fallbacks) { + fallbacks = fallbacks || {}; + if (fallbacks[targetLocale]) return fallbacks[targetLocale]; + var fallBackKey = Object.keys(fallbacks).find(function(key) { + return targetLocale.match(new RegExp('^' + key.replace('*', '.*') + '$')); + }); + return fallBackKey ? fallbacks[fallBackKey] : null; + }; + /** * Logging proxies */ diff --git a/locales/fr-CA.json b/locales/fr-CA.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/locales/fr-CA.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/package.json b/package.json index e9a676e7..a87feeb8 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,10 @@ }, "dependencies": { "debug": "*", - "make-plural": "^3.0.3", + "js-yaml": "^3.10.0", + "make-plural": "^4", "math-interval-parser": "^1.1.0", - "messageformat": "^0.3.1", + "messageformat": "^0", "mustache": "*", "sprintf-js": ">=1.0.3" }, @@ -30,9 +31,9 @@ "cookie-parser": "^1.4.1", "express": "^4.13.4", "jshint": "*", - "mocha": "*", + "mocha": "^3.5.3", "should": "*", - "sinon": "*", + "sinon": "^3.3.0", "url": "^0.11.0", "zombie": "*" }, diff --git a/test/i18n.configureLocales.js b/test/i18n.configureLocales.js index 67c63f0d..bf90f42d 100644 --- a/test/i18n.configureLocales.js +++ b/test/i18n.configureLocales.js @@ -12,7 +12,7 @@ describe('locales configuration', function() { directory: directory }); - var expected = ['de', 'de-AT', 'de-DE', 'en', 'en-GB', 'en-US', 'fr', 'nl', 'ru', 'tr-TR'].sort(); + var expected = ['de', 'de-AT', 'de-DE', 'en', 'en-GB', 'en-US', 'fr', 'fr-CA', 'nl', 'ru', 'tr-TR'].sort(); should.deepEqual(i18n.getLocales(), expected); done(); diff --git a/test/i18n.fallbacks.js b/test/i18n.fallbacks.js index edaa370f..008659cb 100644 --- a/test/i18n.fallbacks.js +++ b/test/i18n.fallbacks.js @@ -15,9 +15,9 @@ describe('Fallbacks', function() { beforeEach(function() { i18n.configure({ - locales: ['en', 'de'], + locales: ['en', 'de', 'fr'], defaultLocale: 'en', - fallbacks: { 'foo': 'de', 'de-AT': 'de' }, + fallbacks: { 'foo': 'de', 'de-AT': 'de', 'fr-*': 'fr' }, directory: './locales', register: req }); @@ -45,11 +45,21 @@ describe('Fallbacks', function() { i18n.init(req); i18n.getLocale(req).should.equal('de'); }); + it('should fall back to "fr" for locale "fr-CA"', function() { + req.headers['accept-language'] = 'fr-CA'; + i18n.init(req); + i18n.getLocale(req).should.equal('fr'); + }); it('should fall back to "de" for second-order locale "de-AT"', function() { req.headers['accept-language'] = 'de-DK,de-AT'; i18n.init(req); i18n.getLocale(req).should.equal('de'); }); + it('should fall back to "fr" for second-order locale "fr-CA"', function() { + req.headers['accept-language'] = 'de-DK,fr-CA,de-AT'; + i18n.init(req); + i18n.getLocale(req).should.equal('fr'); + }); it('should use default "en" for valid locale request (ignoring fallbacks)', function() { req.headers['accept-language'] = 'en-US,en,de-DK,de-AT'; i18n.init(req); @@ -100,9 +110,9 @@ describe('Fallbacks', function() { i18n = require(i18nFilename); i18n.configure({ - locales: ['en-US', 'de-DE'], + locales: ['en-US', 'de-DE', 'fr-CA'], defaultLocale: 'en-US', - fallbacks: { 'de': 'de-DE' }, + fallbacks: { 'de': 'de-DE', 'fr*': 'fr-CA' }, directory: './locales', register: req }); @@ -124,6 +134,11 @@ describe('Fallbacks', function() { i18n.init(req); i18n.getLocale(req).should.equal('de-DE'); }); + it('should fall back to "fr-CA" for language "fr"', function() { + req.headers['accept-language'] = 'fr'; + i18n.init(req); + i18n.getLocale(req).should.equal('fr-CA'); + }); }); describe('Keep valid locale', function() {