Permalink
Browse files

first release

  • Loading branch information...
1 parent 422b76d commit 3ab52406f55a7013b7af33e059dda375c4e14e36 Jean-Philippe Joyal committed Jun 29, 2010
Showing with 1,604 additions and 0 deletions.
  1. +20 −0 MIT-LICENSE.txt
  2. +150 −0 README
  3. +6 −0 TODO.txt
  4. +143 −0 jquery.jsperanto.js
  5. +21 −0 test/index.html
  6. +38 −0 test/locales/testlang.json
  7. +119 −0 test/qunit.css
  8. +1,069 −0 test/qunit.js
  9. +38 −0 test/test.js
View
@@ -0,0 +1,20 @@
+Copyright (c) 2010 Jean-Philippe Joyal, http://leastusedfeature.com
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
150 README
@@ -0,0 +1,150 @@
+jsperanto
+=========
+
+Simple translation for your javascripts, yummy with your favorite templates engine like EJS.
+
+ * Pluralization, interpolation & "nested lookup" support for your translations
+ * Uses XHR to get a JSON dictionary (or load it your own way & format)
+ * JSLint-ed, QUnit-ed
+ * similar to Rails's i18n but sans backend needed
+ * No global pollution (hides under jQuery.jsperanto)
+ * Works with : IE6+, Firefox 3+, Safari 3+, Chrome, Opera 9+
+
+Depends on jQuery 1.3.2+ (uses $.ajax, $.each, $.extend)
+
+Usage example
+=============
+
+ $.jsperanto.init(function(t){
+ t('project.name'); //-> "jsperanto"
+ $.t('project.store'); //-> "JSON"
+
+ $.t('can_speak',{count:1}); //-> "I can only speak one language"
+ $.t('can_speak',{count:3}); //-> "I can speak 3 languages"
+ $.t('can_speak_plural',{count:'any'}); //-> "I can speak any languages"
+
+ $.t('project.size.source',{value:4,unit:"kb"}); //-> "jsperanto is 4 kb"
+ $.t('project.size.min',{value:1727,unit:"bytes"}) //-> "jsperanto is 1727 bytes when minified"
+ $.t('project.size.gzip',{value:833,unit:"bytes"}) //-> "jsperanto is 833 bytes when minified and gzipped"
+ });
+
+ //given this dictionary
+ {
+ "project" : {
+ "name" : "jsperanto",
+ "store" : "JSON",
+ "size" : {
+ "source" : "$t(project.name) is __value__ __unit__",
+ "min" : "$t(project.size.source) when minified",
+ "gzip" : "$t(project.size.min) and gzipped"
+ }
+ },
+ "can_speak" : "I can only speak one language",
+ "can_speak_plural" : "I can speak __count__ languages"
+ }
+
+API
+===
+
+**$.jsperanto.init(function(t),options)**
+
+initialize jsperanto by loading the dictionary, calling back when ready
+
+**function(t)** : is called once jsperanto is ready, passing the translate method ($.jsperanto.translate)
+
+**options** extends these defaults
+
+ o.interpolationPrefix = '__';
+ o.interpolationSuffix = '__';
+ o.pluralSuffix = "_plural";
+ o.maxRecursion = 50; //used while applying reuse of strings to avoid infinite loop
+ o.reusePrefix = "$t("; //nested lookup prefix
+ o.reuseSuffix = ")"; //nested lookup suffix
+ o.fallbackLang = 'en-US'; // see Language fallback section
+ o.dicoPath = 'locales'; // see Dictionary section
+ o.keyseparator = "."; // keys passed to $.jsperanto.translate use this separator
+ o.setDollarT = true; // $.t aliases $.jsperanto.translate, nice shortcut
+ o.dictionary = false; // to supply the dictionary instead of loading it using $.ajax. A (big) javascript object containing your namespaced translations
+ o.lang = false; //specify a language to use i.e en-US
+
+Use init to switch language too :
+
+ $.jsperanto.init(someMethod,{lang:"fr"})
+
+**$.jsperanto.translate(key,options)**
+
+looks up the key in the dictionary applying plural, interpolation & nested lookup.
+
+**key** to lookup in the dictionary, for example "register.error.email"
+
+**options** each prop name are are used for interpolation
+
+**options.count** special prop that indicates to retrieve the plural version (**key**_plural) if its greater than 1. Also used for interpolation
+
+**options.defaultValue** specify default value if the key can't be resolved (the key itself will be sent back if no defaultValue is provided)
+
+**aliases** : $.jsperanto.t , $.t if init option _setDollarT_ is left to true.
+
+Dictionary loading
+==================
+
+Using defaults, jsperanto uses a basic browser language detection
+
+ (navigator.language) ? navigator.language : navigator.userLanguage
+
+to determine what dictionary file to load. You can also instruct jsperanto to load a specific language (via init option _lang_).
+
+Once the language is determined, jsperanto will use $.ajax to load the dictionary using _locales/**somelang**.json_ as url. For example _locales/fr-CA.json_ is used for a french canadian browser. If the json file can't be retrieved, it will try to get the fallback language, which is 'en-US' by default. (you can change this using init option _fallbacklang_). If no dictionary file can be retrieved at all, jsperanto translate method will simply return the provided key.
+
+You can bypass this loading mechanism completely by providing a dictionary object to init (_options.dictionary_)
+
+Switching language
+==================
+
+Simply use init again and specify a language (or dictionary) to use.
+
+ $.jsperanto.init(someMethod,{lang:"fr"})
+
+Licence
+=======
+
+MIT License
+
+Copyright (c) 2010 Jean-Philippe Joyal, <http://leastusedfeature.wordpress.com>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Inspiration & similar projects
+==============================
+
+[Rails i18n](http://guides.rubyonrails.org/i18n.html)
+
+[Babilu](http://tore.darell.no/posts/introducing_babilu_rails_i18n_for_your_javascript)
+
+[l10n.js](http://github.com/eligrey/l10n.js)
+
+[javascript_i18n](http://github.com/qoobaa/javascript_i18n)
+
+
+Thanks
+======
+
+Many thanks to Radialpoint for letting me open source this work
+
View
@@ -0,0 +1,6 @@
+license
+ * file header
+ *
+
+Tests
+ * init options
View
@@ -0,0 +1,143 @@
+//jquery 1.3.2 dependencies : $.each, $.extend, $.ajax
+
+(function($) {
+ //defaults
+ var o = {};
+ o.interpolationPrefix = '__';
+ o.interpolationSuffix = '__';
+ o.pluralSuffix = "_plural";
+ o.maxRecursion = 50; //used while applying reuse of strings to avoid infinite loop
+ o.reusePrefix = "$t(";
+ o.reuseSuffix = ")";
+ o.fallbackLang = 'en-US'; // see Language fallback section
+ o.dicoPath = 'locales'; // see Dictionary section
+ o.keyseparator = "."; // keys passed to $.jsperanto.translate use this separator
+ o.setDollarT = true; // $.t aliases $.jsperanto.translate, nice shortcut
+ o.dictionary = false; // to supply the dictionary instead of loading it using $.ajax. A (big) javascript object containing your namespaced translations
+ o.lang = false; //specify a language to use
+ o.pluralNotFound = ["plural_not_found_", Math.random()].join(''); // used internally by translate
+
+ var dictionary = false; //not yet loaded
+ var currentLang = false;
+ var count_of_replacement = 0;
+
+ function init(callback,options){
+ $.extend(o,options);
+ if(!o.lang){o.lang = detectLanguage();}
+ loadDictionary(o.lang,function(loadedLang){
+ currentLang = loadedLang;
+ if(o.setDollarT){$.t = $.t || translate;} //shortcut
+ callback(translate);
+ });
+ }
+
+ function applyReplacement(string,replacementHash){
+ $.each(replacementHash,function(key,value){
+ string = string.replace([o.interpolationPrefix,key,o.interpolationSuffix].join(''),value);
+ });
+ return string;
+ }
+
+ function applyReuse(translated,options){
+ while (translated.indexOf(o.reusePrefix) != -1){
+ count_of_replacement++;
+ if(count_of_replacement > o.maxRecursion){break;} // safety net for too much recursion
+ var index_of_opening = translated.indexOf(o.reusePrefix);
+ var index_of_end_of_closing = translated.indexOf(o.reuseSuffix,index_of_opening) + o.reuseSuffix.length;
+ var token = translated.substring(index_of_opening,index_of_end_of_closing);
+ var token_sans_symbols = token.replace(o.reusePrefix,"").replace(o.reuseSuffix,"");
+ var translated_token = _translate(token_sans_symbols,options);
+ translated = translated.replace(token,translated_token);
+ }
+ return translated;
+ }
+
+ function detectLanguage(){
+ if(navigator){
+ return (navigator.language) ? navigator.language : navigator.userLanguage;
+ }else{
+ return o.fallbackLang;
+ }
+ }
+
+ function needsPlural(options){
+ return (options.count && typeof options.count != 'string' && options.count > 1);
+ }
+
+
+ function translate(dottedkey,options){
+ count_of_replacement = 0;
+ return _translate(dottedkey,options);
+ }
+
+ /*
+ options.defaultValue
+ options.count
+ */
+ function _translate(dottedkey,options){
+ options = options || {};
+ var notfound = options.defaultValue || dottedkey;
+ if(!dictionary){return notfound;} // No dictionary to translate from
+
+ if(needsPlural(options)){
+ var optionsSansCount = $.extend({},options);
+ delete optionsSansCount.count;
+ optionsSansCount.defaultValue = o.pluralNotFound;
+ var pluralKey = dottedkey + o.pluralSuffix;
+ var translated = translate(pluralKey,optionsSansCount);
+ if(translated != o.pluralNotFound){
+ return applyReplacement(translated,{count:options.count});//apply replacement for count only
+ }// else continue translation with original/singular key
+ }
+
+ var keys = dottedkey.split(o.keyseparator);
+ var i = 0;
+ var value = dictionary;
+ while(keys[i]) {
+ value = value && value[keys[i]];
+ i++;
+ }
+ if(value){
+ value = applyReplacement(value,options);
+ value = applyReuse(value,options);
+ return value;
+ }else{
+ return notfound;
+ }
+ }
+
+ function loadDictionary(lang,doneCallback){
+ if(o.dictionary){
+ dictionary = o.dictionary;
+ doneCallback(lang);
+ return;
+ }
+ $.ajax({
+ url: [o.dicoPath,"/", lang, '.json'].join(''),
+ success: function(data,status,xhr){
+ dictionary = data;
+ doneCallback(lang);
+ },
+ error : function(xhr,status,error){
+ if(lang != o.fallbackLang){
+ loadDictionary(o.fallbackLang,doneCallback);
+ }else{
+ doneCallback(false);
+ }
+ },
+ dataType: "json"
+ });
+ }
+
+ function lang(){
+ return currentLang;
+ }
+
+ $.jsperanto = $.jsperanto || {
+ init:init,
+ t:translate,
+ translate:translate,
+ detectLanguage : detectLanguage,
+ lang : lang
+ };
+})(jQuery);
View
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>QUnit Test Suite</title>
+ <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>
+
+ <script type="text/javascript" src="../jquery.jsperanto.js"></script>
+
+ <link rel="stylesheet" href="qunit.css" type="text/css" media="screen">
+ <script type="text/javascript" src="qunit.js"></script>
+
+ <script type="text/javascript" src="test.js"></script>
+</head>
+<body>
+ <h1 id="qunit-header">QUnit Test Suite</h1>
+ <h2 id="qunit-banner"></h2>
+ <div id="qunit-testrunner-toolbar"></div>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+</body>
+</html>
View
@@ -0,0 +1,38 @@
+{
+ "sections" : {
+ "home" : "Home",
+ "files" : "My Files"
+ },
+ "product" : {
+ "name" : "jsperanto"
+ },
+ "withHTML" : "<b>this would be bold</b>",
+ "withreuse" : "$t(product.name) and $t(sections.home)",
+ "withreplacement" : "since __year__",
+ "4" : {
+ "level" : {
+ "of" : {
+ "nesting": "4 level of nesting"
+ }
+ }
+ },
+ "pluralversionexist" : "singular version of pluralversionexist",
+ "pluralversionexist_plural" : "plural version of pluralversionexist",
+ "pluralversiondoesnotexist" : "plural version does not exist",
+ "count and replacement" : "you have __count__ friend",
+ "count and replacement_plural" : "you have __count__ friends",
+
+ "infinite" : "infinite $t(recursion)",
+ "recursion" : "recursion $t(infinite)",
+ "project" : {
+ "name" : "jsperanto",
+ "store" : "JSON",
+ "size" : {
+ "source" : "$t(project.name) is __value__ __unit__",
+ "min" : "$t(project.size.source) when minified",
+ "gzip" : "$t(project.size.min) and gzipped"
+ }
+ },
+ "can_speak" : "I can only speak one language",
+ "can_speak_plural" : "I can speak __count__ languages"
+}
Oops, something went wrong.

0 comments on commit 3ab5240

Please sign in to comment.