WordPress.org

Make WordPress Core

Ticket #20491: 20491.4.diff

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

    diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php
    index a778d4e..648981a 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,
  • tests/qunit/index.html

    diff --git tests/qunit/index.html tests/qunit/index.html
    index ff8ae00..037adcf 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" />
     
    6676                <script src="wp-admin/js/customize-nav-menus.js"></script>
    6777                <script src="wp-admin/js/customize-widgets.js"></script>
    6878                <script src="wp-admin/js/word-count.js"></script>
     79                <script src="wp-includes/js/wp-i18n.js"></script>
    6980
    7081                <!-- Customizer templates for sections -->
    7182                <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..569b852
    - +  
     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.__' );
     11
     12        test( 'it should translate the added strings', function() {
     13                wp.i18n.addTranslations( 'foobar',
     14                        {
     15                                'How are you?': [ 'Wie geht es dir?' ]
     16                        }
     17                );
     18
     19                equal( wp.i18n.__( 'How are you?', 'foobar' ), 'Wie geht es dir?' );
     20                equal( wp.i18n.__( 'Foo bar', 'foobar' ), 'Foo bar' );
     21        } );
     22
     23        module( 'i18n._x' );
     24
     25        test( 'it should translate the added strings', function() {
     26                wp.i18n.addTranslations( 'foobar',
     27                        {
     28                                'verb\u0004Post': [ 'Posten' ],
     29                                'noun\u0004Post': [ 'Beitrag' ]
     30                        }
     31                );
     32
     33                equal( wp.i18n._x( 'Post', 'verb', 'foobar' ), 'Posten' );
     34                equal( wp.i18n._x( 'Post', 'noun', 'foobar' ), 'Beitrag' );
     35                equal( wp.i18n._x( 'Post', 'verb' ), 'Post' );
     36                equal( wp.i18n._x( 'Post', 'foo', 'foobar' ), 'Post' );
     37        } );
     38
     39        module( 'i18n._n' );
     40
     41        test( 'it should translate the added strings', function() {
     42                wp.i18n.addTranslations( 'foobar',
     43                        {
     44                                'There is %d foo': [ 'Da ist %d Foo', 'Da sind %d Foos' ]
     45                        }
     46                );
     47
     48                equal( wp.i18n._n( 'There is %d foo', 'There are %d foos', 1, 'foobar' ), 'Da ist %d Foo' );
     49                equal( wp.i18n._n( 'There is %d foo', 'There are %d foos', 2, 'foobar' ), 'Da sind %d Foos' );
     50                equal( wp.i18n._n( 'There is %d bar', 'There are %d bar', 1, 'foobar' ), 'There is %d bar' );
     51                equal( wp.i18n._n( 'There is %d bar', 'There are %d bars', 2, 'foobar' ), 'There are %d bars' );
     52        } );
     53
     54        module( 'i18n._nx' );
     55
     56        test( 'it should translate the added strings ', function() {
     57                wp.i18n.addTranslations( 'foobar',
     58                        {
     59                                'context\u0004There is %d foo': [ 'Da ist %d Foo', 'Da sind %d Foos' ],
     60                                "context\u0004%d key":          [ "context %d key", "context %d keys" ]
     61                        }
     62                );
     63
     64                equal( wp.i18n._nx( 'There is %d foo', 'There are %d foos', 1, 'context', 'foobar' ), 'Da ist %d Foo' );
     65                equal( wp.i18n._nx( 'There is %d foo', 'There are %d foos', 2, 'context', 'foobar' ), 'Da sind %d Foos' );
     66                equal( wp.i18n._nx( 'There is %d bar', 'There are %d bar', 1, 'context' ), 'There is %d bar' );
     67                equal( wp.i18n._nx( 'There is %d bar', 'There are %d bars', 2, 'context' ), 'There are %d bars' );
     68        } );
     69
     70        module( 'i18n.esc_attr__' );
     71
     72        test( 'it should translate and escape added strings', function() {
     73                wp.i18n.addTranslations( 'foo',
     74                        {
     75                                '<b>This</b> \'text\' is "escaped" & translated.': [ '<b>Dieser</b> \'Text\' ist "escaped" & übersetzt.' ]
     76                        }
     77                );
     78
     79                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.' );
     80                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.' );
     81        } );
     82
     83        module( 'i18n.esc_html__' );
     84
     85        test( 'it should translate and escape added strings', function() {
     86                wp.i18n.addTranslations( 'foo',
     87                        {
     88                                '<b>This</b> \'text\' is "escaped" & translated.': [ '<b>Dieser</b> \'Text\' ist "escaped" & übersetzt.' ]
     89                        }
     90                );
     91
     92                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.' );
     93                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.' );
     94        } );
     95
     96        module( 'i18n.esc_attr_x' );
     97
     98        test( 'it should translate and escape added strings', function() {
     99                wp.i18n.addTranslations( 'foo',
     100                        {
     101                                'context\u0004<b>This</b> \'text\' is "escaped" & translated.': [ '<b>Dieser</b> \'Text\' ist "escaped" & übersetzt.' ]
     102                        }
     103                );
     104
     105                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.' );
     106                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.' );
     107        } );
     108
     109        module( 'i18n.esc_html_x' );
     110
     111        test( 'it should translate and escape added strings', function() {
     112                wp.i18n.addTranslations( 'foo',
     113                        {
     114                                'context\u0004<b>This</b> \'text\' is "escaped" & translated.': [ '<b>Dieser</b> \'Text\' ist "escaped" & übersetzt.' ]
     115                        }
     116                );
     117
     118                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.' );
     119                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.' );
     120        } );
     121
     122        module( 'i18n.numberFormat' );
     123
     124        test( 'it should format numbers and return strings', function() {
     125                equal( wp.i18n.numberFormat( 123456.789, 2 ), '123,456.79' );
     126                equal( wp.i18n.numberFormat( 123456.789, 0 ), '123,457' );
     127                equal( wp.i18n.numberFormat( 123456.789, 4 ), '123,456.7890' );
     128                equal( wp.i18n.numberFormat( 123456.789, -4 ), '123,456.7890' );
     129        } );
     130} );