├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── jed.js ├── package.json ├── plurals.jison └── test ├── common.js ├── index.html ├── jquery.min.js └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | - 4 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors, https://js.foundation/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REPORTER = dot 2 | 3 | test: 4 | @./node_modules/.bin/mocha \ 5 | --require test/common \ 6 | --reporter $(REPORTER) \ 7 | --growl \ 8 | test/tests.js 9 | 10 | test-browser: 11 | @./node_modules/.bin/serve . 12 | 13 | .PHONY: test 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://secure.travis-ci.org/messageformat/Jed.png)](http://travis-ci.org/messageformat/Jed) 2 | 3 | # Jed 4 | 5 | *Gettext Style i18n for Modern JavaScript Apps* 6 | 7 | For more info, please visit the docs site at . 8 | 9 | ## You sure you don't want something more modern? 10 | 11 | Jed is feature complete in my opinion. I am happy to fix bugs, but generally am not interested in adding more to the library. 12 | 13 | I also maintain [messageformat.js](https://github.com/messageformat/messageformat.js). If you don't specifically need a gettext implementation, I might suggest using MessageFormat instead, as it has better support for plurals/gender and has built-in locale data. 14 | 15 | 16 | ## Parsing Gettext Files 17 | 18 | Jed doesn't include a Gettext file parser, but several third-party parsers exist that can have their output adapted for Jed. 19 | 20 | #### Node 21 | 22 | Just search the npm repository, there are several PO and MO file parsers available. 23 | 24 | #### Browser 25 | 26 | [Jed Gettext Parser](https://github.com/WrinklyNinja/jed-gettext-parser) is the only known browser MO file parser, and it also works in Node, and outputs Jed-compatible data directly. 27 | 28 | [gettext.js](https://code.google.com/p/gettext-js) and [Pomo.js](https://github.com/cfv1984/pomo) both include browser-compatible PO file parsers. 29 | 30 | ## Todo 31 | 32 | * Build time generation of plural form functions 33 | * Web interface for building translation sets 34 | * Code introspection for default values 35 | 36 | ## License 37 | 38 | Jed is a member project of the [JavaScript Foundation](https://js.foundation/) 39 | 40 | You may use this software under the MIT License. 41 | 42 | ## Contributor License Agreement 43 | 44 | We require all contributions to be covered under the JS Foundation's [Contributor License Agreement](https://js.foundation/CLA/). This can be done electronically and essentially ensures that you are making it clear that your contributions are your contributions, you have the legal right to contribute and you are transferring the copyright of your works to the JS Foundation. 45 | 46 | If you are an unfamiliar contributor to the committer assessing your pull request, it is best to make it clear how you are covered by a CLA in the notes of the pull request. The committer will verify your status. 47 | 48 | If your GitHub user id you are submitting your pull request from differs from the e-mail address which you have signed your CLA under, you should specifically note what you have your CLA filed under (and for CCLA that you are listed under your company's authorised contributors). 49 | 50 | 51 | ## Author 52 | 53 | * Alex Sexton - @slexaxton - 54 | 55 | 56 | ## Credits 57 | 58 | A good chunk of sanity checking was done against the gettext.js tests. That was written by: 59 | 60 | * Joshua I. Miller 61 | 62 | The sprintf implementation is from: 63 | 64 | * Alexandru Marasteanu 65 | 66 | 67 | ## The name 68 | 69 | The name jed.js is an homage to Jed Schmidt () the JavaScript community member who is a japanese translator by day, and a "hobbyist" JavaScript programmer by night. Give your kids three character names and they'll probably get software named after them too. 70 | -------------------------------------------------------------------------------- /jed.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve jed.js https://github.com/SlexAxton/Jed 3 | */ 4 | /* 5 | ----------- 6 | A gettext compatible i18n library for modern JavaScript Applications 7 | 8 | by Alex Sexton - AlexSexton [at] gmail - @SlexAxton 9 | 10 | MIT License 11 | 12 | A jQuery Foundation project - requires CLA to contribute - 13 | https://contribute.jquery.org/CLA/ 14 | 15 | 16 | 17 | Jed offers the entire applicable GNU gettext spec'd set of 18 | functions, but also offers some nicer wrappers around them. 19 | The api for gettext was written for a language with no function 20 | overloading, so Jed allows a little more of that. 21 | 22 | Many thanks to Joshua I. Miller - unrtst@cpan.org - who wrote 23 | gettext.js back in 2008. I was able to vet a lot of my ideas 24 | against his. I also made sure Jed passed against his tests 25 | in order to offer easy upgrades -- jsgettext.berlios.de 26 | */ 27 | (function (root, undef) { 28 | 29 | // Set up some underscore-style functions, if you already have 30 | // underscore, feel free to delete this section, and use it 31 | // directly, however, the amount of functions used doesn't 32 | // warrant having underscore as a full dependency. 33 | // Underscore 1.3.0 was used to port and is licensed 34 | // under the MIT License by Jeremy Ashkenas. 35 | var ArrayProto = Array.prototype, 36 | ObjProto = Object.prototype, 37 | slice = ArrayProto.slice, 38 | hasOwnProp = ObjProto.hasOwnProperty, 39 | nativeForEach = ArrayProto.forEach, 40 | breaker = {}; 41 | 42 | // We're not using the OOP style _ so we don't need the 43 | // extra level of indirection. This still means that you 44 | // sub out for real `_` though. 45 | var _ = { 46 | forEach : function( obj, iterator, context ) { 47 | var i, l, key; 48 | if ( obj === null ) { 49 | return; 50 | } 51 | 52 | if ( nativeForEach && obj.forEach === nativeForEach ) { 53 | obj.forEach( iterator, context ); 54 | } 55 | else if ( obj.length === +obj.length ) { 56 | for ( i = 0, l = obj.length; i < l; i++ ) { 57 | if ( i in obj && iterator.call( context, obj[i], i, obj ) === breaker ) { 58 | return; 59 | } 60 | } 61 | } 62 | else { 63 | for ( key in obj) { 64 | if ( hasOwnProp.call( obj, key ) ) { 65 | if ( iterator.call (context, obj[key], key, obj ) === breaker ) { 66 | return; 67 | } 68 | } 69 | } 70 | } 71 | }, 72 | extend : function( obj ) { 73 | this.forEach( slice.call( arguments, 1 ), function ( source ) { 74 | for ( var prop in source ) { 75 | obj[prop] = source[prop]; 76 | } 77 | }); 78 | return obj; 79 | } 80 | }; 81 | // END Miniature underscore impl 82 | 83 | // Jed is a constructor function 84 | var Jed = function ( options ) { 85 | // Some minimal defaults 86 | this.defaults = { 87 | "locale_data" : { 88 | "messages" : { 89 | "" : { 90 | "domain" : "messages", 91 | "lang" : "en", 92 | "plural_forms" : "nplurals=2; plural=(n != 1);" 93 | } 94 | // There are no default keys, though 95 | } 96 | }, 97 | // The default domain if one is missing 98 | "domain" : "messages", 99 | // enable debug mode to log untranslated strings to the console 100 | "debug" : false 101 | }; 102 | 103 | // Mix in the sent options with the default options 104 | this.options = _.extend( {}, this.defaults, options ); 105 | this.textdomain( this.options.domain ); 106 | 107 | if ( options.domain && ! this.options.locale_data[ this.options.domain ] ) { 108 | throw new Error('Text domain set to non-existent domain: `' + options.domain + '`'); 109 | } 110 | }; 111 | 112 | // The gettext spec sets this character as the default 113 | // delimiter for context lookups. 114 | // e.g.: context\u0004key 115 | // If your translation company uses something different, 116 | // just change this at any time and it will use that instead. 117 | Jed.context_delimiter = String.fromCharCode( 4 ); 118 | 119 | function getPluralFormFunc ( plural_form_string ) { 120 | return Jed.PF.compile( plural_form_string || "nplurals=2; plural=(n != 1);"); 121 | } 122 | 123 | function Chain( key, i18n ){ 124 | this._key = key; 125 | this._i18n = i18n; 126 | } 127 | 128 | // Create a chainable api for adding args prettily 129 | _.extend( Chain.prototype, { 130 | onDomain : function ( domain ) { 131 | this._domain = domain; 132 | return this; 133 | }, 134 | withContext : function ( context ) { 135 | this._context = context; 136 | return this; 137 | }, 138 | ifPlural : function ( num, pkey ) { 139 | this._val = num; 140 | this._pkey = pkey; 141 | return this; 142 | }, 143 | fetch : function ( sArr ) { 144 | if ( {}.toString.call( sArr ) != '[object Array]' ) { 145 | sArr = [].slice.call(arguments, 0); 146 | } 147 | return ( sArr && sArr.length ? Jed.sprintf : function(x){ return x; } )( 148 | this._i18n.dcnpgettext(this._domain, this._context, this._key, this._pkey, this._val), 149 | sArr 150 | ); 151 | } 152 | }); 153 | 154 | // Add functions to the Jed prototype. 155 | // These will be the functions on the object that's returned 156 | // from creating a `new Jed()` 157 | // These seem redundant, but they gzip pretty well. 158 | _.extend( Jed.prototype, { 159 | // The sexier api start point 160 | translate : function ( key ) { 161 | return new Chain( key, this ); 162 | }, 163 | 164 | textdomain : function ( domain ) { 165 | if ( ! domain ) { 166 | return this._textdomain; 167 | } 168 | this._textdomain = domain; 169 | }, 170 | 171 | gettext : function ( key ) { 172 | return this.dcnpgettext.call( this, undef, undef, key ); 173 | }, 174 | 175 | dgettext : function ( domain, key ) { 176 | return this.dcnpgettext.call( this, domain, undef, key ); 177 | }, 178 | 179 | dcgettext : function ( domain , key /*, category */ ) { 180 | // Ignores the category anyways 181 | return this.dcnpgettext.call( this, domain, undef, key ); 182 | }, 183 | 184 | ngettext : function ( skey, pkey, val ) { 185 | return this.dcnpgettext.call( this, undef, undef, skey, pkey, val ); 186 | }, 187 | 188 | dngettext : function ( domain, skey, pkey, val ) { 189 | return this.dcnpgettext.call( this, domain, undef, skey, pkey, val ); 190 | }, 191 | 192 | dcngettext : function ( domain, skey, pkey, val/*, category */) { 193 | return this.dcnpgettext.call( this, domain, undef, skey, pkey, val ); 194 | }, 195 | 196 | pgettext : function ( context, key ) { 197 | return this.dcnpgettext.call( this, undef, context, key ); 198 | }, 199 | 200 | dpgettext : function ( domain, context, key ) { 201 | return this.dcnpgettext.call( this, domain, context, key ); 202 | }, 203 | 204 | dcpgettext : function ( domain, context, key/*, category */) { 205 | return this.dcnpgettext.call( this, domain, context, key ); 206 | }, 207 | 208 | npgettext : function ( context, skey, pkey, val ) { 209 | return this.dcnpgettext.call( this, undef, context, skey, pkey, val ); 210 | }, 211 | 212 | dnpgettext : function ( domain, context, skey, pkey, val ) { 213 | return this.dcnpgettext.call( this, domain, context, skey, pkey, val ); 214 | }, 215 | 216 | // The most fully qualified gettext function. It has every option. 217 | // Since it has every option, we can use it from every other method. 218 | // This is the bread and butter. 219 | // Technically there should be one more argument in this function for 'Category', 220 | // but since we never use it, we might as well not waste the bytes to define it. 221 | dcnpgettext : function ( domain, context, singular_key, plural_key, val ) { 222 | // Set some defaults 223 | 224 | plural_key = plural_key || singular_key; 225 | 226 | // Use the global domain default if one 227 | // isn't explicitly passed in 228 | domain = domain || this._textdomain; 229 | 230 | var fallback; 231 | 232 | // Handle special cases 233 | 234 | // No options found 235 | if ( ! this.options ) { 236 | // There's likely something wrong, but we'll return the correct key for english 237 | // We do this by instantiating a brand new Jed instance with the default set 238 | // for everything that could be broken. 239 | fallback = new Jed(); 240 | return fallback.dcnpgettext.call( fallback, undefined, undefined, singular_key, plural_key, val ); 241 | } 242 | 243 | // No translation data provided 244 | if ( ! this.options.locale_data ) { 245 | throw new Error('No locale data provided.'); 246 | } 247 | 248 | if ( ! this.options.locale_data[ domain ] ) { 249 | throw new Error('Domain `' + domain + '` was not found.'); 250 | } 251 | 252 | if ( ! this.options.locale_data[ domain ][ "" ] ) { 253 | throw new Error('No locale meta information provided.'); 254 | } 255 | 256 | // Make sure we have a truthy key. Otherwise we might start looking 257 | // into the empty string key, which is the options for the locale 258 | // data. 259 | if ( ! singular_key ) { 260 | throw new Error('No translation key found.'); 261 | } 262 | 263 | var key = context ? context + Jed.context_delimiter + singular_key : singular_key, 264 | locale_data = this.options.locale_data, 265 | dict = locale_data[ domain ], 266 | defaultConf = (locale_data.messages || this.defaults.locale_data.messages)[""], 267 | pluralForms = dict[""].plural_forms || dict[""]["Plural-Forms"] || dict[""]["plural-forms"] || defaultConf.plural_forms || defaultConf["Plural-Forms"] || defaultConf["plural-forms"], 268 | val_list, 269 | res; 270 | 271 | var val_idx; 272 | if (val === undefined) { 273 | // No value passed in; assume singular key lookup. 274 | val_idx = 0; 275 | 276 | } else { 277 | // Value has been passed in; use plural-forms calculations. 278 | 279 | // Handle invalid numbers, but try casting strings for good measure 280 | if ( typeof val != 'number' ) { 281 | val = parseInt( val, 10 ); 282 | 283 | if ( isNaN( val ) ) { 284 | throw new Error('The number that was passed in is not a number.'); 285 | } 286 | } 287 | 288 | val_idx = getPluralFormFunc(pluralForms)(val); 289 | } 290 | 291 | // Throw an error if a domain isn't found 292 | if ( ! dict ) { 293 | throw new Error('No domain named `' + domain + '` could be found.'); 294 | } 295 | 296 | val_list = dict[ key ]; 297 | 298 | // If there is no match, then revert back to 299 | // english style singular/plural with the keys passed in. 300 | if ( ! val_list || val_idx > val_list.length ) { 301 | if (this.options.missing_key_callback) { 302 | this.options.missing_key_callback(key, domain); 303 | } 304 | res = [ singular_key, plural_key ]; 305 | 306 | // collect untranslated strings 307 | if (this.options.debug===true) { 308 | console.log(res[ getPluralFormFunc(pluralForms)( val ) ]); 309 | } 310 | return res[ getPluralFormFunc()( val ) ]; 311 | } 312 | 313 | res = val_list[ val_idx ]; 314 | 315 | // This includes empty strings on purpose 316 | if ( ! res ) { 317 | res = [ singular_key, plural_key ]; 318 | return res[ getPluralFormFunc()( val ) ]; 319 | } 320 | return res; 321 | } 322 | }); 323 | 324 | 325 | // We add in sprintf capabilities for post translation value interolation 326 | // This is not internally used, so you can remove it if you have this 327 | // available somewhere else, or want to use a different system. 328 | 329 | // We _slightly_ modify the normal sprintf behavior to more gracefully handle 330 | // undefined values. 331 | 332 | /** 333 | sprintf() for JavaScript 0.7-beta1 334 | http://www.diveintojavascript.com/projects/javascript-sprintf 335 | 336 | Copyright (c) Alexandru Marasteanu 337 | All rights reserved. 338 | 339 | Redistribution and use in source and binary forms, with or without 340 | modification, are permitted provided that the following conditions are met: 341 | * Redistributions of source code must retain the above copyright 342 | notice, this list of conditions and the following disclaimer. 343 | * Redistributions in binary form must reproduce the above copyright 344 | notice, this list of conditions and the following disclaimer in the 345 | documentation and/or other materials provided with the distribution. 346 | * Neither the name of sprintf() for JavaScript nor the 347 | names of its contributors may be used to endorse or promote products 348 | derived from this software without specific prior written permission. 349 | 350 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 351 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 352 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 353 | DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY 354 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 355 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 356 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 357 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 358 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 359 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 360 | */ 361 | var sprintf = (function() { 362 | function get_type(variable) { 363 | return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); 364 | } 365 | function str_repeat(input, multiplier) { 366 | for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} 367 | return output.join(''); 368 | } 369 | 370 | var str_format = function() { 371 | if (!str_format.cache.hasOwnProperty(arguments[0])) { 372 | str_format.cache[arguments[0]] = str_format.parse(arguments[0]); 373 | } 374 | return str_format.format.call(null, str_format.cache[arguments[0]], arguments); 375 | }; 376 | 377 | str_format.format = function(parse_tree, argv) { 378 | var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; 379 | for (i = 0; i < tree_length; i++) { 380 | node_type = get_type(parse_tree[i]); 381 | if (node_type === 'string') { 382 | output.push(parse_tree[i]); 383 | } 384 | else if (node_type === 'array') { 385 | match = parse_tree[i]; // convenience purposes only 386 | if (match[2]) { // keyword argument 387 | arg = argv[cursor]; 388 | for (k = 0; k < match[2].length; k++) { 389 | if (!arg.hasOwnProperty(match[2][k])) { 390 | throw(sprintf('[sprintf] property "%s" does not exist', match[2][k])); 391 | } 392 | arg = arg[match[2][k]]; 393 | } 394 | } 395 | else if (match[1]) { // positional argument (explicit) 396 | arg = argv[match[1]]; 397 | } 398 | else { // positional argument (implicit) 399 | arg = argv[cursor++]; 400 | } 401 | 402 | if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) { 403 | throw(sprintf('[sprintf] expecting number but found %s', get_type(arg))); 404 | } 405 | 406 | // Jed EDIT 407 | if ( typeof arg == 'undefined' || arg === null ) { 408 | arg = ''; 409 | } 410 | // Jed EDIT 411 | 412 | switch (match[8]) { 413 | case 'b': arg = arg.toString(2); break; 414 | case 'c': arg = String.fromCharCode(arg); break; 415 | case 'd': arg = parseInt(arg, 10); break; 416 | case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break; 417 | case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break; 418 | case 'o': arg = arg.toString(8); break; 419 | case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; 420 | case 'u': arg = Math.abs(arg); break; 421 | case 'x': arg = arg.toString(16); break; 422 | case 'X': arg = arg.toString(16).toUpperCase(); break; 423 | } 424 | arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); 425 | pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; 426 | pad_length = match[6] - String(arg).length; 427 | pad = match[6] ? str_repeat(pad_character, pad_length) : ''; 428 | output.push(match[5] ? arg + pad : pad + arg); 429 | } 430 | } 431 | return output.join(''); 432 | }; 433 | 434 | str_format.cache = {}; 435 | 436 | str_format.parse = function(fmt) { 437 | var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; 438 | while (_fmt) { 439 | if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { 440 | parse_tree.push(match[0]); 441 | } 442 | else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { 443 | parse_tree.push('%'); 444 | } 445 | else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) { 446 | if (match[2]) { 447 | arg_names |= 1; 448 | var field_list = [], replacement_field = match[2], field_match = []; 449 | if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { 450 | field_list.push(field_match[1]); 451 | while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { 452 | if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { 453 | field_list.push(field_match[1]); 454 | } 455 | else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { 456 | field_list.push(field_match[1]); 457 | } 458 | else { 459 | throw('[sprintf] huh?'); 460 | } 461 | } 462 | } 463 | else { 464 | throw('[sprintf] huh?'); 465 | } 466 | match[2] = field_list; 467 | } 468 | else { 469 | arg_names |= 2; 470 | } 471 | if (arg_names === 3) { 472 | throw('[sprintf] mixing positional and named placeholders is not (yet) supported'); 473 | } 474 | parse_tree.push(match); 475 | } 476 | else { 477 | throw('[sprintf] huh?'); 478 | } 479 | _fmt = _fmt.substring(match[0].length); 480 | } 481 | return parse_tree; 482 | }; 483 | 484 | return str_format; 485 | })(); 486 | 487 | var vsprintf = function(fmt, argv) { 488 | argv.unshift(fmt); 489 | return sprintf.apply(null, argv); 490 | }; 491 | 492 | Jed.parse_plural = function ( plural_forms, n ) { 493 | plural_forms = plural_forms.replace(/n/g, n); 494 | return Jed.parse_expression(plural_forms); 495 | }; 496 | 497 | Jed.sprintf = function ( fmt, args ) { 498 | if ( {}.toString.call( args ) == '[object Array]' ) { 499 | return vsprintf( fmt, [].slice.call(args) ); 500 | } 501 | return sprintf.apply(this, [].slice.call(arguments) ); 502 | }; 503 | 504 | Jed.prototype.sprintf = function () { 505 | return Jed.sprintf.apply(this, arguments); 506 | }; 507 | // END sprintf Implementation 508 | 509 | // Start the Plural forms section 510 | // This is a full plural form expression parser. It is used to avoid 511 | // running 'eval' or 'new Function' directly against the plural 512 | // forms. 513 | // 514 | // This can be important if you get translations done through a 3rd 515 | // party vendor. I encourage you to use this instead, however, I 516 | // also will provide a 'precompiler' that you can use at build time 517 | // to output valid/safe function representations of the plural form 518 | // expressions. This means you can build this code out for the most 519 | // part. 520 | Jed.PF = {}; 521 | 522 | Jed.PF.parse = function ( p ) { 523 | var plural_str = Jed.PF.extractPluralExpr( p ); 524 | return Jed.PF.parser.parse.call(Jed.PF.parser, plural_str); 525 | }; 526 | 527 | Jed.PF.compile = function ( p ) { 528 | // Handle trues and falses as 0 and 1 529 | function imply( val ) { 530 | return (val === true ? 1 : val ? val : 0); 531 | } 532 | 533 | var ast = Jed.PF.parse( p ); 534 | return function ( n ) { 535 | return imply( Jed.PF.interpreter( ast )( n ) ); 536 | }; 537 | }; 538 | 539 | Jed.PF.interpreter = function ( ast ) { 540 | return function ( n ) { 541 | var res; 542 | switch ( ast.type ) { 543 | case 'GROUP': 544 | return Jed.PF.interpreter( ast.expr )( n ); 545 | case 'TERNARY': 546 | if ( Jed.PF.interpreter( ast.expr )( n ) ) { 547 | return Jed.PF.interpreter( ast.truthy )( n ); 548 | } 549 | return Jed.PF.interpreter( ast.falsey )( n ); 550 | case 'OR': 551 | return Jed.PF.interpreter( ast.left )( n ) || Jed.PF.interpreter( ast.right )( n ); 552 | case 'AND': 553 | return Jed.PF.interpreter( ast.left )( n ) && Jed.PF.interpreter( ast.right )( n ); 554 | case 'LT': 555 | return Jed.PF.interpreter( ast.left )( n ) < Jed.PF.interpreter( ast.right )( n ); 556 | case 'GT': 557 | return Jed.PF.interpreter( ast.left )( n ) > Jed.PF.interpreter( ast.right )( n ); 558 | case 'LTE': 559 | return Jed.PF.interpreter( ast.left )( n ) <= Jed.PF.interpreter( ast.right )( n ); 560 | case 'GTE': 561 | return Jed.PF.interpreter( ast.left )( n ) >= Jed.PF.interpreter( ast.right )( n ); 562 | case 'EQ': 563 | return Jed.PF.interpreter( ast.left )( n ) == Jed.PF.interpreter( ast.right )( n ); 564 | case 'NEQ': 565 | return Jed.PF.interpreter( ast.left )( n ) != Jed.PF.interpreter( ast.right )( n ); 566 | case 'MOD': 567 | return Jed.PF.interpreter( ast.left )( n ) % Jed.PF.interpreter( ast.right )( n ); 568 | case 'VAR': 569 | return n; 570 | case 'NUM': 571 | return ast.val; 572 | default: 573 | throw new Error("Invalid Token found."); 574 | } 575 | }; 576 | }; 577 | 578 | Jed.PF.regexps = { 579 | TRIM_BEG: /^\s\s*/, 580 | TRIM_END: /\s\s*$/, 581 | HAS_SEMICOLON: /;\s*$/, 582 | NPLURALS: /nplurals\=(\d+);/, 583 | PLURAL: /plural\=(.*);/ 584 | }; 585 | 586 | Jed.PF.extractPluralExpr = function ( p ) { 587 | // trim first 588 | p = p.replace(Jed.PF.regexps.TRIM_BEG, '').replace(Jed.PF.regexps.TRIM_END, ''); 589 | 590 | if (! Jed.PF.regexps.HAS_SEMICOLON.test(p)) { 591 | p = p.concat(';'); 592 | } 593 | 594 | var nplurals_matches = p.match( Jed.PF.regexps.NPLURALS ), 595 | res = {}, 596 | plural_matches; 597 | 598 | // Find the nplurals number 599 | if ( nplurals_matches.length > 1 ) { 600 | res.nplurals = nplurals_matches[1]; 601 | } 602 | else { 603 | throw new Error('nplurals not found in plural_forms string: ' + p ); 604 | } 605 | 606 | // remove that data to get to the formula 607 | p = p.replace( Jed.PF.regexps.NPLURALS, "" ); 608 | plural_matches = p.match( Jed.PF.regexps.PLURAL ); 609 | 610 | if (!( plural_matches && plural_matches.length > 1 ) ) { 611 | throw new Error('`plural` expression not found: ' + p); 612 | } 613 | return plural_matches[ 1 ]; 614 | }; 615 | 616 | /* Jison generated parser */ 617 | Jed.PF.parser = (function(){ 618 | 619 | var parser = {trace: function trace() { }, 620 | yy: {}, 621 | symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"?":6,":":7,"||":8,"&&":9,"<":10,"<=":11,">":12,">=":13,"!=":14,"==":15,"%":16,"(":17,")":18,"n":19,"NUMBER":20,"$accept":0,"$end":1}, 622 | terminals_: {2:"error",5:"EOF",6:"?",7:":",8:"||",9:"&&",10:"<",11:"<=",12:">",13:">=",14:"!=",15:"==",16:"%",17:"(",18:")",19:"n",20:"NUMBER"}, 623 | productions_: [0,[3,2],[4,5],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,1],[4,1]], 624 | performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { 625 | 626 | var $0 = $$.length - 1; 627 | switch (yystate) { 628 | case 1: return { type : 'GROUP', expr: $$[$0-1] }; 629 | break; 630 | case 2:this.$ = { type: 'TERNARY', expr: $$[$0-4], truthy : $$[$0-2], falsey: $$[$0] }; 631 | break; 632 | case 3:this.$ = { type: "OR", left: $$[$0-2], right: $$[$0] }; 633 | break; 634 | case 4:this.$ = { type: "AND", left: $$[$0-2], right: $$[$0] }; 635 | break; 636 | case 5:this.$ = { type: 'LT', left: $$[$0-2], right: $$[$0] }; 637 | break; 638 | case 6:this.$ = { type: 'LTE', left: $$[$0-2], right: $$[$0] }; 639 | break; 640 | case 7:this.$ = { type: 'GT', left: $$[$0-2], right: $$[$0] }; 641 | break; 642 | case 8:this.$ = { type: 'GTE', left: $$[$0-2], right: $$[$0] }; 643 | break; 644 | case 9:this.$ = { type: 'NEQ', left: $$[$0-2], right: $$[$0] }; 645 | break; 646 | case 10:this.$ = { type: 'EQ', left: $$[$0-2], right: $$[$0] }; 647 | break; 648 | case 11:this.$ = { type: 'MOD', left: $$[$0-2], right: $$[$0] }; 649 | break; 650 | case 12:this.$ = { type: 'GROUP', expr: $$[$0-1] }; 651 | break; 652 | case 13:this.$ = { type: 'VAR' }; 653 | break; 654 | case 14:this.$ = { type: 'NUM', val: Number(yytext) }; 655 | break; 656 | } 657 | }, 658 | table: [{3:1,4:2,17:[1,3],19:[1,4],20:[1,5]},{1:[3]},{5:[1,6],6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{4:17,17:[1,3],19:[1,4],20:[1,5]},{5:[2,13],6:[2,13],7:[2,13],8:[2,13],9:[2,13],10:[2,13],11:[2,13],12:[2,13],13:[2,13],14:[2,13],15:[2,13],16:[2,13],18:[2,13]},{5:[2,14],6:[2,14],7:[2,14],8:[2,14],9:[2,14],10:[2,14],11:[2,14],12:[2,14],13:[2,14],14:[2,14],15:[2,14],16:[2,14],18:[2,14]},{1:[2,1]},{4:18,17:[1,3],19:[1,4],20:[1,5]},{4:19,17:[1,3],19:[1,4],20:[1,5]},{4:20,17:[1,3],19:[1,4],20:[1,5]},{4:21,17:[1,3],19:[1,4],20:[1,5]},{4:22,17:[1,3],19:[1,4],20:[1,5]},{4:23,17:[1,3],19:[1,4],20:[1,5]},{4:24,17:[1,3],19:[1,4],20:[1,5]},{4:25,17:[1,3],19:[1,4],20:[1,5]},{4:26,17:[1,3],19:[1,4],20:[1,5]},{4:27,17:[1,3],19:[1,4],20:[1,5]},{6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[1,28]},{6:[1,7],7:[1,29],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{5:[2,3],6:[2,3],7:[2,3],8:[2,3],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,3]},{5:[2,4],6:[2,4],7:[2,4],8:[2,4],9:[2,4],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,4]},{5:[2,5],6:[2,5],7:[2,5],8:[2,5],9:[2,5],10:[2,5],11:[2,5],12:[2,5],13:[2,5],14:[2,5],15:[2,5],16:[1,16],18:[2,5]},{5:[2,6],6:[2,6],7:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[2,6],12:[2,6],13:[2,6],14:[2,6],15:[2,6],16:[1,16],18:[2,6]},{5:[2,7],6:[2,7],7:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[2,7],12:[2,7],13:[2,7],14:[2,7],15:[2,7],16:[1,16],18:[2,7]},{5:[2,8],6:[2,8],7:[2,8],8:[2,8],9:[2,8],10:[2,8],11:[2,8],12:[2,8],13:[2,8],14:[2,8],15:[2,8],16:[1,16],18:[2,8]},{5:[2,9],6:[2,9],7:[2,9],8:[2,9],9:[2,9],10:[2,9],11:[2,9],12:[2,9],13:[2,9],14:[2,9],15:[2,9],16:[1,16],18:[2,9]},{5:[2,10],6:[2,10],7:[2,10],8:[2,10],9:[2,10],10:[2,10],11:[2,10],12:[2,10],13:[2,10],14:[2,10],15:[2,10],16:[1,16],18:[2,10]},{5:[2,11],6:[2,11],7:[2,11],8:[2,11],9:[2,11],10:[2,11],11:[2,11],12:[2,11],13:[2,11],14:[2,11],15:[2,11],16:[2,11],18:[2,11]},{5:[2,12],6:[2,12],7:[2,12],8:[2,12],9:[2,12],10:[2,12],11:[2,12],12:[2,12],13:[2,12],14:[2,12],15:[2,12],16:[2,12],18:[2,12]},{4:30,17:[1,3],19:[1,4],20:[1,5]},{5:[2,2],6:[1,7],7:[2,2],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,2]}], 659 | defaultActions: {6:[2,1]}, 660 | parseError: function parseError(str, hash) { 661 | throw new Error(str); 662 | }, 663 | parse: function parse(input) { 664 | var self = this, 665 | stack = [0], 666 | vstack = [null], // semantic value stack 667 | lstack = [], // location stack 668 | table = this.table, 669 | yytext = '', 670 | yylineno = 0, 671 | yyleng = 0, 672 | recovering = 0, 673 | TERROR = 2, 674 | EOF = 1; 675 | 676 | //this.reductionCount = this.shiftCount = 0; 677 | 678 | this.lexer.setInput(input); 679 | this.lexer.yy = this.yy; 680 | this.yy.lexer = this.lexer; 681 | if (typeof this.lexer.yylloc == 'undefined') 682 | this.lexer.yylloc = {}; 683 | var yyloc = this.lexer.yylloc; 684 | lstack.push(yyloc); 685 | 686 | if (typeof this.yy.parseError === 'function') 687 | this.parseError = this.yy.parseError; 688 | 689 | function popStack (n) { 690 | stack.length = stack.length - 2*n; 691 | vstack.length = vstack.length - n; 692 | lstack.length = lstack.length - n; 693 | } 694 | 695 | function lex() { 696 | var token; 697 | token = self.lexer.lex() || 1; // $end = 1 698 | // if token isn't its numeric value, convert 699 | if (typeof token !== 'number') { 700 | token = self.symbols_[token] || token; 701 | } 702 | return token; 703 | } 704 | 705 | var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected; 706 | while (true) { 707 | // retreive state number from top of stack 708 | state = stack[stack.length-1]; 709 | 710 | // use default actions if available 711 | if (this.defaultActions[state]) { 712 | action = this.defaultActions[state]; 713 | } else { 714 | if (symbol == null) 715 | symbol = lex(); 716 | // read action for current state and first input 717 | action = table[state] && table[state][symbol]; 718 | } 719 | 720 | // handle parse error 721 | _handle_error: 722 | if (typeof action === 'undefined' || !action.length || !action[0]) { 723 | 724 | if (!recovering) { 725 | // Report error 726 | expected = []; 727 | for (p in table[state]) if (this.terminals_[p] && p > 2) { 728 | expected.push("'"+this.terminals_[p]+"'"); 729 | } 730 | var errStr = ''; 731 | if (this.lexer.showPosition) { 732 | errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'"; 733 | } else { 734 | errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " + 735 | (symbol == 1 /*EOF*/ ? "end of input" : 736 | ("'"+(this.terminals_[symbol] || symbol)+"'")); 737 | } 738 | this.parseError(errStr, 739 | {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); 740 | } 741 | 742 | // just recovered from another error 743 | if (recovering == 3) { 744 | if (symbol == EOF) { 745 | throw new Error(errStr || 'Parsing halted.'); 746 | } 747 | 748 | // discard current lookahead and grab another 749 | yyleng = this.lexer.yyleng; 750 | yytext = this.lexer.yytext; 751 | yylineno = this.lexer.yylineno; 752 | yyloc = this.lexer.yylloc; 753 | symbol = lex(); 754 | } 755 | 756 | // try to recover from error 757 | while (1) { 758 | // check for error recovery rule in this state 759 | if ((TERROR.toString()) in table[state]) { 760 | break; 761 | } 762 | if (state == 0) { 763 | throw new Error(errStr || 'Parsing halted.'); 764 | } 765 | popStack(1); 766 | state = stack[stack.length-1]; 767 | } 768 | 769 | preErrorSymbol = symbol; // save the lookahead token 770 | symbol = TERROR; // insert generic error symbol as new lookahead 771 | state = stack[stack.length-1]; 772 | action = table[state] && table[state][TERROR]; 773 | recovering = 3; // allow 3 real symbols to be shifted before reporting a new error 774 | } 775 | 776 | // this shouldn't happen, unless resolve defaults are off 777 | if (action[0] instanceof Array && action.length > 1) { 778 | throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol); 779 | } 780 | 781 | switch (action[0]) { 782 | 783 | case 1: // shift 784 | //this.shiftCount++; 785 | 786 | stack.push(symbol); 787 | vstack.push(this.lexer.yytext); 788 | lstack.push(this.lexer.yylloc); 789 | stack.push(action[1]); // push state 790 | symbol = null; 791 | if (!preErrorSymbol) { // normal execution/no error 792 | yyleng = this.lexer.yyleng; 793 | yytext = this.lexer.yytext; 794 | yylineno = this.lexer.yylineno; 795 | yyloc = this.lexer.yylloc; 796 | if (recovering > 0) 797 | recovering--; 798 | } else { // error just occurred, resume old lookahead f/ before error 799 | symbol = preErrorSymbol; 800 | preErrorSymbol = null; 801 | } 802 | break; 803 | 804 | case 2: // reduce 805 | //this.reductionCount++; 806 | 807 | len = this.productions_[action[1]][1]; 808 | 809 | // perform semantic action 810 | yyval.$ = vstack[vstack.length-len]; // default to $$ = $1 811 | // default location, uses first token for firsts, last for lasts 812 | yyval._$ = { 813 | first_line: lstack[lstack.length-(len||1)].first_line, 814 | last_line: lstack[lstack.length-1].last_line, 815 | first_column: lstack[lstack.length-(len||1)].first_column, 816 | last_column: lstack[lstack.length-1].last_column 817 | }; 818 | r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); 819 | 820 | if (typeof r !== 'undefined') { 821 | return r; 822 | } 823 | 824 | // pop off stack 825 | if (len) { 826 | stack = stack.slice(0,-1*len*2); 827 | vstack = vstack.slice(0, -1*len); 828 | lstack = lstack.slice(0, -1*len); 829 | } 830 | 831 | stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce) 832 | vstack.push(yyval.$); 833 | lstack.push(yyval._$); 834 | // goto new state = table[STATE][NONTERMINAL] 835 | newState = table[stack[stack.length-2]][stack[stack.length-1]]; 836 | stack.push(newState); 837 | break; 838 | 839 | case 3: // accept 840 | return true; 841 | } 842 | 843 | } 844 | 845 | return true; 846 | }};/* Jison generated lexer */ 847 | var lexer = (function(){ 848 | 849 | var lexer = ({EOF:1, 850 | parseError:function parseError(str, hash) { 851 | if (this.yy.parseError) { 852 | this.yy.parseError(str, hash); 853 | } else { 854 | throw new Error(str); 855 | } 856 | }, 857 | setInput:function (input) { 858 | this._input = input; 859 | this._more = this._less = this.done = false; 860 | this.yylineno = this.yyleng = 0; 861 | this.yytext = this.matched = this.match = ''; 862 | this.conditionStack = ['INITIAL']; 863 | this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; 864 | return this; 865 | }, 866 | input:function () { 867 | var ch = this._input[0]; 868 | this.yytext+=ch; 869 | this.yyleng++; 870 | this.match+=ch; 871 | this.matched+=ch; 872 | var lines = ch.match(/\n/); 873 | if (lines) this.yylineno++; 874 | this._input = this._input.slice(1); 875 | return ch; 876 | }, 877 | unput:function (ch) { 878 | this._input = ch + this._input; 879 | return this; 880 | }, 881 | more:function () { 882 | this._more = true; 883 | return this; 884 | }, 885 | pastInput:function () { 886 | var past = this.matched.substr(0, this.matched.length - this.match.length); 887 | return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); 888 | }, 889 | upcomingInput:function () { 890 | var next = this.match; 891 | if (next.length < 20) { 892 | next += this._input.substr(0, 20-next.length); 893 | } 894 | return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); 895 | }, 896 | showPosition:function () { 897 | var pre = this.pastInput(); 898 | var c = new Array(pre.length + 1).join("-"); 899 | return pre + this.upcomingInput() + "\n" + c+"^"; 900 | }, 901 | next:function () { 902 | if (this.done) { 903 | return this.EOF; 904 | } 905 | if (!this._input) this.done = true; 906 | 907 | var token, 908 | match, 909 | col, 910 | lines; 911 | if (!this._more) { 912 | this.yytext = ''; 913 | this.match = ''; 914 | } 915 | var rules = this._currentRules(); 916 | for (var i=0;i < rules.length; i++) { 917 | match = this._input.match(this.rules[rules[i]]); 918 | if (match) { 919 | lines = match[0].match(/\n.*/g); 920 | if (lines) this.yylineno += lines.length; 921 | this.yylloc = {first_line: this.yylloc.last_line, 922 | last_line: this.yylineno+1, 923 | first_column: this.yylloc.last_column, 924 | last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length} 925 | this.yytext += match[0]; 926 | this.match += match[0]; 927 | this.matches = match; 928 | this.yyleng = this.yytext.length; 929 | this._more = false; 930 | this._input = this._input.slice(match[0].length); 931 | this.matched += match[0]; 932 | token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]); 933 | if (token) return token; 934 | else return; 935 | } 936 | } 937 | if (this._input === "") { 938 | return this.EOF; 939 | } else { 940 | this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), 941 | {text: "", token: null, line: this.yylineno}); 942 | } 943 | }, 944 | lex:function lex() { 945 | var r = this.next(); 946 | if (typeof r !== 'undefined') { 947 | return r; 948 | } else { 949 | return this.lex(); 950 | } 951 | }, 952 | begin:function begin(condition) { 953 | this.conditionStack.push(condition); 954 | }, 955 | popState:function popState() { 956 | return this.conditionStack.pop(); 957 | }, 958 | _currentRules:function _currentRules() { 959 | return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; 960 | }, 961 | topState:function () { 962 | return this.conditionStack[this.conditionStack.length-2]; 963 | }, 964 | pushState:function begin(condition) { 965 | this.begin(condition); 966 | }}); 967 | lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { 968 | 969 | var YYSTATE=YY_START; 970 | switch($avoiding_name_collisions) { 971 | case 0:/* skip whitespace */ 972 | break; 973 | case 1:return 20 974 | break; 975 | case 2:return 19 976 | break; 977 | case 3:return 8 978 | break; 979 | case 4:return 9 980 | break; 981 | case 5:return 6 982 | break; 983 | case 6:return 7 984 | break; 985 | case 7:return 11 986 | break; 987 | case 8:return 13 988 | break; 989 | case 9:return 10 990 | break; 991 | case 10:return 12 992 | break; 993 | case 11:return 14 994 | break; 995 | case 12:return 15 996 | break; 997 | case 13:return 16 998 | break; 999 | case 14:return 17 1000 | break; 1001 | case 15:return 18 1002 | break; 1003 | case 16:return 5 1004 | break; 1005 | case 17:return 'INVALID' 1006 | break; 1007 | } 1008 | }; 1009 | lexer.rules = [/^\s+/,/^[0-9]+(\.[0-9]+)?\b/,/^n\b/,/^\|\|/,/^&&/,/^\?/,/^:/,/^<=/,/^>=/,/^/,/^!=/,/^==/,/^%/,/^\(/,/^\)/,/^$/,/^./]; 1010 | lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"inclusive":true}};return lexer;})() 1011 | parser.lexer = lexer; 1012 | return parser; 1013 | })(); 1014 | // End parser 1015 | 1016 | // Handle node, amd, and global systems 1017 | if (typeof exports !== 'undefined') { 1018 | if (typeof module !== 'undefined' && module.exports) { 1019 | exports = module.exports = Jed; 1020 | } 1021 | exports.Jed = Jed; 1022 | } 1023 | else { 1024 | if (typeof define === 'function' && define.amd) { 1025 | define(function() { 1026 | return Jed; 1027 | }); 1028 | } 1029 | // Leak a global regardless of module system 1030 | root['Jed'] = Jed; 1031 | } 1032 | 1033 | })(this); 1034 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jed", 3 | "version": "1.1.1", 4 | "author": "Alex Sexton ", 5 | "description": "Gettext Style i18n for Modern JavaScript Apps", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://SlexAxton@github.com/SlexAxton/Jed.git" 9 | }, 10 | "scripts": { 11 | "test": "make test" 12 | }, 13 | "main": "./jed", 14 | "keywords": [ 15 | "i18n", 16 | "Jed", 17 | "gettext", 18 | "internationalization" 19 | ], 20 | "dependencies" : {}, 21 | "devDependencies" : { 22 | "mocha" : "*", 23 | "expect.js" : "*", 24 | "serve": "*" 25 | }, 26 | "license": "MIT" 27 | } 28 | -------------------------------------------------------------------------------- /plurals.jison: -------------------------------------------------------------------------------- 1 | /* description: Parses end executes mathematical expressions. */ 2 | 3 | /* lexical grammar */ 4 | %lex 5 | %% 6 | 7 | \s+ /* skip whitespace */ 8 | [0-9]+("."[0-9]+)?\b return 'NUMBER' 9 | "n" return 'n' 10 | "||" return '||' 11 | "&&" return '&&' 12 | "?" return '?' 13 | ":" return ':' 14 | "<=" return '<=' 15 | ">=" return '>=' 16 | "<" return '<' 17 | ">" return '>' 18 | "!=" return '!=' 19 | "==" return '==' 20 | "%" return '%' 21 | "(" return '(' 22 | ")" return ')' 23 | <> return 'EOF' 24 | . return 'INVALID' 25 | 26 | /lex 27 | 28 | /* operator associations and precedence */ 29 | 30 | %right '?' ':' 31 | %left '||' 32 | %left '&&' 33 | %left '<=' '>=' '<' '>' '!=' '==' 34 | %left '%' 35 | 36 | %start expressions 37 | 38 | %% /* language grammar */ 39 | 40 | expressions 41 | : e EOF 42 | { return { type : 'GROUP', expr: $1 }; } 43 | ; 44 | 45 | e 46 | : e '?' e ':' e 47 | {$$ = { type: 'TERNARY', expr: $1, truthy : $3, falsey: $5 }; } 48 | | e '||' e 49 | {$$ = { type: "OR", left: $1, right: $3 };} 50 | | e '&&' e 51 | {$$ = { type: "AND", left: $1, right: $3 };} 52 | | e '<' e 53 | {$$ = { type: 'LT', left: $1, right: $3 }; } 54 | | e '<=' e 55 | {$$ = { type: 'LTE', left: $1, right: $3 };} 56 | | e '>' e 57 | {$$ = { type: 'GT', left: $1, right: $3 };} 58 | | e '>=' e 59 | {$$ = { type: 'GTE', left: $1, right: $3 };} 60 | | e '!=' e 61 | {$$ = { type: 'NEQ', left: $1, right: $3 };} 62 | | e '==' e 63 | {$$ = { type: 'EQ', left: $1, right: $3 };} 64 | | e '%' e 65 | {$$ = { type: 'MOD', left: $1, right: $3 };} 66 | | '(' e ')' 67 | {$$ = { type: 'GROUP', expr: $2 }; } 68 | | 'n' 69 | {$$ = { type: 'VAR' }; } 70 | | NUMBER 71 | {$$ = { type: 'NUM', val: Number(yytext) }; } 72 | ; 73 | -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | expect = require('expect.js'); 2 | Jed = require('../jed'); 3 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jed.js Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

Jed.js Test Suite

17 |
18 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/jquery.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v1.7.1 jquery.com | jquery.org/license */ 2 | (function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"":"")+""),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;g=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
a",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="
"+""+"
",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="
t
",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="
",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")}; 3 | f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&i.push({elem:this,matches:d.slice(e)});for(j=0;j0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
","
"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function() 4 | {for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | (function (Jed){ 2 | 3 | describe("Property Checks", function () { 4 | it("should exist", function () { 5 | expect( Jed ).to.be.ok(); 6 | }); 7 | 8 | it("should have a context delimiter as per the gettext spec", function () { 9 | expect( Jed.context_delimiter ).to.be( "\u0004" ); 10 | expect( Jed.context_delimiter ).to.be( String.fromCharCode( 4 ) ); 11 | }); 12 | }); 13 | 14 | // Group tests that need similar data 15 | (function () { 16 | var locale_data = { 17 | "messages" : { 18 | "" : { 19 | "domain" : "messages", 20 | "lang" : "en", 21 | "plural-forms" : "nplurals=2; plural=(n != 1);" 22 | }, 23 | "test" : ["test_translation_output"] 24 | } 25 | }; 26 | 27 | var locale_data2 = { 28 | "some_domain" : { 29 | "" : { 30 | "domain" : "some_domain", 31 | "lang" : "en", 32 | "plural-forms" : "nplurals=2; plural=(n != 1);" 33 | }, 34 | "test" : ["test_translation_output2"], 35 | "zero length translation" : [""] 36 | } 37 | }; 38 | 39 | var locale_data3 = { 40 | "some_domain" : { 41 | "" : { 42 | "domain" : "some_domain", 43 | "lang" : "ar", 44 | "plural-forms" : "nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5);" 45 | }, 46 | "test" : ["test_translation_output3"], 47 | "zero length translation" : [""] 48 | } 49 | }; 50 | 51 | var i18n = new Jed({ 52 | "domain" : "messages", 53 | "locale_data" : locale_data 54 | }); 55 | 56 | var i18n_2 = new Jed({ 57 | "domain" : "some_domain", 58 | "locale_data" : locale_data2 59 | }); 60 | 61 | var i18n_3 = new Jed({ 62 | "domain" : "some_domain", 63 | "locale_data" : locale_data3 64 | }); 65 | 66 | // Standard shorthand function 67 | function _(msgid) { 68 | return i18n_2.gettext(msgid); 69 | } 70 | 71 | // Actual tests 72 | describe("Instantiation", function () { 73 | it("should exist", function () { 74 | expect( i18n ).to.be.ok(); 75 | expect( i18n_2 ).to.be.ok(); 76 | expect( i18n_3 ).to.be.ok(); 77 | expect( _ ).to.be.ok(); 78 | }); 79 | }); 80 | 81 | describe("Basic", function () { 82 | it("should translate a key that exists in the translation", function () { 83 | expect( i18n.gettext('test') ).to.be( 'test_translation_output' ); 84 | }); 85 | 86 | it("should just pass through strings that aren't translatable", function () { 87 | expect( i18n.gettext('missing') ).to.be( 'missing' ); 88 | }); 89 | 90 | it("should translate a key in a locale with plural-forms rules that don't assume n==1 will return 0", function () { 91 | expect(i18n_3.gettext('test')).to.be('test_translation_output3'); 92 | }); 93 | 94 | it("should allow you to wrap it as a shorthand function", function () { 95 | expect( _('test') ).to.be( 'test_translation_output2' ); 96 | expect( _('missing') ).to.be( 'missing' ); 97 | }); 98 | 99 | it("should have identical output for wrapped and non-wrapped instances", function () { 100 | expect( _('test') ).to.be( i18n_2.gettext('test') ); 101 | expect( _('missing') ).to.be( i18n_2.gettext('missing') ); 102 | }); 103 | 104 | it("should not allow you to use domains that don't exist", function () { 105 | function badCreate() { 106 | var x = new Jed({ 107 | "domain" : "missing_domain", 108 | "locale_data" : locale_data 109 | }); 110 | return x; 111 | } 112 | expect( badCreate ).to.throwException(); 113 | }); 114 | 115 | it("should just pass through translations that are empty strings", function () { 116 | expect( _('zero length translation') ).to.be('zero length translation' ); 117 | }); 118 | 119 | it("should call the callback function (if given) when a key is missing", function() { 120 | var callbackCalled; 121 | function missingKeyCallback(key) { 122 | callbackCalled = true; 123 | } 124 | 125 | callbackCalled = false; 126 | var jedWithCallback = new Jed({ 127 | "missing_key_callback" : missingKeyCallback 128 | }); 129 | jedWithCallback.gettext('missing key'); 130 | expect(callbackCalled).to.be(true); 131 | 132 | callbackCalled = false; 133 | var jedWithoutCallback = new Jed({}); 134 | jedWithoutCallback.gettext('missing key'); 135 | expect(callbackCalled).to.be(false); 136 | }); 137 | }); 138 | })(); 139 | 140 | (function () { 141 | var locale_data = { 142 | "messages_1": { 143 | "": { 144 | "domain": "messages_1", 145 | "lang": "en", 146 | "plural-forms": "nplurals=2; plural=(n != 1);" 147 | }, 148 | "test": ["test_1"], 149 | "test singular": ["test_1 singular", "test_1 plural"], 150 | "context\u0004test": ["test_1 context"], 151 | "context\u0004test singular": ["test_1 context singular", "test_1 context plural"] 152 | }, 153 | "messages_2": { 154 | "": { 155 | "domain": "messages_2", 156 | "lang": "en", 157 | "plural-forms": "nplurals=2; plural=(n != 1);" 158 | }, 159 | "test": ["test_2"], 160 | "test singular": ["test_2 singular", "test_2 plural"], 161 | "context\u0004test": ["test_2 context"], 162 | "context\u0004test singular": ["test_2 context singular", "test_2 context plural"] 163 | } 164 | }; 165 | 166 | describe("Domain", function () { 167 | var i18n1 = new Jed({ 168 | domain : "messages_1", 169 | locale_data : locale_data 170 | }); 171 | 172 | var i18n_2 = new Jed({ 173 | domain : "messages_2", 174 | locale_data : locale_data 175 | }); 176 | 177 | // No default domain 178 | var i18n_3 = new Jed({ 179 | locale_data : locale_data 180 | }); 181 | 182 | it("should use the correct domain when there are multiple", function () { 183 | expect( i18n1.gettext('test') ).to.be('test_1'); 184 | expect( i18n_2.gettext('test') ).to.be('test_2'); 185 | }); 186 | 187 | it("should still pass through non-existent keys", function () { 188 | expect( i18n1.gettext('nope') ).to.be('nope'); 189 | expect( i18n_2.gettext('nope again') ).to.be('nope again'); 190 | }); 191 | 192 | it("should reveal the current domain on any instance", function () { 193 | expect( i18n1.textdomain() ).to.be( 'messages_1' ); 194 | expect( i18n_2.textdomain() ).to.be( 'messages_2' ); 195 | }); 196 | 197 | it("should use `messages` as the default domain if none given", function () { 198 | expect( i18n_3.textdomain() ).to.be('messages'); 199 | }); 200 | 201 | it("should allow on the fly domain switching", function () { 202 | // Switch these up 203 | i18n1.textdomain('messages_2'); 204 | i18n_2.textdomain('messages_1'); 205 | 206 | expect( i18n1.gettext('test') ).to.be('test_2'); 207 | expect( i18n_2.gettext('test') ).to.be('test_1'); 208 | expect( i18n1.textdomain() ).to.be( 'messages_2' ); 209 | expect( i18n_2.textdomain() ).to.be( 'messages_1' ); 210 | }); 211 | 212 | describe("#dgettext", function () { 213 | it("should have the dgettext function", function () { 214 | expect( i18n_3.dgettext ).to.be.ok(); 215 | }); 216 | 217 | it("should allow you to call the domain on the fly", function () { 218 | expect( i18n_3.dgettext('messages_1', 'test') ).to.be('test_1'); 219 | expect( i18n_3.dgettext('messages_2', 'test') ).to.be('test_2'); 220 | }); 221 | 222 | it("should pass through non-existent keys", function () { 223 | expect( i18n_3.dgettext('messages_1', 'nope') ).to.be('nope'); 224 | expect( i18n_3.dgettext('messages_2', 'nope again') ).to.be('nope again'); 225 | }); 226 | }); 227 | 228 | describe("#dcgettext", function () { 229 | var i18n_4 = new Jed({ 230 | locale_data : locale_data 231 | }); 232 | 233 | it("should have the dcgettext function", function () { 234 | expect( i18n_4.dcgettext ).to.be.ok(); 235 | }); 236 | 237 | it("should ignore categories altogether", function () { 238 | expect( i18n_4.dcgettext('messages_1', 'test', 'A_CATEGORY') ).to.be('test_1'); 239 | }); 240 | }); 241 | }); 242 | 243 | describe("Pluralization", function () { 244 | var locale_data1 = { 245 | "plural_test": { 246 | "": { 247 | "domain": "plural_test", 248 | "lang": "en", 249 | "plural-forms": "nplurals=2; plural=(n != 1);" 250 | }, 251 | "test singular": ["test_1"], 252 | "test plural %1$d": ["test_1_singular %1$d", "test_1_plural %1$d"], 253 | "context\u0004test context": ["test_1context"], 254 | "test2": ["test_2"], 255 | "zero length translation": [""], 256 | "context\u0004test2": ["test_2context"], 257 | "Not translated plural": ["asdf", "asdf"], // this should never hit, since it's msgid2 258 | "context\u0004context plural %1$d": ["context_plural_1 singular %1$d", "context_plural_1 plural %1$d"] 259 | } 260 | }; 261 | 262 | var locale_data2 = { 263 | "plural_test2": { 264 | "": { 265 | "domain": "plural_test2", 266 | "lang": "sl", 267 | // actual Slovenian pluralization rules 268 | "plural_forms": "nplurals=4; plural=(n==1 ? 0 : n%10==2 ? 1 : n%10==3 || n%10==4 ? 2 : 3);" 269 | }, 270 | "Singular" : ["Numerus 0", "Numerus 1", "Numerus 2", "Numerus 3" ] 271 | } 272 | }; 273 | 274 | var i18n = new Jed({ 275 | domain: "plural_test", 276 | locale_data: locale_data1 277 | }); 278 | 279 | var i18n_2 = new Jed({ 280 | domain: "plural_test2", 281 | locale_data: locale_data2 282 | }); 283 | 284 | describe("#ngettext", function () { 285 | 286 | it("should have a ngettext function", function () { 287 | expect( i18n.ngettext ).to.be.ok(); 288 | }); 289 | 290 | it("should choose the correct pluralization translation", function () { 291 | expect( i18n.ngettext('test plural %1$d', 'test plural %1$d', 1) ).to.be( 'test_1_singular %1$d' ); 292 | expect( i18n.ngettext('test plural %1$d', 'test plural %1$d', 2) ).to.be( 'test_1_plural %1$d' ); 293 | expect( i18n.ngettext('test plural %1$d', 'test plural %1$d', 0) ).to.be( 'test_1_plural %1$d' ); 294 | }); 295 | 296 | it("should still pass through on plurals", function () { 297 | expect(i18n.ngettext('Not translated', 'Not translated plural', 1) ).to.be( 'Not translated' ); 298 | expect(i18n.ngettext('Not translated', 'Not translated plural', 2) ).to.be( 'Not translated plural' ); 299 | expect(i18n.ngettext('Not translated', 'Not translated plural', 0) ).to.be( 'Not translated plural' ); 300 | expect(i18n_2.ngettext('Not translated', 'Not translated plural', 3) ).to.be( 'Not translated plural' ); 301 | }); 302 | 303 | it("should be able to parse complex pluralization rules", function () { 304 | var strings = ['Singular', 'Plural']; 305 | for (var i=0; i<=40; i++) { 306 | var translation = i18n_2.ngettext(strings[0], strings[1], i); 307 | var plural = ((i == 1) ? 0 : 308 | (i % 10 == 2) ? 1 : 309 | (i % 10 == 3 || i % 10 == 4) ? 2 : 3); 310 | 311 | expect(translation).to.be( 'Numerus ' + plural ); 312 | } 313 | }); 314 | }); 315 | 316 | var locale_data_multi = { 317 | "messages_3": { 318 | "": { 319 | "domain": "messages_3", 320 | "lang": "en", 321 | "plural-forms": "nplurals=2; plural=(n != 1);" 322 | }, 323 | "test": ["test_1"], 324 | "test singular": ["test_1 singular", "test_1 plural"], 325 | "context\u0004test": ["test_1 context"], 326 | "context\u0004test singular": ["test_1 context singular", "test_1 context plural"] 327 | }, 328 | "messages_4": { 329 | "": { 330 | "domain": "messages_4", 331 | "lang": "en", 332 | "plural-forms": "nplurals=2; plural=(n != 1);" 333 | }, 334 | "test": ["test_2"], 335 | "test singular": ["test_2 singular", "test_2 plural"], 336 | "context\u0004test": ["test_2 context"], 337 | "context\u0004test singular": ["test_2 context singular", "test_2 context plural"] 338 | } 339 | }; 340 | 341 | describe("#dngettext", function () { 342 | var i18n = new Jed({ 343 | locale_data : locale_data_multi 344 | }); 345 | 346 | it("should have a dngettext function", function () { 347 | expect( i18n.dngettext).to.be.ok(); 348 | }); 349 | 350 | it("should pluralize correctly, based on domain rules", function () { 351 | expect(i18n.dngettext('messages_3', 'test singular', 'test plural', 1)).to.be('test_1 singular'); 352 | expect(i18n.dngettext('messages_3', 'test singular', 'test plural', 2)).to.be('test_1 plural'); 353 | expect(i18n.dngettext('messages_3', 'test singular', 'test plural', 0)).to.be('test_1 plural'); 354 | 355 | expect(i18n.dngettext('messages_4', 'test singular', 'test plural', 1)).to.be('test_2 singular'); 356 | expect(i18n.dngettext('messages_4', 'test singular', 'test plural', 2)).to.be('test_2 plural'); 357 | expect(i18n.dngettext('messages_4', 'test singular', 'test plural', 0)).to.be('test_2 plural'); 358 | }); 359 | 360 | it("should passthrough non-found keys regardless of pluralization addition", function (){ 361 | expect(i18n.dngettext('messages_3', 'Not translated', 'Not translated plural', 1)).to.be('Not translated'); 362 | expect(i18n.dngettext('messages_3', 'Not translated', 'Not translated plural', 2)).to.be('Not translated plural'); 363 | expect(i18n.dngettext('messages_3', 'Not translated', 'Not translated plural', 0)).to.be('Not translated plural'); 364 | 365 | expect(i18n.dngettext('messages_4', 'Not translated', 'Not translated plural', 1)).to.be('Not translated'); 366 | expect(i18n.dngettext('messages_4', 'Not translated', 'Not translated plural', 2)).to.be('Not translated plural'); 367 | expect(i18n.dngettext('messages_4', 'Not translated', 'Not translated plural', 0)).to.be('Not translated plural'); 368 | }); 369 | }); 370 | 371 | describe("#dcngettext", function () { 372 | var i18n = new Jed({ 373 | locale_data : locale_data_multi 374 | }); 375 | 376 | it("should more or less ignore the category", function () { 377 | expect(i18n.dcngettext('messages_3', 'test singular', 'test plural', 1, 'LC_MESSAGES')).to.be('test_1 singular'); 378 | expect(i18n.dcngettext('messages_3', 'test singular', 'test plural', 2, 'LC_MESSAGES')).to.be('test_1 plural'); 379 | expect(i18n.dcngettext('messages_3', 'test singular', 'test plural', 0, 'LC_MESSAGES')).to.be('test_1 plural'); 380 | 381 | expect(i18n.dcngettext('messages_4', 'test singular', 'test plural', 1, 'LC_MESSAGES')).to.be('test_2 singular'); 382 | expect(i18n.dcngettext('messages_4', 'test singular', 'test plural', 2, 'LC_MESSAGES')).to.be('test_2 plural'); 383 | expect(i18n.dcngettext('messages_4', 'test singular', 'test plural', 0, 'LC_MESSAGES')).to.be('test_2 plural'); 384 | 385 | expect(i18n.dcngettext('messages_3', 'Not translated', 'Not translated plural', 1, 'LC_MESSAGES')).to.be('Not translated'); 386 | expect(i18n.dcngettext('messages_3', 'Not translated', 'Not translated plural', 2, 'LC_MESSAGES')).to.be('Not translated plural'); 387 | expect(i18n.dcngettext('messages_3', 'Not translated', 'Not translated plural', 0, 'LC_MESSAGES')).to.be('Not translated plural'); 388 | 389 | expect(i18n.dcngettext('messages_4', 'Not translated', 'Not translated plural', 1, 'LC_MESSAGES')).to.be('Not translated'); 390 | expect(i18n.dcngettext('messages_4', 'Not translated', 'Not translated plural', 2, 'LC_MESSAGES')).to.be('Not translated plural'); 391 | expect(i18n.dcngettext('messages_4', 'Not translated', 'Not translated plural', 0, 'LC_MESSAGES')).to.be('Not translated plural'); 392 | }); 393 | }); 394 | 395 | describe("#pgettext", function () { 396 | var locale_data_w_context = { 397 | "context_test": { 398 | "": { 399 | "domain": "context_test", 400 | "lang": "en", 401 | "plural-forms": "nplurals=2; plural=(n != 1);" 402 | }, 403 | "test singular": ["test_1"], 404 | "test plural %1$d": ["test_1_singular %1$d", "test_1_plural %1$d"], 405 | "context\u0004test context": ["test_1context"], 406 | "test2": ["test_2"], 407 | "zero length translation": [""], 408 | "context\u0004test2": ["test_2context"], 409 | "context\u0004context plural %1$d": ["context_plural_1 singular %1$d", "context_plural_1 plural %1$d"] 410 | } 411 | }; 412 | 413 | var i18n = new Jed({ 414 | domain : "context_test", 415 | locale_data : locale_data_w_context 416 | }); 417 | 418 | it("should expose the pgettext function", function () { 419 | expect( i18n.pgettext ).to.be.ok(); 420 | }); 421 | 422 | it("should accept a context and look up a new key using the context_glue", function () { 423 | expect( i18n.pgettext('context', 'test context') ).to.be( 'test_1context' ); 424 | }); 425 | 426 | it("should still pass through missing keys", function () { 427 | expect( i18n.pgettext('context', 'Not translated') ).to.be( 'Not translated' ); 428 | }); 429 | 430 | it("should make sure same msgid returns diff results w/ context when appropriate", function () { 431 | expect(i18n.gettext('test2')).to.be('test_2'); 432 | expect(i18n.pgettext('context', 'test2')).to.be( 'test_2context' ); 433 | }); 434 | }); 435 | 436 | describe("#dpgettext", function () { 437 | var i18n = new Jed({ 438 | locale_data : locale_data_multi 439 | }); 440 | 441 | it("should have a dpgettext function", function () { 442 | expect( i18n.dpgettext ).to.be.ok(); 443 | }); 444 | 445 | it("should use the domain and the context simultaneously", function () { 446 | expect(i18n.dpgettext('messages_3', 'context', 'test')).to.be('test_1 context'); 447 | expect(i18n.dpgettext('messages_4', 'context', 'test')).to.be('test_2 context'); 448 | }); 449 | 450 | it("should pass through if either the domain, the key or the context isn't found", function () { 451 | expect(i18n.dpgettext('messages_3', 'context', 'Not translated')).to.be('Not translated'); 452 | expect(i18n.dpgettext('messages_4', 'context', 'Not translated')).to.be('Not translated'); 453 | }); 454 | 455 | }); 456 | 457 | describe("#dcpgettext", function () { 458 | var i18n = new Jed({ 459 | locale_data : locale_data_multi 460 | }); 461 | 462 | it("should have a dcpgettext function", function () { 463 | expect( i18n.dcpgettext ).to.be.ok(); 464 | }); 465 | 466 | it("should use the domain and the context simultaneously - ignore the category", function () { 467 | expect(i18n.dcpgettext('messages_3', 'context', 'test', 'LC_MESSAGES')).to.be('test_1 context'); 468 | expect(i18n.dcpgettext('messages_4', 'context', 'test', 'LC_MESSAGES')).to.be('test_2 context'); 469 | }); 470 | 471 | it("should pass through if either the domain, the key or the context isn't found", function () { 472 | expect(i18n.dcpgettext('messages_3', 'context', 'Not translated', 'LC_MESSAGES')).to.be('Not translated'); 473 | expect(i18n.dcpgettext('messages_4', 'context', 'Not translated', 'LC_MESSAGES')).to.be('Not translated'); 474 | }); 475 | 476 | }); 477 | 478 | describe("#npgettext", function () { 479 | var locale_data_w_context = { 480 | "context_plural_test": { 481 | "": { 482 | "domain": "context_plural_test", 483 | "lang": "en", 484 | "plural-forms": "nplurals=2; plural=(n != 1);" 485 | }, 486 | "test singular": ["test_1"], 487 | "test plural %1$d": ["test_1_singular %1$d", "test_1_plural %1$d"], 488 | "context\u0004test context": ["test_1context"], 489 | "test2": ["test_2"], 490 | "zero length translation": [""], 491 | "context\u0004test2": ["test_2context"], 492 | "context\u0004context plural %1$d": ["context_plural_1 singular %1$d", "context_plural_1 plural %1$d"] 493 | } 494 | }; 495 | 496 | var i18n = new Jed({ 497 | domain : "context_plural_test", 498 | locale_data : locale_data_w_context 499 | }); 500 | 501 | it("should have a dcpgettext function", function () { 502 | expect( i18n.dcpgettext ).to.be.ok(); 503 | }); 504 | 505 | it("should handle plurals at the same time as contexts", function () { 506 | expect(i18n.npgettext('context', 'context plural %1$d', 'plural %1$d', 1)).to.be('context_plural_1 singular %1$d'); 507 | expect(i18n.npgettext('context', 'context plural %1$d', 'plural %1$d', 2)).to.be('context_plural_1 plural %1$d'); 508 | expect(i18n.npgettext('context', 'context plural %1$d', 'plural %1$d', 0)).to.be('context_plural_1 plural %1$d'); 509 | }); 510 | 511 | it("should just pass through on not-found cases", function () { 512 | expect(i18n.npgettext('context', 'Not translated', 'Not translated plural', 1)).to.be('Not translated'); 513 | expect(i18n.npgettext('context', 'Not translated', 'Not translated plural', 2)).to.be('Not translated plural'); 514 | expect(i18n.npgettext('context', 'Not translated', 'Not translated plural', 0)).to.be('Not translated plural'); 515 | }); 516 | }); 517 | 518 | describe("#dnpgettext", function () { 519 | var i18n = new Jed({ 520 | locale_data : locale_data_multi 521 | }); 522 | 523 | it("should have a dnpgettext function", function () { 524 | expect( i18n.dnpgettext ).to.be.ok(); 525 | }); 526 | 527 | it("should be able to do a domain, context, and pluralization lookup all at once", function () { 528 | expect(i18n.dnpgettext('messages_3', 'context', 'test singular', 'test plural', 1)).to.be('test_1 context singular'); 529 | expect(i18n.dnpgettext('messages_3', 'context', 'test singular', 'test plural', 2)).to.be('test_1 context plural'); 530 | expect(i18n.dnpgettext('messages_3', 'context', 'test singular', 'test plural', 0)).to.be('test_1 context plural'); 531 | 532 | expect(i18n.dnpgettext('messages_4', 'context', 'test singular', 'test plural', 1)).to.be('test_2 context singular'); 533 | expect(i18n.dnpgettext('messages_4', 'context', 'test singular', 'test plural', 2)).to.be('test_2 context plural'); 534 | expect(i18n.dnpgettext('messages_4', 'context', 'test singular', 'test plural', 0)).to.be('test_2 context plural'); 535 | }); 536 | 537 | it("should pass through if everything doesn't point towards a key", function () { 538 | expect(i18n.dnpgettext('messages_3', 'context', 'Not translated', 'Not translated plural', 1)).to.be('Not translated'); 539 | expect(i18n.dnpgettext('messages_3', 'context', 'Not translated', 'Not translated plural', 2)).to.be('Not translated plural'); 540 | expect(i18n.dnpgettext('messages_3', 'context', 'Not translated', 'Not translated plural', 0)).to.be('Not translated plural'); 541 | 542 | expect(i18n.dnpgettext('messages_4', 'context', 'Not translated', 'Not translated plural', 1)).to.be('Not translated'); 543 | expect(i18n.dnpgettext('messages_4', 'context', 'Not translated', 'Not translated plural', 2)).to.be('Not translated plural'); 544 | expect(i18n.dnpgettext('messages_4', 'context', 'Not translated', 'Not translated plural', 0)).to.be('Not translated plural'); 545 | }); 546 | }); 547 | 548 | describe("#dcnpgettext", function () { 549 | var i18n = new Jed({ 550 | locale_data : locale_data_multi 551 | }); 552 | 553 | it("should have a dcnpgettext function", function () { 554 | expect( i18n.dcnpgettext ).to.be.ok(); 555 | }); 556 | 557 | it("should be able to do a domain, context, and pluralization lookup all at once - ignore category", function () { 558 | expect(i18n.dcnpgettext('messages_3', 'context', 'test singular', 'test plural', 1, "LC_MESSAGES")).to.be('test_1 context singular'); 559 | expect(i18n.dcnpgettext('messages_3', 'context', 'test singular', 'test plural', 2, "LC_MESSAGES")).to.be('test_1 context plural'); 560 | expect(i18n.dcnpgettext('messages_3', 'context', 'test singular', 'test plural', 0, "LC_MESSAGES")).to.be('test_1 context plural'); 561 | 562 | expect(i18n.dcnpgettext('messages_4', 'context', 'test singular', 'test plural', 1, "LC_MESSAGES")).to.be('test_2 context singular'); 563 | expect(i18n.dcnpgettext('messages_4', 'context', 'test singular', 'test plural', 2, "LC_MESSAGES")).to.be('test_2 context plural'); 564 | expect(i18n.dcnpgettext('messages_4', 'context', 'test singular', 'test plural', 0, "LC_MESSAGES")).to.be('test_2 context plural'); 565 | }); 566 | 567 | it("should pass through if everything doesn't point towards a key", function () { 568 | expect(i18n.dcnpgettext('messages_3', 'context', 'Not translated', 'Not translated plural', 1, "LC_MESSAGES")).to.be('Not translated'); 569 | expect(i18n.dcnpgettext('messages_3', 'context', 'Not translated', 'Not translated plural', 2, "LC_MESSAGES")).to.be('Not translated plural'); 570 | expect(i18n.dcnpgettext('messages_3', 'context', 'Not translated', 'Not translated plural', 0, "LC_MESSAGES")).to.be('Not translated plural'); 571 | 572 | expect(i18n.dcnpgettext('messages_4', 'context', 'Not translated', 'Not translated plural', 1, "LC_MESSAGES")).to.be('Not translated'); 573 | expect(i18n.dcnpgettext('messages_4', 'context', 'Not translated', 'Not translated plural', 2, "LC_MESSAGES")).to.be('Not translated plural'); 574 | expect(i18n.dcnpgettext('messages_4', 'context', 'Not translated', 'Not translated plural', 0, "LC_MESSAGES")).to.be('Not translated plural'); 575 | }); 576 | }); 577 | }); 578 | 579 | describe("Plural Forms Parsing", function (){ 580 | // This is the method from the original gettext.js that uses new Function 581 | function evalParse( plural_forms ) { 582 | var pf_re = new RegExp('^(\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;a-zA-Z0-9_\(\)])+)', 'm'); 583 | if (pf_re.test(plural_forms)) { 584 | var pf = plural_forms; 585 | if (! /;\s*$/.test(pf)) pf = pf.concat(';'); 586 | 587 | var code = 'var plural; var nplurals; '+pf+' return { "nplural" : nplurals, "plural" : (plural === true ? 1 : plural ? plural : 0) };'; 588 | return (new Function("n", code)); 589 | } else { 590 | throw new Error("Syntax error in language file. Plural-Forms header is invalid ["+plural_forms+"]"); 591 | } 592 | } 593 | 594 | // http://translate.sourceforge.net/wiki/l10n/pluralforms 595 | it("should have the same result as doing an eval on the expression for all known plural-forms.", function (){ 596 | var pfs = ["nplurals=2; plural=(n > 1)","nplurals=2; plural=(n != 1)","nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;","nplurals=1; plural=0","nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)","nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2","nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2","nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3","nplurals=2; plural=n > 1","nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4","nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3","nplurals=2; plural= (n > 1)","nplurals=2; plural=(n%10!=1 || n%100==11)","nplurals=2; plural=n!=0","nplurals=2; plural=(n!=1)","nplurals=2; plural=(n!= 1)","nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3","nplurals=2; plural=n>1;","nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2)","nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2)","nplurals=2; plural= n==1 || n%10==1 ? 0 : 1","nplurals=3; plural=(n==0 ? 0 : n==1 ? 1 : 2)","nplurals=4; plural=(n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3)","nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)","nplurals=2; plural=(n!=1);","nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2);","nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0)","nplurals=2; plural=n != 1","nplurals=2; plural=(n>1)","nplurals=1; plural=0;"], 597 | pf, pfc, pfe, pfi, i; 598 | for ( pfi = 0; pfi < pfs.length; pfi++ ) { 599 | pf = ""+pfs[ pfi ]; 600 | for( i = 0; i < 106; i++ ){ 601 | pfc = Jed.PF.compile( ""+pf )( i ); 602 | pfe = evalParse( ""+pf )( i ).plural; 603 | if (pfc !== pfe) { 604 | throw new Error('expected ' + pfe + ' but got ' + pfc); 605 | } 606 | } 607 | } 608 | }); 609 | 610 | }); 611 | 612 | describe("Chainable API", function () { 613 | var locale_data_w_context = { 614 | "context_sprintf_test": { 615 | "": { 616 | "domain": "context_sprintf_test", 617 | "lang": "en", 618 | "plural-forms": "nplurals=2; plural=(n != 1);" 619 | }, 620 | "test singular": ["test_1"], 621 | "test plural %1$d": ["test_1_singular %1$d", "test_1_plural %1$d"], 622 | "context\u0004test context": ["test_1context"], 623 | "test2": ["test_2"], 624 | "zero length translation": [""], 625 | "context\u0004test2": ["test_2context"], 626 | "context\u0004context plural %1$d": ["context_plural_1 singular %1$d", "context_plural_1 plural %1$d"] 627 | }, 628 | "other_domain": { 629 | "": { 630 | "domain": "other_domain", 631 | "lang": "en", 632 | "plural-forms": "nplurals=2; plural=(n != 1);" 633 | }, 634 | "test other_domain singular": ["other domain test 1"], 635 | "context\u0004context other plural %1$d": ["context_plural_1 singular %1$d", "context_plural_1 plural %1$d"] 636 | } 637 | }; 638 | var i18n = new Jed({ 639 | "locale_data" : locale_data_w_context, 640 | "domain": "context_sprintf_test" 641 | }); 642 | 643 | it("should handle a simple gettext passthrough", function (){ 644 | expect( i18n.translate('test singular').fetch() ).to.be('test_1'); 645 | }); 646 | 647 | it("should handle changing domains", function (){ 648 | expect( i18n.translate('test other_domain singular').onDomain('other_domain').fetch() ).to.be('other domain test 1'); 649 | }); 650 | 651 | it("should allow you to add plural information in the chain.", function () { 652 | expect( i18n.translate("test plural %1$d").ifPlural(5, "dont matta").fetch() ).to.be( "test_1_plural %1$d" ); 653 | }); 654 | 655 | it("should take in a sprintf set of args (as array) on the plural lookup", function(){ 656 | expect( i18n.translate("test plural %1$d").ifPlural(5, "dont matta").fetch([5]) ).to.be( "test_1_plural 5" ); 657 | expect( i18n.translate("test plural %1$d %2$d").ifPlural(5, "dont matta %1$d %2$d").fetch([5, 6]) ).to.be( "dont matta 5 6" ); 658 | expect( i18n.translate("test plural %1$d %2$d").ifPlural(1, "dont matta %1$d %2$d").fetch([1, 6]) ).to.be( "test plural 1 6" ); 659 | }); 660 | 661 | it("should take in a sprintf set of args (as args) on the plural lookup", function(){ 662 | expect( i18n.translate("test plural %1$d %2$d").ifPlural(5, "dont matta %1$d %2$d").fetch(5, 6) ).to.be( "dont matta 5 6" ); 663 | expect( i18n.translate("test plural %1$d %2$d").ifPlural(1, "dont matta %1$d %2$d").fetch(1, 6) ).to.be( "test plural 1 6" ); 664 | }); 665 | 666 | it("should handle context information.", function () { 667 | expect(i18n.translate('test context').withContext('context').fetch() ).to.be('test_1context'); 668 | }); 669 | 670 | it("should be able to do all at the same time.", function () { 671 | expect( i18n.translate("context other plural %1$d").withContext('context').onDomain('other_domain').ifPlural(5, "ignored %1$d").fetch(5) ).to.be( "context_plural_1 plural 5" ); 672 | expect( i18n.translate("context other plural %1$d").withContext('context').onDomain('other_domain').ifPlural(1, "ignored %1$d").fetch(1) ).to.be( "context_plural_1 singular 1" ); 673 | }); 674 | 675 | }); 676 | 677 | describe("Sprintf", function () { 678 | var locale_data_w_context = { 679 | "context_sprintf_test": { 680 | "": { 681 | "domain": "context_sprintf_test", 682 | "lang": "en", 683 | "plural-forms": "nplurals=2; plural=(n != 1);" 684 | }, 685 | "test singular": ["test_1"], 686 | "test plural %1$d": ["test_1_singular %1$d", "test_1_plural %1$d"], 687 | "context\u0004test context": ["test_1context"], 688 | "test2": ["test_2"], 689 | "zero length translation": [""], 690 | "context\u0004test2": ["test_2context"], 691 | "context\u0004context plural %1$d": ["context_plural_1 singular %1$d", "context_plural_1 plural %1$d"] 692 | } 693 | }; 694 | 695 | var i18n = new Jed({ 696 | "locale_data" : locale_data_w_context, 697 | "domain": "context_sprintf_test" 698 | }); 699 | 700 | 701 | it("should take multiple types of arrays as input", function () { 702 | var strings = { 703 | "blah" : "blah", 704 | "thing%1$sbob" : "thing[one]bob", 705 | "thing%1$s%2$sbob" : "thing[one][two]bob", 706 | "thing%1$sasdf%2$sasdf" : "thing[one]asdf[two]asdf", 707 | "%1$s%2$s%3$s" : "[one][two]", 708 | "tom%1$saDick" : "tom[one]aDick" 709 | }; 710 | var args = ["[one]", "[two]"]; 711 | 712 | for (var i in strings) { 713 | // test using new Array 714 | expect(Jed.sprintf(i, ["[one]","[two]"])).to.be(strings[i]); 715 | expect(i18n.sprintf(i, ["[one]","[two]"])).to.be(strings[i]); 716 | // test using predefined array 717 | expect(Jed.sprintf(i, args)).to.be(strings[i]); 718 | expect(i18n.sprintf(i, args)).to.be(strings[i]); 719 | } 720 | }); 721 | 722 | 723 | 724 | it("should accept a single string instead of an array", function () { 725 | // test using scalar rather than array 726 | var strings = { 727 | "blah" : "blah", 728 | "" : "", 729 | "%%" : "%", 730 | "tom%%dick" : "tom%dick", 731 | "thing%1$sbob" : "thing[one]bob", 732 | "thing%1$s%2$sbob" : "thing[one]bob", 733 | "thing%1$sasdf%2$sasdf" : "thing[one]asdfasdf", 734 | "%1$s%2$s%3$s" : "[one]" 735 | }; 736 | var arg = "[one]"; 737 | 738 | for (var i in strings) { 739 | expect(Jed.sprintf(i, arg)).to.be(strings[i]); 740 | expect(i18n.sprintf(i, arg)).to.be(strings[i]); 741 | } 742 | }); 743 | }); 744 | })(); 745 | 746 | })( Jed ); 747 | --------------------------------------------------------------------------------