diff --git a/README.md b/README.md index 609313a..0a13266 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,49 @@ $("[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` plugin, just like this: + +```javascript +// Define a javascript object with your texts +// Its structure must match that of a json object +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. 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)

+``` + +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 diff --git a/dist/jquery.localize.js b/dist/jquery.localize.js index 7999965..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, 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, sanitizedCallback, setAttrFromValueForKey, setTextFromValueForKey, useFileAsDataSource, useObjectAsDataSource, valueForKey, wrappedSet; if (options == null) { options = {}; } @@ -180,11 +180,32 @@ 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(); + 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(filename, lang, 1); + } + }; + sanitizedCallback = function(object) { + var data; + data = JSON.parse(JSON.stringify(object)); + return defaultCallback(data); + }; + useObjectAsDataSource = function(object) { + if (options.callback != null) { + options.callback(object, sanitizedCallback); + } else { + sanitizedCallback(object); + } + return deferred.resolve(); + }; + if (typeof pkg === "object") { + useObjectAsDataSource(pkg); } else { - loadLanguage(pkg, lang, 1); + useFileAsDataSource(pkg); } wrappedSet.localizePromise = deferred; return wrappedSet; diff --git a/dist/jquery.localize.min.js b/dist/jquery.localize.min.js index fa39064..df9522e 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-09 * 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,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/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/objectDS/localize_from_object.html b/examples/objectDS/localize_from_object.html new file mode 100644 index 0000000..d20e7be --- /dev/null +++ b/examples/objectDS/localize_from_object.html @@ -0,0 +1,69 @@ + + + + + + + Localize Test + + + + + +

Test localization...

+

puts 2 + 2

+ + +

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

+

It failed :(

+

Optional callback never happened.

+ + + + + 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 diff --git a/src/jquery.localize.coffee b/src/jquery.localize.coffee index 6c1996a..b6baccc 100644 --- a/src/jquery.localize.coffee +++ b/src/jquery.localize.coffee @@ -136,11 +136,33 @@ 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 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(filename, lang, 1) + + # 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 + if typeof(pkg) == "object" + useObjectAsDataSource(pkg) else - loadLanguage(pkg, lang, 1) + useFileAsDataSource(pkg) wrappedSet.localizePromise = deferred diff --git a/test/localize_test.coffee b/test/localize_test.coffee index 9936ad9..3b295f5 100644 --- a/test/localize_test.coffee +++ b/test/localize_test.coffee @@ -203,3 +203,42 @@ 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 "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" + + 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 8318972..922328d 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,69 @@ 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"); + }); + }); + 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" + }); + 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(), "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);