diff --git a/.github/main.workflow b/.github/main.workflow new file mode 100644 index 0000000..0620ad0 --- /dev/null +++ b/.github/main.workflow @@ -0,0 +1,17 @@ +workflow "New workflow" { + on = "push" + resolves = ["Run Tests"] +} + +action "Install Dependencies" { + uses = "actions/npm@master" + runs = "npm" + args = "install" +} + +action "Run Tests" { + uses = "actions/npm@master" + runs = "npm" + args = "test" + needs = ["Install Dependencies"] +} diff --git a/.github/next-version.svg b/.github/next-version.svg new file mode 100644 index 0000000..644d625 --- /dev/null +++ b/.github/next-version.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + Are you interested in the next version? + + + + + + + diff --git a/.travis.yml b/.travis.yml index f790335..49b2706 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "0.10" + - "7" install: - npm install script: npm run build diff --git a/README.md b/README.md index 3b3ffd1..9905598 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ ![NPM Montly Downloads](https://img.shields.io/npm/dm/lang.js.svg) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/rmariuzzo/Lang.js/master/LICENSE) +
+
+
+ ## Installation Different installation methods: @@ -14,6 +18,16 @@ Different installation methods: - Bower: `bower install lang.js` - Manually: [Download latest release](https://github.com/rmariuzzo/Lang.js/releases/latest) +
+
+
+ +[![Are you interested in the next version?](.github/next-version.svg)](https://github.com/rmariuzzo/Lang.js/tree/next) + +
+
+
+ ## Documentation ### Initialization @@ -205,7 +219,7 @@ var lang = new Lang({ 'en.fruits': { 'apple': 'apple|apples' }, - 'es.greetings': { + 'es.fruits': { 'apple': 'manzana|manzanas' } } @@ -251,6 +265,9 @@ lang.choice('fruits.apple', 22); This method act as an alias of [`choice()`](#choice). +
+
+
## Development @@ -260,6 +277,10 @@ This method act as an alias of [`choice()`](#choice). **[Get help!](https://gitter.im/rmariuzzo/Lang.js)** +
+
+
+ ## Testing To run the tests use the following commands: diff --git a/dist/lang.min.js b/dist/lang.min.js index dd78f5d..714c3b9 100644 --- a/dist/lang.min.js +++ b/dist/lang.min.js @@ -6,4 +6,4 @@ * @site https://github.com/rmariuzzo/Lang.js * @author Rubens Mariuzzo */ -(function(root,factory){"use strict";if(typeof define==="function"&&define.amd){define([],factory)}else if(typeof exports==="object"){module.exports=factory()}else{root.Lang=factory()}})(this,function(){"use strict";function inferLocale(){if(typeof document!=="undefined"&&document.documentElement){return document.documentElement.lang}}function convertNumber(str){if(str==="-Inf"){return-Infinity}else if(str==="+Inf"||str==="Inf"||str==="*"){return Infinity}return parseInt(str,10)}var intervalRegexp=/^({\s*(\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)\s*})|([\[\]])\s*(-Inf|\*|\-?\d+(\.\d+)?)\s*,\s*(\+?Inf|\*|\-?\d+(\.\d+)?)\s*([\[\]])$/;var anyIntervalRegexp=/({\s*(\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)\s*})|([\[\]])\s*(-Inf|\*|\-?\d+(\.\d+)?)\s*,\s*(\+?Inf|\*|\-?\d+(\.\d+)?)\s*([\[\]])/;var defaults={locale:"en"};var Lang=function(options){options=options||{};this.locale=options.locale||inferLocale()||defaults.locale;this.fallback=options.fallback;this.messages=options.messages};Lang.prototype.setMessages=function(messages){this.messages=messages};Lang.prototype.getLocale=function(){return this.locale||this.fallback};Lang.prototype.setLocale=function(locale){this.locale=locale};Lang.prototype.getFallback=function(){return this.fallback};Lang.prototype.setFallback=function(fallback){this.fallback=fallback};Lang.prototype.has=function(key,locale){if(typeof key!=="string"||!this.messages){return false}return this._getMessage(key,locale)!==null};Lang.prototype.get=function(key,replacements,locale){if(!this.has(key,locale)){return key}var message=this._getMessage(key,locale);if(message===null){return key}if(replacements){message=this._applyReplacements(message,replacements)}return message};Lang.prototype.trans=function(key,replacements){return this.get(key,replacements)};Lang.prototype.choice=function(key,number,replacements,locale){replacements=typeof replacements!=="undefined"?replacements:{};replacements.count=number;var message=this.get(key,replacements,locale);if(message===null||message===undefined){return message}var messageParts=message.split("|");var explicitRules=[];for(var i=0;i=leftNumber:count>leftNumber)&&(rightDelimiter==="]"?count<=rightNumber:count=2&&count%10<=4&&(count%100<10||count%100>=20)?1:2;case"cs":case"sk":return count==1?0:count>=2&&count<=4?1:2;case"ga":return count==1?0:count==2?1:2;case"lt":return count%10==1&&count%100!=11?0:count%10>=2&&(count%100<10||count%100>=20)?1:2;case"sl":return count%100==1?0:count%100==2?1:count%100==3||count%100==4?2:3;case"mk":return count%10==1?0:1;case"mt":return count==1?0:count===0||count%100>1&&count%100<11?1:count%100>10&&count%100<20?2:3;case"lv":return count===0?0:count%10==1&&count%100!=11?1:2;case"pl":return count==1?0:count%10>=2&&count%10<=4&&(count%100<12||count%100>14)?1:2;case"cy":return count==1?0:count==2?1:count==8||count==11?2:3;case"ro":return count==1?0:count===0||count%100>0&&count%100<20?1:2;case"ar":return count===0?0:count==1?1:count==2?2:count%100>=3&&count%100<=10?3:count%100>=11&&count%100<=99?4:5;default:return 0}};return Lang}); \ No newline at end of file +(function(root,factory){"use strict";if(typeof define==="function"&&define.amd){define([],factory)}else if(typeof exports==="object"){module.exports=factory()}else{root.Lang=factory()}})(this,function(){"use strict";function inferLocale(){if(typeof document!=="undefined"&&document.documentElement){return document.documentElement.lang}}function convertNumber(str){if(str==="-Inf"){return-Infinity}else if(str==="+Inf"||str==="Inf"||str==="*"){return Infinity}return parseInt(str,10)}var intervalRegexp=/^({\s*(\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)\s*})|([\[\]])\s*(-Inf|\*|\-?\d+(\.\d+)?)\s*,\s*(\+?Inf|\*|\-?\d+(\.\d+)?)\s*([\[\]])$/;var anyIntervalRegexp=/({\s*(\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)\s*})|([\[\]])\s*(-Inf|\*|\-?\d+(\.\d+)?)\s*,\s*(\+?Inf|\*|\-?\d+(\.\d+)?)\s*([\[\]])/;var defaults={locale:"en"};var Lang=function(options){options=options||{};this.locale=options.locale||inferLocale()||defaults.locale;this.fallback=options.fallback;this.messages=options.messages};Lang.prototype.setMessages=function(messages){this.messages=messages};Lang.prototype.getLocale=function(){return this.locale||this.fallback};Lang.prototype.setLocale=function(locale){this.locale=locale};Lang.prototype.getFallback=function(){return this.fallback};Lang.prototype.setFallback=function(fallback){this.fallback=fallback};Lang.prototype.has=function(key,locale){if(typeof key!=="string"||!this.messages){return false}return this._getMessage(key,locale)!==null};Lang.prototype.get=function(key,replacements,locale){if(!this.has(key,locale)){return key}var message=this._getMessage(key,locale);if(message===null){return key}if(replacements){message=this._applyReplacements(message,replacements)}return message};Lang.prototype.trans=function(key,replacements){return this.get(key,replacements)};Lang.prototype.choice=function(key,number,replacements,locale){replacements=typeof replacements!=="undefined"?replacements:{};replacements.count=number;var message=this.get(key,replacements,locale);if(message===null||message===undefined){return message}var messageParts=message.split("|");var explicitRules=[];for(var i=0;i=leftNumber:count>leftNumber)&&(rightDelimiter==="]"?count<=rightNumber:count=2&&count%10<=4&&(count%100<10||count%100>=20)?1:2;case"cs":case"sk":return count==1?0:count>=2&&count<=4?1:2;case"ga":return count==1?0:count==2?1:2;case"lt":return count%10==1&&count%100!=11?0:count%10>=2&&(count%100<10||count%100>=20)?1:2;case"sl":return count%100==1?0:count%100==2?1:count%100==3||count%100==4?2:3;case"mk":return count%10==1?0:1;case"mt":return count==1?0:count===0||count%100>1&&count%100<11?1:count%100>10&&count%100<20?2:3;case"lv":return count===0?0:count%10==1&&count%100!=11?1:2;case"pl":return count==1?0:count%10>=2&&count%10<=4&&(count%100<12||count%100>14)?1:2;case"cy":return count==1?0:count==2?1:count==8||count==11?2:3;case"ro":return count==1?0:count===0||count%100>0&&count%100<20?1:2;case"ar":return count===0?0:count==1?1:count==2?2:count%100>=3&&count%100<=10?3:count%100>=11&&count%100<=99?4:5;default:return 0}};return Lang}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d273656..f6b8a8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lang.js", - "version": "1.1.13", + "version": "1.1.14", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8b6c64d..63afd9f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lang.js", - "version": "1.1.13", + "version": "1.1.14", "description": "Laravel's Lang in JavaScript!", "main": "src/lang.js", "types": "index.d.ts", diff --git a/src/lang.js b/src/lang.js index 6335d98..1386607 100644 --- a/src/lang.js +++ b/src/lang.js @@ -272,6 +272,42 @@ */ Lang.prototype._getMessage = function(key, locale) { locale = locale || this.getLocale(); + let originalLocale = locale; + + // Handle the scenario where the tranlation string is used as the key. + // (https://laravel.com/docs/6.x/localization#using-translation-strings-as-keys) + // In this case the Key should be present at the root of the locale. + if (typeof(this.messages[locale]) === 'undefined') { + // The given locale does not have keys at the root, use the fallback instead. + locale = this.getFallback(); + } + + + // See if the key is defined. + if (typeof(this.messages[locale]) !== 'undefined') { + if (typeof(this.messages[locale][key]) !== 'undefined') { + return this.messages[locale][key]; + } + } + + //Try with the fallback as well. if we haven't looked there already. + if (locale === originalLocale) { + locale = this.getFallback(); + if (typeof(this.messages[locale]) !== 'undefined') { + if (typeof(this.messages[locale][key]) !== 'undefined') { + return this.messages[locale][key]; + } + } + } + + // If we reach here, that means the traslation key did not exist in the provided locale + // nor the fallback locale. At this point proceed as normal and expect the rest of the code + // to find a valid translation or return the key itself as the translation + // (which also takes care of 'Translation strings as key') + + // Reset to the original locale. + locale = originalLocale; + key = this._parseKey(key, locale); // Ensure message source exists. @@ -282,14 +318,9 @@ // Get message from default locale. var message = this.messages[key.source]; var entries = key.entries.slice(); - var subKey = ''; - while (entries.length && message !== undefined) { - var subKey = !subKey ? entries.shift() : subKey.concat('.', entries.shift()); - if (message[subKey] !== undefined) { - message = message[subKey] - subKey = ''; - } - } + var subKey = entries.join('.'); + message = message !== undefined ? this._getValueInKey(message, subKey) : undefined; + // Get message from fallback locale. if (typeof message !== 'string' && this.messages[key.sourceFallback]) { @@ -312,6 +343,29 @@ return message; }; + Lang.prototype._getValueInKey = function(obj, str) { + // If the full key exists just return the value + if (typeof obj[str] === 'string') { + return obj[str] + } + + str = str.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties + str = str.replace(/^\./, ''); // strip a leading dot + + var parts = str.split('.'); + + for (var i = 0, n = parts.length; i < n; ++i) { + var currentKey = parts.slice(0, i + 1).join('.'); + var restOfTheKey = parts.slice(i + 1, parts.length).join('.') + + if (obj[currentKey]) { + return this._getValueInKey(obj[currentKey], restOfTheKey) + } + } + + return obj; + }; + /** * Return the locale to be used between default and fallback. * @param {String} key diff --git a/test/fixture/messages.json b/test/fixture/messages.json index 2fab709..4e27f62 100644 --- a/test/fixture/messages.json +++ b/test/fixture/messages.json @@ -109,6 +109,8 @@ }, "plural": "one apple|a million apples", "dot.in.key": "Dot In Key", + "with_parent": "Key That Is Subpart Of A Parent Key", + "with_parent.dot.in.key": "Dot In Key With a Parent Key", "dot.in.key2.nested": { "dot.in.key2.nested": "Dot In Key Nested Tricky" }, @@ -214,5 +216,9 @@ }, "en.unique": { "samePrefixKeys": "Your order contains :items items with :itemsPapayas papayas and :itemsMangoes mangoes" + }, + "es": { + "Hello": "Hola", + "Payment received. Thanks!": "Pago recibido. Gracias!" } } diff --git a/test/spec/lang_fallback_spec.js b/test/spec/lang_fallback_spec.js index 6844fae..87a8eac 100644 --- a/test/spec/lang_fallback_spec.js +++ b/test/spec/lang_fallback_spec.js @@ -58,4 +58,23 @@ describe('The lang\'s fallback locale feature', function() { lang.get('greetings.hello'); }).not.toThrow(); }); + + // JSON Translation string as keys + it('should return the fallback if tranlation is not availble.', function() { + var messages = { + 'en': { + 'Welcome': 'Welcome to the site.' + }, + 'es': { + 'Hello': 'Hola', + } + }; + lang = new Lang({ + messages: messages + }); + lang.setLocale('es'); + lang.setFallback('en'); + + expect(lang.get('Welcome', [], 'es')).toBe('Welcome to the site.'); + }) }); diff --git a/test/spec/lang_get_spec.js b/test/spec/lang_get_spec.js index 6c0f531..c492d8b 100644 --- a/test/spec/lang_get_spec.js +++ b/test/spec/lang_get_spec.js @@ -69,6 +69,10 @@ describe('The lang.get() method', function () { expect(lang.get('messages.dot.in.key')).toBe('Dot In Key'); }); + it('should prioritize the dot in key', function() { + expect(lang.get('messages.with_parent.dot.in.key')).toBe('Dot In Key With a Parent Key'); + }); + it('should return the expected message if the key is nested and has a dot', function() { expect(lang.get('messages.dotInKey.dot.in.key')).toBe('Dot In Key Nested Simple'); expect(lang.get('messages.dot.in.key2.nested.dot.in.key2.nested')).toBe('Dot In Key Nested Tricky'); @@ -85,4 +89,13 @@ describe('The lang.get() method', function () { itemsMangoes: 2 })).toBe('Your order contains 5 items with 3 papayas and 2 mangoes'); }); + + // JSON Translation string as key + it('should return the spanish translation when a key exists', function() { + expect(lang.get('Hello', [], 'es')).toBe('Hola'); + }); + + it('should return the spanish translation when a key exists and key contains a period (.)', function() { + expect(lang.get('Payment received. Thanks!', [], 'es')).toBe('Pago recibido. Gracias!'); + }); });