Making WordPress.org

Ticket #1078: translator.diff

File translator.diff, 99.1 KB (added by yoavf, 9 years ago)
  • translator/community-translator.css

     
     1/*
     2 *  webui popover plugin  - v1.0.5
     3 *  A lightWeight popover plugin with jquery ,enchance the  popover plugin of bootstrap with some awesome new features. It works well with bootstrap ,but bootstrap is not necessary!
     4 *  https://github.com/sandywalker/webui-popover
     5 *
     6 *  Made by Sandy Duan
     7 *  Under MIT License
     8 */
     9/*  webui popover  */
     10.webui-popover {
     11  position: absolute;
     12  top: 0;
     13  left: 0;
     14  z-index: 1060;
     15  display: none;
     16  width: 276px;
     17  min-height: 50px;
     18  padding: 1px;
     19  text-align: left;
     20  white-space: normal;
     21  background-color: #ffffff;
     22  background-clip: padding-box;
     23  border: 1px solid #cccccc;
     24  border: 1px solid rgba(0, 0, 0, 0.2);
     25  border-radius: 6px;
     26  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
     27  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
     28}
     29.webui-popover.top,
     30.webui-popover.top-left,
     31.webui-popover.top-right {
     32  margin-top: -10px;
     33}
     34.webui-popover.right,
     35.webui-popover.right-top,
     36.webui-popover.right-bottom {
     37  margin-left: 10px;
     38}
     39.webui-popover.bottom,
     40.webui-popover.bottom-left,
     41.webui-popover.bottom-right {
     42  margin-top: 10px;
     43}
     44.webui-popover.left,
     45.webui-popover.left-top,
     46.webui-popover.left-bottom {
     47  margin-left: -10px;
     48}
     49.webui-popover-inner .close {
     50  font-family: arial;
     51  margin: 5px 10px 0 0;
     52  float: right;
     53  font-size: 20px;
     54  font-weight: bold;
     55  line-height: 20px;
     56  color: #000000;
     57  text-shadow: 0 1px 0 #fff;
     58  opacity: 0.2;
     59  filter: alpha(opacity=20);
     60  text-decoration: none;
     61}
     62.webui-popover-inner .close:hover,
     63.webui-popover-inner .close:focus {
     64  opacity: 0.5;
     65  filter: alpha(opacity=50);
     66}
     67.webui-popover-title {
     68  padding: 8px 14px;
     69  margin: 0;
     70  font-size: 14px;
     71  font-weight: normal;
     72  line-height: 18px;
     73  background-color: #f7f7f7;
     74  border-bottom: 1px solid #ebebeb;
     75  border-radius: 5px 5px 0 0;
     76}
     77.webui-popover-content {
     78  padding: 9px 14px;
     79  overflow: auto;
     80}
     81.webui-popover-inverse {
     82  background-color: #333333;
     83  color: #eeeeee;
     84}
     85.webui-popover-inverse .webui-popover-title {
     86  background: #3b3b3b;
     87  border-bottom: none;
     88  color: #eeeeee;
     89}
     90.webui-no-padding .webui-popover-content {
     91  padding: 0;
     92}
     93.webui-no-padding .list-group-item {
     94  border-right: none;
     95  border-left: none;
     96}
     97.webui-no-padding .list-group-item:first-child {
     98  border-top: 0;
     99}
     100.webui-no-padding .list-group-item:last-child {
     101  border-bottom: 0;
     102}
     103.webui-popover > .arrow,
     104.webui-popover > .arrow:after {
     105  position: absolute;
     106  display: block;
     107  width: 0;
     108  height: 0;
     109  border-color: transparent;
     110  border-style: solid;
     111}
     112.webui-popover > .arrow {
     113  border-width: 11px;
     114}
     115.webui-popover > .arrow:after {
     116  border-width: 10px;
     117  content: "";
     118}
     119.webui-popover.top > .arrow,
     120.webui-popover.top-right > .arrow,
     121.webui-popover.top-left > .arrow {
     122  bottom: -11px;
     123  left: 50%;
     124  margin-left: -11px;
     125  border-top-color: #999999;
     126  border-top-color: rgba(0, 0, 0, 0.25);
     127  border-bottom-width: 0;
     128}
     129.webui-popover.top > .arrow:after,
     130.webui-popover.top-right > .arrow:after,
     131.webui-popover.top-left > .arrow:after {
     132  content: " ";
     133  bottom: 1px;
     134  margin-left: -10px;
     135  border-top-color: #ffffff;
     136  border-bottom-width: 0;
     137}
     138.webui-popover.right > .arrow,
     139.webui-popover.right-top > .arrow,
     140.webui-popover.right-bottom > .arrow {
     141  top: 50%;
     142  left: -11px;
     143  margin-top: -11px;
     144  border-left-width: 0;
     145  border-right-color: #999999;
     146  border-right-color: rgba(0, 0, 0, 0.25);
     147}
     148.webui-popover.right > .arrow:after,
     149.webui-popover.right-top > .arrow:after,
     150.webui-popover.right-bottom > .arrow:after {
     151  content: " ";
     152  left: 1px;
     153  bottom: -10px;
     154  border-left-width: 0;
     155  border-right-color: #ffffff;
     156}
     157.webui-popover.bottom > .arrow,
     158.webui-popover.bottom-right > .arrow,
     159.webui-popover.bottom-left > .arrow {
     160  top: -11px;
     161  left: 50%;
     162  margin-left: -11px;
     163  border-bottom-color: #999999;
     164  border-bottom-color: rgba(0, 0, 0, 0.25);
     165  border-top-width: 0;
     166}
     167.webui-popover.bottom > .arrow:after,
     168.webui-popover.bottom-right > .arrow:after,
     169.webui-popover.bottom-left > .arrow:after {
     170  content: " ";
     171  top: 1px;
     172  margin-left: -10px;
     173  border-bottom-color: #ffffff;
     174  border-top-width: 0;
     175}
     176.webui-popover.left > .arrow,
     177.webui-popover.left-top > .arrow,
     178.webui-popover.left-bottom > .arrow {
     179  top: 50%;
     180  right: -11px;
     181  margin-top: -11px;
     182  border-right-width: 0;
     183  border-left-color: #999999;
     184  border-left-color: rgba(0, 0, 0, 0.25);
     185}
     186.webui-popover.left > .arrow:after,
     187.webui-popover.left-top > .arrow:after,
     188.webui-popover.left-bottom > .arrow:after {
     189  content: " ";
     190  right: 1px;
     191  border-right-width: 0;
     192  border-left-color: #ffffff;
     193  bottom: -10px;
     194}
     195.webui-popover-inverse.top > .arrow,
     196.webui-popover-inverse.top-left > .arrow,
     197.webui-popover-inverse.top-right > .arrow,
     198.webui-popover-inverse.top > .arrow:after,
     199.webui-popover-inverse.top-left > .arrow:after,
     200.webui-popover-inverse.top-right > .arrow:after {
     201  border-top-color: #333333;
     202}
     203.webui-popover-inverse.right > .arrow,
     204.webui-popover-inverse.right-top > .arrow,
     205.webui-popover-inverse.right-bottom > .arrow,
     206.webui-popover-inverse.right > .arrow:after,
     207.webui-popover-inverse.right-top > .arrow:after,
     208.webui-popover-inverse.right-bottom > .arrow:after {
     209  border-right-color: #333333;
     210}
     211.webui-popover-inverse.bottom > .arrow,
     212.webui-popover-inverse.bottom-left > .arrow,
     213.webui-popover-inverse.bottom-right > .arrow,
     214.webui-popover-inverse.bottom > .arrow:after,
     215.webui-popover-inverse.bottom-left > .arrow:after,
     216.webui-popover-inverse.bottom-right > .arrow:after {
     217  border-bottom-color: #333333;
     218}
     219.webui-popover-inverse.left > .arrow,
     220.webui-popover-inverse.left-top > .arrow,
     221.webui-popover-inverse.left-bottom > .arrow,
     222.webui-popover-inverse.left > .arrow:after,
     223.webui-popover-inverse.left-top > .arrow:after,
     224.webui-popover-inverse.left-bottom > .arrow:after {
     225  border-left-color: #333333;
     226}
     227.webui-popover i.icon-refresh {
     228  display: block;
     229  width: 30px;
     230  height: 30px;
     231  font-size: 20px;
     232  top: 50%;
     233  left: 50%;
     234  position: absolute;
     235  background: url(../img/loading.gif) no-repeat;
     236}
     237@-webkit-keyframes rotate {
     238  100% {
     239    -webkit-transform: rotate(360deg);
     240  }
     241}
     242@keyframes rotate {
     243  100% {
     244    transform: rotate(360deg);
     245  }
     246}
     247.webui-popover {
     248        z-index: 100000;
     249        direction: ltr;
     250}
     251
     252.webui-popover p {
     253        margin: 0;
     254}
     255
     256.webui-popover p.context, .webui-popover p.comment {
     257        margin: 0 0 .5em 0;
     258        display: none;
     259        font-color: #999;
     260        font-style: italic;
     261}
     262
     263.webui-popover p.info {
     264        margin: .5em 0;
     265        display: none;
     266}
     267
     268.webui-popover tt {
     269        padding: .3em;
     270        background-color: #eee;
     271}
     272
     273.webui-popover textarea {
     274        height: 100px;
     275        margin-bottom: 0.8em;
     276        color: #000;
     277        background: #fff
     278}
     279
     280/* We override color & background-color with and without ::selection to
     281 * ensure that input methods are rendered visibly.
     282 */
     283.webui-popover textarea::selection {
     284        color: #2e4453;
     285        background: rgba(120, 220, 250, 0.7);
     286}
     287
     288.webui-popover textarea::-moz-selection {
     289        color: #2e4453;
     290        background: rgba(120, 220, 250, 0.7);
     291}
     292
     293.webui-popover button {
     294        float: right;
     295        margin-top: 8px;
     296}
     297
     298.webui-popover div.a8c-similar-translations {
     299        display: none;
     300        height: 30px;
     301        overflow: auto;
     302}
     303
     304.webui-popover-title span {
     305        float: right;
     306        margin-left:6px;
     307}
     308
     309.webui-popover-title a {
     310        text-decoration: none;
     311}
     312
     313.webui-popover hr {
     314        margin-bottom: 0;
     315}
     316
     317.webui-popover .pairs {
     318        margin-top: 1em
     319}
     320
     321.translator-translated {
     322        text-shadow: 0px 0px 10px #0f0, 0px 0px 2px #4cb420 !important;
     323        cursor: pointer;
     324}
     325.translator-checking {
     326        text-shadow: 0px 0px 1px #e83 !important;
     327}
     328.translator-user-translated {
     329        text-shadow: 0px 0px 10px #ff0, 0px 0px 2px #ffba00 !important;
     330        cursor: pointer;
     331}
     332.translator-untranslated {
     333        text-shadow: 0px 0px 10px #f00, 0px 0px 2px #dd3d36 !important;
     334        cursor: pointer;
     335}
     336
     337.translator-highlight {
     338        border: 5px solid #dd3d36 !important;
     339        margin: -5px !important;
     340}
     341
     342iframe.translator-untranslatable::after {
     343        content: 'Cannot be translated:';
     344        color: #00f;
     345}
     346
     347iframe.translator-untranslatable {
     348        border: 1px dotted #00f !important;
     349}
  • translator/community-translator.js

    Property changes on: translator/community-translator.css
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.communityTranslator = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     2function handleBatchedResponse(e,n){var t,o,a,r;for("undefined"==typeof e[0]&&(e=[e]),t=0;(o=e[t])&&("undefined"!=typeof o&&"undefined"!=typeof o.original);t++)if(r=o.original.singular,"undefined"!=typeof o.original.context&&o.original.context&&(r=o.original.context+""+r),"undefined"!=typeof n[r]&&n[r]){for(a=0;a<n[r].length;a++)n[r][a].resolve(o);n[r]=null,delete n[r]}for(r in n)if(n[r])for(a=0;a<n[r].length;a++)n[r][a].reject()}var debug=require("debug")("automattic:community-translator");module.exports=function(e,n){var t,o,a=200,r={},i=[];return"function"!=typeof e?(debug("batcher expects the first argument to be a function that takes an array and a callback, got ",e),null):(o=function(e){var n=e.singular;return"undefined"!=typeof e.context&&(n=e.context+""+n),n},delayMore=function(){t&&clearTimeout(t),t=setTimeout(resolveBatch,a)},resolveBatch=function(){var n=i.slice(),o=r;t=null,r={},i=[],0!==n.length&&e(n,function(e){handleBatchedResponse(e,o)})},n&&(n.batchDelay&&(a=n.batchDelay),n.hash&&(o=n.hash)),function(e){var n=new jQuery.Deferred,t=o(e);return t in r?r[t].push(n):(i.push(e),r[t]=[n]),delayMore(),n})};
     3
     4},{"debug":11}],2:[function(require,module,exports){
     5"use strict";function GlotPress(e){function t(e){return e=jQuery.extend({type:"POST",data:{},dataType:"json",xhrFields:{withCredentials:!0},crossDomain:!0},e),jQuery.ajax(e)}function r(e){return o.url+e}function n(n,i){t({url:r("/api/translations/-query-by-originals"),data:{project:o.project,locale_slug:e.getLocaleCode(),original_strings:JSON.stringify(n)}}).done(function(e){i(e)})}var o={url:"",project:""};return{getPermalink:function(t){var r,n=t.getOriginal().getId(),i=o.project;t.getGlotPressProject()&&(i=t.getGlotPressProject());var a=o.url+"/projects/"+i+"/"+e.getLocaleCode()+"/default?filters[original_id]="+n;return"undefined"!=typeof r&&(a+="&filters[translation_id]="+r),a},loadSettings:function(e){"undefined"!=typeof e.url?o.url=e.url:debug("Missing GP server url"),"undefined"!=typeof e.url?o.project=e.project:debug("Missing GP project path")},queryByOriginal:batcher(n),submitTranslation:function(n){return t({url:r("/api/translations/-new"),data:{project:o.project,locale_slug:e.getLocaleCode(),translation:n}})}}}var debug=require("debug")("automattic:community-translator"),batcher=require("./batcher.js");module.exports=GlotPress;
     6
     7},{"./batcher.js":1,"debug":11}],3:[function(require,module,exports){
     8"use strict";function notifyTranslated(t){debug("Notifying string translated",t.serialize()),translationUpdateCallbacks.forEach(function(a){a(t.serialize())})}function removeCssClasses(){var t=["translator-checked","translator-untranslated","translator-translated","translator-user-translated","translator-untranslatable","translator-dont-translate"];jQuery("."+t.join(", .")).removeClass(t.join(" "))}function unRegisterPopoverHandlers(){jQuery(document).off("submit","form.community-translator"),jQuery(".translator-translatable").webuiPopover("destroy")}function makeUntranslatable(t){debug("makeUntranslatable:",t),t.removeClass("translator-untranslated translator-translated translator-translatable translator-checking"),t.addClass("translator-dont-translate")}function makeTranslatable(t,a){t.createPopover(a,glotPress),a.removeClass("translator-checking").addClass("translator-translatable"),t.isFullyTranslated()?t.isTranslationWaiting()?a.removeClass("translator-translated").addClass("translator-user-translated"):a.removeClass("translator-user-translated").addClass("translator-translated"):a.addClass("translator-untranslated")}var debug=require("debug")("community-translator"),TranslationPair=require("./translation-pair"),Walker=require("./walker"),Locale=require("./locale"),Popover=require("./popover"),GlotPress=require("./glotpress"),WebUIPopover=require("./jquery.webui-popover.js"),debounceTimeout,currentlyWalkingTheDom=!1,loadCSS,loadData,registerContentChangedCallback,registerDomChangedCallback,registerPopoverHandlers,findNewTranslatableTexts,glotPress,currentUserId,walker,antiCache="?"+Math.random(),translationData={currentUserId:!1,localeCode:"en",languageName:"English",pluralForms:"nplurals=2; plural=(n != 1)",contentChangedCallback:function(){},glotPress:{url:"https://glotpress.dev",project:"test"}},translationUpdateCallbacks=[];module.exports={load:function(){return"undefined"==typeof window.translatorJumpstart?!1:(loadCSS(),loadData(window.translatorJumpstart),registerPopoverHandlers(),registerContentChangedCallback(),void findNewTranslatableTexts())},unload:function(){debounceTimeout&&clearTimeout(debounceTimeout),"object"==typeof window.translatorJumpstart&&(window.translatorJumpstart.contentChangedCallback=function(){}),unRegisterPopoverHandlers(),removeCssClasses()},registerTranslatedCallback:function(t){translationUpdateCallbacks.push(t)}},loadCSS=function(){var t=document.createElement("link");t.setAttribute("rel","stylesheet"),t.setAttribute("type","text/css"),t.setAttribute("href","https://s1.wp.com/i/noticons/noticons.css"),document.getElementsByTagName("head")[0].appendChild(t),jQuery("iframe").addClass("translator-untranslatable")},loadData=function(t){"object"==typeof t&&"string"==typeof t.localeCode&&(translationData=t),translationData.locale=new Locale(translationData.localeCode,translationData.languageName,translationData.pluralForms),currentUserId=translationData.currentUserId,glotPress=new GlotPress(translationData.locale),"undefined"!=typeof translationData.glotPress?glotPress.loadSettings(translationData.glotPress):debug("Missing GlotPress settings"),TranslationPair.setTranslationData(translationData),walker=new Walker(TranslationPair,jQuery,document)},registerContentChangedCallback=function(){"object"==typeof window.translatorJumpstart&&(debug("Registering translator contentChangedCallback"),window.translatorJumpstart.contentChangedCallback=function(){debounceTimeout&&clearTimeout(debounceTimeout),debounceTimeout=setTimeout(findNewTranslatableTexts,250)},"object"==typeof window.translatorJumpstart.stringsUsedOnPage&&registerDomChangedCallback())},registerDomChangedCallback=function(){var t=10,a=document.body.innerHTML.length,e=function(){var n;--t<=0||(n=document.body.innerHTML.length,a!==n&&(a=n,debounceTimeout&&clearTimeout(debounceTimeout),debounceTimeout=setTimeout(findNewTranslatableTexts,1700)),setTimeout(e,1500))};setTimeout(e,1500)},registerPopoverHandlers=function(){jQuery(document).on("keyup","textarea.translation",function(){var t,a=jQuery(this).parents("form.community-translator"),e=a.find("textarea"),n=a.find("button");t=e.filter(function(){return this.value.length}),n.prop("disabled",0===t.length)}),jQuery(document).on("submit","form.community-translator",function(){function t(t){return t.trim().length>0}var a=jQuery(this),e=jQuery("."+a.data("nodes")),n=(a.find("textarea").val(),a.data("translationPair")),r=a.find("textarea").map(function(){return jQuery(this).val()}).get();return r.every(t)?(e.addClass("translator-user-translated").removeClass("translator-untranslated"),a.closest(".webui-popover").hide(),jQuery.when(n.getOriginal().getId()).done(function(t){var a=jQuery.makeArray(r),o={};o[t]=a,glotPress.submitTranslation(o).done(function(a){"undefined"!=typeof a[t]&&(n.updateAllTranslations(a[t],currentUserId),makeTranslatable(n,e),notifyTranslated(n))}).fail(function(){debug("Submitting new translation failed",o)})}).fail(function(){debug("Original cannot be found in GlotPress")}),!1):!1}),jQuery(document).on("submit","form.ct-existing-translation",function(){var t,a=jQuery(this),e=a.data("translationPair");return"object"!=typeof e?(debug("could not find translation for node",a),!1):(t=new Popover(e,translationData.locale,glotPress),a.parent().empty().append(t.getTranslationHtml()),!1)})},findNewTranslatableTexts=function(){return currentlyWalkingTheDom?(debounceTimeout&&clearTimeout(debounceTimeout),void(debounceTimeout=setTimeout(findNewTranslatableTexts,1500))):(currentlyWalkingTheDom=!0,debug("Searching for translatable texts"),void walker.walkTextNodes(document.body,function(t,a){a.addClass("translator-checking"),t.fetchOriginalAndTranslations(glotPress,currentUserId).fail(makeUntranslatable.bind(null,a)).done(makeTranslatable.bind(null,t,a))},function(){currentlyWalkingTheDom=!1}))};
     9},{"./glotpress":2,"./jquery.webui-popover.js":4,"./locale":5,"./popover":7,"./translation-pair":8,"./walker":10,"debug":11}],4:[function(require,module,exports){
     10!function(t,e,i,o){function n(e,i){this.$element=t(e),this.$eventDelegate=this.$element,this.$element.closest("button").length&&(this.$eventDelegate=this.$element.closest("button")),this.options=t.extend({},l,i),this._defaults=l,this._name=s,this.init()}var s="webuiPopover",h="webui-popover",r="webui.popover",l={placement:"auto",width:400,height:"auto",trigger:"rightclick",style:"",delay:300,cache:!1,multi:!1,arrow:!0,title:"",content:"",closeable:!1,padding:!0,url:"",type:"html",onload:!1,translationPair:null,template:'<div class="webui-popover"><div class="arrow"></div><div class="webui-popover-inner"><a href="#" class="close">x</a><h3 class="webui-popover-title"></h3><div class="webui-popover-content"><i class="icon-refresh"></i> <p>&nbsp;</p></div></div></div>'};n.prototype={init:function(){"click"===this.options.trigger?this.$eventDelegate.off("click").on("click",t.proxy(this.toggle,this)):"rightclick"===this.options.trigger?(this.$eventDelegate.off("contextmenu").on("contextmenu",t.proxy(this.toggle,this)),this.$eventDelegate.off("click").on("click",function(t){return t.ctrlKey?!1:void 0})):this.$eventDelegate.off("mouseenter mouseleave").on("mouseenter",t.proxy(this.mouseenterHandler,this)).on("mouseleave",t.proxy(this.mouseleaveHandler,this)),this._poped=!1,this._inited=!0},destroy:function(){this.hide(),this.$element.data("plugin_"+s,null),this.$eventDelegate.off(),this.$target&&this.$target.remove()},hide:function(e){e&&(e.preventDefault(),e.stopPropagation());var e=t.Event("hide."+r);this.$element.trigger(e),this.$target&&this.$target.removeClass("in").hide(),this.$element.trigger("hidden."+r)},toggle:function(t){t&&(t.preventDefault(),t.stopPropagation()),this[this.getTarget().hasClass("in")?"hide":"show"](t)},hideAll:function(){t("div.webui-popover").not(".webui-popover-fixed").removeClass("in").hide()},show:function(t){t&&(t.preventDefault(),t.stopPropagation());var e=this.getTarget().removeClass().addClass(h);if(this.options.multi||this.hideAll(),!this.options.cache||!this._poped){if(this.setTitle(this.getTitle()),this.options.closeable||e.find(".close").off("click").remove(),this.isAsync())return this.setContentASync(this.options.content),void this.displayContent();this.setContent(this.getContent()),e.show()}this.displayContent(),this.bindBodyEvents()},displayContent:function(){var e=this.getElementPosition(),o=this.getTarget().removeClass().addClass(h),n=this.getContentElement(),s=o[0].offsetWidth,l=o[0].offsetHeight,a="bottom",p=t.Event("show."+r);this.$element.trigger(p),"auto"!==this.options.width&&o.width(this.options.width),"auto"!==this.options.height&&n.height(this.options.height),this.options.arrow||o.find(".arrow").remove(),o.remove().css({top:-1e3,left:-1e3,display:"block"}).appendTo(i.body),s=o[0].offsetWidth,l=o[0].offsetHeight,a=this.getPlacement(e,l),this.initTargetEvents();var c=this.getTargetPositin(e,a,s,l);if(this.$target.css(c.position).addClass(a).addClass("in"),"iframe"===this.options.type){var f=o.find("iframe");f.width(o.width()).height(f.parent().height())}if(this.options.style&&this.$target.addClass(h+"-"+this.options.style),this.options.padding||(n.css("height",n.outerHeight()),this.$target.addClass("webui-no-padding")),this.options.arrow||this.$target.css({margin:0}),this.options.arrow){var g=this.$target.find(".arrow");g.removeAttr("style"),c.arrowOffset&&g.css(c.arrowOffset)}this.options.onload&&"function"==typeof this.options.onload&&this.options.onload(n,this.$target),this.options.translationPair&&n.find("form").data("translationPair",this.options.translationPair),this._poped=!0,this.$element.trigger("shown."+r)},isTargetLoaded:function(){return 0===this.getTarget().find("i.glyphicon-refresh").length},getTarget:function(){return this.$target||(this.$target=t(this.options.template)),this.$target},getTitleElement:function(){return this.getTarget().find("."+h+"-title")},getContentElement:function(){return this.getTarget().find("."+h+"-content")},getTitle:function(){return this.options.title||this.$element.attr("data-title")||this.$element.attr("title")},setTitle:function(t){var e=this.getTitleElement();t?e.html(t):e.remove()},hasContent:function(){return this.getContent()},getContent:function(){if(this.options.url)"iframe"===this.options.type&&(this.content=t('<iframe frameborder="0"></iframe>').attr("src",this.options.url));else if(!this.content){var e="";e=t.isFunction(this.options.content)?this.options.content.apply(this.$element[0],arguments):this.options.content,this.content=this.$element.attr("data-content")||e}return this.content},setContent:function(t){var e=this.getTarget();this.getContentElement().html(t),this.$target=e},isAsync:function(){return"async"===this.options.type},setContentASync:function(e){var i=this;t.ajax({url:this.options.url,type:"GET",cache:this.options.cache,success:function(o){e&&t.isFunction(e)?i.content=e.apply(i.$element[0],[o]):i.content=o,i.setContent(i.content);var n=i.getContentElement();n.removeAttr("style"),i.displayContent()}})},bindBodyEvents:function(){t("body").off("keyup.webui-popover").on("keyup.webui-popover",t.proxy(this.escapeHandler,this)),t("body").off("click.webui-popover").on("click.webui-popover",t.proxy(this.bodyClickHandler,this))},mouseenterHandler:function(){var t=this;t._timeout&&clearTimeout(t._timeout),t.getTarget().is(":visible")||t.show()},mouseleaveHandler:function(){var t=this;t._timeout=setTimeout(function(){t.hide()},t.options.delay)},escapeHandler:function(t){27===t.keyCode&&this.hideAll()},bodyClickHandler:function(t){this.hideAll()},targetClickHandler:function(t){t.stopPropagation()},initTargetEvents:function(){"click"===this.options.trigger||("rightclick"===this.options.trigger?this.$target.find(".close").off("click").on("click",t.proxy(this.hide,this)):this.$target.off("mouseenter mouseleave").on("mouseenter",t.proxy(this.mouseenterHandler,this)).on("mouseleave",t.proxy(this.mouseleaveHandler,this))),this.$target.find(".close").off("click").on("click",t.proxy(this.hide,this)),this.$target.off("click.webui-popover").on("click.webui-popover",t.proxy(this.targetClickHandler,this))},getPlacement:function(t,e){var o,n=i.documentElement,s=i.body,h=n.clientWidth,r=n.clientHeight,l=Math.max(s.scrollTop,n.scrollTop),a=Math.max(s.scrollLeft,n.scrollLeft),p=Math.max(0,t.left-a),c=Math.max(0,t.top-l),f=20;return o="function"==typeof this.options.placement?this.options.placement.call(this,this.getTarget()[0],this.$element[0]):this.$element.data("placement")||this.options.placement,"auto"===o&&(h/3>p?o=r/3>c?"bottom-right":2*r/3>c?"right":"top-right":2*h/3>p?o=r/3>c?"bottom":2*r/3>c?"bottom":"top":(o=c>e+f?"top-left":"bottom-left",o=r/3>c?"bottom-left":2*r/3>c?"left":"top-left")),o},getElementPosition:function(){return t.extend({},this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTargetPositin:function(t,e,i,o){var n=t,s=this.$element.outerWidth(),h=this.$element.outerHeight(),r={},l=null,a=this.options.arrow?28:0,p=a+10>s?a:0,c=a+10>h?a:0;switch(e){case"bottom":r={top:n.top+n.height,left:n.left+n.width/2-i/2};break;case"top":r={top:n.top-o,left:n.left+n.width/2-i/2};break;case"left":r={top:n.top+n.height/2-o/2,left:n.left-i};break;case"right":r={top:n.top+n.height/2-o/2,left:n.left+n.width};break;case"top-right":r={top:n.top-o,left:n.left-p},l={left:s/2+p};break;case"top-left":r={top:n.top-o,left:n.left-i+n.width+p},l={left:i-s/2-p};break;case"bottom-right":r={top:n.top+n.height,left:n.left-p},l={left:s/2+p};break;case"bottom-left":r={top:n.top+n.height,left:n.left-i+n.width+p},l={left:i-s/2-p};break;case"right-top":r={top:n.top-o+n.height+c,left:n.left+n.width},l={top:o-h/2-c};break;case"right-bottom":r={top:n.top-c,left:n.left+n.width},l={top:h/2+c};break;case"left-top":r={top:n.top-o+n.height+c,left:n.left-i},l={top:o-h/2-c};break;case"left-bottom":r={top:n.top,left:n.left-i},l={top:h/2}}return{position:r,arrowOffset:l}}},t.fn[s]=function(e){return this.each(function(){var i=t.data(this,"plugin_"+s);i?"destroy"===e?i.destroy():"string"==typeof e&&i[e]():(e?"string"==typeof e?"destroy"!==e&&(i=new n(this,null),i[e]()):"object"==typeof e&&(i=new n(this,e)):i=new n(this,null),t.data(this,"plugin_"+s,i))})}}(jQuery,window,document);
     11
     12},{}],5:[function(require,module,exports){
     13function Locale(e,n,r){var t=Jed.PF.compile(r),u=/nplurals\=(\d+);/,o=r.match(u),a=2;return o.length>1&&(a=o[1]),{getLocaleCode:function(){return e},getLanguageName:function(){return n},getInfo:function(){return e},getPluralCount:function(){return a},getNumbersForIndex:function(e){var n,r=3,u=1e3,o=[];for(n=0;u>n&&!(t(n)==e&&(o.push(n),o.length>=r));++n);return o}}}var Jed=require("jed");module.exports=Locale;
     14
     15},{"jed":14}],6:[function(require,module,exports){
     16function Original(n){function t(n){var t={singular:r};return e&&(t.plural=e),n&&(t.context=n),t}var r,e=null,o=null,u=null;return"string"==typeof n?r=n:"object"==typeof n&&"string"==typeof n.singular?(r=n.singular,e=n.plural):(r=n[0],e=n[1]),("undefined"==typeof e||""===e)&&(e=null),"undefined"!=typeof n.originalId&&(u=n.originalId),"undefined"!=typeof n.comment&&(o=n.comment),{type:"Original",getSingular:function(){return r},getPlural:function(){return e},generateJsonHash:function(n){return"string"==typeof n&&""!==n?n+""+r:r},getEmptyTranslation:function(n){var t=[""];if(null!==e)for(i=1;i<n.getPluralCount();i++)t.push("");return new Translation(n,t)},objectify:t,fetchIdAndTranslations:function(n,r){return n.queryByOriginal(t(r)).done(function(n){u=n.original_id,"string"==typeof n.original_comment&&(o=n.original_comment.replace(/^translators: /,""))})},getId:function(){return u},getComment:function(){return o}}}var Translation=require("./translation");module.exports=Original;
     17
     18},{"./translation":9}],7:[function(require,module,exports){
     19function Popover(t,n,a){var e,i;locale=n,e=t.isFullyTranslated()?getOverview(t):getInputForm(t),i="translator-original-"+t.getOriginal().getId();var r=function(){return e.attr("data-nodes",i),e.data("translationPair",t),e},o=function(){return'Translate <a title="Help & Instructions" target="_blank" href="https://en.support.wordpress.com/in-page-translator/"><span class="noticon noticon-help"></span></a><a title="View in GlotPress" href="'+a.getPermalink(t)+'" target="_blank" class="gpPermalink"><span class="noticon noticon-external"></span></a>'};return{attachTo:function(n){n.addClass(i).webuiPopover({title:o(),content:jQuery("<div>").append(r()).html(),onload:popoverOnload,translationPair:t})},getTranslationHtml:function(){return e=getInputForm(t),r()}}}function popoverOnload(t){jQuery(t).find("textarea").eq(0).focus()}function getOriginalHtml(t){var n,a=t.getOriginal().getPlural();return n=a?'Singular: <strong class="singular"></strong><br/>Plural:  <strong class="plural"></strong>':'<strong class="singular"></strong>',n=jQuery("<div>"+n),n.find("strong.singular").text(t.getOriginal().getSingular()),a&&n.find("strong.plural").text(a),n}function getInputForm(t){var n,a=getHtmlTemplate("new-translation").clone(),e=a.find("div.original"),i=a.find("div.pair"),r=a.find("div.pairs");e.html(getOriginalHtml(t)),t.getContext()&&a.find("p.context").text(t.getContext()).show(),t.getOriginal().getComment()&&a.find("p.comment").text(t.getOriginal().getComment()).show(),n=t.getTranslation().getTextItems();for(var o=0;o<n.length;o++)o>0&&(i=i.eq(0).clone()),i.find("p").text(n[o].getCaption()),i.find("textarea").text(n[o].getText()).attr("placeholder","Translate to "+locale.getLanguageName()),o>0&&r.append(i);return a}function getOverview(t){var n,a,e=getHtmlTemplate("existing-translation").clone(),i=e.find("div.original"),r=e.find("div.pair"),o=e.find("div.pairs");i.html(getOriginalHtml(t)),t.getContext()&&e.find("p.context").text(t.getContext()).show(),t.getOriginal().getComment()&&e.find("p.comment").text(t.getOriginal().getComment()).show(),n=t.getTranslation().getTextItems();for(var s=0;s<n.length;s++)s>0&&(r=r.eq(0).clone()),a=n[s].getInfoText(),""!==a&&r.find("span.type").text(a+": "),r.find("span.translation").text(n[s].getText()),s>0&&o.append(r);return e}function getHtmlTemplate(t){switch(t){case"existing-translation":return jQuery('<form class="ct-existing-translation"><div class="original"></div><p class="context"></p><p class="comment"></p><hr /><p class="info"></p><div class="pairs"><div class="pair"><p dir="auto"><span class="type"></span><span class="translation"></span></p></div></div><button class="button button-primary">New Translation</button></form>');case"new-translation":return jQuery('<form class="community-translator"><div class="original"></div><p class="context"></p><p class="comment"></p><p class="info"></p><div class="pairs"><div class="pair"><p></p><input type="hidden" class="original" name="original[]" /><textarea dir="auto" class="translation" name="translation[]"></textarea></div></div><button disabled class="button button-primary">Submit translation</button></form>')}}var locale;module.exports=Popover;
     20
     21},{}],8:[function(require,module,exports){
     22function TranslationPair(t,n,e,a){function r(n){return("object"!=typeof n||"Translation"!==n.type)&&(n=new Translation(t,n.slice())),c.getTextItems().length!==n.getTextItems().length?!1:(g.push(n),void(c=n))}function i(n){var e,a,i,o;for(g=[],e=0;e<n.length;e++){for(o=[],a=0;i=n[e]["translation_"+a];a++)o.push(i);o=new Translation(t,o.slice(),n[e]),r(o)}}function o(){g.length<=1||g.sort(function(t,n){return n.getComparableDate()-t.getComparableDate()})}function s(t){"number"==typeof t&&(t=t.toString()),o();for(var n=0;n<g.length;n++){if(g[n].getUserId()===t&&g[n].getStatus())return void(c=g[n]);g[n].isCurrent()&&(c=g[n])}}function l(t){return u=t}var c,u,g=[],f=!1;return("object"!=typeof n||"Original"!==n.type)&&(n=new Original(n)),"object"==typeof a?("Translation"!==a.type&&(a=new Translation(t,a)),g.push(a)):a=n.getEmptyTranslation(t),c=a,{type:"TranslationPair",createPopover:function(n,e){var a=new Popover(this,t,e);a.attachTo(n)},isFullyTranslated:function(){return c.isFullyTranslated()},isTranslationWaiting:function(){return c.isWaiting()},getOriginal:function(){return n},getContext:function(){return e},getLocale:function(){return t},getScreenText:function(){return f},setScreenText:function(t){f=t},getTranslation:function(){return c},getGlotPressProject:function(){return u},updateAllTranslations:function(t,n){return i(t)?void("undefined"==typeof n&&s(n)):!1},serialize:function(){return{singular:n.getSingular(),plural:n.getPlural(),context:e,translations:c.serialize(),key:n.generateJsonHash(e)}},fetchOriginalAndTranslations:function(t,a){var r;return r=n.fetchIdAndTranslations(t,e).done(function(t){"undefined"!=typeof t.translations&&(i(t.translations),s(a),"undefined"!=typeof t.project&&l(t.project))})}}}function extractFromDataElement(t){var n,e={singular:t.attr("value")};return t.data("plural")&&(e.plural=t.data("plural")),t.data("context")&&(e.context=t.data("context")),n=new TranslationPair(translationData.locale,e,e.context),n.setScreenText(t.text()),n}function trim(t){return"undefined"==typeof t?"":t.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g,"")}function extractWithStringsUsedOnPage(t){var n,e,a;return"object"!=typeof translationData.stringsUsedOnPage||t.is("style,script")||t.closest("#querylist").length?!1:(t.is("[data-i18n-context]")?a=t.data("i18n-context"):(a=t.closest("[data-i18n-context]"),a=a.length?a.data("i18n-context"):!1),translationPair=getTranslationPairForTextUsedOnPage(t,a),!1===translationPair&&(t=t.clone(!0),e=trim(t.find("*").remove().end().text()),n!==e&&(translationPair=getTranslationPairForTextUsedOnPage(t,a))),translationPair)}function anyChildMatches(t,n){var e,a;if("string"==typeof n&&(n=new RegExp(n)),n instanceof RegExp)for(a=t.children(),e=0;e<a.length;e++)if(n.test(a[e].innerHTML)||n.test(a[e].textContent))return!0;return!1}function getTranslationPairForTextUsedOnPage(t,n){var e,a,r,o=!1;if(a=trim(t.text()),!a.length||a.length>3e3)return!1;if("undefined"!=typeof translationData.stringsUsedOnPage[a])return o=translationData.stringsUsedOnPage[a],n=o[1],"undefined"!=typeof n&&n&&1===n.length&&(n=n[0]),e=new TranslationPair(translationData.locale,o[0],n),e.setScreenText(a),e;for(r=trim(t.html()),i=0;i<translationData.placeholdersUsedOnPage.length;i++)if(o=translationData.placeholdersUsedOnPage[i],o.regex.test(r)){if(anyChildMatches(t,o.regex))continue;return e=new TranslationPair(translationData.locale,o.original,o.context),e.setScreenText(a),e}return!1}var Original=require("./original"),Translation=require("./translation"),Popover=require("./popover"),translationData;TranslationPair.extractFrom=function(t){return"object"!=typeof translationData?!1:t.is("data.translatable")?extractFromDataElement(t):t.closest("data.translatable").length?extractFromDataElement(t.closest("data.translatable")):extractWithStringsUsedOnPage(t)},TranslationPair.setTranslationData=function(t){var n,e,a=[];if(translationData=t,"object"==typeof translationData.placeholdersUsedOnPage)for(n in translationData.placeholdersUsedOnPage)e=translationData.placeholdersUsedOnPage[n],"undefined"==typeof e.regex&&(e={original:e[0],regex:new RegExp("^\\s*"+e[1]+"\\s*$"),context:e[2]}),a.push(e);translationData.placeholdersUsedOnPage=a},TranslationPair._test={anyChildMatches:anyChildMatches},module.exports=TranslationPair;
     23
     24},{"./original":6,"./popover":7,"./translation":9}],9:[function(require,module,exports){
     25function Translation(t,n,e){function r(t){var n=t.split("-"),e=n[2].substr(3).split(":");return new Date(n[0],n[1]-1,n[2].substr(0,2),e[0],e[1],e[2])}var u,i,a,o,s,l,f=0;if("object"==typeof e&&("undefined"!==e.status&&(a=e.status),"undefined"!==e.translation_id&&(o=e.translation_id),"undefined"!==e.user_id&&(s=e.user_id),"undefined"!==e.date_added&&(l=e.date_added)),"string"!=typeof a&&(a="current"),isNaN(o)&&(o=!1),isNaN(s)&&(s=!1),l&&(f=r(l)),u=function(e,r){return{isTranslated:function(){return r.length>0},getCaption:function(){var r;return 1===n.length?"":2===n.length?0===e?"Singular":"Plural":(r=t.getNumbersForIndex(e),r.length?"For numbers like: "+r.join(", "):"")},getInfoText:function(){var r;return 1===n.length?"":2===n.length?0===e?"Singular":"Plural":(r=t.getNumbersForIndex(e),r.length?r.join(", "):"")},getText:function(){return r}}},"object"!=typeof n||"number"!=typeof n.length)return!1;for(i=0;i<n.length;i++)n[i]=new u(i,n[i]);return{type:"Translation",isFullyTranslated:function(){var t;for(t=0;t<n.length;t++)if(!1===n[t].isTranslated())return!1;return!0},isCurrent:function(){return"current"===a},isWaiting:function(){return"waiting"===a},getStatus:function(){return a},getDate:function(){return l},getComparableDate:function(){return f},getUserId:function(){return s},getTextItems:function(){return n},serialize:function(){var t,e=[];for(t=0;t<n.length;t++)e.push(n[t].getText());return e}}}module.exports=Translation;
     26
     27},{}],10:[function(require,module,exports){
     28module.exports=function(e,t,n){return{walkTextNodes:function(o,r,a){function s(n){var o,a=t(n.parentNode);return a.is("script")||a.hasClass("translator-checked")?!1:(a.addClass("translator-checked"),a.closest(".webui-popover").length?!1:(o=e.extractFrom(a),!1===o?(a.addClass("translator-dont-translate"),!1):("function"==typeof r&&r(o,a),!0)))}var c,l;if("object"==typeof n)for(l=n.createTreeWalker(o,NodeFilter.SHOW_TEXT,null,!1);c=l.nextNode();)s(c);else t(o).find("*").contents().filter(function(){return 3===this.nodeType}).each(function(){s(this)});"function"==typeof a&&a()}}};
     29
     30},{}],11:[function(require,module,exports){
     31
     32/**
     33 * This is the web browser implementation of `debug()`.
     34 *
     35 * Expose `debug()` as the module.
     36 */
     37
     38exports = module.exports = require('./debug');
     39exports.log = log;
     40exports.save = save;
     41exports.load = load;
     42exports.useColors = useColors;
     43
     44/**
     45 * Colors.
     46 */
     47
     48exports.colors = [
     49  'cyan',
     50  'green',
     51  'goldenrod', // "yellow" is just too bright on a white background...
     52  'blue',
     53  'purple',
     54  'red'
     55];
     56
     57/**
     58 * Currently only WebKit-based Web Inspectors and the Firebug
     59 * extension (*not* the built-in Firefox web inpector) are
     60 * known to support "%c" CSS customizations.
     61 *
     62 * TODO: add a `localStorage` variable to explicitly enable/disable colors
     63 */
     64
     65function useColors() {
     66  // is webkit? http://stackoverflow.com/a/16459606/376773
     67  return ('WebkitAppearance' in document.documentElement.style) ||
     68    // is firebug? http://stackoverflow.com/a/398120/376773
     69    (window.console && (console.firebug || (console.exception && console.table)));
     70}
     71
     72/**
     73 * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
     74 */
     75
     76exports.formatters.j = function(v) {
     77  return JSON.stringify(v);
     78};
     79
     80/**
     81 * Invokes `console.log()` when available.
     82 * No-op when `console.log` is not a "function".
     83 *
     84 * @api public
     85 */
     86
     87function log() {
     88  var args = arguments;
     89  var useColors = this.useColors;
     90
     91  args[0] = (useColors ? '%c' : '')
     92    + this.namespace
     93    + (useColors ? '%c ' : ' ')
     94    + args[0]
     95    + (useColors ? '%c ' : ' ')
     96    + '+' + exports.humanize(this.diff);
     97
     98  if (useColors) {
     99    var c = 'color: ' + this.color;
     100    args = [args[0], c, ''].concat(Array.prototype.slice.call(args, 1));
     101
     102    // the final "%c" is somewhat tricky, because there could be other
     103    // arguments passed either before or after the %c, so we need to
     104    // figure out the correct index to insert the CSS into
     105    var index = 0;
     106    var lastC = 0;
     107    args[0].replace(/%[a-z%]/g, function(match) {
     108      if ('%%' === match) return;
     109      index++;
     110      if ('%c' === match) {
     111        // we only are interested in the *last* %c
     112        // (the user may have provided their own)
     113        lastC = index;
     114      }
     115    });
     116
     117    args.splice(lastC, 0, c);
     118  }
     119
     120  // This hackery is required for IE8,
     121  // where the `console.log` function doesn't have 'apply'
     122  return 'object' == typeof console
     123    && 'function' == typeof console.log
     124    && Function.prototype.apply.call(console.log, console, args);
     125}
     126
     127/**
     128 * Save `namespaces`.
     129 *
     130 * @param {String} namespaces
     131 * @api private
     132 */
     133
     134function save(namespaces) {
     135  try {
     136    if (null == namespaces) {
     137      localStorage.removeItem('debug');
     138    } else {
     139      localStorage.debug = namespaces;
     140    }
     141  } catch(e) {}
     142}
     143
     144/**
     145 * Load `namespaces`.
     146 *
     147 * @return {String} returns the previously persisted debug modes
     148 * @api private
     149 */
     150
     151function load() {
     152  var r;
     153  try {
     154    r = localStorage.debug;
     155  } catch(e) {}
     156  return r;
     157}
     158
     159/**
     160 * Enable namespaces listed in `localStorage.debug` initially.
     161 */
     162
     163exports.enable(load());
     164
     165},{"./debug":12}],12:[function(require,module,exports){
     166
     167/**
     168 * This is the common logic for both the Node.js and web browser
     169 * implementations of `debug()`.
     170 *
     171 * Expose `debug()` as the module.
     172 */
     173
     174exports = module.exports = debug;
     175exports.coerce = coerce;
     176exports.disable = disable;
     177exports.enable = enable;
     178exports.enabled = enabled;
     179exports.humanize = require('ms');
     180
     181/**
     182 * The currently active debug mode names, and names to skip.
     183 */
     184
     185exports.names = [];
     186exports.skips = [];
     187
     188/**
     189 * Map of special "%n" handling functions, for the debug "format" argument.
     190 *
     191 * Valid key names are a single, lowercased letter, i.e. "n".
     192 */
     193
     194exports.formatters = {};
     195
     196/**
     197 * Previously assigned color.
     198 */
     199
     200var prevColor = 0;
     201
     202/**
     203 * Previous log timestamp.
     204 */
     205
     206var prevTime;
     207
     208/**
     209 * Select a color.
     210 *
     211 * @return {Number}
     212 * @api private
     213 */
     214
     215function selectColor() {
     216  return exports.colors[prevColor++ % exports.colors.length];
     217}
     218
     219/**
     220 * Create a debugger with the given `namespace`.
     221 *
     222 * @param {String} namespace
     223 * @return {Function}
     224 * @api public
     225 */
     226
     227function debug(namespace) {
     228
     229  // define the `disabled` version
     230  function disabled() {
     231  }
     232  disabled.enabled = false;
     233
     234  // define the `enabled` version
     235  function enabled() {
     236
     237    var self = enabled;
     238
     239    // set `diff` timestamp
     240    var curr = +new Date();
     241    var ms = curr - (prevTime || curr);
     242    self.diff = ms;
     243    self.prev = prevTime;
     244    self.curr = curr;
     245    prevTime = curr;
     246
     247    // add the `color` if not set
     248    if (null == self.useColors) self.useColors = exports.useColors();
     249    if (null == self.color && self.useColors) self.color = selectColor();
     250
     251    var args = Array.prototype.slice.call(arguments);
     252
     253    args[0] = exports.coerce(args[0]);
     254
     255    if ('string' !== typeof args[0]) {
     256      // anything else let's inspect with %o
     257      args = ['%o'].concat(args);
     258    }
     259
     260    // apply any `formatters` transformations
     261    var index = 0;
     262    args[0] = args[0].replace(/%([a-z%])/g, function(match, format) {
     263      // if we encounter an escaped % then don't increase the array index
     264      if (match === '%%') return match;
     265      index++;
     266      var formatter = exports.formatters[format];
     267      if ('function' === typeof formatter) {
     268        var val = args[index];
     269        match = formatter.call(self, val);
     270
     271        // now we need to remove `args[index]` since it's inlined in the `format`
     272        args.splice(index, 1);
     273        index--;
     274      }
     275      return match;
     276    });
     277
     278    exports.log.apply(self, args);
     279  }
     280  enabled.enabled = true;
     281
     282  var fn = exports.enabled(namespace) ? enabled : disabled;
     283
     284  fn.namespace = namespace;
     285
     286  return fn;
     287}
     288
     289/**
     290 * Enables a debug mode by namespaces. This can include modes
     291 * separated by a colon and wildcards.
     292 *
     293 * @param {String} namespaces
     294 * @api public
     295 */
     296
     297function enable(namespaces) {
     298  exports.save(namespaces);
     299
     300  var split = (namespaces || '').split(/[\s,]+/);
     301  var len = split.length;
     302
     303  for (var i = 0; i < len; i++) {
     304    if (!split[i]) continue; // ignore empty strings
     305    namespaces = split[i].replace('*', '.*?');
     306    if (namespaces[0] === '-') {
     307      exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
     308    } else {
     309      exports.names.push(new RegExp('^' + namespaces + '$'));
     310    }
     311  }
     312}
     313
     314/**
     315 * Disable debug output.
     316 *
     317 * @api public
     318 */
     319
     320function disable() {
     321  exports.enable('');
     322}
     323
     324/**
     325 * Returns true if the given mode name is enabled, false otherwise.
     326 *
     327 * @param {String} name
     328 * @return {Boolean}
     329 * @api public
     330 */
     331
     332function enabled(name) {
     333  var i, len;
     334  for (i = 0, len = exports.skips.length; i < len; i++) {
     335    if (exports.skips[i].test(name)) {
     336      return false;
     337    }
     338  }
     339  for (i = 0, len = exports.names.length; i < len; i++) {
     340    if (exports.names[i].test(name)) {
     341      return true;
     342    }
     343  }
     344  return false;
     345}
     346
     347/**
     348 * Coerce `val`.
     349 *
     350 * @param {Mixed} val
     351 * @return {Mixed}
     352 * @api private
     353 */
     354
     355function coerce(val) {
     356  if (val instanceof Error) return val.stack || val.message;
     357  return val;
     358}
     359
     360},{"ms":13}],13:[function(require,module,exports){
     361/**
     362 * Helpers.
     363 */
     364
     365var s = 1000;
     366var m = s * 60;
     367var h = m * 60;
     368var d = h * 24;
     369var y = d * 365.25;
     370
     371/**
     372 * Parse or format the given `val`.
     373 *
     374 * Options:
     375 *
     376 *  - `long` verbose formatting [false]
     377 *
     378 * @param {String|Number} val
     379 * @param {Object} options
     380 * @return {String|Number}
     381 * @api public
     382 */
     383
     384module.exports = function(val, options){
     385  options = options || {};
     386  if ('string' == typeof val) return parse(val);
     387  return options.long
     388    ? long(val)
     389    : short(val);
     390};
     391
     392/**
     393 * Parse the given `str` and return milliseconds.
     394 *
     395 * @param {String} str
     396 * @return {Number}
     397 * @api private
     398 */
     399
     400function parse(str) {
     401  var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str);
     402  if (!match) return;
     403  var n = parseFloat(match[1]);
     404  var type = (match[2] || 'ms').toLowerCase();
     405  switch (type) {
     406    case 'years':
     407    case 'year':
     408    case 'y':
     409      return n * y;
     410    case 'days':
     411    case 'day':
     412    case 'd':
     413      return n * d;
     414    case 'hours':
     415    case 'hour':
     416    case 'h':
     417      return n * h;
     418    case 'minutes':
     419    case 'minute':
     420    case 'm':
     421      return n * m;
     422    case 'seconds':
     423    case 'second':
     424    case 's':
     425      return n * s;
     426    case 'ms':
     427      return n;
     428  }
     429}
     430
     431/**
     432 * Short format for `ms`.
     433 *
     434 * @param {Number} ms
     435 * @return {String}
     436 * @api private
     437 */
     438
     439function short(ms) {
     440  if (ms >= d) return Math.round(ms / d) + 'd';
     441  if (ms >= h) return Math.round(ms / h) + 'h';
     442  if (ms >= m) return Math.round(ms / m) + 'm';
     443  if (ms >= s) return Math.round(ms / s) + 's';
     444  return ms + 'ms';
     445}
     446
     447/**
     448 * Long format for `ms`.
     449 *
     450 * @param {Number} ms
     451 * @return {String}
     452 * @api private
     453 */
     454
     455function long(ms) {
     456  return plural(ms, d, 'day')
     457    || plural(ms, h, 'hour')
     458    || plural(ms, m, 'minute')
     459    || plural(ms, s, 'second')
     460    || ms + ' ms';
     461}
     462
     463/**
     464 * Pluralization helper.
     465 */
     466
     467function plural(ms, n, name) {
     468  if (ms < n) return;
     469  if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name;
     470  return Math.ceil(ms / n) + ' ' + name + 's';
     471}
     472
     473},{}],14:[function(require,module,exports){
     474/**
     475 * @preserve jed.js https://github.com/SlexAxton/Jed
     476 */
     477/*
     478-----------
     479A gettext compatible i18n library for modern JavaScript Applications
     480
     481by Alex Sexton - AlexSexton [at] gmail - @SlexAxton
     482WTFPL license for use
     483Dojo CLA for contributions
     484
     485Jed offers the entire applicable GNU gettext spec'd set of
     486functions, but also offers some nicer wrappers around them.
     487The api for gettext was written for a language with no function
     488overloading, so Jed allows a little more of that.
     489
     490Many thanks to Joshua I. Miller - unrtst@cpan.org - who wrote
     491gettext.js back in 2008. I was able to vet a lot of my ideas
     492against his. I also made sure Jed passed against his tests
     493in order to offer easy upgrades -- jsgettext.berlios.de
     494*/
     495(function (root, undef) {
     496
     497  // Set up some underscore-style functions, if you already have
     498  // underscore, feel free to delete this section, and use it
     499  // directly, however, the amount of functions used doesn't
     500  // warrant having underscore as a full dependency.
     501  // Underscore 1.3.0 was used to port and is licensed
     502  // under the MIT License by Jeremy Ashkenas.
     503  var ArrayProto    = Array.prototype,
     504      ObjProto      = Object.prototype,
     505      slice         = ArrayProto.slice,
     506      hasOwnProp    = ObjProto.hasOwnProperty,
     507      nativeForEach = ArrayProto.forEach,
     508      breaker       = {};
     509
     510  // We're not using the OOP style _ so we don't need the
     511  // extra level of indirection. This still means that you
     512  // sub out for real `_` though.
     513  var _ = {
     514    forEach : function( obj, iterator, context ) {
     515      var i, l, key;
     516      if ( obj === null ) {
     517        return;
     518      }
     519
     520      if ( nativeForEach && obj.forEach === nativeForEach ) {
     521        obj.forEach( iterator, context );
     522      }
     523      else if ( obj.length === +obj.length ) {
     524        for ( i = 0, l = obj.length; i < l; i++ ) {
     525          if ( i in obj && iterator.call( context, obj[i], i, obj ) === breaker ) {
     526            return;
     527          }
     528        }
     529      }
     530      else {
     531        for ( key in obj) {
     532          if ( hasOwnProp.call( obj, key ) ) {
     533            if ( iterator.call (context, obj[key], key, obj ) === breaker ) {
     534              return;
     535            }
     536          }
     537        }
     538      }
     539    },
     540    extend : function( obj ) {
     541      this.forEach( slice.call( arguments, 1 ), function ( source ) {
     542        for ( var prop in source ) {
     543          obj[prop] = source[prop];
     544        }
     545      });
     546      return obj;
     547    }
     548  };
     549  // END Miniature underscore impl
     550
     551  // Jed is a constructor function
     552  var Jed = function ( options ) {
     553    // Some minimal defaults
     554    this.defaults = {
     555      "locale_data" : {
     556        "messages" : {
     557          "" : {
     558            "domain"       : "messages",
     559            "lang"         : "en",
     560            "plural_forms" : "nplurals=2; plural=(n != 1);"
     561          }
     562          // There are no default keys, though
     563        }
     564      },
     565      // The default domain if one is missing
     566      "domain" : "messages",
     567      // enable debug mode to log untranslated strings to the console
     568      "debug" : false
     569    };
     570
     571    // Mix in the sent options with the default options
     572    this.options = _.extend( {}, this.defaults, options );
     573    this.textdomain( this.options.domain );
     574
     575    if ( options.domain && ! this.options.locale_data[ this.options.domain ] ) {
     576      throw new Error('Text domain set to non-existent domain: `' + options.domain + '`');
     577    }
     578  };
     579
     580  // The gettext spec sets this character as the default
     581  // delimiter for context lookups.
     582  // e.g.: context\u0004key
     583  // If your translation company uses something different,
     584  // just change this at any time and it will use that instead.
     585  Jed.context_delimiter = String.fromCharCode( 4 );
     586
     587  function getPluralFormFunc ( plural_form_string ) {
     588    return Jed.PF.compile( plural_form_string || "nplurals=2; plural=(n != 1);");
     589  }
     590
     591  function Chain( key, i18n ){
     592    this._key = key;
     593    this._i18n = i18n;
     594  }
     595
     596  // Create a chainable api for adding args prettily
     597  _.extend( Chain.prototype, {
     598    onDomain : function ( domain ) {
     599      this._domain = domain;
     600      return this;
     601    },
     602    withContext : function ( context ) {
     603      this._context = context;
     604      return this;
     605    },
     606    ifPlural : function ( num, pkey ) {
     607      this._val = num;
     608      this._pkey = pkey;
     609      return this;
     610    },
     611    fetch : function ( sArr ) {
     612      if ( {}.toString.call( sArr ) != '[object Array]' ) {
     613        sArr = [].slice.call(arguments, 0);
     614      }
     615      return ( sArr && sArr.length ? Jed.sprintf : function(x){ return x; } )(
     616        this._i18n.dcnpgettext(this._domain, this._context, this._key, this._pkey, this._val),
     617        sArr
     618      );
     619    }
     620  });
     621
     622  // Add functions to the Jed prototype.
     623  // These will be the functions on the object that's returned
     624  // from creating a `new Jed()`
     625  // These seem redundant, but they gzip pretty well.
     626  _.extend( Jed.prototype, {
     627    // The sexier api start point
     628    translate : function ( key ) {
     629      return new Chain( key, this );
     630    },
     631
     632    textdomain : function ( domain ) {
     633      if ( ! domain ) {
     634        return this._textdomain;
     635      }
     636      this._textdomain = domain;
     637    },
     638
     639    gettext : function ( key ) {
     640      return this.dcnpgettext.call( this, undef, undef, key );
     641    },
     642
     643    dgettext : function ( domain, key ) {
     644     return this.dcnpgettext.call( this, domain, undef, key );
     645    },
     646
     647    dcgettext : function ( domain , key /*, category */ ) {
     648      // Ignores the category anyways
     649      return this.dcnpgettext.call( this, domain, undef, key );
     650    },
     651
     652    ngettext : function ( skey, pkey, val ) {
     653      return this.dcnpgettext.call( this, undef, undef, skey, pkey, val );
     654    },
     655
     656    dngettext : function ( domain, skey, pkey, val ) {
     657      return this.dcnpgettext.call( this, domain, undef, skey, pkey, val );
     658    },
     659
     660    dcngettext : function ( domain, skey, pkey, val/*, category */) {
     661      return this.dcnpgettext.call( this, domain, undef, skey, pkey, val );
     662    },
     663
     664    pgettext : function ( context, key ) {
     665      return this.dcnpgettext.call( this, undef, context, key );
     666    },
     667
     668    dpgettext : function ( domain, context, key ) {
     669      return this.dcnpgettext.call( this, domain, context, key );
     670    },
     671
     672    dcpgettext : function ( domain, context, key/*, category */) {
     673      return this.dcnpgettext.call( this, domain, context, key );
     674    },
     675
     676    npgettext : function ( context, skey, pkey, val ) {
     677      return this.dcnpgettext.call( this, undef, context, skey, pkey, val );
     678    },
     679
     680    dnpgettext : function ( domain, context, skey, pkey, val ) {
     681      return this.dcnpgettext.call( this, domain, context, skey, pkey, val );
     682    },
     683
     684    // The most fully qualified gettext function. It has every option.
     685    // Since it has every option, we can use it from every other method.
     686    // This is the bread and butter.
     687    // Technically there should be one more argument in this function for 'Category',
     688    // but since we never use it, we might as well not waste the bytes to define it.
     689    dcnpgettext : function ( domain, context, singular_key, plural_key, val ) {
     690      // Set some defaults
     691
     692      plural_key = plural_key || singular_key;
     693
     694      // Use the global domain default if one
     695      // isn't explicitly passed in
     696      domain = domain || this._textdomain;
     697
     698      var fallback;
     699
     700      // Handle special cases
     701
     702      // No options found
     703      if ( ! this.options ) {
     704        // There's likely something wrong, but we'll return the correct key for english
     705        // We do this by instantiating a brand new Jed instance with the default set
     706        // for everything that could be broken.
     707        fallback = new Jed();
     708        return fallback.dcnpgettext.call( fallback, undefined, undefined, singular_key, plural_key, val );
     709      }
     710
     711      // No translation data provided
     712      if ( ! this.options.locale_data ) {
     713        throw new Error('No locale data provided.');
     714      }
     715
     716      if ( ! this.options.locale_data[ domain ] ) {
     717        throw new Error('Domain `' + domain + '` was not found.');
     718      }
     719
     720      if ( ! this.options.locale_data[ domain ][ "" ] ) {
     721        throw new Error('No locale meta information provided.');
     722      }
     723
     724      // Make sure we have a truthy key. Otherwise we might start looking
     725      // into the empty string key, which is the options for the locale
     726      // data.
     727      if ( ! singular_key ) {
     728        throw new Error('No translation key found.');
     729      }
     730
     731      var key  = context ? context + Jed.context_delimiter + singular_key : singular_key,
     732          locale_data = this.options.locale_data,
     733          dict = locale_data[ domain ],
     734          defaultConf = (locale_data.messages || this.defaults.locale_data.messages)[""],
     735          pluralForms = dict[""].plural_forms || dict[""]["Plural-Forms"] || dict[""]["plural-forms"] || defaultConf.plural_forms || defaultConf["Plural-Forms"] || defaultConf["plural-forms"],
     736          val_list,
     737          res;
     738
     739      var val_idx;
     740      if (val === undefined) {
     741        // No value passed in; assume singular key lookup.
     742        val_idx = 0;
     743
     744      } else {
     745        // Value has been passed in; use plural-forms calculations.
     746
     747        // Handle invalid numbers, but try casting strings for good measure
     748        if ( typeof val != 'number' ) {
     749          val = parseInt( val, 10 );
     750
     751          if ( isNaN( val ) ) {
     752            throw new Error('The number that was passed in is not a number.');
     753          }
     754        }
     755
     756        val_idx = getPluralFormFunc(pluralForms)(val);
     757      }
     758
     759      // Throw an error if a domain isn't found
     760      if ( ! dict ) {
     761        throw new Error('No domain named `' + domain + '` could be found.');
     762      }
     763
     764      val_list = dict[ key ];
     765
     766      // If there is no match, then revert back to
     767      // english style singular/plural with the keys passed in.
     768      if ( ! val_list || val_idx > val_list.length ) {
     769        if (this.options.missing_key_callback) {
     770          this.options.missing_key_callback(key, domain);
     771        }
     772        res = [ singular_key, plural_key ];
     773
     774        // collect untranslated strings
     775        if (this.options.debug===true) {
     776          console.log(res[ getPluralFormFunc(pluralForms)( val ) ]);
     777        }
     778        return res[ getPluralFormFunc()( val ) ];
     779      }
     780
     781      res = val_list[ val_idx ];
     782
     783      // This includes empty strings on purpose
     784      if ( ! res  ) {
     785        res = [ singular_key, plural_key ];
     786        return res[ getPluralFormFunc()( val ) ];
     787      }
     788      return res;
     789    }
     790  });
     791
     792
     793  // We add in sprintf capabilities for post translation value interolation
     794  // This is not internally used, so you can remove it if you have this
     795  // available somewhere else, or want to use a different system.
     796
     797  // We _slightly_ modify the normal sprintf behavior to more gracefully handle
     798  // undefined values.
     799
     800  /**
     801   sprintf() for JavaScript 0.7-beta1
     802   http://www.diveintojavascript.com/projects/javascript-sprintf
     803
     804   Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
     805   All rights reserved.
     806
     807   Redistribution and use in source and binary forms, with or without
     808   modification, are permitted provided that the following conditions are met:
     809       * Redistributions of source code must retain the above copyright
     810         notice, this list of conditions and the following disclaimer.
     811       * Redistributions in binary form must reproduce the above copyright
     812         notice, this list of conditions and the following disclaimer in the
     813         documentation and/or other materials provided with the distribution.
     814       * Neither the name of sprintf() for JavaScript nor the
     815         names of its contributors may be used to endorse or promote products
     816         derived from this software without specific prior written permission.
     817
     818   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
     819   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     820   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     821   DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
     822   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     823   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     824   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     825   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     826   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     827   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     828  */
     829  var sprintf = (function() {
     830    function get_type(variable) {
     831      return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
     832    }
     833    function str_repeat(input, multiplier) {
     834      for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
     835      return output.join('');
     836    }
     837
     838    var str_format = function() {
     839      if (!str_format.cache.hasOwnProperty(arguments[0])) {
     840        str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
     841      }
     842      return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
     843    };
     844
     845    str_format.format = function(parse_tree, argv) {
     846      var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
     847      for (i = 0; i < tree_length; i++) {
     848        node_type = get_type(parse_tree[i]);
     849        if (node_type === 'string') {
     850          output.push(parse_tree[i]);
     851        }
     852        else if (node_type === 'array') {
     853          match = parse_tree[i]; // convenience purposes only
     854          if (match[2]) { // keyword argument
     855            arg = argv[cursor];
     856            for (k = 0; k < match[2].length; k++) {
     857              if (!arg.hasOwnProperty(match[2][k])) {
     858                throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
     859              }
     860              arg = arg[match[2][k]];
     861            }
     862          }
     863          else if (match[1]) { // positional argument (explicit)
     864            arg = argv[match[1]];
     865          }
     866          else { // positional argument (implicit)
     867            arg = argv[cursor++];
     868          }
     869
     870          if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
     871            throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
     872          }
     873
     874          // Jed EDIT
     875          if ( typeof arg == 'undefined' || arg === null ) {
     876            arg = '';
     877          }
     878          // Jed EDIT
     879
     880          switch (match[8]) {
     881            case 'b': arg = arg.toString(2); break;
     882            case 'c': arg = String.fromCharCode(arg); break;
     883            case 'd': arg = parseInt(arg, 10); break;
     884            case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
     885            case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
     886            case 'o': arg = arg.toString(8); break;
     887            case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
     888            case 'u': arg = Math.abs(arg); break;
     889            case 'x': arg = arg.toString(16); break;
     890            case 'X': arg = arg.toString(16).toUpperCase(); break;
     891          }
     892          arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
     893          pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
     894          pad_length = match[6] - String(arg).length;
     895          pad = match[6] ? str_repeat(pad_character, pad_length) : '';
     896          output.push(match[5] ? arg + pad : pad + arg);
     897        }
     898      }
     899      return output.join('');
     900    };
     901
     902    str_format.cache = {};
     903
     904    str_format.parse = function(fmt) {
     905      var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
     906      while (_fmt) {
     907        if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
     908          parse_tree.push(match[0]);
     909        }
     910        else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
     911          parse_tree.push('%');
     912        }
     913        else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
     914          if (match[2]) {
     915            arg_names |= 1;
     916            var field_list = [], replacement_field = match[2], field_match = [];
     917            if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
     918              field_list.push(field_match[1]);
     919              while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
     920                if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
     921                  field_list.push(field_match[1]);
     922                }
     923                else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
     924                  field_list.push(field_match[1]);
     925                }
     926                else {
     927                  throw('[sprintf] huh?');
     928                }
     929              }
     930            }
     931            else {
     932              throw('[sprintf] huh?');
     933            }
     934            match[2] = field_list;
     935          }
     936          else {
     937            arg_names |= 2;
     938          }
     939          if (arg_names === 3) {
     940            throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
     941          }
     942          parse_tree.push(match);
     943        }
     944        else {
     945          throw('[sprintf] huh?');
     946        }
     947        _fmt = _fmt.substring(match[0].length);
     948      }
     949      return parse_tree;
     950    };
     951
     952    return str_format;
     953  })();
     954
     955  var vsprintf = function(fmt, argv) {
     956    argv.unshift(fmt);
     957    return sprintf.apply(null, argv);
     958  };
     959
     960  Jed.parse_plural = function ( plural_forms, n ) {
     961    plural_forms = plural_forms.replace(/n/g, n);
     962    return Jed.parse_expression(plural_forms);
     963  };
     964
     965  Jed.sprintf = function ( fmt, args ) {
     966    if ( {}.toString.call( args ) == '[object Array]' ) {
     967      return vsprintf( fmt, [].slice.call(args) );
     968    }
     969    return sprintf.apply(this, [].slice.call(arguments) );
     970  };
     971
     972  Jed.prototype.sprintf = function () {
     973    return Jed.sprintf.apply(this, arguments);
     974  };
     975  // END sprintf Implementation
     976
     977  // Start the Plural forms section
     978  // This is a full plural form expression parser. It is used to avoid
     979  // running 'eval' or 'new Function' directly against the plural
     980  // forms.
     981  //
     982  // This can be important if you get translations done through a 3rd
     983  // party vendor. I encourage you to use this instead, however, I
     984  // also will provide a 'precompiler' that you can use at build time
     985  // to output valid/safe function representations of the plural form
     986  // expressions. This means you can build this code out for the most
     987  // part.
     988  Jed.PF = {};
     989
     990  Jed.PF.parse = function ( p ) {
     991    var plural_str = Jed.PF.extractPluralExpr( p );
     992    return Jed.PF.parser.parse.call(Jed.PF.parser, plural_str);
     993  };
     994
     995  Jed.PF.compile = function ( p ) {
     996    // Handle trues and falses as 0 and 1
     997    function imply( val ) {
     998      return (val === true ? 1 : val ? val : 0);
     999    }
     1000
     1001    var ast = Jed.PF.parse( p );
     1002    return function ( n ) {
     1003      return imply( Jed.PF.interpreter( ast )( n ) );
     1004    };
     1005  };
     1006
     1007  Jed.PF.interpreter = function ( ast ) {
     1008    return function ( n ) {
     1009      var res;
     1010      switch ( ast.type ) {
     1011        case 'GROUP':
     1012          return Jed.PF.interpreter( ast.expr )( n );
     1013        case 'TERNARY':
     1014          if ( Jed.PF.interpreter( ast.expr )( n ) ) {
     1015            return Jed.PF.interpreter( ast.truthy )( n );
     1016          }
     1017          return Jed.PF.interpreter( ast.falsey )( n );
     1018        case 'OR':
     1019          return Jed.PF.interpreter( ast.left )( n ) || Jed.PF.interpreter( ast.right )( n );
     1020        case 'AND':
     1021          return Jed.PF.interpreter( ast.left )( n ) && Jed.PF.interpreter( ast.right )( n );
     1022        case 'LT':
     1023          return Jed.PF.interpreter( ast.left )( n ) < Jed.PF.interpreter( ast.right )( n );
     1024        case 'GT':
     1025          return Jed.PF.interpreter( ast.left )( n ) > Jed.PF.interpreter( ast.right )( n );
     1026        case 'LTE':
     1027          return Jed.PF.interpreter( ast.left )( n ) <= Jed.PF.interpreter( ast.right )( n );
     1028        case 'GTE':
     1029          return Jed.PF.interpreter( ast.left )( n ) >= Jed.PF.interpreter( ast.right )( n );
     1030        case 'EQ':
     1031          return Jed.PF.interpreter( ast.left )( n ) == Jed.PF.interpreter( ast.right )( n );
     1032        case 'NEQ':
     1033          return Jed.PF.interpreter( ast.left )( n ) != Jed.PF.interpreter( ast.right )( n );
     1034        case 'MOD':
     1035          return Jed.PF.interpreter( ast.left )( n ) % Jed.PF.interpreter( ast.right )( n );
     1036        case 'VAR':
     1037          return n;
     1038        case 'NUM':
     1039          return ast.val;
     1040        default:
     1041          throw new Error("Invalid Token found.");
     1042      }
     1043    };
     1044  };
     1045
     1046  Jed.PF.extractPluralExpr = function ( p ) {
     1047    // trim first
     1048    p = p.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
     1049
     1050    if (! /;\s*$/.test(p)) {
     1051      p = p.concat(';');
     1052    }
     1053
     1054    var nplurals_re = /nplurals\=(\d+);/,
     1055        plural_re = /plural\=(.*);/,
     1056        nplurals_matches = p.match( nplurals_re ),
     1057        res = {},
     1058        plural_matches;
     1059
     1060    // Find the nplurals number
     1061    if ( nplurals_matches.length > 1 ) {
     1062      res.nplurals = nplurals_matches[1];
     1063    }
     1064    else {
     1065      throw new Error('nplurals not found in plural_forms string: ' + p );
     1066    }
     1067
     1068    // remove that data to get to the formula
     1069    p = p.replace( nplurals_re, "" );
     1070    plural_matches = p.match( plural_re );
     1071
     1072    if (!( plural_matches && plural_matches.length > 1 ) ) {
     1073      throw new Error('`plural` expression not found: ' + p);
     1074    }
     1075    return plural_matches[ 1 ];
     1076  };
     1077
     1078  /* Jison generated parser */
     1079  Jed.PF.parser = (function(){
     1080
     1081var parser = {trace: function trace() { },
     1082yy: {},
     1083symbols_: {"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},
     1084terminals_: {2:"error",5:"EOF",6:"?",7:":",8:"||",9:"&&",10:"<",11:"<=",12:">",13:">=",14:"!=",15:"==",16:"%",17:"(",18:")",19:"n",20:"NUMBER"},
     1085productions_: [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]],
     1086performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
     1087
     1088var $0 = $$.length - 1;
     1089switch (yystate) {
     1090case 1: return { type : 'GROUP', expr: $$[$0-1] };
     1091break;
     1092case 2:this.$ = { type: 'TERNARY', expr: $$[$0-4], truthy : $$[$0-2], falsey: $$[$0] };
     1093break;
     1094case 3:this.$ = { type: "OR", left: $$[$0-2], right: $$[$0] };
     1095break;
     1096case 4:this.$ = { type: "AND", left: $$[$0-2], right: $$[$0] };
     1097break;
     1098case 5:this.$ = { type: 'LT', left: $$[$0-2], right: $$[$0] };
     1099break;
     1100case 6:this.$ = { type: 'LTE', left: $$[$0-2], right: $$[$0] };
     1101break;
     1102case 7:this.$ = { type: 'GT', left: $$[$0-2], right: $$[$0] };
     1103break;
     1104case 8:this.$ = { type: 'GTE', left: $$[$0-2], right: $$[$0] };
     1105break;
     1106case 9:this.$ = { type: 'NEQ', left: $$[$0-2], right: $$[$0] };
     1107break;
     1108case 10:this.$ = { type: 'EQ', left: $$[$0-2], right: $$[$0] };
     1109break;
     1110case 11:this.$ = { type: 'MOD', left: $$[$0-2], right: $$[$0] };
     1111break;
     1112case 12:this.$ = { type: 'GROUP', expr: $$[$0-1] };
     1113break;
     1114case 13:this.$ = { type: 'VAR' };
     1115break;
     1116case 14:this.$ = { type: 'NUM', val: Number(yytext) };
     1117break;
     1118}
     1119},
     1120table: [{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]}],
     1121defaultActions: {6:[2,1]},
     1122parseError: function parseError(str, hash) {
     1123    throw new Error(str);
     1124},
     1125parse: function parse(input) {
     1126    var self = this,
     1127        stack = [0],
     1128        vstack = [null], // semantic value stack
     1129        lstack = [], // location stack
     1130        table = this.table,
     1131        yytext = '',
     1132        yylineno = 0,
     1133        yyleng = 0,
     1134        recovering = 0,
     1135        TERROR = 2,
     1136        EOF = 1;
     1137
     1138    //this.reductionCount = this.shiftCount = 0;
     1139
     1140    this.lexer.setInput(input);
     1141    this.lexer.yy = this.yy;
     1142    this.yy.lexer = this.lexer;
     1143    if (typeof this.lexer.yylloc == 'undefined')
     1144        this.lexer.yylloc = {};
     1145    var yyloc = this.lexer.yylloc;
     1146    lstack.push(yyloc);
     1147
     1148    if (typeof this.yy.parseError === 'function')
     1149        this.parseError = this.yy.parseError;
     1150
     1151    function popStack (n) {
     1152        stack.length = stack.length - 2*n;
     1153        vstack.length = vstack.length - n;
     1154        lstack.length = lstack.length - n;
     1155    }
     1156
     1157    function lex() {
     1158        var token;
     1159        token = self.lexer.lex() || 1; // $end = 1
     1160        // if token isn't its numeric value, convert
     1161        if (typeof token !== 'number') {
     1162            token = self.symbols_[token] || token;
     1163        }
     1164        return token;
     1165    }
     1166
     1167    var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;
     1168    while (true) {
     1169        // retreive state number from top of stack
     1170        state = stack[stack.length-1];
     1171
     1172        // use default actions if available
     1173        if (this.defaultActions[state]) {
     1174            action = this.defaultActions[state];
     1175        } else {
     1176            if (symbol == null)
     1177                symbol = lex();
     1178            // read action for current state and first input
     1179            action = table[state] && table[state][symbol];
     1180        }
     1181
     1182        // handle parse error
     1183        _handle_error:
     1184        if (typeof action === 'undefined' || !action.length || !action[0]) {
     1185
     1186            if (!recovering) {
     1187                // Report error
     1188                expected = [];
     1189                for (p in table[state]) if (this.terminals_[p] && p > 2) {
     1190                    expected.push("'"+this.terminals_[p]+"'");
     1191                }
     1192                var errStr = '';
     1193                if (this.lexer.showPosition) {
     1194                    errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'";
     1195                } else {
     1196                    errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
     1197                                  (symbol == 1 /*EOF*/ ? "end of input" :
     1198                                              ("'"+(this.terminals_[symbol] || symbol)+"'"));
     1199                }
     1200                this.parseError(errStr,
     1201                    {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
     1202            }
     1203
     1204            // just recovered from another error
     1205            if (recovering == 3) {
     1206                if (symbol == EOF) {
     1207                    throw new Error(errStr || 'Parsing halted.');
     1208                }
     1209
     1210                // discard current lookahead and grab another
     1211                yyleng = this.lexer.yyleng;
     1212                yytext = this.lexer.yytext;
     1213                yylineno = this.lexer.yylineno;
     1214                yyloc = this.lexer.yylloc;
     1215                symbol = lex();
     1216            }
     1217
     1218            // try to recover from error
     1219            while (1) {
     1220                // check for error recovery rule in this state
     1221                if ((TERROR.toString()) in table[state]) {
     1222                    break;
     1223                }
     1224                if (state == 0) {
     1225                    throw new Error(errStr || 'Parsing halted.');
     1226                }
     1227                popStack(1);
     1228                state = stack[stack.length-1];
     1229            }
     1230
     1231            preErrorSymbol = symbol; // save the lookahead token
     1232            symbol = TERROR;         // insert generic error symbol as new lookahead
     1233            state = stack[stack.length-1];
     1234            action = table[state] && table[state][TERROR];
     1235            recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
     1236        }
     1237
     1238        // this shouldn't happen, unless resolve defaults are off
     1239        if (action[0] instanceof Array && action.length > 1) {
     1240            throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);
     1241        }
     1242
     1243        switch (action[0]) {
     1244
     1245            case 1: // shift
     1246                //this.shiftCount++;
     1247
     1248                stack.push(symbol);
     1249                vstack.push(this.lexer.yytext);
     1250                lstack.push(this.lexer.yylloc);
     1251                stack.push(action[1]); // push state
     1252                symbol = null;
     1253                if (!preErrorSymbol) { // normal execution/no error
     1254                    yyleng = this.lexer.yyleng;
     1255                    yytext = this.lexer.yytext;
     1256                    yylineno = this.lexer.yylineno;
     1257                    yyloc = this.lexer.yylloc;
     1258                    if (recovering > 0)
     1259                        recovering--;
     1260                } else { // error just occurred, resume old lookahead f/ before error
     1261                    symbol = preErrorSymbol;
     1262                    preErrorSymbol = null;
     1263                }
     1264                break;
     1265
     1266            case 2: // reduce
     1267                //this.reductionCount++;
     1268
     1269                len = this.productions_[action[1]][1];
     1270
     1271                // perform semantic action
     1272                yyval.$ = vstack[vstack.length-len]; // default to $$ = $1
     1273                // default location, uses first token for firsts, last for lasts
     1274                yyval._$ = {
     1275                    first_line: lstack[lstack.length-(len||1)].first_line,
     1276                    last_line: lstack[lstack.length-1].last_line,
     1277                    first_column: lstack[lstack.length-(len||1)].first_column,
     1278                    last_column: lstack[lstack.length-1].last_column
     1279                };
     1280                r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
     1281
     1282                if (typeof r !== 'undefined') {
     1283                    return r;
     1284                }
     1285
     1286                // pop off stack
     1287                if (len) {
     1288                    stack = stack.slice(0,-1*len*2);
     1289                    vstack = vstack.slice(0, -1*len);
     1290                    lstack = lstack.slice(0, -1*len);
     1291                }
     1292
     1293                stack.push(this.productions_[action[1]][0]);    // push nonterminal (reduce)
     1294                vstack.push(yyval.$);
     1295                lstack.push(yyval._$);
     1296                // goto new state = table[STATE][NONTERMINAL]
     1297                newState = table[stack[stack.length-2]][stack[stack.length-1]];
     1298                stack.push(newState);
     1299                break;
     1300
     1301            case 3: // accept
     1302                return true;
     1303        }
     1304
     1305    }
     1306
     1307    return true;
     1308}};/* Jison generated lexer */
     1309var lexer = (function(){
     1310
     1311var lexer = ({EOF:1,
     1312parseError:function parseError(str, hash) {
     1313        if (this.yy.parseError) {
     1314            this.yy.parseError(str, hash);
     1315        } else {
     1316            throw new Error(str);
     1317        }
     1318    },
     1319setInput:function (input) {
     1320        this._input = input;
     1321        this._more = this._less = this.done = false;
     1322        this.yylineno = this.yyleng = 0;
     1323        this.yytext = this.matched = this.match = '';
     1324        this.conditionStack = ['INITIAL'];
     1325        this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
     1326        return this;
     1327    },
     1328input:function () {
     1329        var ch = this._input[0];
     1330        this.yytext+=ch;
     1331        this.yyleng++;
     1332        this.match+=ch;
     1333        this.matched+=ch;
     1334        var lines = ch.match(/\n/);
     1335        if (lines) this.yylineno++;
     1336        this._input = this._input.slice(1);
     1337        return ch;
     1338    },
     1339unput:function (ch) {
     1340        this._input = ch + this._input;
     1341        return this;
     1342    },
     1343more:function () {
     1344        this._more = true;
     1345        return this;
     1346    },
     1347pastInput:function () {
     1348        var past = this.matched.substr(0, this.matched.length - this.match.length);
     1349        return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
     1350    },
     1351upcomingInput:function () {
     1352        var next = this.match;
     1353        if (next.length < 20) {
     1354            next += this._input.substr(0, 20-next.length);
     1355        }
     1356        return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
     1357    },
     1358showPosition:function () {
     1359        var pre = this.pastInput();
     1360        var c = new Array(pre.length + 1).join("-");
     1361        return pre + this.upcomingInput() + "\n" + c+"^";
     1362    },
     1363next:function () {
     1364        if (this.done) {
     1365            return this.EOF;
     1366        }
     1367        if (!this._input) this.done = true;
     1368
     1369        var token,
     1370            match,
     1371            col,
     1372            lines;
     1373        if (!this._more) {
     1374            this.yytext = '';
     1375            this.match = '';
     1376        }
     1377        var rules = this._currentRules();
     1378        for (var i=0;i < rules.length; i++) {
     1379            match = this._input.match(this.rules[rules[i]]);
     1380            if (match) {
     1381                lines = match[0].match(/\n.*/g);
     1382                if (lines) this.yylineno += lines.length;
     1383                this.yylloc = {first_line: this.yylloc.last_line,
     1384                               last_line: this.yylineno+1,
     1385                               first_column: this.yylloc.last_column,
     1386                               last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}
     1387                this.yytext += match[0];
     1388                this.match += match[0];
     1389                this.matches = match;
     1390                this.yyleng = this.yytext.length;
     1391                this._more = false;
     1392                this._input = this._input.slice(match[0].length);
     1393                this.matched += match[0];
     1394                token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]);
     1395                if (token) return token;
     1396                else return;
     1397            }
     1398        }
     1399        if (this._input === "") {
     1400            return this.EOF;
     1401        } else {
     1402            this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
     1403                    {text: "", token: null, line: this.yylineno});
     1404        }
     1405    },
     1406lex:function lex() {
     1407        var r = this.next();
     1408        if (typeof r !== 'undefined') {
     1409            return r;
     1410        } else {
     1411            return this.lex();
     1412        }
     1413    },
     1414begin:function begin(condition) {
     1415        this.conditionStack.push(condition);
     1416    },
     1417popState:function popState() {
     1418        return this.conditionStack.pop();
     1419    },
     1420_currentRules:function _currentRules() {
     1421        return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
     1422    },
     1423topState:function () {
     1424        return this.conditionStack[this.conditionStack.length-2];
     1425    },
     1426pushState:function begin(condition) {
     1427        this.begin(condition);
     1428    }});
     1429lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
     1430
     1431var YYSTATE=YY_START;
     1432switch($avoiding_name_collisions) {
     1433case 0:/* skip whitespace */
     1434break;
     1435case 1:return 20
     1436break;
     1437case 2:return 19
     1438break;
     1439case 3:return 8
     1440break;
     1441case 4:return 9
     1442break;
     1443case 5:return 6
     1444break;
     1445case 6:return 7
     1446break;
     1447case 7:return 11
     1448break;
     1449case 8:return 13
     1450break;
     1451case 9:return 10
     1452break;
     1453case 10:return 12
     1454break;
     1455case 11:return 14
     1456break;
     1457case 12:return 15
     1458break;
     1459case 13:return 16
     1460break;
     1461case 14:return 17
     1462break;
     1463case 15:return 18
     1464break;
     1465case 16:return 5
     1466break;
     1467case 17:return 'INVALID'
     1468break;
     1469}
     1470};
     1471lexer.rules = [/^\s+/,/^[0-9]+(\.[0-9]+)?\b/,/^n\b/,/^\|\|/,/^&&/,/^\?/,/^:/,/^<=/,/^>=/,/^</,/^>/,/^!=/,/^==/,/^%/,/^\(/,/^\)/,/^$/,/^./];
     1472lexer.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;})()
     1473parser.lexer = lexer;
     1474return parser;
     1475})();
     1476// End parser
     1477
     1478  // Handle node, amd, and global systems
     1479  if (typeof exports !== 'undefined') {
     1480    if (typeof module !== 'undefined' && module.exports) {
     1481      exports = module.exports = Jed;
     1482    }
     1483    exports.Jed = Jed;
     1484  }
     1485  else {
     1486    if (typeof define === 'function' && define.amd) {
     1487      define('jed', function() {
     1488        return Jed;
     1489      });
     1490    }
     1491    // Leak a global regardless of module system
     1492    root['Jed'] = Jed;
     1493  }
     1494
     1495})(this);
     1496
     1497},{}]},{},[3])(3)
     1498});
     1499 No newline at end of file
  • translator/rtl/translator-jumpstart-rtl.css

     
     1/* This file was automatically generated on Mar 19 2015 10:30:21 */
     2
     3
     4#translator-launcher {
     5        position: fixed;
     6        bottom: 45px;
     7        left: 20px;
     8        border-radius: 27px;
     9        background: #0085be;
     10        padding: 4px;
     11        font-size: 16px;
     12        z-index: 99;
     13        display: none;
     14}
     15#translator-launcher a {
     16        color: white;
     17        text-decoration: none;
     18        outline: 0;
     19}
     20#translator-launcher a .dashicons {
     21        font-size: 32px;
     22        width: 32px;
     23        height:32px;
     24}
     25#translator-launcher a span.dashicons.dashicons-translation:before {
     26        content: "\f326"; !important;
     27}
     28#translator-launcher a .text {
     29        float: left;
     30        display: inline;
     31        width: 0;
     32        overflow: hidden;
     33        line-height: 32px;
     34        white-space: nowrap;
     35        color: #fff;
     36}
     37#translator-launcher a:hover .text {
     38        transition: all 0.25s ease-in-out;
     39        width: auto;
     40        margin-left: 6px;
     41        padding: 0 10px;
     42}
     43#translator-launcher.active {
     44        background: white;
     45}
     46#translator-launcher.active a {
     47        color: #0085be;
     48}
     49
     50#translator-launcher a .text .enable, #translator-launcher a .text .disable {
     51    display: none;
     52}
     53
     54#translator-launcher a:hover .text.disabled .enable, #translator-launcher a:hover .text.enabled .disable {
     55        display: inline;
     56}
  • translator/translator-jumpstart.css

    Property changes on: translator/rtl/translator-jumpstart-rtl.css
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1
     2#translator-launcher {
     3        position: fixed;
     4        bottom: 45px;
     5        right: 20px;
     6        border-radius: 27px;
     7        background: #0085be !important;
     8        padding: 4px !important;
     9        font-size: 16px;
     10        z-index: 99;
     11        display: none;
     12}
     13#translator-launcher a {
     14        color: white;
     15        text-decoration: none;
     16        outline: 0;
     17}
     18#translator-launcher a .dashicons {
     19        font-size: 32px;
     20        width: 32px;
     21        height:32px;
     22}
     23#translator-launcher a span.dashicons.dashicons-translation:before {
     24        content: "\f326"; !important;
     25}
     26#translator-launcher a .text {
     27        float: right;
     28        display: inline;
     29        width: 0;
     30        overflow: hidden;
     31        line-height: 32px;
     32        white-space: nowrap;
     33        color: #fff;
     34}
     35#translator-launcher a:hover .text {
     36        transition: all 0.25s ease-in-out;
     37        width: auto;
     38        margin-right: 6px;
     39        padding: 0 10px;
     40}
     41#translator-launcher.active {
     42        background: white;
     43}
     44#translator-launcher.active a {
     45        color: #0085be;
     46}
     47
     48#translator-launcher a .text .enable, #translator-launcher a .text .disable {
     49    display: none;
     50}
     51
     52#translator-launcher a:hover .text.disabled .enable, #translator-launcher a:hover .text.enabled .disable {
     53        display: inline;
     54}
  • translator/translator-jumpstart.js

    Property changes on: translator/translator-jumpstart.css
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1(function( $ ) {
     2        $( function() {
     3                if ( ! $( '#translator-launcher' ).length || typeof communityTranslator !== 'object' ) {
     4                        // translator Jumpstart not available, maybe interface is in English
     5                        return false;
     6                }
     7
     8                function loadTranslator() {
     9                        $( '#translator-launcher .text' ).addClass( 'enabled' ).removeClass( 'disabled' );
     10                        if ( communityTranslator.load() !== false ) {
     11                                // was loaded successfully
     12                                autoloadTranslator( true );
     13                        }
     14                }
     15
     16                function unloadTranslator() {
     17                        $( '#translator-launcher .text' ).removeClass( 'enabled' ).addClass( 'disabled' );
     18                        communityTranslator.unload();
     19                        autoloadTranslator( false );
     20                }
     21
     22                $( document.body ).on( 'click', '#translator-launcher', function() {
     23
     24                        if ( $( '#translator-launcher .text' ).hasClass( 'disabled' ) ) {
     25                                loadTranslator();
     26                        } else {
     27                                unloadTranslator();
     28                        }
     29                        return false;
     30                } );
     31
     32                // only show the button when the translator has been loaded
     33                runWhenTranslatorIsLoaded( function() {
     34                        $( '#translator-launcher' ).show();
     35                        if ( shouldAutoloadTranslator() ) {
     36                                loadTranslator();
     37                        }
     38                } );
     39
     40                // because of the nature of wp_enqueue_script and the fact that we can only insert the translatorJumpstart at the bottom of the page, we have to wait until the object exists
     41                function runWhenTranslatorIsLoaded( callback ) {
     42                        if ( 'undefined' === typeof window.translatorJumpstart ) {
     43                                setTimeout( function() {
     44                                        runWhenTranslatorIsLoaded( callback );
     45                                }, 100 );
     46                                return;
     47                        }
     48                        callback();
     49                }
     50
     51                function autoloadTranslator( enable ) {
     52                        if ( enable ) {
     53                                document.cookie = 'ct_en=1;path=/;domain=.wordpress.com';
     54                        } else {
     55                                document.cookie = 'ct_en=;expires=Sat,%201%20Jan%202000%2000:00:00%20GMT;path=/;domain=.wordpress.com';
     56                        }
     57                }
     58
     59                function shouldAutoloadTranslator( enable ) {
     60                        return !! document.cookie.match( /ct_en=1/ );
     61                }
     62        } );
     63})( jQuery );
     64
  • translator/translator-jumpstart.php

     
     1<?php
     2class Translator_Jumpstart {
     3        // this is a regex that we output, therefore the backslashes are doubled
     4        const PLACEHOLDER_REGEX = '%([0-9]\\\\*\\$)?';
     5        const PLACEHOLDER_MAXLENGTH = 200;
     6
     7        private $glotpress_project_paths = array();
     8
     9        private $strings_used = array(), $placeholders_used = array();
     10        private $blacklisted = array( 'Loading&#8230;' => true );
     11        private static $instance = array();
     12
     13        public static function init() {
     14                if ( ! self::$instance ) {
     15                        self::$instance = new self;
     16                }
     17
     18                return self::$instance;
     19        }
     20
     21        public function __construct() {
     22                add_action( 'gettext', array( $this, 'translate' ), 10, 3 );
     23                add_action( 'gettext_with_context', array( $this, 'translate_with_context' ), 10, 4 );
     24                add_action( 'gettext', array( $this, 'translate' ), 10, 4 );
     25                add_action( 'gettext_with_context', array( $this, 'translate_with_context' ), 10, 5 );
     26
     27                if ( defined( 'BB_PATH' ) ) {
     28                        $this->enqueue_scripts();
     29                        add_action( 'bb_foot', array( $this, 'load_translator' ), 1000 );
     30                        //add_action( 'bb_init', array( $this, 'enqueue_scripts' ) ) ;
     31                } else {
     32                        add_action( 'wp_footer', array( $this, 'load_translator' ), 1000 );
     33                        add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ) ;
     34                        add_action( 'admin_footer', array( $this, 'load_translator' ), 1000 );
     35                        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ) ;
     36                }
     37
     38                add_action( 'wp_ajax_inform_translator', array( $this, 'load_ajax_translator' ), 1000 );
     39                add_action( 'wp_ajax_inform_admin_translator', array( $this, 'load_ajax_admin_translator' ), 1000 );
     40
     41                $this->set_glotpress_paths();
     42        }
     43
     44        public function enqueue_scripts() {
     45                //TODO: fix URLS
     46                wp_enqueue_style( 'dashicons' );
     47                if ( function_exists( 'is_rtl' ) && is_rtl() ) {
     48                        wp_enqueue_style( 'translator-jumpstart',   'https://wordpress.org/translator/rtl/translator-jumpstart-rtl.css' );
     49                } else {
     50                        wp_enqueue_style( 'translator-jumpstart', 'https://wordpress.org/translator/translator-jumpstart.css' );
     51                        wp_enqueue_style( 'translator-jumpstart', 'https://wordpress.org/translator/community-translator.css' );
     52                }
     53                wp_enqueue_style( 'translator', 'https://wordpress.org/translator/community-translator.css' );
     54
     55                wp_enqueue_script('jquery');
     56                wp_enqueue_script( 'translator', 'https://wordpress.org/translator/community-translator.js' );
     57                wp_enqueue_script( 'translator-jumpstart', 'https://wordpress.org/translator/translator-jumpstart.js', array( 'translator' ) );
     58        }
     59
     60        private function set_glotpress_paths() {
     61
     62                $this->glotpress_project_paths = apply_filters( 'translator_project_paths', $this->glotpress_project_paths );
     63
     64                /*$this->glotpress_project_slugs[] = 'meta/forums';
     65                $this->glotpress_project_slugs[] = 'meta/rosetta';
     66                $this->glotpress_project_slugs[] = 'meta/wordcamp-theme';
     67                $this->glotpress_project_slugs[] = 'meta/themes';
     68                $this->glotpress_project_slugs[] = 'meta/plugins';
     69*/
     70        }
     71
     72        /**
     73         * This returns true for text that consists just of placeholders or placeholders + one letter,
     74         * for example '%sy': time in years abbreviation
     75         * as it leads to lots of translatable text which just matches the regex
     76         *
     77         * @param  string $text the string to check
     78         * @return boolean      true if it contains just placeholders
     79         */
     80        private function contains_just_placeholders( $text ) {
     81                $placeholderless_text = trim( preg_replace( '#' . self::PLACEHOLDER_REGEX . '[sd]#', '', $text ) );
     82                return strlen( $text ) !== strlen( $placeholderless_text ) && strlen( $placeholderless_text ) <= 1;
     83        }
     84
     85        private function contains_placeholder( $text ) {
     86                return (bool) preg_match( '#' . self::PLACEHOLDER_REGEX . '[sd]#', $text );
     87        }
     88
     89        private function already_checked( $key ) {
     90                return
     91                        isset( $this->placeholders_used[ $key ] ) ||
     92                        isset( $this->strings_used[ $key ] );
     93        }
     94
     95        private function convert_placeholders_to_regex( $text, $string_placeholder = null, $numeric_placeholder = null ) {
     96                if ( is_null( $string_placeholder ) ) {
     97                        $string_placeholder = '(.{0,' . self::PLACEHOLDER_MAXLENGTH . '}?)';
     98                }
     99                if ( is_null( $numeric_placeholder ) ) {
     100                        $numeric_placeholder = '([0-9]{0,15}?)';
     101                }
     102
     103                $text = html_entity_decode( $text );
     104                $text = preg_quote( $text, '/' );
     105                $text = preg_replace( '#' . self::PLACEHOLDER_REGEX . 's#', $string_placeholder, $text );
     106                $text = preg_replace( '#' . self::PLACEHOLDER_REGEX . 'd#', $numeric_placeholder, $text );
     107                $text = str_replace( '%%', '%', $text );
     108                return $text;
     109        }
     110
     111        private function get_hash_key( $original, $context = null ) {
     112                if ( ! empty( $context ) && $context !== 'default' ) {
     113                        $context .= "\u0004";
     114                } else {
     115                        $context = '';
     116                }
     117
     118                return $context . html_entity_decode( $original );
     119        }
     120
     121        private function add_context( $key, $context = null, $new_entry = false ) {
     122                if ( ! $context ) {
     123                        return;
     124                }
     125
     126                if ( isset( $this->strings_used[ $key ] ) ) {
     127                        if ( ! isset( $this->strings_used[ $key ][ 1 ] ) ) {
     128                                $this->strings_used[ $key ][ 1 ] = array();
     129
     130                                if ( ! $new_entry ) {
     131                                        // the first entry had an empty context, so add it now
     132                                        $this->strings_used[ $key ][ 1 ][] = '';
     133                                }
     134                        }
     135
     136                        if ( ! in_array( $context, $this->strings_used[ $key ][ 1 ] ) ) {
     137                                $this->strings_used[ $key ][ 1 ][] = $context;
     138                        }
     139
     140                } elseif ( isset( $this->placeholders_used[ $key ] ) ) {
     141                        if ( ! isset( $this->placeholders_used[ $key ][ 2 ] ) ) {
     142                                $this->placeholders_used[ $key ][ 2 ] = array();
     143
     144                                if ( ! $new_entry ) {
     145                                        // the first entry had an empty context, so add it now
     146                                        $this->placeholders_used[ $key ][ 2 ][] = '';
     147                                }
     148                        }
     149
     150                        if ( ! in_array( $context, $this->placeholders_used[ $key ][ 2 ] ) ) {
     151                                $this->placeholders_used[ $key ][ 2 ][] = $context;
     152                        }
     153                }
     154        }
     155
     156        public function translate( $translation, $original = null, $domain = null ) {
     157                return $this->translate_with_context( $translation, $original, null, $domain );
     158        }
     159
     160        public function translate_with_context( $translation, $original = null, $context = null, $domain = null ) {
     161                if ( ! $original ) {
     162                        $original = $translation;
     163                }
     164                if ( isset( $this->blacklisted[ $original ] ) )  {
     165                        return $translation;
     166                }
     167
     168                if ( $this->contains_just_placeholders( $original ) ) {
     169                        $this->blacklisted[ $original ] = true;
     170                        return $translation;
     171                }
     172                $key = $this->get_hash_key( $translation );
     173
     174                if ( $this->already_checked( $key ) ) {
     175
     176                        $this->add_context( $key, $context );
     177
     178                } else {
     179
     180                        if ( $this->contains_placeholder( $translation ) ) {
     181                                $string_placeholder = null;
     182
     183                                if ( $original === '%1$s on %2$s' && $context == 'Recent Comments Widget' ) {
     184                                        // for this original both variables will be HTML Links
     185                                        $string_placeholder = '(<a [^>]+>.{0,' . self::PLACEHOLDER_MAXLENGTH . '}?</a>)';
     186                                }
     187
     188                                $this->placeholders_used[ $key ] = array(
     189                                        $original,
     190                                        $this->convert_placeholders_to_regex( $translation, $string_placeholder ),
     191                                );
     192
     193                        } else {
     194                                $this->strings_used[ $key ] = array(
     195                                        $original,
     196                                );
     197                        }
     198
     199                        $this->add_context( $key, $context, true );
     200                }
     201
     202
     203                return $translation;
     204        }
     205
     206        public function load_ajax_translator( $locale_code = null ) {
     207                if ( empty( $locale_code ) ) {
     208                        $locale_code = get_locale();
     209                }
     210
     211                if ( $locale_code === 'en_US' ) {
     212                        return false;
     213                }
     214
     215                echo '<script type="text','/javascript">';
     216                echo 'var newTranslatorJumpstart = ';
     217                echo json_encode( array(
     218                        'stringsUsedOnPage' => $this->strings_used,
     219                        'placeholdersUsedOnPage' => $this->placeholders_used
     220                ) );
     221                echo ';';
     222                echo '</script>';
     223
     224        }
     225
     226        public function load_translator( $locale_code = null ) {
     227                if ( empty( $locale_code ) ) {
     228                        $locale_code = get_locale();
     229                }
     230
     231                if ( $locale_code === 'en_us' ) {
     232                        return false;
     233                }
     234
     235                echo '<script type="text','/javascript">';
     236                echo 'translatorJumpstart = ', json_encode( $this->get_jumpstart_object( $locale_code ) ), ';';
     237                echo '</script>';
     238
     239                ?><div id="translator-launcher" class="translator">
     240                <a href="" title="<?php _e( 'Community Translator' ); ?>">
     241                                <span class="dashicons dashicons-translation">
     242                                </span>
     243                        <div class="text disabled">
     244                                <div class="enable">
     245                                        Enable Translator
     246                                </div>
     247                                <div class="disable">
     248                                        Disable Translator
     249                                </div>
     250                        </div>
     251                </a>
     252                </div><?php
     253        }
     254
     255        private function get_jumpstart_object( $locale_code ) {
     256
     257                if ( ! class_exists( 'GP_Locales' ) ) {
     258                        require_once __DIR__ . '/../../translate/glotpress/locales/locales.php';
     259                }
     260
     261                $plural_forms = 'nplurals=2; plural=(n != 1)';
     262
     263                $gp_locale = GP_Locales::by_slug( $locale_code );
     264                if( ! $gp_locale ) {
     265                        $gp_locale = GP_Locales::by_field( 'wp_locale', $locale_code );
     266                }
     267
     268                if ( property_exists( $gp_locale, 'nplurals' ) || property_exists( $gp_locale, 'plural_expression' ) ) {
     269                        $plural_forms = 'nplurals=' . $gp_locale->nplurals . '; plural='. $gp_locale->plural_expression;
     270                }
     271
     272                return array(
     273                        'stringsUsedOnPage' => $this->strings_used,
     274                        'placeholdersUsedOnPage' => $this->placeholders_used,
     275                        'localeCode' => isset( $gp_locale->slug ) ? $gp_locale->slug : $locale_code,
     276                        'languageName' => html_entity_decode( $gp_locale->native_name ),
     277                        'pluralForms' => $plural_forms,
     278                        'glotPress' => array(
     279                                'url' => 'https://translate.wordpress.org',
     280                                'project' => implode( ',', $this->glotpress_project_paths ),
     281                        )
     282                );
     283        }
     284}