WordPress.org

Make WordPress Core

Ticket #20491: 20491.6.diff

File 20491.6.diff, 61.5 KB (added by swissspidy, 21 months ago)
  • src/wp-includes/class.wp-scripts.php

    diff --git src/wp-includes/class.wp-scripts.php src/wp-includes/class.wp-scripts.php
    index 81634ec..9a543d2 100644
    class WP_Scripts extends WP_Dependencies { 
    461461        }
    462462
    463463        /**
     464         * Load a .JSON file for the given text domain.
     465         *
     466         * @since 4.7.0
     467         * @access public
     468         *
     469         * @param string $handle Name of the script to add the inline script to. Must be lowercase.
     470         * @param string $domain Text domain. Unique identifier for retrieving translated strings.
     471         * @param string $file   Optional. Path to the .json file. Default `$domain-$locale.json`.
     472         * @return bool True on success, false on failure.
     473         */
     474        public function load_translation_file( $handle, $domain = 'default', $file = null ) {
     475                return $this->add_inline_script( $handle, "wp && wp.i18n && wp.i18n.addTranslations( '$domain', " . get_js_i18n_data( $domain, $file ) . ");" );
     476        }
     477
     478        /**
    464479         * Sets handle group.
    465480         *
    466481         * @since 2.8.0
  • new file src/wp-includes/js/jed.js

    diff --git src/wp-includes/js/jed.js src/wp-includes/js/jed.js
    new file mode 100644
    index 0000000..2146917
    - +  
     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 <alexaholic [at) gmail (dot] com>
     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);
  • new file src/wp-includes/js/wp-i18n.js

    diff --git src/wp-includes/js/wp-i18n.js src/wp-includes/js/wp-i18n.js
    new file mode 100644
    index 0000000..28d5bb5
    - +  
     1/**
     2 * I18n helper functions.
     3 *
     4 * @version 4.7.0
     5 *
     6 * @package WordPress
     7 * @subpackage i18n
     8 */
     9
     10/**
     11 * @param {object} wp                          WP object.
     12 * @param {Jed}    Jed                         The Jed Gettext library.
     13 * @param {object} settings                    WP Updates settings.
     14 * @param {string} settings.locale             The current locale
     15 * @param {string} settings.pluralForms        Plural forms of the current locale.
     16 * @param {object} settings.thousandsSeparator Thousands separator character.
     17 * @param {object} settings.decimalPoint       Decimal point character.
     18 */
     19( function( wp, Jed, settings ) {
     20        'use strict';
     21
     22        wp       = wp || {};
     23        settings = settings || {};
     24
     25        var i18n = new Jed( {
     26                'locale_data': {
     27                        // This is the domain key.
     28                        'default': {
     29                                // The empty string key is used as the configuration.
     30                                '': {
     31                                        'domain':       'default',
     32                                        'lang':         settings.locale || 'en_US',
     33                                        'plural_forms': settings.pluralForms || 'nplurals=2; plural=(n != 1);'
     34                                }
     35                                // Other keys in a domain contain arrays as values that map to the translations for that key.
     36                        }
     37                },
     38                'domain':      'default',
     39                'debug':       true
     40        } );
     41
     42        /**
     43         * Retrieves the current locale.
     44         *
     45         * @since 4.7.0
     46         *
     47         * @returns {string} The locale of the blog.
     48         */
     49        function getLocale() {
     50                return i18n.options.locale_data.default[''].lang;
     51        }
     52
     53        /**
     54         * Loads Jed 1.x-compatible JSON data.
     55         *
     56         * @param {*} data Locale data.
     57         */
     58        function loadLocaleData( data ) {
     59                if ( data.domain
     60                        && data.locale_data
     61                        && data.locale_data[ data.domain ]
     62                        && data.locale_data[ data.domain ][ '' ]
     63                ) {
     64                        i18n.options.locale_data[ data.domain ] = data.locale_data[ data.domain ];
     65                }
     66        }
     67
     68        /**
     69         * Adds translations for a given domain so that they can be used later on.
     70         *
     71         * Overwrites any existing translations with a matching key
     72         *
     73         * @since 4.7.0
     74         *
     75         * @param {string} domain Text domain
     76         * @param {*}      data   An array of translation entries.
     77         */
     78        function addTranslations( domain, data ) {
     79                if ( ! i18n.options.locale_data[ domain ] ) {
     80                        // Create new based on i18n.options.locale_data.default
     81                        i18n.options.locale_data[ domain ] = {
     82                                '': {
     83                                        domain:       domain,
     84                                        lang:         i18n.options.locale_data.default[ '' ].lang,
     85                                        plural_forms: i18n.options.locale_data.default[ '' ].plural_forms
     86                                }
     87                        };
     88                }
     89
     90                for ( var prop in data ) {
     91                        if ( data.hasOwnProperty( prop ) && '' !== prop ) {
     92                                i18n.options.locale_data[ domain ][prop] = data[prop];
     93                        }
     94                }
     95        }
     96
     97        /**
     98         * Retrieves the translation of the given text.
     99         *
     100         * If there is no translation, or the text domain isn't loaded, the original text is returned.
     101         *
     102         * @since 4.7.0
     103         *
     104         * @param {string}  text             Text to translate.
     105         * @param {string=} [domain=default] Optional. Text domain. Unique identifier for retrieving translated strings.
     106         *                                   Default 'default'.
     107         * @returns {string} Translated text.
     108         */
     109        function __( text, domain ) {
     110                domain = domain || 'default';
     111
     112                return i18n.dgettext( domain, text );
     113        }
     114
     115        /**
     116         * Retrieves the translation of the given text with gettext context.
     117         *
     118         * Quite a few times, there will be collisions with similar translatable text
     119         * found in more than two places, but with different translated context.
     120         *
     121         * By adding the context, translators can translate the two strings differently.
     122         *
     123         * @since 4.7.0
     124         *
     125         * @param {string}  text             Text to translate.
     126         * @param {string}  context          Context information for the translators.
     127         * @param {string=} [domain=default] Optional. Text domain. Unique identifier for retrieving translated strings.
     128         *                                   Default 'default'.
     129         * @returns {string} Translated text.
     130         */
     131        function _x( text, context, domain ) {
     132                domain = domain || 'default';
     133
     134                return i18n.dpgettext( domain, context, text );
     135        }
     136
     137        /**
     138         * Translates and retrieves the singular or plural form based on the supplied number.
     139         *
     140         * Used when you want to use the appropriate form of a string based on whether a
     141         * number is singular or plural.
     142         *
     143         * @since 4.7.0
     144         *
     145         * @param {string}  single           The text to be used if the number is singular.
     146         * @param {string}  plural           The text to be used if the number is plural.
     147         * @param {Number}  number           The number to compare against to use either the singular or plural form.
     148         * @param {string=} [domain=default] Optional. Text domain. Unique identifier for retrieving translated strings.
     149         *                                   Default 'default'.
     150         * @returns {string} Translated text.
     151         */
     152        function _n( single, plural, number, domain ) {
     153                domain = domain || 'default';
     154
     155                return i18n.dngettext( domain, single, plural, number );
     156        }
     157
     158        /**
     159         * Translates and retrieves the singular or plural form based on the supplied number, with gettext context.
     160         *
     161         * Used when you want to use the appropriate form of a string based on whether a
     162         * number is singular or plural.
     163         *
     164         * @since 4.7.0
     165         *
     166         * @param {string}  single           The text to be used if the number is singular.
     167         * @param {string}  plural           The text to be used if the number is plural.
     168         * @param {Number}  number           The number to compare against to use either the singular or plural form.
     169         * @param {string}  context          Context information for the translators.
     170         * @param {string?} [domain=default] Optional. Text domain. Unique identifier for retrieving translated strings.
     171         *                                   Default 'default'.
     172         * @returns {string} Translated text.
     173         */
     174        function _nx( single, plural, number, context, domain ) {
     175                domain = domain || 'default';
     176
     177                return i18n.dnpgettext( domain, context, single, plural, number );
     178        }
     179
     180        /**
     181         * Retrieve the translation of the given text and escapes it for safe use in an attribute.
     182         *
     183         * If there is no translation, or the text domain isn't loaded, the original text is returned.
     184         *
     185         * @since 4.7.0
     186         *
     187         * @param {string}  text             Text to translate.
     188         * @param {string=} [domain=default] Optional. Text domain. Unique identifier for retrieving translated strings.
     189         *                                   Default 'default'.
     190         * @returns {string} Translated text.
     191         */
     192        function esc_attr__( text, domain ) {
     193                domain = domain || 'default';
     194
     195                return escape( i18n.dgettext( domain, text ) );
     196        }
     197
     198        /**
     199         * Retrieve the translation of the given text and escapes it for safe use in HTML output.
     200         *
     201         * If there is no translation, or the text domain isn't loaded, the original text is returned.
     202         *
     203         * @since 4.7.0
     204         *
     205         * @param {string}  text             Text to translate.
     206         * @param {string=} [domain=default] Optional. Text domain. Unique identifier for retrieving translated strings.
     207         *                                   Default 'default'.
     208         * @returns {string} Translated text.
     209         */
     210        function esc_html__( text, domain ) {
     211                domain = domain || 'default';
     212
     213                return escape( i18n.dgettext( domain, text ) );
     214        }
     215
     216        /**
     217         * Retrieves the translation of the given text with gettext context, and escapes it for safe use in an attribute.
     218         *
     219         * Quite a few times, there will be collisions with similar translatable text
     220         * found in more than two places, but with different translated context.
     221         *
     222         * By adding the context, translators can translate the two strings differently.
     223         *
     224         * @since 4.7.0
     225         *
     226         * @param {string}  text             Text to translate.
     227         * @param {string}  context          Context information for the translators.
     228         * @param {string=} [domain=default] Optional. Text domain. Unique identifier for retrieving translated strings.
     229         *                                   Default 'default'.
     230         * @returns {string} Translated text.
     231         */
     232        function esc_attr_x( text, context, domain ) {
     233                domain = domain || 'default';
     234
     235                return escape( i18n.dpgettext( domain, context, text ) );
     236        }
     237
     238        /**
     239         * Retrieves the translation of the given text with gettext context, and escapes it for safe use in HTML output.
     240         *
     241         * Quite a few times, there will be collisions with similar translatable text
     242         * found in more than two places, but with different translated context.
     243         *
     244         * By adding the context, translators can translate the two strings differently.
     245         *
     246         * @since 4.7.0
     247         *
     248         * @param {string}  text             Text to translate.
     249         * @param {string}  context          Context information for the translators.
     250         * @param {string=} [domain=default] Optional. Text domain. Unique identifier for retrieving translated strings.
     251         *                                   Default 'default'.
     252         * @returns {string} Translated text.
     253         */
     254        function esc_html_x( text, context, domain ) {
     255                domain = domain || 'default';
     256
     257                return escape( i18n.dpgettext( domain, context, text ) );
     258        }
     259
     260        /**
     261         * Escapes text for use in HTML blocks and attributes.
     262         *
     263         * @since 4.7.0
     264         *
     265         * @param {string} text Text to escape.
     266         * @returns {string} Escaped text.
     267         */
     268        function escape( text ) {
     269                var map    = {
     270                            '&': '&amp;',
     271                            '<': '&lt;',
     272                            '>': '&gt;',
     273                            '"': '&quot;',
     274                            "'": '&#x27;'
     275                    },
     276                    source = '(?:' + _.keys( map ).join( '|' ) + ')';
     277
     278                if ( ! new RegExp( source ) ) {
     279                        return text;
     280                }
     281
     282                return text.replace( new RegExp( source, 'g' ), function( match ) {
     283                        return map[ match ];
     284                } );
     285        }
     286
     287        /**
     288         * Converts a float number to format based on the locale.
     289         *
     290         * @since 4.7.0
     291         *
     292         * @param {Number}  number      The number to convert based on locale.
     293         * @param {Number=} [decimals=] Optional. Precision of the number of decimal places. Default 0.
     294         * @returns {string} Converted number in string format.
     295         */
     296        function numberFormat( number, decimals ) {
     297                var formatted = '',
     298                    roundedNumber, numbersString, decimalsString;
     299
     300                if ( isNaN( number ) ) {
     301                        return formatted;
     302                }
     303
     304                decimals = decimals || 0;
     305                decimals = Math.abs( decimals );
     306                number   = parseFloat( number );
     307
     308                roundedNumber  = Math.round( Math.abs( number ) * ('1e' + decimals) ) + '';
     309                numbersString  = decimals ? roundedNumber.slice( 0, decimals * -1 ) : roundedNumber;
     310                decimalsString = decimals ? roundedNumber.slice( decimals * -1 ) : '';
     311
     312                while ( numbersString.length > 3 ) {
     313                        formatted += settings.thousandsSeparator + numbersString.slice( -3 );
     314                        numbersString = numbersString.slice( 0, -3 );
     315                }
     316
     317                formatted = numbersString + formatted;
     318
     319                if ( decimalsString ) {
     320                        formatted += settings.decimalPoint + decimalsString;
     321                }
     322
     323                if ( number < 0 ) {
     324                        formatted = '-' + formatted;
     325                }
     326
     327                return formatted;
     328        }
     329
     330        wp.i18n                 = wp.i18n || {};
     331        wp.i18n.getLocale       = getLocale;
     332        wp.i18n.loadLocaleData  = loadLocaleData;
     333        wp.i18n.addTranslations = addTranslations;
     334        wp.i18n.__              = __;
     335        wp.i18n._x              = _x;
     336        wp.i18n._n              = _n;
     337        wp.i18n._nx             = _nx;
     338        wp.i18n.esc_attr__      = esc_attr__;
     339        wp.i18n.esc_html__      = esc_html__;
     340        wp.i18n.esc_attr_x      = esc_attr_x;
     341        wp.i18n.esc_html_x      = esc_html_x;
     342        wp.i18n.numberFormat    = numberFormat;
     343
     344}( window.wp, window.Jed, window._wpI18nSettings ));
  • src/wp-includes/l10n.php

    diff --git src/wp-includes/l10n.php src/wp-includes/l10n.php
    index aaa7611..5701e29 100644
    function is_textdomain_loaded( $domain ) { 
    892892}
    893893
    894894/**
     895 * Loads a .JSON file for the given text domain.
     896 *
     897 * @since 4.7.0
     898 *
     899 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
     900 * @param string $file Optional. Path to the .json file. Default `$domain-$locale.json`.
     901 * @return string JSON string.
     902 */
     903function get_js_i18n_data( $domain = 'default', $file = null ) {
     904        if ( $file ) {
     905                return file_get_contents( $file );
     906        }
     907
     908        $locale    = get_locale();
     909        $json_file = ( 'default' !== $domain ) ? "{$domain}-{$locale}.json" : "{$locale}.json";
     910
     911        foreach (
     912                array(
     913                        WP_LANG_DIR,
     914                        WP_LANG_DIR . '/plugins',
     915                        WP_LANG_DIR . '/themes',
     916                ) as $location
     917        ) {
     918                $file = trailingslashit( $location ) . $json_file;
     919                if ( file_exists( $file ) && is_readable( $file ) ) {
     920                        return file_get_contents( $file );
     921                }
     922        }
     923
     924        // Return empty if nothing found so it doesn't fail when parsed in JS.
     925        return '{}';
     926}
     927
     928/**
    895929 * Translates role name.
    896930 *
    897931 * Since the role names are in the database and not in the source there
    function is_rtl() { 
    11481182                return false;
    11491183        }
    11501184        return $wp_locale->is_rtl();
    1151 }
    1152  No newline at end of file
     1185}
  • src/wp-includes/script-loader.php

    diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php
    index a7019b0..e7c2bcb 100644
    function wp_default_scripts( &$scripts ) { 
    6767        $suffix = SCRIPT_DEBUG ? '' : '.min';
    6868        $dev_suffix = $develop_src ? '' : '.min';
    6969
     70        $scripts->add( 'jed', "/wp-includes/js/jed$suffix.js", array(), '1.1.0', 1 );
     71        $scripts->add( 'wp-i18n', "/wp-includes/js/wp-i18n$suffix.js", array( 'jed' ), false, 1 );
     72        $translations = get_translations_for_domain( 'default' );
     73        did_action( 'init' ) && $scripts->localize( 'wp-i18n', '_wpI18nSettings', array(
     74                'locale'      => get_locale(),
     75                'pluralForms' => $translations->get_header( 'Plural-Forms' ),
     76        ) );
     77
    7078        $scripts->add( 'utils', "/wp-includes/js/utils$suffix.js" );
    7179        did_action( 'init' ) && $scripts->localize( 'utils', 'userSettings', array(
    7280                'url' => (string) SITECOOKIEPATH,
    function wp_default_scripts( &$scripts ) { 
    7583                'secure' => (string) ( 'https' === parse_url( site_url(), PHP_URL_SCHEME ) ),
    7684        ) );
    7785
    78         $scripts->add( 'common', "/wp-admin/js/common$suffix.js", array('jquery', 'hoverIntent', 'utils'), false, 1 );
     86        $scripts->add( 'common', "/wp-admin/js/common$suffix.js", array( 'jquery', 'hoverIntent', 'utils', 'wp-i18n' ), false, 1 );
     87        did_action( 'init' ) && $scripts->load_translation_file( 'common' );
    7988        did_action( 'init' ) && $scripts->localize( 'common', 'commonL10n', array(
    8089                'warnDelete' => __( "You are about to permanently delete these items.\n  'Cancel' to stop, 'OK' to delete." ),
    8190                'dismiss'    => __( 'Dismiss this notice.' ),
  • new file tests/phpunit/data/languages/de_DE.json

    diff --git tests/phpunit/data/languages/de_DE.json tests/phpunit/data/languages/de_DE.json
    new file mode 100644
    index 0000000..df3a321
    - +  
     1{
     2  "domain":      "messages",
     3  "locale_data": {
     4    "messages": {
     5      "":                                                             {
     6        "domain":       "messages",
     7        "plural-forms": "nplurals=2; plural=(n != 1);",
     8        "lang":         "de-de"
     9      },
     10      "This file is too big. Files must be less than %d KB in size.": [
     11        "Diese Datei ist zu gross. Dateien m\u00fcssen kleiner als %d KB sein."
     12      ],
     13      "%d Theme Update":                                              [
     14        "%d Theme-Aktualisierung",
     15        "%d Theme-Aktualisierungen"
     16      ],
     17      "password strength\u0004Medium":                                [
     18        "Medium"
     19      ],
     20      "taxonomy singular name\u0004Category":                         [
     21        "Kategorie"
     22      ],
     23      "post type general name\u0004Pages":                            [
     24        "Seiten"
     25      ]
     26    }
     27  }
     28}
  • tests/qunit/index.html

    diff --git tests/qunit/index.html tests/qunit/index.html
    index ff8ae00..948c76f 100644
     
    1919                </script>
    2020                <script src="../../src/wp-includes/js/wp-util.js"></script>
    2121                <script src="../../src/wp-includes/js/wp-a11y.js"></script>
     22                <script src="../../src/wp-includes/js/jed.js"></script>
     23                <script>
     24                        window._wpI18nSettings = {
     25                                locale:             'de_DE',
     26                                pluralForms:        'nplurals=2; plural=(n != 1);',
     27                                thousandsSeparator: ',',
     28                                decimalPoint:       '.'
     29                        };
     30                </script>
     31                <script src="../../src/wp-includes/js/wp-i18n.js"></script>
    2232
    2333                <!-- QUnit -->
    2434                <link rel="stylesheet" href="vendor/qunit.css" type="text/css" media="screen" />
     
    3444                        <script src="fixtures/customize-settings.js"></script>
    3545                        <script src="fixtures/customize-menus.js"></script>
    3646                        <script src="fixtures/customize-widgets.js"></script>
     47                        <script src="fixtures/wp-i18n.js"></script>
    3748                </div>
    3849                <p><a href="editor">TinyMCE tests</a></p>
    3950
     
    6677                <script src="wp-admin/js/customize-nav-menus.js"></script>
    6778                <script src="wp-admin/js/customize-widgets.js"></script>
    6879                <script src="wp-admin/js/word-count.js"></script>
     80                <script src="wp-includes/js/wp-i18n.js"></script>
    6981
    7082                <!-- Customizer templates for sections -->
    7183                <script type="text/html" id="tmpl-customize-section-default">
  • new file tests/qunit/wp-includes/js/wp-i18n.js

    diff --git tests/qunit/wp-includes/js/wp-i18n.js tests/qunit/wp-includes/js/wp-i18n.js
    new file mode 100644
    index 0000000..ea63e33
    - +  
     1/*global wp, jQuery */
     2jQuery( function() {
     3
     4        module( 'i18n' );
     5
     6        test( 'fixtures should be present', function() {
     7                notEqual( window._wpI18nSettings, undefined );
     8        } );
     9
     10        module( 'i18n.getLocale' );
     11
     12        test( 'it should return the current locale', function() {
     13                equal( wp.i18n.getLocale(), 'de_DE' );
     14        } );
     15
     16        module( 'i18n.loadLocaleData' );
     17
     18        test( 'it should bail if domain is missing', function() {
     19                wp.i18n.loadLocaleData( {
     20                        "locale_data": {
     21                                "invalid": {
     22                                        "": {
     23                                                "domain":       "invalid",
     24                                                "plural-forms": "nplurals=2; plural=(n != 1);",
     25                                                "lang":         "de-de"
     26                                        },
     27                                        "Foo": [
     28                                                "Bar"
     29                                        ],
     30                                }
     31                        }
     32                } );
     33
     34                try {
     35                        wp.i18n.__( 'Foo', 'invalid' );
     36                } catch ( error ) {
     37                        equal( error.message, 'Domain `invalid` was not found.' );
     38                }
     39        } );
     40
     41        test( 'it should bail if locale data is missing', function() {
     42                wp.i18n.loadLocaleData( {
     43                        "domain": "invalid",
     44                } );
     45
     46                try {
     47                        wp.i18n.__( 'Foo', 'invalid' )
     48                } catch( error ) {
     49                        equal( error.message, 'Domain `invalid` was not found.')
     50                }
     51        } );
     52
     53        test( 'it should bail if domain does not match', function() {
     54                wp.i18n.loadLocaleData( {
     55                        "domain": "foo",
     56                        "locale_data": {
     57                                "bar": {
     58                                        "": {
     59                                                "domain":       "foo",
     60                                                "plural-forms": "nplurals=2; plural=(n != 1);",
     61                                                "lang":         "de-de"
     62                                        },
     63                                        "Foo": [
     64                                                "Bar"
     65                                        ],
     66                                }
     67                        }
     68                } );
     69
     70                try {
     71                        wp.i18n.__( 'Foo', 'foo' );
     72                } catch ( error ) {
     73                        equal( error.message, 'Domain `foo` was not found.' );
     74                }
     75        } );
     76
     77        test( 'it should bail if meta information is missing', function() {
     78                wp.i18n.loadLocaleData( {
     79                        "domain": "missing",
     80                        "locale_data": {
     81                                "missing": {
     82                                        "Foo": [
     83                                                "Bar"
     84                                        ],
     85                                }
     86                        }
     87                } );
     88
     89                try {
     90                        wp.i18n.__( 'Foo', 'missing' );
     91                } catch ( error ) {
     92                        equal( error.message, 'Domain `missing` was not found.' );
     93                }
     94        } );
     95
     96        test( 'it should translate the added strings', function() {
     97                wp.i18n.loadLocaleData( {
     98                        "domain":      "example",
     99                        "locale_data": {
     100                                "example": {
     101                                        "": {
     102                                                "domain":       "example",
     103                                                "plural-forms": "nplurals=2; plural=(n != 1);",
     104                                                "lang":         "de-de"
     105                                        },
     106                                        "How are you?": [
     107                                                "Wie geht es dir?"
     108                                        ],
     109                                        "%d Theme Update": [
     110                                                "%d Theme-Aktualisierung",
     111                                                "%d Theme-Aktualisierungen"
     112                                        ],
     113                                        "post type general name\u0004Pages": [
     114                                                "Seiten"
     115                                        ]
     116                                }
     117                        }
     118                } );
     119
     120                equal( wp.i18n.__( 'How are you?', 'example' ), 'Wie geht es dir?' );
     121                equal( wp.i18n._n( '%d Theme Update', '%d Theme Updates', 1, 'example' ), '%d Theme-Aktualisierung' );
     122                equal( wp.i18n._n( '%d Theme Update', '%d Theme Updates', 2, 'example' ), '%d Theme-Aktualisierungen' );
     123                equal( wp.i18n._x( 'Pages', 'post type general name', 'example' ), 'Seiten' );
     124        } );
     125
     126        module( 'i18n.__' );
     127
     128        test( 'it should translate the added strings', function() {
     129                wp.i18n.addTranslations( 'foobar',
     130                        {
     131                                'How are you?': [ 'Wie geht es dir?' ]
     132                        }
     133                );
     134
     135                equal( wp.i18n.__( 'How are you?', 'foobar' ), 'Wie geht es dir?' );
     136                equal( wp.i18n.__( 'Foo bar', 'foobar' ), 'Foo bar' );
     137        } );
     138
     139        module( 'i18n._x' );
     140
     141        test( 'it should translate the added strings', function() {
     142                wp.i18n.addTranslations( 'foobar',
     143                        {
     144                                'verb\u0004Post': [ 'Posten' ],
     145                                'noun\u0004Post': [ 'Beitrag' ]
     146                        }
     147                );
     148
     149                equal( wp.i18n._x( 'Post', 'verb', 'foobar' ), 'Posten' );
     150                equal( wp.i18n._x( 'Post', 'noun', 'foobar' ), 'Beitrag' );
     151                equal( wp.i18n._x( 'Post', 'verb' ), 'Post' );
     152                equal( wp.i18n._x( 'Post', 'foo', 'foobar' ), 'Post' );
     153        } );
     154
     155        module( 'i18n._n' );
     156
     157        test( 'it should translate the added strings', function() {
     158                wp.i18n.addTranslations( 'foobar',
     159                        {
     160                                'There is %d foo': [ 'Da ist %d Foo', 'Da sind %d Foos' ]
     161                        }
     162                );
     163
     164                equal( wp.i18n._n( 'There is %d foo', 'There are %d foos', 1, 'foobar' ), 'Da ist %d Foo' );
     165                equal( wp.i18n._n( 'There is %d foo', 'There are %d foos', 2, 'foobar' ), 'Da sind %d Foos' );
     166                equal( wp.i18n._n( 'There is %d bar', 'There are %d bar', 1, 'foobar' ), 'There is %d bar' );
     167                equal( wp.i18n._n( 'There is %d bar', 'There are %d bars', 2, 'foobar' ), 'There are %d bars' );
     168        } );
     169
     170        module( 'i18n._nx' );
     171
     172        test( 'it should translate the added strings ', function() {
     173                wp.i18n.addTranslations( 'foobar',
     174                        {
     175                                'context\u0004There is %d foo': [ 'Da ist %d Foo', 'Da sind %d Foos' ],
     176                                "context\u0004%d key":          [ "context %d key", "context %d keys" ]
     177                        }
     178                );
     179
     180                equal( wp.i18n._nx( 'There is %d foo', 'There are %d foos', 1, 'context', 'foobar' ), 'Da ist %d Foo' );
     181                equal( wp.i18n._nx( 'There is %d foo', 'There are %d foos', 2, 'context', 'foobar' ), 'Da sind %d Foos' );
     182                equal( wp.i18n._nx( 'There is %d bar', 'There are %d bar', 1, 'context' ), 'There is %d bar' );
     183                equal( wp.i18n._nx( 'There is %d bar', 'There are %d bars', 2, 'context' ), 'There are %d bars' );
     184        } );
     185
     186        module( 'i18n.esc_attr__' );
     187
     188        test( 'it should translate and escape added strings', function() {
     189                wp.i18n.addTranslations( 'foo',
     190                        {
     191                                '<b>This</b> \'text\' is "escaped" & translated.': [ '<b>Dieser</b> \'Text\' ist "escaped" & übersetzt.' ]
     192                        }
     193                );
     194
     195                equal( wp.i18n.esc_attr__( '<b>This</b> \'text\' is "escaped" & translated.', 'foo' ), '&lt;b&gt;Dieser&lt;/b&gt; \&#x27;Text\&#x27; ist &quot;escaped&quot; &amp; übersetzt.' );
     196                equal( wp.i18n.esc_attr__( '<b>This</b> \'text\' is "escaped" & not translated.', 'foo' ), '&lt;b&gt;This&lt;/b&gt; \&#x27;text\&#x27; is &quot;escaped&quot; &amp; not translated.' );
     197        } );
     198
     199        module( 'i18n.esc_html__' );
     200
     201        test( 'it should translate and escape added strings', function() {
     202                wp.i18n.addTranslations( 'foo',
     203                        {
     204                                '<b>This</b> \'text\' is "escaped" & translated.': [ '<b>Dieser</b> \'Text\' ist "escaped" & übersetzt.' ]
     205                        }
     206                );
     207
     208                equal( wp.i18n.esc_html__( '<b>This</b> \'text\' is "escaped" & translated.', 'foo' ), '&lt;b&gt;Dieser&lt;/b&gt; \&#x27;Text\&#x27; ist &quot;escaped&quot; &amp; übersetzt.' );
     209                equal( wp.i18n.esc_html__( '<b>This</b> \'text\' is "escaped" & not translated.', 'foo' ), '&lt;b&gt;This&lt;/b&gt; \&#x27;text\&#x27; is &quot;escaped&quot; &amp; not translated.' );
     210        } );
     211
     212        module( 'i18n.esc_attr_x' );
     213
     214        test( 'it should translate and escape added strings', function() {
     215                wp.i18n.addTranslations( 'foo',
     216                        {
     217                                'context\u0004<b>This</b> \'text\' is "escaped" & translated.': [ '<b>Dieser</b> \'Text\' ist "escaped" & übersetzt.' ]
     218                        }
     219                );
     220
     221                equal( wp.i18n.esc_attr_x( '<b>This</b> \'text\' is "escaped" & translated.', 'context', 'foo' ), '&lt;b&gt;Dieser&lt;/b&gt; \&#x27;Text\&#x27; ist &quot;escaped&quot; &amp; übersetzt.' );
     222                equal( wp.i18n.esc_attr_x( '<b>This</b> \'text\' is "escaped" & not translated.', 'context', 'foo' ), '&lt;b&gt;This&lt;/b&gt; \&#x27;text\&#x27; is &quot;escaped&quot; &amp; not translated.' );
     223        } );
     224
     225        module( 'i18n.esc_html_x' );
     226
     227        test( 'it should translate and escape added strings', function() {
     228                wp.i18n.addTranslations( 'foo',
     229                        {
     230                                'context\u0004<b>This</b> \'text\' is "escaped" & translated.': [ '<b>Dieser</b> \'Text\' ist "escaped" & übersetzt.' ]
     231                        }
     232                );
     233
     234                equal( wp.i18n.esc_html_x( '<b>This</b> \'text\' is "escaped" & translated.', 'context', 'foo' ), '&lt;b&gt;Dieser&lt;/b&gt; \&#x27;Text\&#x27; ist &quot;escaped&quot; &amp; übersetzt.' );
     235                equal( wp.i18n.esc_html_x( '<b>This</b> \'text\' is "escaped" & not translated.', 'context', 'foo' ), '&lt;b&gt;This&lt;/b&gt; \&#x27;text\&#x27; is &quot;escaped&quot; &amp; not translated.' );
     236        } );
     237
     238        module( 'i18n.numberFormat' );
     239
     240        test( 'it should format numbers and return strings', function() {
     241                equal( wp.i18n.numberFormat( 123456.789, 2 ), '123,456.79' );
     242                equal( wp.i18n.numberFormat( 123456.789, 0 ), '123,457' );
     243                equal( wp.i18n.numberFormat( 123456.789, 4 ), '123,456.7890' );
     244                equal( wp.i18n.numberFormat( 123456.789, -4 ), '123,456.7890' );
     245        } );
     246} );