From 5d89c7a7e9d16f240df584c7b760cb27930e2ba6 Mon Sep 17 00:00:00 2001 From: Sara Date: Sat, 8 Jul 2017 17:41:44 +0100 Subject: [PATCH 01/10] Using raw object as data source New code has been added so that raw javascript objects can now be used as a source for the translations. If localize receives an object as a first parameter, items with 'data-localize' tags will be updated with the text contained in the matching object properties. Said object will be converted to a valid json object beforehand, so no problems shall arise if the given object include functions as a value for any of its keys. In the case localize method receives any parameter that is not an object, the default behaviour where the translations are retrieved from an external file will be applied. This should solve the following issue: https://github.com/coderifous/jquery-localize/issues/62 --- dist/jquery.localize.js | 24 +++++++++++++++++++----- dist/jquery.localize.min.js | 6 +++--- src/jquery.localize.coffee | 22 +++++++++++++++++++--- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/dist/jquery.localize.js b/dist/jquery.localize.js index 7999965..5d97360 100644 --- a/dist/jquery.localize.js +++ b/dist/jquery.localize.js @@ -18,7 +18,7 @@ http://keith-wood.name/localisation.html }; $.defaultLanguage = normaliseLang(navigator.languages && navigator.languages.length > 0 ? navigator.languages[0] : navigator.language || navigator.userLanguage); $.localize = function(pkg, options) { - var defaultCallback, deferred, fileExtension, intermediateLangData, jsonCall, lang, loadLanguage, localizeElement, localizeForSpecialKeys, localizeImageElement, localizeInputElement, localizeOptgroupElement, notifyDelegateLanguageLoaded, regexify, setAttrFromValueForKey, setTextFromValueForKey, valueForKey, wrappedSet; + var defaultCallback, deferred, fileExtension, intermediateLangData, jsonCall, loadLanguage, localizeElement, localizeForSpecialKeys, localizeImageElement, localizeInputElement, localizeOptgroupElement, notifyDelegateLanguageLoaded, regexify, setAttrFromValueForKey, setTextFromValueForKey, translateFromFile, translateFromObject, valueForKey, wrappedSet; if (options == null) { options = {}; } @@ -180,11 +180,25 @@ http://keith-wood.name/localisation.html return string_or_regex_or_array; } }; - lang = normaliseLang(options.language ? options.language : $.defaultLanguage); - if (options.skipLanguage && lang.match(regexify(options.skipLanguage))) { - deferred.resolve(); + translateFromFile = function() { + var lang; + lang = normaliseLang(options.language ? options.language : $.defaultLanguage); + if (options.skipLanguage && lang.match(regexify(options.skipLanguage))) { + return deferred.resolve(); + } else { + return loadLanguage(pkg, lang, 1); + } + }; + translateFromObject = function(object) { + var data; + data = JSON.parse(JSON.stringify(object)); + defaultCallback(data); + return deferred.resolve(); + }; + if (typeof pkg === "object") { + translateFromObject(pkg); } else { - loadLanguage(pkg, lang, 1); + translateFromFile(); } wrappedSet.localizePromise = deferred; return wrappedSet; diff --git a/dist/jquery.localize.min.js b/dist/jquery.localize.min.js index fa39064..ec0b316 100644 --- a/dist/jquery.localize.min.js +++ b/dist/jquery.localize.min.js @@ -1,4 +1,4 @@ -/*! Localize - v0.2.0 - 2016-10-13 +/*! Localize - v0.2.0 - 2017-07-08 * https://github.com/coderifous/jquery-localize - * Copyright (c) 2016 coderifous; Licensed MIT */ -!function(a){var b;return b=function(a){return a=a.replace(/_/,"-").toLowerCase(),a.length>3&&(a=a.substring(0,3)+a.substring(3).toUpperCase()),a},a.defaultLanguage=b(navigator.languages&&navigator.languages.length>0?navigator.languages[0]:navigator.language||navigator.userLanguage),a.localize=function(c,d){var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v;return null==d&&(d={}),v=this,h={},g=d.fileExtension||"json",f=a.Deferred(),k=function(a,b,c){var e;switch(null==c&&(c=1),c){case 1:return h={},d.loadBase?(e=a+("."+g),i(e,a,b,c)):k(a,b,2);case 2:return e=""+a+"-"+b.split("-")[0]+"."+g,i(e,a,b,c);case 3:return e=""+a+"-"+b.split("-").slice(0,2).join("-")+"."+g,i(e,a,b,c);default:return f.resolve()}},i=function(b,c,e,f){var g,i,j;return null!=d.pathPrefix&&(b=""+d.pathPrefix+"/"+b),j=function(b){return a.extend(h,b),q(h),k(c,e,f+1)},i=function(){return 2===f&&e.indexOf("-")>-1?k(c,e,f+1):d.fallback&&d.fallback!==e?k(c,d.fallback):void 0},g={url:b,dataType:"json",async:!0,timeout:null!=d.timeout?d.timeout:500,success:j,error:i},"file:"===window.location.protocol&&(g.error=function(b){return j(a.parseJSON(b.responseText))}),a.ajax(g)},q=function(a){return null!=d.callback?d.callback(a,e):e(a)},e=function(b){return a.localize.data[c]=b,v.each(function(){var c,d,e;return c=a(this),d=c.data("localize"),d||(d=c.attr("rel").match(/localize\[(.*?)\]/)[1]),e=u(d,b),null!=e?l(c,d,e):void 0})},l=function(b,c,d){return b.is("input")?o(b,c,d):b.is("textarea")?o(b,c,d):b.is("img")?n(b,c,d):b.is("optgroup")?p(b,c,d):a.isPlainObject(d)||b.html(d),a.isPlainObject(d)?m(b,d):void 0},o=function(b,c,d){var e;return e=a.isPlainObject(d)?d.value:d,b.is("[placeholder]")?b.attr("placeholder",e):b.val(e)},m=function(a,b){return s(a,"title",b),s(a,"href",b),t(a,"text",b)},p=function(a,b,c){return a.attr("label",c)},n=function(a,b,c){return s(a,"alt",c),s(a,"src",c)},u=function(a,b){var c,d,e,f;for(c=a.split(/\./),d=b,e=0,f=c.length;f>e;e++)a=c[e],d=null!=d?d[a]:null;return d},s=function(a,b,c){return c=u(b,c),null!=c?a.attr(b,c):void 0},t=function(a,b,c){return c=u(b,c),null!=c?a.text(c):void 0},r=function(a){var b;return"string"==typeof a?"^"+a+"$":null!=a.length?function(){var c,d,e;for(e=[],c=0,d=a.length;d>c;c++)b=a[c],e.push(r(b));return e}().join("|"):a},j=b(d.language?d.language:a.defaultLanguage),d.skipLanguage&&j.match(r(d.skipLanguage))?f.resolve():k(c,j,1),v.localizePromise=f,v},a.fn.localize=a.localize,a.localize.data={}}(jQuery); \ No newline at end of file + * Copyright (c) 2017 coderifous; Licensed MIT */ +!function(a){var b;return b=function(a){return a=a.replace(/_/,"-").toLowerCase(),a.length>3&&(a=a.substring(0,3)+a.substring(3).toUpperCase()),a},a.defaultLanguage=b(navigator.languages&&navigator.languages.length>0?navigator.languages[0]:navigator.language||navigator.userLanguage),a.localize=function(c,d){var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w;return null==d&&(d={}),w=this,h={},g=d.fileExtension||"json",f=a.Deferred(),j=function(a,b,c){var e;switch(null==c&&(c=1),c){case 1:return h={},d.loadBase?(e=a+("."+g),i(e,a,b,c)):j(a,b,2);case 2:return e=""+a+"-"+b.split("-")[0]+"."+g,i(e,a,b,c);case 3:return e=""+a+"-"+b.split("-").slice(0,2).join("-")+"."+g,i(e,a,b,c);default:return f.resolve()}},i=function(b,c,e,f){var g,i,k;return null!=d.pathPrefix&&(b=""+d.pathPrefix+"/"+b),k=function(b){return a.extend(h,b),p(h),j(c,e,f+1)},i=function(){return 2===f&&e.indexOf("-")>-1?j(c,e,f+1):d.fallback&&d.fallback!==e?j(c,d.fallback):void 0},g={url:b,dataType:"json",async:!0,timeout:null!=d.timeout?d.timeout:500,success:k,error:i},"file:"===window.location.protocol&&(g.error=function(b){return k(a.parseJSON(b.responseText))}),a.ajax(g)},p=function(a){return null!=d.callback?d.callback(a,e):e(a)},e=function(b){return a.localize.data[c]=b,w.each(function(){var c,d,e;return c=a(this),d=c.data("localize"),d||(d=c.attr("rel").match(/localize\[(.*?)\]/)[1]),e=v(d,b),null!=e?k(c,d,e):void 0})},k=function(b,c,d){return b.is("input")?n(b,c,d):b.is("textarea")?n(b,c,d):b.is("img")?m(b,c,d):b.is("optgroup")?o(b,c,d):a.isPlainObject(d)||b.html(d),a.isPlainObject(d)?l(b,d):void 0},n=function(b,c,d){var e;return e=a.isPlainObject(d)?d.value:d,b.is("[placeholder]")?b.attr("placeholder",e):b.val(e)},l=function(a,b){return r(a,"title",b),r(a,"href",b),s(a,"text",b)},o=function(a,b,c){return a.attr("label",c)},m=function(a,b,c){return r(a,"alt",c),r(a,"src",c)},v=function(a,b){var c,d,e,f;for(c=a.split(/\./),d=b,e=0,f=c.length;f>e;e++)a=c[e],d=null!=d?d[a]:null;return d},r=function(a,b,c){return c=v(b,c),null!=c?a.attr(b,c):void 0},s=function(a,b,c){return c=v(b,c),null!=c?a.text(c):void 0},q=function(a){var b;return"string"==typeof a?"^"+a+"$":null!=a.length?function(){var c,d,e;for(e=[],c=0,d=a.length;d>c;c++)b=a[c],e.push(q(b));return e}().join("|"):a},t=function(){var e;return e=b(d.language?d.language:a.defaultLanguage),d.skipLanguage&&e.match(q(d.skipLanguage))?f.resolve():j(c,e,1)},u=function(a){var b;return b=JSON.parse(JSON.stringify(a)),e(b),f.resolve()},"object"==typeof c?u(c):t(),w.localizePromise=f,w},a.fn.localize=a.localize,a.localize.data={}}(jQuery); \ No newline at end of file diff --git a/src/jquery.localize.coffee b/src/jquery.localize.coffee index 6c1996a..40d4b19 100644 --- a/src/jquery.localize.coffee +++ b/src/jquery.localize.coffee @@ -136,11 +136,27 @@ do ($ = jQuery) -> else string_or_regex_or_array - lang = normaliseLang(if options.language then options.language else $.defaultLanguage) - if (options.skipLanguage && lang.match(regexify(options.skipLanguage))) + # Retrieve translations from an external file based on required language + translateFromFile = () -> + lang = normaliseLang(if options.language then options.language else $.defaultLanguage) + if (options.skipLanguage && lang.match(regexify(options.skipLanguage))) + deferred.resolve() + else + loadLanguage(pkg, lang, 1) + + # Retrieve translations from an object + translateFromObject = (object) -> + # We stringify and parse the received object to ensure the object is a valid json + # Any functions defined within the object will be removed during this process + data = JSON.parse(JSON.stringify(object)) + defaultCallback(data) deferred.resolve() + + # If 'pkg' is an object, use it as the source for translations + if typeof(pkg) == "object" + translateFromObject(pkg) else - loadLanguage(pkg, lang, 1) + translateFromFile() wrappedSet.localizePromise = deferred From f86d01c38388ff3a46d2cec15c841169b580ddb8 Mon Sep 17 00:00:00 2001 From: Sara Date: Sat, 8 Jul 2017 17:43:36 +0100 Subject: [PATCH 02/10] Added new example by using a js object as a source --- examples/localize_from_object.html | 63 ++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 examples/localize_from_object.html diff --git a/examples/localize_from_object.html b/examples/localize_from_object.html new file mode 100644 index 0000000..8a54927 --- /dev/null +++ b/examples/localize_from_object.html @@ -0,0 +1,63 @@ + + + + + + + Localize Test + + + + + +

Test localization...

+

puts 2 + 2

+ + +

+ a square ruby + Ruby image should be round. +

+

It failed :(

+

This text should remain unchanged

+ + + + From e138c72c280f237c305f0a5f64d264073f82ae17 Mon Sep 17 00:00:00 2001 From: Sara Date: Sun, 9 Jul 2017 10:21:18 +0100 Subject: [PATCH 03/10] The method wrapping default behaviour now receives the filename as a parameter --- dist/jquery.localize.js | 12 ++++++------ dist/jquery.localize.min.js | 4 ++-- src/jquery.localize.coffee | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/dist/jquery.localize.js b/dist/jquery.localize.js index 5d97360..ca49dee 100644 --- a/dist/jquery.localize.js +++ b/dist/jquery.localize.js @@ -18,7 +18,7 @@ http://keith-wood.name/localisation.html }; $.defaultLanguage = normaliseLang(navigator.languages && navigator.languages.length > 0 ? navigator.languages[0] : navigator.language || navigator.userLanguage); $.localize = function(pkg, options) { - var defaultCallback, deferred, fileExtension, intermediateLangData, jsonCall, loadLanguage, localizeElement, localizeForSpecialKeys, localizeImageElement, localizeInputElement, localizeOptgroupElement, notifyDelegateLanguageLoaded, regexify, setAttrFromValueForKey, setTextFromValueForKey, translateFromFile, translateFromObject, valueForKey, wrappedSet; + var defaultCallback, deferred, fileExtension, intermediateLangData, jsonCall, loadLanguage, localizeElement, localizeForSpecialKeys, localizeImageElement, localizeInputElement, localizeOptgroupElement, notifyDelegateLanguageLoaded, regexify, setAttrFromValueForKey, setTextFromValueForKey, useFileAsDataSource, useObjectAsDataSource, valueForKey, wrappedSet; if (options == null) { options = {}; } @@ -180,25 +180,25 @@ http://keith-wood.name/localisation.html return string_or_regex_or_array; } }; - translateFromFile = function() { + useFileAsDataSource = function(filename) { var lang; lang = normaliseLang(options.language ? options.language : $.defaultLanguage); if (options.skipLanguage && lang.match(regexify(options.skipLanguage))) { return deferred.resolve(); } else { - return loadLanguage(pkg, lang, 1); + return loadLanguage(filename, lang, 1); } }; - translateFromObject = function(object) { + useObjectAsDataSource = function(object) { var data; data = JSON.parse(JSON.stringify(object)); defaultCallback(data); return deferred.resolve(); }; if (typeof pkg === "object") { - translateFromObject(pkg); + useObjectAsDataSource(pkg); } else { - translateFromFile(); + useFileAsDataSource(pkg); } wrappedSet.localizePromise = deferred; return wrappedSet; diff --git a/dist/jquery.localize.min.js b/dist/jquery.localize.min.js index ec0b316..34548a4 100644 --- a/dist/jquery.localize.min.js +++ b/dist/jquery.localize.min.js @@ -1,4 +1,4 @@ -/*! Localize - v0.2.0 - 2017-07-08 +/*! Localize - v0.2.0 - 2017-07-09 * https://github.com/coderifous/jquery-localize * Copyright (c) 2017 coderifous; Licensed MIT */ -!function(a){var b;return b=function(a){return a=a.replace(/_/,"-").toLowerCase(),a.length>3&&(a=a.substring(0,3)+a.substring(3).toUpperCase()),a},a.defaultLanguage=b(navigator.languages&&navigator.languages.length>0?navigator.languages[0]:navigator.language||navigator.userLanguage),a.localize=function(c,d){var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w;return null==d&&(d={}),w=this,h={},g=d.fileExtension||"json",f=a.Deferred(),j=function(a,b,c){var e;switch(null==c&&(c=1),c){case 1:return h={},d.loadBase?(e=a+("."+g),i(e,a,b,c)):j(a,b,2);case 2:return e=""+a+"-"+b.split("-")[0]+"."+g,i(e,a,b,c);case 3:return e=""+a+"-"+b.split("-").slice(0,2).join("-")+"."+g,i(e,a,b,c);default:return f.resolve()}},i=function(b,c,e,f){var g,i,k;return null!=d.pathPrefix&&(b=""+d.pathPrefix+"/"+b),k=function(b){return a.extend(h,b),p(h),j(c,e,f+1)},i=function(){return 2===f&&e.indexOf("-")>-1?j(c,e,f+1):d.fallback&&d.fallback!==e?j(c,d.fallback):void 0},g={url:b,dataType:"json",async:!0,timeout:null!=d.timeout?d.timeout:500,success:k,error:i},"file:"===window.location.protocol&&(g.error=function(b){return k(a.parseJSON(b.responseText))}),a.ajax(g)},p=function(a){return null!=d.callback?d.callback(a,e):e(a)},e=function(b){return a.localize.data[c]=b,w.each(function(){var c,d,e;return c=a(this),d=c.data("localize"),d||(d=c.attr("rel").match(/localize\[(.*?)\]/)[1]),e=v(d,b),null!=e?k(c,d,e):void 0})},k=function(b,c,d){return b.is("input")?n(b,c,d):b.is("textarea")?n(b,c,d):b.is("img")?m(b,c,d):b.is("optgroup")?o(b,c,d):a.isPlainObject(d)||b.html(d),a.isPlainObject(d)?l(b,d):void 0},n=function(b,c,d){var e;return e=a.isPlainObject(d)?d.value:d,b.is("[placeholder]")?b.attr("placeholder",e):b.val(e)},l=function(a,b){return r(a,"title",b),r(a,"href",b),s(a,"text",b)},o=function(a,b,c){return a.attr("label",c)},m=function(a,b,c){return r(a,"alt",c),r(a,"src",c)},v=function(a,b){var c,d,e,f;for(c=a.split(/\./),d=b,e=0,f=c.length;f>e;e++)a=c[e],d=null!=d?d[a]:null;return d},r=function(a,b,c){return c=v(b,c),null!=c?a.attr(b,c):void 0},s=function(a,b,c){return c=v(b,c),null!=c?a.text(c):void 0},q=function(a){var b;return"string"==typeof a?"^"+a+"$":null!=a.length?function(){var c,d,e;for(e=[],c=0,d=a.length;d>c;c++)b=a[c],e.push(q(b));return e}().join("|"):a},t=function(){var e;return e=b(d.language?d.language:a.defaultLanguage),d.skipLanguage&&e.match(q(d.skipLanguage))?f.resolve():j(c,e,1)},u=function(a){var b;return b=JSON.parse(JSON.stringify(a)),e(b),f.resolve()},"object"==typeof c?u(c):t(),w.localizePromise=f,w},a.fn.localize=a.localize,a.localize.data={}}(jQuery); \ No newline at end of file +!function(a){var b;return b=function(a){return a=a.replace(/_/,"-").toLowerCase(),a.length>3&&(a=a.substring(0,3)+a.substring(3).toUpperCase()),a},a.defaultLanguage=b(navigator.languages&&navigator.languages.length>0?navigator.languages[0]:navigator.language||navigator.userLanguage),a.localize=function(c,d){var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w;return null==d&&(d={}),w=this,h={},g=d.fileExtension||"json",f=a.Deferred(),j=function(a,b,c){var e;switch(null==c&&(c=1),c){case 1:return h={},d.loadBase?(e=a+("."+g),i(e,a,b,c)):j(a,b,2);case 2:return e=""+a+"-"+b.split("-")[0]+"."+g,i(e,a,b,c);case 3:return e=""+a+"-"+b.split("-").slice(0,2).join("-")+"."+g,i(e,a,b,c);default:return f.resolve()}},i=function(b,c,e,f){var g,i,k;return null!=d.pathPrefix&&(b=""+d.pathPrefix+"/"+b),k=function(b){return a.extend(h,b),p(h),j(c,e,f+1)},i=function(){return 2===f&&e.indexOf("-")>-1?j(c,e,f+1):d.fallback&&d.fallback!==e?j(c,d.fallback):void 0},g={url:b,dataType:"json",async:!0,timeout:null!=d.timeout?d.timeout:500,success:k,error:i},"file:"===window.location.protocol&&(g.error=function(b){return k(a.parseJSON(b.responseText))}),a.ajax(g)},p=function(a){return null!=d.callback?d.callback(a,e):e(a)},e=function(b){return a.localize.data[c]=b,w.each(function(){var c,d,e;return c=a(this),d=c.data("localize"),d||(d=c.attr("rel").match(/localize\[(.*?)\]/)[1]),e=v(d,b),null!=e?k(c,d,e):void 0})},k=function(b,c,d){return b.is("input")?n(b,c,d):b.is("textarea")?n(b,c,d):b.is("img")?m(b,c,d):b.is("optgroup")?o(b,c,d):a.isPlainObject(d)||b.html(d),a.isPlainObject(d)?l(b,d):void 0},n=function(b,c,d){var e;return e=a.isPlainObject(d)?d.value:d,b.is("[placeholder]")?b.attr("placeholder",e):b.val(e)},l=function(a,b){return r(a,"title",b),r(a,"href",b),s(a,"text",b)},o=function(a,b,c){return a.attr("label",c)},m=function(a,b,c){return r(a,"alt",c),r(a,"src",c)},v=function(a,b){var c,d,e,f;for(c=a.split(/\./),d=b,e=0,f=c.length;f>e;e++)a=c[e],d=null!=d?d[a]:null;return d},r=function(a,b,c){return c=v(b,c),null!=c?a.attr(b,c):void 0},s=function(a,b,c){return c=v(b,c),null!=c?a.text(c):void 0},q=function(a){var b;return"string"==typeof a?"^"+a+"$":null!=a.length?function(){var c,d,e;for(e=[],c=0,d=a.length;d>c;c++)b=a[c],e.push(q(b));return e}().join("|"):a},t=function(c){var e;return e=b(d.language?d.language:a.defaultLanguage),d.skipLanguage&&e.match(q(d.skipLanguage))?f.resolve():j(c,e,1)},u=function(a){var b;return b=JSON.parse(JSON.stringify(a)),e(b),f.resolve()},"object"==typeof c?u(c):t(c),w.localizePromise=f,w},a.fn.localize=a.localize,a.localize.data={}}(jQuery); \ No newline at end of file diff --git a/src/jquery.localize.coffee b/src/jquery.localize.coffee index 40d4b19..dd186ab 100644 --- a/src/jquery.localize.coffee +++ b/src/jquery.localize.coffee @@ -136,16 +136,16 @@ do ($ = jQuery) -> else string_or_regex_or_array - # Retrieve translations from an external file based on required language - translateFromFile = () -> + # Retrieve translations from an external file depending on required language + useFileAsDataSource = (filename) -> lang = normaliseLang(if options.language then options.language else $.defaultLanguage) if (options.skipLanguage && lang.match(regexify(options.skipLanguage))) deferred.resolve() else - loadLanguage(pkg, lang, 1) + loadLanguage(filename, lang, 1) # Retrieve translations from an object - translateFromObject = (object) -> + useObjectAsDataSource = (object) -> # We stringify and parse the received object to ensure the object is a valid json # Any functions defined within the object will be removed during this process data = JSON.parse(JSON.stringify(object)) @@ -154,9 +154,9 @@ do ($ = jQuery) -> # If 'pkg' is an object, use it as the source for translations if typeof(pkg) == "object" - translateFromObject(pkg) + useObjectAsDataSource(pkg) else - translateFromFile() + useFileAsDataSource(pkg) wrappedSet.localizePromise = deferred From 71cd51a3b4e458e351e616a0a1e9f1b8307c83f2 Mon Sep 17 00:00:00 2001 From: Sara Date: Sun, 9 Jul 2017 10:33:01 +0100 Subject: [PATCH 04/10] Added two new tests where an object is used as data source --- test/localize_test.coffee | 15 +++++++++++++++ test/localize_test.js | 27 ++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/test/localize_test.coffee b/test/localize_test.coffee index 9936ad9..9b5319a 100644 --- a/test/localize_test.coffee +++ b/test/localize_test.coffee @@ -203,3 +203,18 @@ do ($ = jQuery) -> t = localizableTagWithRel("p", "en_us_message", text: "en-US not loaded") t.localize("test", opts).localizePromise.then -> assert.equal t.text(), "en-US not loaded" + + # Ref: https://github.com/coderifous/jquery-localize/issues/62 + module "Using object as data source" + + asyncTest "basic tag text substitution using object as data source", (assert) -> + obj = { "basic": "basic success" } + t = localizableTagWithRel("p", "basic", text: "basic fail") + t.localize(obj).localizePromise.then -> + assert.equal t.text(), "basic success" + + asyncTest "don't replace tag text if matching object property contains a function", (assert) -> + obj = { "function": (->) } + t = localizableTagWithRel("p", "function", text: "This text should remain unchanged") + t.localize(obj).localizePromise.then -> + assert.equal t.text(), "This text should remain unchanged" diff --git a/test/localize_test.js b/test/localize_test.js index 8318972..54aa408 100644 --- a/test/localize_test.js +++ b/test/localize_test.js @@ -348,7 +348,7 @@ return assert.equal(t.text(), "en not loaded"); }); }); - return asyncTest("skipping region language using array match", function(assert) { + asyncTest("skipping region language using array match", function(assert) { var opts, t; opts = { language: "en-US", @@ -362,4 +362,29 @@ return assert.equal(t.text(), "en-US not loaded"); }); }); + module("Using object as data source"); + asyncTest("basic tag text substitution using object as data source", function(assert) { + var obj, t; + obj = { + "basic": "basic success" + }; + t = localizableTagWithRel("p", "basic", { + text: "basic fail" + }); + return t.localize(obj).localizePromise.then(function() { + return assert.equal(t.text(), "basic success"); + }); + }); + return asyncTest("don't replace tag text if matching object property contains a function", function(assert) { + var obj, t; + obj = { + "function": (function() {}) + }; + t = localizableTagWithRel("p", "function", { + text: "This text should remain unchanged" + }); + return t.localize(obj).localizePromise.then(function() { + return assert.equal(t.text(), "This text should remain unchanged"); + }); + }); })(jQuery); From 8094a54bb265b36cab8d531d5c7d9510214ca59f Mon Sep 17 00:00:00 2001 From: Sara Date: Sun, 9 Jul 2017 12:08:33 +0100 Subject: [PATCH 05/10] Support for custom callback when using raw objects The method that replace the localized tags by the texts defined inside of a raw object did not consider any of the possible options you could receive as a second parameter in a 'localize' call. The 'callback' option is now supported. The 'data' object will be converted to a valid json object before calling the default callback, so if the user sets any of the data properties to a function, the elements for which the 'data-localize' attribute references any of those invalid property values will not be updated. The 'localize_test.coffee' file do now include new methods to test the new functionality. The 'localize_from_object.html' has been also updated and seems to be working as expected in the following browsers: - Google Chrome (59.0.3071.115) - Mozilla Firefox (45.0, 47.0.1) - Microsoft Edge (40.15063.0.0) --- dist/jquery.localize.js | 13 ++++++-- dist/jquery.localize.min.js | 2 +- examples/localize_from_object.html | 14 ++++++++- src/jquery.localize.coffee | 14 ++++++--- test/localize_test.coffee | 34 +++++++++++++++++---- test/localize_test.js | 48 +++++++++++++++++++++++++++--- 6 files changed, 107 insertions(+), 18 deletions(-) diff --git a/dist/jquery.localize.js b/dist/jquery.localize.js index ca49dee..ff75d13 100644 --- a/dist/jquery.localize.js +++ b/dist/jquery.localize.js @@ -18,7 +18,7 @@ http://keith-wood.name/localisation.html }; $.defaultLanguage = normaliseLang(navigator.languages && navigator.languages.length > 0 ? navigator.languages[0] : navigator.language || navigator.userLanguage); $.localize = function(pkg, options) { - var defaultCallback, deferred, fileExtension, intermediateLangData, jsonCall, loadLanguage, localizeElement, localizeForSpecialKeys, localizeImageElement, localizeInputElement, localizeOptgroupElement, notifyDelegateLanguageLoaded, regexify, setAttrFromValueForKey, setTextFromValueForKey, useFileAsDataSource, useObjectAsDataSource, valueForKey, wrappedSet; + var defaultCallback, deferred, fileExtension, intermediateLangData, jsonCall, loadLanguage, localizeElement, localizeForSpecialKeys, localizeImageElement, localizeInputElement, localizeOptgroupElement, notifyDelegateLanguageLoaded, regexify, sanitizedCallback, setAttrFromValueForKey, setTextFromValueForKey, useFileAsDataSource, useObjectAsDataSource, valueForKey, wrappedSet; if (options == null) { options = {}; } @@ -189,10 +189,17 @@ http://keith-wood.name/localisation.html return loadLanguage(filename, lang, 1); } }; - useObjectAsDataSource = function(object) { + sanitizedCallback = function(object) { var data; data = JSON.parse(JSON.stringify(object)); - defaultCallback(data); + return defaultCallback(data); + }; + useObjectAsDataSource = function(object) { + if (options.callback != null) { + options.callback(object, sanitizedCallback); + } else { + sanitizedCallback(object); + } return deferred.resolve(); }; if (typeof pkg === "object") { diff --git a/dist/jquery.localize.min.js b/dist/jquery.localize.min.js index 34548a4..df9522e 100644 --- a/dist/jquery.localize.min.js +++ b/dist/jquery.localize.min.js @@ -1,4 +1,4 @@ /*! Localize - v0.2.0 - 2017-07-09 * https://github.com/coderifous/jquery-localize * Copyright (c) 2017 coderifous; Licensed MIT */ -!function(a){var b;return b=function(a){return a=a.replace(/_/,"-").toLowerCase(),a.length>3&&(a=a.substring(0,3)+a.substring(3).toUpperCase()),a},a.defaultLanguage=b(navigator.languages&&navigator.languages.length>0?navigator.languages[0]:navigator.language||navigator.userLanguage),a.localize=function(c,d){var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w;return null==d&&(d={}),w=this,h={},g=d.fileExtension||"json",f=a.Deferred(),j=function(a,b,c){var e;switch(null==c&&(c=1),c){case 1:return h={},d.loadBase?(e=a+("."+g),i(e,a,b,c)):j(a,b,2);case 2:return e=""+a+"-"+b.split("-")[0]+"."+g,i(e,a,b,c);case 3:return e=""+a+"-"+b.split("-").slice(0,2).join("-")+"."+g,i(e,a,b,c);default:return f.resolve()}},i=function(b,c,e,f){var g,i,k;return null!=d.pathPrefix&&(b=""+d.pathPrefix+"/"+b),k=function(b){return a.extend(h,b),p(h),j(c,e,f+1)},i=function(){return 2===f&&e.indexOf("-")>-1?j(c,e,f+1):d.fallback&&d.fallback!==e?j(c,d.fallback):void 0},g={url:b,dataType:"json",async:!0,timeout:null!=d.timeout?d.timeout:500,success:k,error:i},"file:"===window.location.protocol&&(g.error=function(b){return k(a.parseJSON(b.responseText))}),a.ajax(g)},p=function(a){return null!=d.callback?d.callback(a,e):e(a)},e=function(b){return a.localize.data[c]=b,w.each(function(){var c,d,e;return c=a(this),d=c.data("localize"),d||(d=c.attr("rel").match(/localize\[(.*?)\]/)[1]),e=v(d,b),null!=e?k(c,d,e):void 0})},k=function(b,c,d){return b.is("input")?n(b,c,d):b.is("textarea")?n(b,c,d):b.is("img")?m(b,c,d):b.is("optgroup")?o(b,c,d):a.isPlainObject(d)||b.html(d),a.isPlainObject(d)?l(b,d):void 0},n=function(b,c,d){var e;return e=a.isPlainObject(d)?d.value:d,b.is("[placeholder]")?b.attr("placeholder",e):b.val(e)},l=function(a,b){return r(a,"title",b),r(a,"href",b),s(a,"text",b)},o=function(a,b,c){return a.attr("label",c)},m=function(a,b,c){return r(a,"alt",c),r(a,"src",c)},v=function(a,b){var c,d,e,f;for(c=a.split(/\./),d=b,e=0,f=c.length;f>e;e++)a=c[e],d=null!=d?d[a]:null;return d},r=function(a,b,c){return c=v(b,c),null!=c?a.attr(b,c):void 0},s=function(a,b,c){return c=v(b,c),null!=c?a.text(c):void 0},q=function(a){var b;return"string"==typeof a?"^"+a+"$":null!=a.length?function(){var c,d,e;for(e=[],c=0,d=a.length;d>c;c++)b=a[c],e.push(q(b));return e}().join("|"):a},t=function(c){var e;return e=b(d.language?d.language:a.defaultLanguage),d.skipLanguage&&e.match(q(d.skipLanguage))?f.resolve():j(c,e,1)},u=function(a){var b;return b=JSON.parse(JSON.stringify(a)),e(b),f.resolve()},"object"==typeof c?u(c):t(c),w.localizePromise=f,w},a.fn.localize=a.localize,a.localize.data={}}(jQuery); \ No newline at end of file +!function(a){var b;return b=function(a){return a=a.replace(/_/,"-").toLowerCase(),a.length>3&&(a=a.substring(0,3)+a.substring(3).toUpperCase()),a},a.defaultLanguage=b(navigator.languages&&navigator.languages.length>0?navigator.languages[0]:navigator.language||navigator.userLanguage),a.localize=function(c,d){var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x;return null==d&&(d={}),x=this,h={},g=d.fileExtension||"json",f=a.Deferred(),j=function(a,b,c){var e;switch(null==c&&(c=1),c){case 1:return h={},d.loadBase?(e=a+("."+g),i(e,a,b,c)):j(a,b,2);case 2:return e=""+a+"-"+b.split("-")[0]+"."+g,i(e,a,b,c);case 3:return e=""+a+"-"+b.split("-").slice(0,2).join("-")+"."+g,i(e,a,b,c);default:return f.resolve()}},i=function(b,c,e,f){var g,i,k;return null!=d.pathPrefix&&(b=""+d.pathPrefix+"/"+b),k=function(b){return a.extend(h,b),p(h),j(c,e,f+1)},i=function(){return 2===f&&e.indexOf("-")>-1?j(c,e,f+1):d.fallback&&d.fallback!==e?j(c,d.fallback):void 0},g={url:b,dataType:"json",async:!0,timeout:null!=d.timeout?d.timeout:500,success:k,error:i},"file:"===window.location.protocol&&(g.error=function(b){return k(a.parseJSON(b.responseText))}),a.ajax(g)},p=function(a){return null!=d.callback?d.callback(a,e):e(a)},e=function(b){return a.localize.data[c]=b,x.each(function(){var c,d,e;return c=a(this),d=c.data("localize"),d||(d=c.attr("rel").match(/localize\[(.*?)\]/)[1]),e=w(d,b),null!=e?k(c,d,e):void 0})},k=function(b,c,d){return b.is("input")?n(b,c,d):b.is("textarea")?n(b,c,d):b.is("img")?m(b,c,d):b.is("optgroup")?o(b,c,d):a.isPlainObject(d)||b.html(d),a.isPlainObject(d)?l(b,d):void 0},n=function(b,c,d){var e;return e=a.isPlainObject(d)?d.value:d,b.is("[placeholder]")?b.attr("placeholder",e):b.val(e)},l=function(a,b){return s(a,"title",b),s(a,"href",b),t(a,"text",b)},o=function(a,b,c){return a.attr("label",c)},m=function(a,b,c){return s(a,"alt",c),s(a,"src",c)},w=function(a,b){var c,d,e,f;for(c=a.split(/\./),d=b,e=0,f=c.length;f>e;e++)a=c[e],d=null!=d?d[a]:null;return d},s=function(a,b,c){return c=w(b,c),null!=c?a.attr(b,c):void 0},t=function(a,b,c){return c=w(b,c),null!=c?a.text(c):void 0},q=function(a){var b;return"string"==typeof a?"^"+a+"$":null!=a.length?function(){var c,d,e;for(e=[],c=0,d=a.length;d>c;c++)b=a[c],e.push(q(b));return e}().join("|"):a},u=function(c){var e;return e=b(d.language?d.language:a.defaultLanguage),d.skipLanguage&&e.match(q(d.skipLanguage))?f.resolve():j(c,e,1)},r=function(a){var b;return b=JSON.parse(JSON.stringify(a)),e(b)},v=function(a){return null!=d.callback?d.callback(a,r):r(a),f.resolve()},"object"==typeof c?v(c):u(c),x.localizePromise=f,x},a.fn.localize=a.localize,a.localize.data={}}(jQuery); \ No newline at end of file diff --git a/examples/localize_from_object.html b/examples/localize_from_object.html index 8a54927..df67432 100644 --- a/examples/localize_from_object.html +++ b/examples/localize_from_object.html @@ -24,7 +24,12 @@

Test localization...

Ruby image should be round.

It failed :(

+

Optional callback never happened.

+

This text should remain unchanged

+ + + diff --git a/src/jquery.localize.coffee b/src/jquery.localize.coffee index dd186ab..b6baccc 100644 --- a/src/jquery.localize.coffee +++ b/src/jquery.localize.coffee @@ -144,12 +144,18 @@ do ($ = jQuery) -> else loadLanguage(filename, lang, 1) - # Retrieve translations from an object - useObjectAsDataSource = (object) -> - # We stringify and parse the received object to ensure the object is a valid json - # Any functions defined within the object will be removed during this process + # We stringify and parse the received object to ensure the object is a valid json + # Any functions defined within the object will be removed during this process + sanitizedCallback = (object) -> data = JSON.parse(JSON.stringify(object)) defaultCallback(data) + + # Retrieve translations from an object + useObjectAsDataSource = (object) -> + if options.callback? + options.callback(object, sanitizedCallback) + else + sanitizedCallback(object) deferred.resolve() # If 'pkg' is an object, use it as the source for translations diff --git a/test/localize_test.coffee b/test/localize_test.coffee index 9b5319a..3b295f5 100644 --- a/test/localize_test.coffee +++ b/test/localize_test.coffee @@ -208,13 +208,37 @@ do ($ = jQuery) -> module "Using object as data source" asyncTest "basic tag text substitution using object as data source", (assert) -> - obj = { "basic": "basic success" } + obj = basic: "basic success" t = localizableTagWithRel("p", "basic", text: "basic fail") t.localize(obj).localizePromise.then -> assert.equal t.text(), "basic success" - asyncTest "don't replace tag text if matching object property contains a function", (assert) -> - obj = { "function": (->) } - t = localizableTagWithRel("p", "function", text: "This text should remain unchanged") + asyncTest "custom callback is fired when object is used as data source", (assert) -> + opts = {} + opts.callback = (data, defaultCallback) -> + data.custom_callback = "custom callback success" + defaultCallback(data) + t = localizableTagWithRel("p", "custom_callback", text: "custom callback fail") + t.localize({}, opts).localizePromise.then -> + assert.equal t.text(), "custom callback success" + + asyncTest "tag text must not be replaced if matching object property contains a function", (assert) -> + obj = "function": (->) + t = localizableTagWithRel("p", "function", text: "this text should remain unchanged") t.localize(obj).localizePromise.then -> - assert.equal t.text(), "This text should remain unchanged" + assert.equal t.text(), "this text should remain unchanged" + + asyncTest "input value must not be replaced if matching object property contains a function", (assert) -> + obj = "function": (->) + t = localizableTagWithRel("input", "function", text: "remain after default callback") + t.localize(obj).localizePromise.then -> + assert.equal t.text(), "remain after default callback" + + asyncTest "input value must not be replaced if custom callback introduced a matching property that contains a function", (assert) -> + opts = {} + opts.callback = (data, defaultCallback) -> + data.added_function = (->) + defaultCallback(data) + t = localizableTagWithRel("input", "added_function", text: "remain after custom callback") + t.localize({}, opts).localizePromise.then -> + assert.equal t.text(), "remain after custom callback" diff --git a/test/localize_test.js b/test/localize_test.js index 54aa408..922328d 100644 --- a/test/localize_test.js +++ b/test/localize_test.js @@ -366,7 +366,7 @@ asyncTest("basic tag text substitution using object as data source", function(assert) { var obj, t; obj = { - "basic": "basic success" + basic: "basic success" }; t = localizableTagWithRel("p", "basic", { text: "basic fail" @@ -375,16 +375,56 @@ return assert.equal(t.text(), "basic success"); }); }); - return asyncTest("don't replace tag text if matching object property contains a function", function(assert) { + asyncTest("custom callback is fired when object is used as data source", function(assert) { + var opts, t; + opts = {}; + opts.callback = function(data, defaultCallback) { + data.custom_callback = "custom callback success"; + return defaultCallback(data); + }; + t = localizableTagWithRel("p", "custom_callback", { + text: "custom callback fail" + }); + return t.localize({}, opts).localizePromise.then(function() { + return assert.equal(t.text(), "custom callback success"); + }); + }); + asyncTest("tag text must not be replaced if matching object property contains a function", function(assert) { var obj, t; obj = { "function": (function() {}) }; t = localizableTagWithRel("p", "function", { - text: "This text should remain unchanged" + text: "this text should remain unchanged" + }); + return t.localize(obj).localizePromise.then(function() { + return assert.equal(t.text(), "this text should remain unchanged"); + }); + }); + asyncTest("input value must not be replaced if matching object property contains a function", function(assert) { + var obj, t; + obj = { + "function": (function() {}) + }; + t = localizableTagWithRel("input", "function", { + text: "remain after default callback" }); return t.localize(obj).localizePromise.then(function() { - return assert.equal(t.text(), "This text should remain unchanged"); + return assert.equal(t.text(), "remain after default callback"); + }); + }); + return asyncTest("input value must not be replaced if custom callback introduced a matching property that contains a function", function(assert) { + var opts, t; + opts = {}; + opts.callback = function(data, defaultCallback) { + data.added_function = (function() {}); + return defaultCallback(data); + }; + t = localizableTagWithRel("input", "added_function", { + text: "remain after custom callback" + }); + return t.localize({}, opts).localizePromise.then(function() { + return assert.equal(t.text(), "remain after custom callback"); }); }); })(jQuery); From 4f65cee22be5eee0d15eed0df875dfbf743de507 Mon Sep 17 00:00:00 2001 From: Sara Date: Sun, 9 Jul 2017 13:55:23 +0100 Subject: [PATCH 06/10] Added new example for localize with objects I've added a new example to illustrate on how to call localize by passing the response of a webservice as an argument to 'jquery-localize'. Both of the examples created for this new feature have been moved to a new 'objectDS' folder, as to keep them separated from the default examples. --- .../objectDS/localize_from_external_ws.html | 36 +++++++++++++++++++ .../{ => objectDS}/localize_from_object.html | 6 ++-- examples/objectDS/res/ws-response | 7 ++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 examples/objectDS/localize_from_external_ws.html rename examples/{ => objectDS}/localize_from_object.html (91%) create mode 100644 examples/objectDS/res/ws-response diff --git a/examples/objectDS/localize_from_external_ws.html b/examples/objectDS/localize_from_external_ws.html new file mode 100644 index 0000000..2325eeb --- /dev/null +++ b/examples/objectDS/localize_from_external_ws.html @@ -0,0 +1,36 @@ + + + + + + + Localize Test + + + + + +

Test localization...

+

Waiting for server response...

+

Waiting for server response...

+

+ Waiting for server response... +

+ + + + + diff --git a/examples/localize_from_object.html b/examples/objectDS/localize_from_object.html similarity index 91% rename from examples/localize_from_object.html rename to examples/objectDS/localize_from_object.html index df67432..9816196 100644 --- a/examples/localize_from_object.html +++ b/examples/objectDS/localize_from_object.html @@ -7,7 +7,7 @@ Localize Test - + @@ -20,7 +20,7 @@

Test localization...

- a square ruby + a square ruby Ruby image should be round.

It failed :(

@@ -43,7 +43,7 @@

Test localization...

"optgroup": "optgroup success", "option": "option success", "ruby_image": { - "src": "ruby_round.gif", + "src": "../ruby_round.gif", "alt": "a round ruby", "title": "A Round Ruby" }, diff --git a/examples/objectDS/res/ws-response b/examples/objectDS/res/ws-response new file mode 100644 index 0000000..40e6a78 --- /dev/null +++ b/examples/objectDS/res/ws-response @@ -0,0 +1,7 @@ +{ + "app": { + "name": "MyApp", + "version": "1.2", + "description": "App using 'jquery-localize'" + } +} \ No newline at end of file From efaf3e120ed3d778dd6e07813ea0b9126e102d8b Mon Sep 17 00:00:00 2001 From: Sara Date: Sun, 9 Jul 2017 13:59:16 +0100 Subject: [PATCH 07/10] Readme file updated with info on how to use localize with raw objects --- README.md | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 609313a..73f02a5 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,48 @@ $("[data-localize]").localize("application", { ``` -See the test/samples for working examples. +See the _examples_ folder for working examples. + +### Using raw objects as data source +Given you can't have your texts stored inside of a json file that follows the naming convention defined earlier, you may also pass a raw javascript object to the 'localize' method, just like this: + +```javascript +var myTexts = { + "app": { + "name": "MyApp", + "version": "1.2", + "description": "App using 'jquery-localize'" + } +}; + +// Localize your content by using the object values +$("[data-localize]").localize(myTexts); +``` + +The way of referencing the different object properties from the html file is exactly the same as before. + +```html + +

(text to replace)

+``` + +This feature might be useful if you'll be retrieving your texts from an external service whose url doesn't match the _'{pathPrefix}/{filename}-{lang}.{extension}'_ syntax. + +In that case, you'll need to perform an ajax request to said service from inside your own code and call 'localize' by passing in the results returned by the server: + +```javascript +$.ajax({ + method: "GET", + url: "api/app/details", + data: { lang: "en" }, + dataType: "json", + success: function (data) { + $("[data-localize]").localize(data); + } +}); +``` + +Remember to keep a json friendly structure for every object you send as an argument to 'localize', such as in the previous examples. # Contributing From 56ffb48b72aed1192c95aede9b909c3e941fb32d Mon Sep 17 00:00:00 2001 From: Sara Date: Sun, 9 Jul 2017 16:53:13 +0100 Subject: [PATCH 08/10] Minor changes in readme file --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 73f02a5..14f446a 100644 --- a/README.md +++ b/README.md @@ -177,10 +177,9 @@ var myTexts = { $("[data-localize]").localize(myTexts); ``` -The way of referencing the different object properties from the html file is exactly the same as before. +The way of referencing the different object properties from the html file is exactly the same as before. In the following example, the 'p' tag content will be replaced with the value of the 'app.name' property inside our 'myTexts' object: ```html -

(text to replace)

``` From b7dff520a9213e70d84da9f7e080fcb3027a9033 Mon Sep 17 00:00:00 2001 From: Sara Date: Sun, 9 Jul 2017 16:53:36 +0100 Subject: [PATCH 09/10] Removed function properties in sample object Since we want the examples to be fully functional and show how the library is meant to be used, I've removed the properties for which I've assigned a function as their value. Still, the sanitizer callback will be kept and so will the test to guarantee that there will be no problem if the user passes an object that include a function among their keys. --- examples/objectDS/localize_from_object.html | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/examples/objectDS/localize_from_object.html b/examples/objectDS/localize_from_object.html index 9816196..d20e7be 100644 --- a/examples/objectDS/localize_from_object.html +++ b/examples/objectDS/localize_from_object.html @@ -26,10 +26,6 @@

Test localization...

It failed :(

Optional callback never happened.

-

This text should remain unchanged

- - -