pub/css/skins/light.css000064400000002521147206622240011077 0ustar00.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(136,136,136,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#888}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}.wp-core-ui .button-primary.loco-loading[disabled]{color:#c7cfd1 !important;background:#0384a4 !important;border-color:#0384a4 !important}.wp-core-ui .button-primary.loco-loading[disabled]:before{background:rgba(0,0,0,0) url(../../img/skins/light/spin-primary-button.gif?v=2.6.11) 0 0 no-repeat !important}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.wp-core-ui .button-primary.loco-loading[disabled]:before{background-size:16px !important;background-image:url(../../img/skins/light/spin-primary-button@2x.gif?v=2.6.11) !important}}pub/css/skins/blue.css000064400000002520147206622240010716 0ustar00.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(9,100,132,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#096484}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}.wp-core-ui .button-primary.loco-loading[disabled]{color:#d1cdc7 !important;background:#db9925 !important;border-color:#db9925 !important}.wp-core-ui .button-primary.loco-loading[disabled]:before{background:rgba(0,0,0,0) url(../../img/skins/blue/spin-primary-button.gif?v=2.6.11) 0 0 no-repeat !important}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.wp-core-ui .button-primary.loco-loading[disabled]:before{background-size:16px !important;background-image:url(../../img/skins/blue/spin-primary-button@2x.gif?v=2.6.11) !important}}pub/css/skins/ocean.css000064400000002524147206622240011060 0ustar00.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(158,186,160,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#9ebaa0}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}.wp-core-ui .button-primary.loco-loading[disabled]{color:#c7d1c8 !important;background:#86a989 !important;border-color:#86a989 !important}.wp-core-ui .button-primary.loco-loading[disabled]:before{background:rgba(0,0,0,0) url(../../img/skins/ocean/spin-primary-button.gif?v=2.6.11) 0 0 no-repeat !important}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.wp-core-ui .button-primary.loco-loading[disabled]:before{background-size:16px !important;background-image:url(../../img/skins/ocean/spin-primary-button@2x.gif?v=2.6.11) !important}}pub/css/skins/midnight.css000064400000002530147206622240011573 0ustar00.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(225,77,67,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#e14d43}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}.wp-core-ui .button-primary.loco-loading[disabled]{color:#d1c8c7 !important;background:#d92e23 !important;border-color:#d92e23 !important}.wp-core-ui .button-primary.loco-loading[disabled]:before{background:rgba(0,0,0,0) url(../../img/skins/midnight/spin-primary-button.gif?v=2.6.11) 0 0 no-repeat !important}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.wp-core-ui .button-primary.loco-loading[disabled]:before{background-size:16px !important;background-image:url(../../img/skins/midnight/spin-primary-button@2x.gif?v=2.6.11) !important}}pub/css/skins/sunrise.css000064400000002527147206622240011466 0ustar00.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(221,130,59,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#dd823b}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}.wp-core-ui .button-primary.loco-loading[disabled]{color:#d1cbc7 !important;background:#cc6d23 !important;border-color:#cc6d23 !important}.wp-core-ui .button-primary.loco-loading[disabled]:before{background:rgba(0,0,0,0) url(../../img/skins/sunrise/spin-primary-button.gif?v=2.6.11) 0 0 no-repeat !important}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.wp-core-ui .button-primary.loco-loading[disabled]:before{background-size:16px !important;background-image:url(../../img/skins/sunrise/spin-primary-button@2x.gif?v=2.6.11) !important}}pub/css/skins/modern.css000064400000002524147206622240011257 0ustar00.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(56,88,233,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#3858e9}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}.wp-core-ui .button-primary.loco-loading[disabled]{color:#c7c9d1 !important;background:#193ddf !important;border-color:#193ddf !important}.wp-core-ui .button-primary.loco-loading[disabled]:before{background:rgba(0,0,0,0) url(../../img/skins/modern/spin-primary-button.gif?v=2.6.11) 0 0 no-repeat !important}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.wp-core-ui .button-primary.loco-loading[disabled]:before{background-size:16px !important;background-image:url(../../img/skins/modern/spin-primary-button@2x.gif?v=2.6.11) !important}}pub/css/skins/coffee.css000064400000002526147206622240011224 0ustar00.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(199,165,137,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#c7a589}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}.wp-core-ui .button-primary.loco-loading[disabled]{color:#d1ccc7 !important;background:#ba906d !important;border-color:#ba906d !important}.wp-core-ui .button-primary.loco-loading[disabled]:before{background:rgba(0,0,0,0) url(../../img/skins/coffee/spin-primary-button.gif?v=2.6.11) 0 0 no-repeat !important}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.wp-core-ui .button-primary.loco-loading[disabled]:before{background-size:16px !important;background-image:url(../../img/skins/coffee/spin-primary-button@2x.gif?v=2.6.11) !important}}pub/css/skins/ectoplasm.css000064400000002533147206622240011762 0ustar00.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(163,183,69,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#a3b745}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}.wp-core-ui .button-primary.loco-loading[disabled]{color:#cfd1c7 !important;background:#89993a !important;border-color:#89993a !important}.wp-core-ui .button-primary.loco-loading[disabled]:before{background:rgba(0,0,0,0) url(../../img/skins/ectoplasm/spin-primary-button.gif?v=2.6.11) 0 0 no-repeat !important}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.wp-core-ui .button-primary.loco-loading[disabled]:before{background-size:16px !important;background-image:url(../../img/skins/ectoplasm/spin-primary-button@2x.gif?v=2.6.11) !important}}pub/css/poview.css000064400000003676147206622240010166 0ustar00.js #loco-admin.wrap .loco-loading{min-height:100px;background:#fff url(../img/spin-modal.gif?v=2.6.11) center center no-repeat}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.js #loco-admin.wrap .loco-loading{background-size:16px;background-image:url(../img/spin-modal@2x.gif?v=2.6.11)}}.js #loco-admin.wrap .loco-loading ol.msgcat{display:none}#loco-admin.wrap #loco-po{padding-right:0;overflow:auto}#loco-admin.wrap ol.msgcat{margin-left:3em;padding-top:1em;border-top:1px dashed #ccc}#loco-admin.wrap ol.msgcat:first-child{padding-top:0;border-top:none}#loco-admin.wrap ol.msgcat li{color:#aaa;margin:0;padding:0 0 0 1em;font:normal 12px/17px "Monaco","Menlo","Ubuntu Mono","Consolas","source-code-pro",monospace;border-left:1px solid #eee}#loco-admin.wrap ol.msgcat li>*{color:#333;white-space:pre}#loco-admin.wrap ol.msgcat li>.po-comment{color:#3cc200}#loco-admin.wrap ol.msgcat li>.po-refs{color:#0073aa}#loco-admin.wrap ol.msgcat li>.po-refs a{color:inherit;text-decoration:none}#loco-admin.wrap ol.msgcat li>.po-refs a:hover{text-decoration:underline}#loco-admin.wrap ol.msgcat li>.po-flags{color:#77904a}#loco-admin.wrap ol.msgcat li>.po-flags em{font-style:normal}#loco-admin.wrap ol.msgcat li>.po-word{color:#000}#loco-admin.wrap ol.msgcat li>.po-junk{font-style:italic;color:#ccc}#loco-admin.wrap ol.msgcat li>.po-string>span{color:#c931c7}#loco-admin.wrap form.loco-filter{top:0;right:0;position:absolute}#loco-admin.wrap .loco-invalid form.loco-filter input[type=text]:focus{border-color:#c00;-webkit-box-shadow:0 0 2px rgba(153,0,0,.5);-moz-box-shadow:0 0 2px rgba(153,0,0,.5);box-shadow:0 0 2px rgba(153,0,0,.5)}#loco-admin.wrap .loco-invalid ol.msgcat{list-style-type:none}#loco-admin.wrap .loco-invalid ol.msgcat li{color:#000}pub/css/bundle.css000064400000000171147206622240010111 0ustar00form.loco-filter{float:right}@media only screen and (max-width: 1024px){table.wp-list-table a.row-title{max-width:100px}}pub/css/locale.css000064400000000255147206622240010102 0ustar00#loco-admin.wrap td.loco-not-active{color:#aaa}#loco-admin.wrap div.loco-projects>h3{float:left}#loco-admin.wrap div.loco-projects form.loco-filter{float:right;margin:1em 0}pub/css/podiff.css000064400000003316147206622240010113 0ustar00#loco-admin.wrap .revisions-diff{padding:10px;min-height:20px}#loco-admin.wrap table.diff{border-collapse:collapse;table-layout:auto}#loco-admin.wrap table.diff td{white-space:nowrap;overflow:hidden;font:normal 12px/17px "Monaco","Menlo","Ubuntu Mono","Consolas","source-code-pro",monospace;padding:2px}#loco-admin.wrap table.diff td>span{color:#aaa}#loco-admin.wrap table.diff td>span:after{content:". "}#loco-admin.wrap table.diff tbody{border-top:1px dashed #ccc}#loco-admin.wrap table.diff tbody:first-child{border-top:none}#loco-admin.wrap table.diff td>.dashicons{display:none}#loco-admin.wrap .revisions.loading .diff-meta{color:#eee}#loco-admin.wrap .revisions.loading .loading-indicator span.spinner{visibility:visible;background:#fff url(../img/spin-modal.gif?v=2.6.11) center center no-repeat}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){#loco-admin.wrap .revisions.loading .loading-indicator span.spinner{background-size:16px;background-image:url(../img/spin-modal@2x.gif?v=2.6.11)}}#loco-admin.wrap .revisions-meta{clear:both;padding:10px 12px;margin:0;position:relative;top:10px}#loco-admin.wrap .revisions-meta .diff-meta{clear:none;float:left;width:50%;padding:0;min-height:auto}#loco-admin.wrap .revisions-meta .diff-meta button{margin-top:5px}#loco-admin.wrap .revisions-meta .diff-meta-current{float:right;text-align:right}#loco-admin.wrap .revisions-meta time{color:#72777c}#loco-admin.wrap .revisions-control-frame{margin:10px 0}#loco-admin.wrap .revisions-diff-frame{margin-top:20px}pub/css/config.css000064400000001137147206622240010110 0ustar00form#loco-conf>div{overflow:visible;border-bottom:solid 1px #ccc;padding-top:2em}form#loco-conf>div h2{margin-top:0}form#loco-conf td.twin>div{float:left;clear:none;width:50%}form#loco-conf td .description:first-child{margin-top:0;margin-bottom:4px}form#loco-conf a.icon-del{display:block;float:right;z-index:99;color:#aaa;outline:none}form#loco-conf a.icon-del:hover{color:#c00}form#loco-conf>div:first-child a.icon-del{display:none}form#loco-conf p.description{color:#aaa;font-size:12px;text-indent:.25em}form#loco-conf tr:hover p.description{color:#666}form#loco-reset{position:absolute;bottom:0;right:0}pub/css/poinit.css000064400000001502147206622240010141 0ustar00form#loco-poinit .loco-locales fieldset{float:left;margin-right:2em}form#loco-poinit .loco-locales fieldset.disabled span.lang{visibility:hidden !important}form#loco-poinit .loco-locales fieldset span.nolang{background:#999}form#loco-poinit .loco-locales fieldset>label span{width:20px;text-align:center;display:inline-block;margin-right:4px}form#loco-poinit a.icon-help{color:#999;font-style:normal;text-decoration:none}form#loco-poinit a.icon-help:hover{color:#666}form#loco-poinit .form-table th{padding:15px 10px}form#loco-poinit .form-table tr:first-child td,form#loco-poinit .form-table tr:first-child th{padding-top:25px}form#loco-poinit label.for-disabled input{visibility:hidden}form#loco-poinit label.for-disabled .icon-lock{top:0;left:0;display:block;position:absolute;width:1em;font-size:14px;text-align:center;color:gray}pub/css/admin.css000064400000174110147206622240007735 0ustar00._ajax_loader_f2{background-image:url(../img/ajax-loader-f2.gif?v=2.6.11);background-repeat:no-repeat;min-height:16px}._ajax_loader_f2x4{background:rgba(0,0,0,0) url(../img/ajax-loader-f2-x4.gif?v=2.6.11) 0 0 no-repeat;min-height:75px}._ajax_loader_cc{background-image:url(../img/ajax-loader-cc.gif?v=2.6.11);background-repeat:no-repeat;min-height:16px}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){._ajax_loader_f2{background-image:url(../img/ajax-loader-f2-x2.gif?v=2.6.11);background-size:16px}._ajax_loader_cc{background-image:url(../img/ajax-loader-cc-x2.gif?v=2.6.11);background-size:16px}}._green_glow_inner{-webkit-box-shadow:inset 0 0 10px 0 #3db63d;-moz-box-shadow:inset 0 0 10px 0 #3db63d;box-shadow:inset 0 0 10px 0 #3db63d}._green_glow_outer{-webkit-box-shadow:0 0 .5em 0 #3db63d;-moz-box-shadow:0 0 .5em 0 #3db63d;box-shadow:0 0 .5em 0 #3db63d}.loco-font,#loco-admin.wrap .wp-list-table th.loco-sort.loco-asc:after,#loco-admin.wrap .wp-list-table th.loco-sort.loco-desc:after,#loco-admin.wrap .icon,#loco-admin.wrap .has-icon:before{font-family:"loco";speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@font-face{font-family:"loco";src:url("../font/loco.eot?v=2.6.11");src:url("../font/loco.eot?v=2.6.11?#iefix") format("embedded-opentype"),url("../font/loco.woff?v=2.6.11") format("woff"),url("../font/loco.ttf?v=2.6.11") format("truetype"),url("../font/loco.svg?v=2.6.11#loco") format("svg");font-weight:normal;font-style:normal}.tipsy{font-size:11px;position:absolute;padding:5px;z-index:500001;opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.tipsy.in{opacity:1}.tipsy-inner{background-color:#000;color:#fff;white-space:nowrap;padding:6px 8px;line-height:1.3em;text-align:center;-webkit-border-radius:3px;-moz-border-radius:3px;-ms-border-radius:3px;-o-border-radius:3px;border-radius:3px;-webkit-font-smoothing:subpixel-antialiased}.tipsy-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed #000}.tipsy-n .tipsy-arrow{top:0px;left:50%;margin-left:-5px;border-bottom-style:solid;border-top:none;border-left-color:rgba(0,0,0,0);border-right-color:rgba(0,0,0,0)}.tipsy-nw .tipsy-arrow{top:0;left:10px;border-bottom-style:solid;border-top:none;border-left-color:rgba(0,0,0,0);border-right-color:rgba(0,0,0,0)}.tipsy-ne .tipsy-arrow{top:0;right:10px;border-bottom-style:solid;border-top:none;border-left-color:rgba(0,0,0,0);border-right-color:rgba(0,0,0,0)}.tipsy-s .tipsy-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:none;border-left-color:rgba(0,0,0,0);border-right-color:rgba(0,0,0,0)}.tipsy-sw .tipsy-arrow{bottom:0;left:10px;border-top-style:solid;border-bottom:none;border-left-color:rgba(0,0,0,0);border-right-color:rgba(0,0,0,0)}.tipsy-se .tipsy-arrow{bottom:0;right:10px;border-top-style:solid;border-bottom:none;border-left-color:rgba(0,0,0,0);border-right-color:rgba(0,0,0,0)}.tipsy-e .tipsy-arrow{right:0;top:50%;margin-top:-5px;border-left-style:solid;border-right:none;border-top-color:rgba(0,0,0,0);border-bottom-color:rgba(0,0,0,0)}.tipsy-w .tipsy-arrow{left:0;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:rgba(0,0,0,0);border-bottom-color:rgba(0,0,0,0)}#loco-admin.wrap .selector li,#loco-admin.wrap .selector .handle,#loco-admin.wrap .selectorsep:before,#loco-admin.wrap .selectoradd a,#loco-admin.wrap .selectorsep a{padding:.3em .75em}#loco-admin.wrap .selector{text-align:left;display:inline-block;white-space:nowrap}#loco-admin.wrap .selectoradd a,#loco-admin.wrap .selector .handle{cursor:default;display:block;position:relative;border-top:solid 1px #fff;border-right:solid 1px #ddd;border-bottom:solid 1px #ddd;border-left:solid 1px #fff;color:#666;height:1.3em;overflow:hidden;white-space:normal}#loco-admin.wrap .selectorsep{display:inline-block;border:solid 1px rgba(0,0,0,0)}#loco-admin.wrap .selectorsep:before{display:block;position:relative;color:#999;height:1.3em;overflow:hidden}#loco-admin.wrap .selectorsep a.icon{display:block;position:relative;line-height:1em;color:#666;height:1.3em;overflow:hidden}#loco-admin.wrap .selectorsep a.icon:before{padding:0}#loco-admin.wrap .selector .handle{outline:none;white-space:nowrap;padding-right:2.3em;max-width:250px;text-overflow:ellipsis}#loco-admin.wrap .selector .handle .prefix{padding-right:.6ex}#loco-admin.wrap .selector .handle:after{font-family:loco;font-size:1.3em;color:#ccc;display:block;position:absolute;top:.25em;right:.6em;content:"▼"}#loco-admin.wrap .selector .handle:focus:after,#loco-admin.wrap .selector .handle:hover:after{color:#666}#loco-admin.wrap .selector.no-caret .handle{padding-right:.75em}#loco-admin.wrap .selector.no-caret .handle:after{display:none}#loco-admin.wrap .selector.no-title .handle{padding-left:.5em;padding-right:.5em}#loco-admin.wrap .selector.no-title .handle .label{display:none}#loco-admin.wrap .selector.no-title .handle .icon{margin-right:0px}#loco-admin.wrap .selector.dummy .handle{border-color:rgba(0,0,0,0)}#loco-admin.wrap .selector.dummy .handle:after{display:none}#loco-admin.wrap .selectoradd{position:relative}#loco-admin.wrap .selectoradd>*{display:block;float:left;clear:none}#loco-admin.wrap .selectoradd>a.has-icon{width:1.3em}#loco-admin.wrap .selectoradd>a.has-icon:before{color:#999;display:inline;padding-left:0;padding-right:0;line-height:1.4em}#loco-admin.wrap .selectoradd>a.has-icon:hover:after{color:#2e892e}#loco-admin.wrap .selectorsep{display:inline-block}#loco-admin.wrap .selectorsep span{color:#666;height:1.3em}#loco-admin.wrap .selector ul,#loco-admin.wrap .selector li{display:block;position:relative;cursor:default;margin:0}#loco-admin.wrap .selector ul{padding:0;overflow:auto}#loco-admin.wrap .selector.multi li input{display:none}#loco-admin.wrap .selector li.selected{background:#e5e5e5}#loco-admin.wrap .selector li.over{background-color:#ccc;color:#fff;text-shadow:1px 1px #aaa}#loco-admin.wrap .selector li.over .label{color:#fff}#loco-admin.wrap .selector .label{font:inherit;color:inherit}#loco-admin.wrap .selector .icon{display:inline-block;min-width:1.2em;text-align:center;font-size:14px}#loco-admin.wrap .selector .icon.no-icon{display:none}#loco-admin.wrap .selector .icon:before{color:#666}#loco-admin.wrap .selector .label{line-height:1;vertical-align:text-bottom}#loco-admin.wrap .selector .lang{vertical-align:middle}#loco-admin.wrap .selector .icon,#loco-admin.wrap .selector .icon-16{vertical-align:text-bottom}#loco-admin.wrap .selector .icon,#loco-admin.wrap .selector .lang,#loco-admin.wrap .selector .icon-16{margin-right:5px}#loco-admin.wrap .selector .region,#loco-admin.wrap .selector .lang-tlh,#loco-admin.wrap .selector .variant-wales,#loco-admin.wrap .selector .region-gb.lang-cy,#loco-admin.wrap .selector .variant-scotland,#loco-admin.wrap .selector .region-gb.lang-gd,#loco-admin.wrap .selector .variant-valencia,#loco-admin.wrap .selector .region-es.variant-valencia,#loco-admin.wrap .selector .lang-ca,#loco-admin.wrap .selector .region-es.lang-ca,#loco-admin.wrap .selector .lang-eo,#loco-admin.wrap .selector .lang-eu{margin-left:2px;margin-right:7px}#loco-admin.wrap .selector.avatars .label{line-height:18px}#loco-admin.wrap .selector.avatars .avtr,#loco-admin.wrap .selector.avatars .icon-user{width:18px;height:18px;line-height:18px}#loco-admin.wrap .selector.avatars .avtr{background-size:18px;background-repeat:no-repeat}#loco-admin.wrap .selector.ticked .icon.no-icon{font-size:12px;display:inline-block}#loco-admin.wrap .selector.ticked .selected .icon.no-icon:before{content:"✓"}#loco-admin.wrap .selectoradd a:focus,#loco-admin.wrap .selectoradd a:hover,#loco-admin.wrap .selector .handle:focus,#loco-admin.wrap .selector .handle:hover{color:#000;border-color:#999}#loco-admin.wrap .selectoradd a:focus{outline:none}#loco-admin.wrap .selector.active .handle{border-color:#999;background-color:#eee;color:#999}#loco-admin.wrap .selector.active .handle:after{color:#999;content:"▲"}#loco-admin.wrap .selector.disabled,#loco-admin.wrap .selectoradd.disabled{cursor:default;cursor:no-drop;cursor:not-allowed}#loco-admin.wrap .selectoradd.disabled a,#loco-admin.wrap .selector.disabled .handle{pointer-events:none;color:#999}#loco-admin.wrap .selector.readonly .handle{pointer-events:none;border-color:#ddd}#loco-admin.wrap .selector.readonly .handle,#loco-admin.wrap .selector.disabled .handle{padding-right:1em}#loco-admin.wrap .selector.readonly .handle:after,#loco-admin.wrap .selector.disabled .handle:after{display:none}#loco-admin.wrap .selector.readonly .handle span.icon,#loco-admin.wrap .selector.disabled .handle span.icon{color:#999 !important}#loco-admin.wrap .selector li.disabled,#loco-admin.wrap .selector li.disabled.over{color:#aaa;text-shadow:1px 1px #fff;pointer-events:none}#loco-admin.wrap .selector li.disabled *{cursor:text}#loco-admin.wrap .selector li.disabled .icon:before{color:#aaa}#loco-admin.wrap .selector li.disabled .flag,#loco-admin.wrap .selector li.disabled .icon-16{opacity:.4}#loco-admin.wrap .selectorset .selector{float:left;clear:left}#loco-admin.wrap .selectorset .selectoradd,#loco-admin.wrap .selectorset .selectorsep{float:left;clear:none}#loco-admin.wrap body>.selector{display:block;position:fixed;top:0;left:0;margin:0;padding:0;background:#f5f5f5;box-shadow:0 10px 5px rgba(0,0,0,.2);border:solid 1px #ccc;z-index:400001;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}#loco-admin.wrap body>.selector form{display:none}#loco-admin.wrap body>.selector.filterable form{display:block;border-bottom:solid 1px #ddd}#loco-admin.wrap body>.selector.filterable form input{margin:0;display:block;position:relative;background:#fff;width:100%;-webkit-border-radius:0;-moz-border-radius:0;-ms-border-radius:0;-o-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;padding-left:.75em;border:none}#loco-admin.wrap body>.selector.filterable form input:focus{-webkit-box-shadow:inset 0 0 5px 0 #3db63d;-moz-box-shadow:inset 0 0 5px 0 #3db63d;box-shadow:inset 0 0 5px 0 #3db63d}#loco-admin.wrap body>.selector.filterable form ::placeholder{color:#ddd;font-family:"loco";display:block;position:relative;font-size:17px;top:1px}#loco-admin.wrap body>.selector.filterable form ::-webkit-input-placeholder{color:#ddd;font-family:"loco";display:block;position:relative;font-size:17px;top:1px}#loco-admin.wrap body>.selector.filterable form :-moz-placeholder{color:#ddd;font-family:"loco";display:block;position:relative;font-size:17px;top:1px;opacity:1}#loco-admin.wrap body>.selector.filterable form ::-moz-placeholder{color:#ddd;font-family:"loco";display:block;position:relative;font-size:17px;top:1px;opacity:1}#loco-admin.wrap body>.selector.filterable form :-ms-input-placeholder{color:#ddd;font-family:"loco";display:block;position:relative;font-size:17px;top:1px}#loco-admin.wrap body>.selector.filterable form .ielt10 .placeheld{color:#ddd;font-family:"loco";display:block;position:relative;font-size:17px;top:1px}#loco-admin.wrap body>.selector.none{border-color:#bd2c00}#loco-admin.wrap body>.selector.none form input:focus{-webkit-box-shadow:inset 0 0 5px 0 rgba(153,0,0,.5);-moz-box-shadow:inset 0 0 5px 0 rgba(153,0,0,.5);box-shadow:inset 0 0 5px 0 rgba(153,0,0,.5)}#loco-admin.wrap body>.selector.none li{opacity:.4}#loco-admin.wrap div.auto-comp-wrap{width:100%}#loco-admin.wrap div.auto-comp-wrap input{display:inline-block}#loco-admin.wrap div.auto-comp-wrap.loading input{background:rgba(0,0,0,0) url(../img/ajax-loader.gif?v=2.6.11) right 2px no-repeat}#loco-admin.wrap div.auto-comp-drop{color:#333;background:#fff;border-top:none;position:absolute;width:auto;top:0;left:0;z-index:99;-webkit-box-shadow:0 5px 5px rgba(0,0,0,.4);-moz-box-shadow:0 5px 5px rgba(0,0,0,.4);box-shadow:0 5px 5px rgba(0,0,0,.4)}#loco-admin.wrap div.auto-comp-result{padding:5px 10px;cursor:pointer;background:#f0f0f0;border-top:solid 1px #fff;border-bottom:solid 1px #ddd;white-space:nowrap}#loco-admin.wrap div.auto-comp-result:first-child{border-top:solid 1px #ddd}#loco-admin.wrap div.auto-comp-result>*{display:inline-block;vertical-align:middle;line-height:normal}#loco-admin.wrap div.auto-comp-result .icon:before{padding-right:5px}#loco-admin.wrap div.auto-comp-result:hover{background:#a8a8a8;color:#fff;border-top-color:#a8a8a8;border-bottom-color:#999}#loco-admin.wrap div.auto-comp-result.selected{background:#666 !important;color:#fff;border-top-color:#666;border-bottom-color:#666}#loco-admin.wrap div.auto-comp-result .lang,#loco-admin.wrap div.auto-comp-result .region,#loco-admin.wrap div.auto-comp-result .lang-tlh,#loco-admin.wrap div.auto-comp-result .variant-wales,#loco-admin.wrap div.auto-comp-result .region-gb.lang-cy,#loco-admin.wrap div.auto-comp-result .variant-scotland,#loco-admin.wrap div.auto-comp-result .region-gb.lang-gd,#loco-admin.wrap div.auto-comp-result .variant-valencia,#loco-admin.wrap div.auto-comp-result .region-es.variant-valencia,#loco-admin.wrap div.auto-comp-result .lang-ca,#loco-admin.wrap div.auto-comp-result .region-es.lang-ca,#loco-admin.wrap div.auto-comp-result .lang-eo,#loco-admin.wrap div.auto-comp-result .lang-eu,#loco-admin.wrap div.auto-comp-result .avtr{margin-right:5px}#loco-admin.wrap div.auto-comp-wrap.error a.icon.clear:before{color:#c00;opacity:1}#loco-admin.wrap div.auto-comp-wrap.error input[type=text]{border-color:#c00;color:#c00}#loco-admin.wrap div.auto-comp-wrap.error input[type=text]:focus{-webkit-box-shadow:0 0 .5em 0 rgba(153,0,0,.5);-moz-box-shadow:0 0 .5em 0 rgba(153,0,0,.5);box-shadow:0 0 .5em 0 rgba(153,0,0,.5)}#loco-admin.wrap ._ajax_loader_f2{background-image:url(../img/ajax-loader-f2.gif?v=2.6.11);background-repeat:no-repeat;min-height:16px}#loco-admin.wrap ._ajax_loader_f2x4{background:rgba(0,0,0,0) url(../img/ajax-loader-f2-x4.gif?v=2.6.11) 0 0 no-repeat;min-height:75px}#loco-admin.wrap ._ajax_loader_cc{background-image:url(../img/ajax-loader-cc.gif?v=2.6.11);background-repeat:no-repeat;min-height:16px}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){#loco-admin.wrap ._ajax_loader_f2{background-image:url(../img/ajax-loader-f2-x2.gif?v=2.6.11);background-size:16px}#loco-admin.wrap ._ajax_loader_cc{background-image:url(../img/ajax-loader-cc-x2.gif?v=2.6.11);background-size:16px}}#loco-admin.wrap ._green_glow_inner{-webkit-box-shadow:inset 0 0 10px 0 #3db63d;-moz-box-shadow:inset 0 0 10px 0 #3db63d;box-shadow:inset 0 0 10px 0 #3db63d}#loco-admin.wrap ._green_glow_outer{-webkit-box-shadow:0 0 .5em 0 #3db63d;-moz-box-shadow:0 0 .5em 0 #3db63d;box-shadow:0 0 .5em 0 #3db63d}#loco-admin.wrap .loco-font,#loco-admin.wrap .wp-list-table th.loco-sort.loco-asc:after,#loco-admin.wrap .wp-list-table th.loco-sort.loco-desc:after,#loco-admin.wrap .icon,#loco-admin.wrap .has-icon:before{font-family:"loco";speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#loco-admin.wrap .has-icon:before{display:inline-block;padding-right:.3em}#loco-admin.wrap a.icon-help{cursor:help}#loco-admin.wrap a.icon-help span{display:none}#loco-admin.wrap .icon-share:before{content:"↑"}#loco-admin.wrap .icon-notag:before{content:""}#loco-admin.wrap .icon-magnify:before{content:"🔍"}#loco-admin.wrap .icon-python:before{content:"🐍"}#loco-admin.wrap .icon-unicode:before{content:"u"}#loco-admin.wrap .icon-json:before{content:"{"}#loco-admin.wrap .icon-translate:before{content:""}#loco-admin.wrap .icon-project:before{content:""}#loco-admin.wrap .icon-plural:before{content:"᛬"}#loco-admin.wrap .icon-max:before,#loco-admin.wrap .icon-full-screen:before{content:""}#loco-admin.wrap .icon-min:before{content:""}#loco-admin.wrap .icon-sort:before{content:""}#loco-admin.wrap .icon-ok:before,#loco-admin.wrap .notice-success>.has-icon:before,#loco-admin.wrap .notice-success p>strong.has-icon:before,#loco-admin.wrap .panel-success>.has-icon:before,#loco-admin.wrap .panel-success p>strong.has-icon:before{content:"✓"}#loco-admin.wrap .icon-ok-empty:before{content:"🗌"}#loco-admin.wrap .icon-play:before{content:"⏵"}#loco-admin.wrap .icon-checkbox-checked:before,#loco-admin.wrap .selector.multi li.selected .icon-checkbox:before{content:"☑"}#loco-admin.wrap .icon-checkbox-unchecked:before{content:"☐"}#loco-admin.wrap .icon-checkbox-partial:before{content:"☉"}#loco-admin.wrap .icon-radio-checked:before{content:"⚫"}#loco-admin.wrap .icon-radio-unchecked:before{content:"⚪"}#loco-admin.wrap .icon-ext:before{content:"⬈"}#loco-admin.wrap .icon-pdf:before{content:"P"}#loco-admin.wrap .icon-zip:before{content:"Z"}#loco-admin.wrap .icon-bullist:before{content:""}#loco-admin.wrap .icon-numlist:before{content:""}#loco-admin.wrap .icon-indent:before{content:""}#loco-admin.wrap .icon-outdent:before{content:""}#loco-admin.wrap .icon-link:before{content:""}#loco-admin.wrap .icon-unlink:before{content:""}#loco-admin.wrap .icon-bold:before{content:"B"}#loco-admin.wrap .icon-italic:before{content:"I"}#loco-admin.wrap .icon-library:before,#loco-admin.wrap .icon-glossary:before{content:"📚"}#loco-admin.wrap .icon-star:before{content:""}#loco-admin.wrap .icon-user:before{content:""}#loco-admin.wrap .icon-remove:before,#loco-admin.wrap a.icon.close:before{content:"×"}#loco-admin.wrap .icon-cog:before{content:""}#loco-admin.wrap .icon-trash:before{content:""}#loco-admin.wrap .icon-time:before,#loco-admin.wrap .icon-history:before{content:""}#loco-admin.wrap .icon-download:before{content:""}#loco-admin.wrap .icon-revert:before{content:""}#loco-admin.wrap .icon-sync:before{content:""}#loco-admin.wrap .icon-lock:before,#loco-admin.wrap .notice-locked>.has-icon:before,#loco-admin.wrap .notice-locked p>strong.has-icon:before,#loco-admin.wrap .panel-locked>.has-icon:before,#loco-admin.wrap .panel-locked p>strong.has-icon:before{content:""}#loco-admin.wrap .icon-flag:before{content:""}#loco-admin.wrap .icon-tag:before{content:""}#loco-admin.wrap .icon-tags:before{content:""}#loco-admin.wrap .icon-print:before{content:""}#loco-admin.wrap .icon-camera:before{content:""}#loco-admin.wrap .icon-pencil:before{content:""}#loco-admin.wrap .icon-add:before{content:""}#loco-admin.wrap .icon-del:before{content:""}#loco-admin.wrap .icon-clear:before,#loco-admin.wrap a.icon.clear:before{content:""}#loco-admin.wrap .icon-ok-sign:before{content:"✔"}#loco-admin.wrap .icon-help:before{content:"?"}#loco-admin.wrap .icon-info:before,#loco-admin.wrap .notice-info>.has-icon:before,#loco-admin.wrap .notice-info p>strong.has-icon:before,#loco-admin.wrap .panel-info>.has-icon:before,#loco-admin.wrap .panel-info p>strong.has-icon:before{content:"ℹ"}#loco-admin.wrap .icon-cancel:before{content:""}#loco-admin.wrap .icon-warn:before,#loco-admin.wrap .notice-error>.has-icon:before,#loco-admin.wrap .notice-error p>strong.has-icon:before,#loco-admin.wrap .panel-error>.has-icon:before,#loco-admin.wrap .panel-error p>strong.has-icon:before,#loco-admin.wrap .notice-warning>.has-icon:before,#loco-admin.wrap .notice-warning p>strong.has-icon:before,#loco-admin.wrap .panel-warning>.has-icon:before,#loco-admin.wrap .panel-warning p>strong.has-icon:before{content:""}#loco-admin.wrap .icon-comment:before{content:""}#loco-admin.wrap .icon-bar-chart:before{content:""}#loco-admin.wrap .icon-key:before{content:""}#loco-admin.wrap .icon-cogs:before{content:""}#loco-admin.wrap .icon-comments:before{content:""}#loco-admin.wrap .icon-signout:before{content:""}#loco-admin.wrap .icon-signin:before{content:""}#loco-admin.wrap .icon-upload:before{content:""}#loco-admin.wrap .icon-twitter:before{content:"🐦"}#loco-admin.wrap .icon-facebook:before{content:""}#loco-admin.wrap .icon-github:before{content:""}#loco-admin.wrap .icon-feed:before{content:""}#loco-admin.wrap .icon-globe:before{content:""}#loco-admin.wrap .icon-wrench:before,#loco-admin.wrap .notice-debug>.has-icon:before,#loco-admin.wrap .notice-debug p>strong.has-icon:before,#loco-admin.wrap .panel-debug>.has-icon:before,#loco-admin.wrap .panel-debug p>strong.has-icon:before{content:""}#loco-admin.wrap .icon-group:before{content:""}#loco-admin.wrap .icon-cloud:before{content:""}#loco-admin.wrap .icon-copy:before{content:""}#loco-admin.wrap .icon-save:before{content:""}#loco-admin.wrap .icon-menu:before{content:""}#loco-admin.wrap .icon-table:before{content:""}#loco-admin.wrap .icon-caret-down:before{content:"▼"}#loco-admin.wrap .icon-caret-up:before{content:"▲"}#loco-admin.wrap .icon-caret-right:before{content:"▶"}#loco-admin.wrap .icon-mail:before{content:""}#loco-admin.wrap .icon-cloud-upload:before{content:""}#loco-admin.wrap .icon-file:before{content:""}#loco-admin.wrap .icon-circle-white:before{content:"⚬"}#loco-admin.wrap .icon-circle-black:before{content:"●"}#loco-admin.wrap .icon-eraser:before{content:""}#loco-admin.wrap .icon-unlock:before{content:""}#loco-admin.wrap .icon-apple:before{content:""}#loco-admin.wrap .icon-android:before{content:""}#loco-admin.wrap .icon-robot:before{content:"🤖"}#loco-admin.wrap .icon-back:before{content:"⬅"}#loco-admin.wrap .icon-next:before{content:"➔"}#loco-admin.wrap .icon-arrow-up:before{content:"⬆"}#loco-admin.wrap .icon-arrow-down:before{content:"⬇"}#loco-admin.wrap .icon-confirm:before{content:"!"}#loco-admin.wrap .icon-visible:before{content:"⏿"}#loco-admin.wrap .icon-hidden:before{content:"*"}#loco-admin.wrap .icon-calendar:before{content:""}#loco-admin.wrap .icon-ccard:before{content:""}#loco-admin.wrap .icon-caret-left:before{content:"◀"}#loco-admin.wrap .icon-pro:before{content:"⚡"}#loco-admin.wrap .icon-bell:before{content:""}#loco-admin.wrap .icon-code:before{content:""}#loco-admin.wrap .icon-privacy:before{content:"🛡"}#loco-admin.wrap .icon-hellip:before{content:"…"}#loco-admin.wrap .icon-vellip:before{content:"⁞"}#loco-admin.wrap .icon-collapse:before{content:""}#loco-admin.wrap .icon-expand:before{content:""}#loco-admin.wrap .icon-wordpress:before{content:""}#loco-admin.wrap .icon-restore:before{content:""}#loco-admin.wrap .icon-pilcrow:before{content:"¶"}#loco-admin.wrap .icon-google:before{content:" ";background:rgba(0,0,0,0) url(../img/sso/g.svg?v=2.6.11) 0 1px no-repeat;background-size:18px}#loco-admin.wrap .icon-status.is-translated:before,#loco-admin.wrap .is-translated>header>.icon-status:before{color:#2e892e;content:"✓";text-align:center}#loco-admin.wrap .icon-status.is-untranslated:before,#loco-admin.wrap .is-untranslated>header>.icon-status:before{color:#aaa;content:"×";text-align:center;font-size:1.1em}#loco-admin.wrap .icon-status.is-flagged:before,#loco-admin.wrap .is-flagged>header>.icon-status:before{color:#bd2c00;content:"";text-align:center}#loco-admin.wrap .icon-status.is-translated.is-blank:before,#loco-admin.wrap .icon-status.is-translated.is-inherit:before,#loco-admin.wrap .is-translated.is-blank>header>.icon-status:before,#loco-admin.wrap .is-translated.is-inherit>header>.icon-status:before{content:"🗌"}#loco-admin.wrap .icon-status.is-translated.is-blank:before,#loco-admin.wrap .is-translated.is-blank>header>.icon-status:before{color:#aaa}#loco-admin.wrap .icon-status.is-fuzzy:before,#loco-admin.wrap .is-fuzzy>header>.icon-status:before{color:#666;content:""}#loco-admin.wrap .lang,#loco-admin.wrap .region,#loco-admin.wrap .lang-tlh,#loco-admin.wrap .variant-wales,#loco-admin.wrap .region-gb.lang-cy,#loco-admin.wrap .variant-scotland,#loco-admin.wrap .region-gb.lang-gd,#loco-admin.wrap .variant-valencia,#loco-admin.wrap .region-es.variant-valencia,#loco-admin.wrap .lang-ca,#loco-admin.wrap .region-es.lang-ca,#loco-admin.wrap .lang-eo,#loco-admin.wrap .lang-eu{color:#fff;display:inline-block;text-transform:uppercase;overflow:hidden;font-family:Verdana,Arial,sans-serif;font-size:9px;font-weight:normal;font-style:normal;line-height:1.33;text-align:center;white-space:normal;text-shadow:none;vertical-align:middle}#loco-admin.wrap .lang{width:20px;height:12px;background-color:#2e892e}#loco-admin.wrap .lang:before{content:attr(lang);vertical-align:baseline}#loco-admin.wrap .lang-el{background-color:#1d48a3}#loco-admin.wrap .lang-el:before{content:"Ελ"}#loco-admin.wrap .region,#loco-admin.wrap .lang-tlh,#loco-admin.wrap .variant-wales,#loco-admin.wrap .region-gb.lang-cy,#loco-admin.wrap .variant-scotland,#loco-admin.wrap .region-gb.lang-gd,#loco-admin.wrap .variant-valencia,#loco-admin.wrap .region-es.variant-valencia,#loco-admin.wrap .lang-ca,#loco-admin.wrap .region-es.lang-ca,#loco-admin.wrap .lang-eo,#loco-admin.wrap .lang-eu{width:16px;height:12px;margin:0 2px;background-image:url(../img/flags-16.png?v=2.6.11);background-repeat:no-repeat;background-color:rgba(0,0,0,0);background-size:100%}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){#loco-admin.wrap .region,#loco-admin.wrap .lang-tlh,#loco-admin.wrap .variant-wales,#loco-admin.wrap .region-gb.lang-cy,#loco-admin.wrap .variant-scotland,#loco-admin.wrap .region-gb.lang-gd,#loco-admin.wrap .variant-valencia,#loco-admin.wrap .region-es.variant-valencia,#loco-admin.wrap .lang-ca,#loco-admin.wrap .region-es.lang-ca,#loco-admin.wrap .lang-eo,#loco-admin.wrap .lang-eu{background-image:url(../img/flags-32.png?v=2.6.11)}}#loco-admin.wrap .region:before,#loco-admin.wrap .lang-tlh:before,#loco-admin.wrap .variant-wales:before,#loco-admin.wrap .region-gb.lang-cy:before,#loco-admin.wrap .variant-scotland:before,#loco-admin.wrap .region-gb.lang-gd:before,#loco-admin.wrap .variant-valencia:before,#loco-admin.wrap .region-es.variant-valencia:before,#loco-admin.wrap .lang-ca:before,#loco-admin.wrap .region-es.lang-ca:before,#loco-admin.wrap .lang-eo:before,#loco-admin.wrap .lang-eu:before{visibility:hidden}#loco-admin.wrap .lang-zh{background-color:#b90000}#loco-admin.wrap .lang-zh.script-hans:before{content:"中国"}#loco-admin.wrap .lang-zh.script-hant:before{content:"中國"}#loco-admin.wrap .__{background-position:0 0}#loco-admin.wrap .lang-eo{background-position:0 -13px}#loco-admin.wrap .lang-tlh{background-position:0 -26px}#loco-admin.wrap .lang-eu{background-position:0 -39px}#loco-admin.wrap .lang-ca,#loco-admin.wrap .region-es.lang-ca{background-position:0 -52px}#loco-admin.wrap .region-tg{background-position:0 -65px}#loco-admin.wrap .region-me{background-position:0 -78px}#loco-admin.wrap .region-la{background-position:0 -91px}#loco-admin.wrap .region-mr{background-position:0 -104px}#loco-admin.wrap .region-ni{background-position:0 -117px}#loco-admin.wrap .region-lv{background-position:0 -130px}#loco-admin.wrap .region-om{background-position:0 -143px}#loco-admin.wrap .region-af{background-position:0 -156px}#loco-admin.wrap .region-cy{background-position:0 -169px}#loco-admin.wrap .region-bj{background-position:0 -182px}#loco-admin.wrap .region-aq{background-position:0 -195px}#loco-admin.wrap .region-cn{background-position:0 -208px}#loco-admin.wrap .region-co{background-position:0 -221px}#loco-admin.wrap .region-cx{background-position:0 -234px}#loco-admin.wrap .region-ag{background-position:0 -247px}#loco-admin.wrap .region-ms{background-position:0 -260px}#loco-admin.wrap .region-md{background-position:0 -273px}#loco-admin.wrap .region-zm{background-position:0 -286px}#loco-admin.wrap .region-vn{background-position:0 -299px}#loco-admin.wrap .region-tf{background-position:0 -312px}#loco-admin.wrap .region-td{background-position:0 -325px}#loco-admin.wrap .region-yt{background-position:0 -338px}#loco-admin.wrap .region-lb{background-position:0 -351px}#loco-admin.wrap .region-mf{background-position:0 -364px}#loco-admin.wrap .region-lu{background-position:0 -377px}#loco-admin.wrap .region-mq{background-position:0 -390px}#loco-admin.wrap .region-cz{background-position:0 -403px}#loco-admin.wrap .region-ae{background-position:0 -416px}#loco-admin.wrap .region-cm{background-position:0 -429px}#loco-admin.wrap .region-bi{background-position:0 -442px}#loco-admin.wrap .region-ar{background-position:0 -455px}#loco-admin.wrap .region-as{background-position:0 -468px}#loco-admin.wrap .region-bh{background-position:0 -481px}#loco-admin.wrap .region-cl{background-position:0 -494px}#loco-admin.wrap .region-ad{background-position:0 -507px}#loco-admin.wrap .region-mp{background-position:0 -520px}#loco-admin.wrap .region-lt{background-position:0 -533px}#loco-admin.wrap .region-mg{background-position:0 -546px}#loco-admin.wrap .region-lc{background-position:0 -559px}#loco-admin.wrap .region-tr{background-position:0 -572px}#loco-admin.wrap .region-ua{background-position:0 -585px}#loco-admin.wrap .region-tv{background-position:0 -598px}#loco-admin.wrap .region-vi{background-position:0 -611px}#loco-admin.wrap .region-mt{background-position:0 -624px}#loco-admin.wrap .region-no{background-position:0 -637px}#loco-admin.wrap .region-mc{background-position:0 -650px}#loco-admin.wrap .region-ch{background-position:0 -663px}#loco-admin.wrap .region-bl{background-position:0 -676px}#loco-admin.wrap .region-aw{background-position:0 -689px}#loco-admin.wrap .region-bz{background-position:0 -702px}#loco-admin.wrap .region-bm{background-position:0 -715px}#loco-admin.wrap .region-ci{background-position:0 -728px}#loco-admin.wrap .region-mu{background-position:0 -741px}#loco-admin.wrap .region-us{background-position:0 -754px}#loco-admin.wrap .region-tw{background-position:0 -767px}#loco-admin.wrap .region-ye{background-position:0 -780px}#loco-admin.wrap .region-mw{background-position:0 -793px}#loco-admin.wrap .region-nl{background-position:0 -806px}#loco-admin.wrap .region-ls{background-position:0 -819px}#loco-admin.wrap .region-bo{background-position:0 -832px}#loco-admin.wrap .region-at{background-position:0 -845px}#loco-admin.wrap .region-ck{background-position:0 -858px}#loco-admin.wrap .region-by{background-position:0 -871px}#loco-admin.wrap .region-au{background-position:0 -884px}#loco-admin.wrap .region-bn{background-position:0 -897px}#loco-admin.wrap .region-ma{background-position:0 -910px}#loco-admin.wrap .region-nz{background-position:0 -923px}#loco-admin.wrap .region-lr{background-position:0 -936px}#loco-admin.wrap .region-mv{background-position:0 -949px}#loco-admin.wrap .region-tc{background-position:0 -962px}#loco-admin.wrap .region-ug{background-position:0 -975px}#loco-admin.wrap .region-tt{background-position:0 -988px}#loco-admin.wrap .region-pl{background-position:0 -1001px}#loco-admin.wrap .region-rs{background-position:0 -1014px}#loco-admin.wrap .region-in{background-position:0 -1027px}#loco-admin.wrap .region-ge{background-position:0 -1040px}#loco-admin.wrap .region-gr{background-position:0 -1053px}#loco-admin.wrap .region-gs{background-position:0 -1066px}#loco-admin.wrap .region-gd{background-position:0 -1079px}#loco-admin.wrap .region-io{background-position:0 -1092px}#loco-admin.wrap .region-hk{background-position:0 -1105px}#loco-admin.wrap .region-kp{background-position:0 -1118px}#loco-admin.wrap .region-kg{background-position:0 -1131px}#loco-admin.wrap .region-pm{background-position:0 -1144px}#loco-admin.wrap .region-sv{background-position:0 -1157px}#loco-admin.wrap .region-re{background-position:0 -1170px}#loco-admin.wrap .region-sa{background-position:0 -1183px}#loco-admin.wrap .region-sc{background-position:0 -1196px}#loco-admin.wrap .region-st{background-position:0 -1209px}#loco-admin.wrap .region-ke{background-position:0 -1222px}#loco-admin.wrap .region-im{background-position:0 -1235px}#loco-admin.wrap .region-kr{background-position:0 -1248px}#loco-admin.wrap .region-gf{background-position:0 -1261px}#loco-admin.wrap .region-dj{background-position:0 -1274px}#loco-admin.wrap .region-gq{background-position:0 -1287px}#loco-admin.wrap .region-gp{background-position:0 -1300px}#loco-admin.wrap .region-dk{background-position:0 -1313px}#loco-admin.wrap .region-gg{background-position:0 -1326px}#loco-admin.wrap .region-il{background-position:0 -1339px}#loco-admin.wrap .region-pn{background-position:0 -1352px}#loco-admin.wrap .region-sb{background-position:0 -1365px}#loco-admin.wrap .region-py{background-position:0 -1378px}#loco-admin.wrap .region-ru{background-position:0 -1391px}#loco-admin.wrap .region-kw{background-position:0 -1404px}#loco-admin.wrap .region-do{background-position:0 -1417px}#loco-admin.wrap .region-gt{background-position:0 -1430px}#loco-admin.wrap .region-gb{background-position:0 -1443px}#loco-admin.wrap .region-gu{background-position:0 -1456px}#loco-admin.wrap .region-je{background-position:0 -1469px}#loco-admin.wrap .region-hm{background-position:0 -1482px}#loco-admin.wrap .region-sg{background-position:0 -1495px}#loco-admin.wrap .region-pk{background-position:0 -1508px}#loco-admin.wrap .region-sr{background-position:0 -1521px}#loco-admin.wrap .region-se{background-position:0 -1534px}#loco-admin.wrap .region-jp{background-position:0 -1547px}#loco-admin.wrap .region-gw{background-position:0 -1560px}#loco-admin.wrap .region-eh{background-position:0 -1573px}#loco-admin.wrap .region-dz{background-position:0 -1586px}#loco-admin.wrap .region-ga{background-position:0 -1599px}#loco-admin.wrap .region-fr{background-position:0 -1612px}#loco-admin.wrap .region-dm{background-position:0 -1625px}#loco-admin.wrap .region-hn{background-position:0 -1638px}#loco-admin.wrap .region-sd{background-position:0 -1651px}#loco-admin.wrap .region-rw{background-position:0 -1664px}#loco-admin.wrap .region-ph{background-position:0 -1677px}#loco-admin.wrap .region-ss{background-position:0 -1690px}#loco-admin.wrap .region-qa{background-position:0 -1703px}#loco-admin.wrap .region-pe{background-position:0 -1716px}#loco-admin.wrap .region-pr{background-position:0 -1729px}#loco-admin.wrap .region-si{background-position:0 -1742px}#loco-admin.wrap .region-ht{background-position:0 -1755px}#loco-admin.wrap .region-es{background-position:0 -1768px}#loco-admin.wrap .region-gl{background-position:0 -1781px}#loco-admin.wrap .region-gm{background-position:0 -1794px}#loco-admin.wrap .region-er{background-position:0 -1807px}#loco-admin.wrap .region-fi{background-position:0 -1820px}#loco-admin.wrap .region-ee{background-position:0 -1833px}#loco-admin.wrap .region-kn{background-position:0 -1846px}#loco-admin.wrap .region-hu{background-position:0 -1859px}#loco-admin.wrap .region-iq{background-position:0 -1872px}#loco-admin.wrap .region-ky{background-position:0 -1885px}#loco-admin.wrap .region-sh{background-position:0 -1898px}#loco-admin.wrap .region-ps{background-position:0 -1911px}#loco-admin.wrap .region-pf{background-position:0 -1924px}#loco-admin.wrap .region-sj{background-position:0 -1937px}#loco-admin.wrap .region-id{background-position:0 -1950px}#loco-admin.wrap .region-is{background-position:0 -1963px}#loco-admin.wrap .region-eg{background-position:0 -1976px}#loco-admin.wrap .region-fk{background-position:0 -1989px}#loco-admin.wrap .region-fj{background-position:0 -2002px}#loco-admin.wrap .region-gn{background-position:0 -2015px}#loco-admin.wrap .region-gy{background-position:0 -2028px}#loco-admin.wrap .region-ir{background-position:0 -2041px}#loco-admin.wrap .region-km{background-position:0 -2054px}#loco-admin.wrap .region-ie{background-position:0 -2067px}#loco-admin.wrap .region-kz{background-position:0 -2080px}#loco-admin.wrap .region-ro{background-position:0 -2093px}#loco-admin.wrap .region-sk{background-position:0 -2106px}#loco-admin.wrap .region-pg{background-position:0 -2119px}#loco-admin.wrap .region-pt{background-position:0 -2132px}#loco-admin.wrap .region-so{background-position:0 -2145px}#loco-admin.wrap .region-sx{background-position:0 -2158px}#loco-admin.wrap .region-hr{background-position:0 -2171px}#loco-admin.wrap .region-ki{background-position:0 -2184px}#loco-admin.wrap .region-jm{background-position:0 -2197px}#loco-admin.wrap .region-ec{background-position:0 -2210px}#loco-admin.wrap .region-et{background-position:0 -2223px}#loco-admin.wrap .region-fo{background-position:0 -2236px}#loco-admin.wrap .region-kh{background-position:0 -2249px}#loco-admin.wrap .region-sy{background-position:0 -2262px}#loco-admin.wrap .region-sn{background-position:0 -2275px}#loco-admin.wrap .region-pw{background-position:0 -2288px}#loco-admin.wrap .region-sl{background-position:0 -2301px}#loco-admin.wrap .region-fm{background-position:0 -2314px}#loco-admin.wrap .region-gi{background-position:0 -2327px}#loco-admin.wrap .region-de{background-position:0 -2340px}#loco-admin.wrap .region-gh{background-position:0 -2353px}#loco-admin.wrap .region-jo{background-position:0 -2366px}#loco-admin.wrap .region-it{background-position:0 -2379px}#loco-admin.wrap .region-pa{background-position:0 -2392px}#loco-admin.wrap .region-sz{background-position:0 -2405px}#loco-admin.wrap .region-sm{background-position:0 -2418px}#loco-admin.wrap .region-tn{background-position:0 -2431px}#loco-admin.wrap .region-ml{background-position:0 -2444px}#loco-admin.wrap .region-cg{background-position:0 -2457px}#loco-admin.wrap .region-ax{background-position:0 -2470px}#loco-admin.wrap .region-ao{background-position:0 -2483px}#loco-admin.wrap .region-bt{background-position:0 -2496px}#loco-admin.wrap .region-bb{background-position:0 -2509px}#loco-admin.wrap .region-cf{background-position:0 -2522px}#loco-admin.wrap .region-mm{background-position:0 -2535px}#loco-admin.wrap .region-li{background-position:0 -2548px}#loco-admin.wrap .region-na{background-position:0 -2561px}#loco-admin.wrap .region-mz{background-position:0 -2574px}#loco-admin.wrap .region-to{background-position:0 -2587px}#loco-admin.wrap .region-vg{background-position:0 -2600px}#loco-admin.wrap .region-ve{background-position:0 -2613px}#loco-admin.wrap .region-tz{background-position:0 -2626px}#loco-admin.wrap .region-tm{background-position:0 -2639px}#loco-admin.wrap .region-mx{background-position:0 -2652px}#loco-admin.wrap .region-nc{background-position:0 -2665px}#loco-admin.wrap .region-mo{background-position:0 -2678px}#loco-admin.wrap .region-lk{background-position:0 -2691px}#loco-admin.wrap .region-cd{background-position:0 -2704px}#loco-admin.wrap .region-al{background-position:0 -2717px}#loco-admin.wrap .region-bw{background-position:0 -2730px}#loco-admin.wrap .region-cr{background-position:0 -2743px}#loco-admin.wrap .region-bv{background-position:0 -2756px}#loco-admin.wrap .region-am{background-position:0 -2769px}#loco-admin.wrap .region-az{background-position:0 -2782px}#loco-admin.wrap .region-ba{background-position:0 -2795px}#loco-admin.wrap .region-mn{background-position:0 -2808px}#loco-admin.wrap .region-nu{background-position:0 -2821px}#loco-admin.wrap .region-my{background-position:0 -2834px}#loco-admin.wrap .region-tl{background-position:0 -2847px}#loco-admin.wrap .region-ws{background-position:0 -2860px}#loco-admin.wrap .region-th{background-position:0 -2873px}#loco-admin.wrap .region-nf{background-position:0 -2886px}#loco-admin.wrap .region-ly{background-position:0 -2899px}#loco-admin.wrap .region-ai{background-position:0 -2912px}#loco-admin.wrap .region-br{background-position:0 -2925px}#loco-admin.wrap .region-cv{background-position:0 -2938px}#loco-admin.wrap .region-be{background-position:0 -2951px}#loco-admin.wrap .region-ca{background-position:0 -2964px}#loco-admin.wrap .region-bd{background-position:0 -2977px}#loco-admin.wrap .region-cw{background-position:0 -2990px}#loco-admin.wrap .region-bs{background-position:0 -3003px}#loco-admin.wrap .region-ng{background-position:0 -3016px}#loco-admin.wrap .region-mk{background-position:0 -3029px}#loco-admin.wrap .region-np{background-position:0 -3042px}#loco-admin.wrap .region-va{background-position:0 -3055px}#loco-admin.wrap .region-uz{background-position:0 -3068px}#loco-admin.wrap .region-um{background-position:0 -3081px}#loco-admin.wrap .region-tk{background-position:0 -3094px}#loco-admin.wrap .region-vc{background-position:0 -3107px}#loco-admin.wrap .region-zw{background-position:0 -3120px}#loco-admin.wrap .region-nr{background-position:0 -3133px}#loco-admin.wrap .region-ne{background-position:0 -3146px}#loco-admin.wrap .region-cu{background-position:0 -3159px}#loco-admin.wrap .region-bq{background-position:0 -3172px}#loco-admin.wrap .region-bf{background-position:0 -3185px}#loco-admin.wrap .region-bg{background-position:0 -3198px}#loco-admin.wrap .region-cc{background-position:0 -3211px}#loco-admin.wrap .region-mh{background-position:0 -3224px}#loco-admin.wrap .region-za{background-position:0 -3237px}#loco-admin.wrap .region-uy{background-position:0 -3250px}#loco-admin.wrap .region-wf{background-position:0 -3263px}#loco-admin.wrap .region-vu{background-position:0 -3276px}#loco-admin.wrap .region-tj{background-position:0 -3289px}#loco-admin.wrap .variant-scotland,#loco-admin.wrap .region-gb.lang-gd{background-position:0 -3302px}#loco-admin.wrap .variant-valencia,#loco-admin.wrap .region-es.variant-valencia{background-position:0 -3315px}#loco-admin.wrap .variant-wales,#loco-admin.wrap .region-gb.lang-cy{background-position:0 -3328px}#loco-admin.wrap .x-eu{background-position:0 -3341px}#loco-admin.wrap .region-jp,#loco-admin.wrap .region-fi,#loco-admin.wrap .region-cy,#loco-admin.wrap .region-pl,#loco-admin.wrap .region-fo,#loco-admin.wrap .region-kr,#loco-admin.wrap .region-ru,#loco-admin.wrap .region-va{outline:solid 1px #ddd}#loco-admin.wrap .overlay-title .region,#loco-admin.wrap .overlay-title .lang-ca,#loco-admin.wrap .overlay-title .lang-eo,#loco-admin.wrap .overlay-title .lang-eu,#loco-admin.wrap .overlay-title .variant-wales,#loco-admin.wrap .overlay-title .variant-scotland,#loco-admin.wrap .overlay-title .variant-valencia,#loco-admin.wrap .overlay-title .region-gb.lang-gd,#loco-admin.wrap .overlay-title .region-gb.lang-cy,#loco-admin.wrap .overlay-title .region-es.lang-ca,#loco-admin.wrap .overlay-title .region-es.variant-valencia,#loco-admin.wrap .overlay-title .lang-tlh{outline:none}#loco-admin.wrap span.lang code{display:none}#loco-admin.wrap .icon-share:before{content:"↑"}#loco-admin.wrap .icon-notag:before{content:""}#loco-admin.wrap .icon-magnify:before{content:"🔍"}#loco-admin.wrap .icon-python:before{content:"🐍"}#loco-admin.wrap .icon-unicode:before{content:"u"}#loco-admin.wrap .icon-json:before{content:"{"}#loco-admin.wrap .icon-translate:before{content:""}#loco-admin.wrap .icon-project:before{content:""}#loco-admin.wrap .icon-plural:before{content:"᛬"}#loco-admin.wrap .icon-max:before,#loco-admin.wrap .icon-full-screen:before{content:""}#loco-admin.wrap .icon-min:before{content:""}#loco-admin.wrap .icon-sort:before{content:""}#loco-admin.wrap .icon-ok:before,#loco-admin.wrap .notice-success>.has-icon:before,#loco-admin.wrap .notice-success p>strong.has-icon:before,#loco-admin.wrap .panel-success>.has-icon:before,#loco-admin.wrap .panel-success p>strong.has-icon:before{content:"✓"}#loco-admin.wrap .icon-ok-empty:before{content:"🗌"}#loco-admin.wrap .icon-play:before{content:"⏵"}#loco-admin.wrap .icon-checkbox-checked:before,#loco-admin.wrap .selector.multi li.selected .icon-checkbox:before{content:"☑"}#loco-admin.wrap .icon-checkbox-unchecked:before{content:"☐"}#loco-admin.wrap .icon-checkbox-partial:before{content:"☉"}#loco-admin.wrap .icon-radio-checked:before{content:"⚫"}#loco-admin.wrap .icon-radio-unchecked:before{content:"⚪"}#loco-admin.wrap .icon-ext:before{content:"⬈"}#loco-admin.wrap .icon-pdf:before{content:"P"}#loco-admin.wrap .icon-zip:before{content:"Z"}#loco-admin.wrap .icon-bullist:before{content:""}#loco-admin.wrap .icon-numlist:before{content:""}#loco-admin.wrap .icon-indent:before{content:""}#loco-admin.wrap .icon-outdent:before{content:""}#loco-admin.wrap .icon-link:before{content:""}#loco-admin.wrap .icon-unlink:before{content:""}#loco-admin.wrap .icon-bold:before{content:"B"}#loco-admin.wrap .icon-italic:before{content:"I"}#loco-admin.wrap .icon-library:before,#loco-admin.wrap .icon-glossary:before{content:"📚"}#loco-admin.wrap .icon-star:before{content:""}#loco-admin.wrap .icon-user:before{content:""}#loco-admin.wrap .icon-remove:before,#loco-admin.wrap a.icon.close:before{content:"×"}#loco-admin.wrap .icon-cog:before{content:""}#loco-admin.wrap .icon-trash:before{content:""}#loco-admin.wrap .icon-time:before,#loco-admin.wrap .icon-history:before{content:""}#loco-admin.wrap .icon-download:before{content:""}#loco-admin.wrap .icon-revert:before{content:""}#loco-admin.wrap .icon-sync:before{content:""}#loco-admin.wrap .icon-lock:before,#loco-admin.wrap .notice-locked>.has-icon:before,#loco-admin.wrap .notice-locked p>strong.has-icon:before,#loco-admin.wrap .panel-locked>.has-icon:before,#loco-admin.wrap .panel-locked p>strong.has-icon:before{content:""}#loco-admin.wrap .icon-flag:before{content:""}#loco-admin.wrap .icon-tag:before{content:""}#loco-admin.wrap .icon-tags:before{content:""}#loco-admin.wrap .icon-print:before{content:""}#loco-admin.wrap .icon-camera:before{content:""}#loco-admin.wrap .icon-pencil:before{content:""}#loco-admin.wrap .icon-add:before{content:""}#loco-admin.wrap .icon-del:before{content:""}#loco-admin.wrap .icon-clear:before,#loco-admin.wrap a.icon.clear:before{content:""}#loco-admin.wrap .icon-ok-sign:before{content:"✔"}#loco-admin.wrap .icon-help:before{content:"?"}#loco-admin.wrap .icon-info:before,#loco-admin.wrap .notice-info>.has-icon:before,#loco-admin.wrap .notice-info p>strong.has-icon:before,#loco-admin.wrap .panel-info>.has-icon:before,#loco-admin.wrap .panel-info p>strong.has-icon:before{content:"ℹ"}#loco-admin.wrap .icon-cancel:before{content:""}#loco-admin.wrap .icon-warn:before,#loco-admin.wrap .notice-error>.has-icon:before,#loco-admin.wrap .notice-error p>strong.has-icon:before,#loco-admin.wrap .panel-error>.has-icon:before,#loco-admin.wrap .panel-error p>strong.has-icon:before,#loco-admin.wrap .notice-warning>.has-icon:before,#loco-admin.wrap .notice-warning p>strong.has-icon:before,#loco-admin.wrap .panel-warning>.has-icon:before,#loco-admin.wrap .panel-warning p>strong.has-icon:before{content:""}#loco-admin.wrap .icon-comment:before{content:""}#loco-admin.wrap .icon-bar-chart:before{content:""}#loco-admin.wrap .icon-key:before{content:""}#loco-admin.wrap .icon-cogs:before{content:""}#loco-admin.wrap .icon-comments:before{content:""}#loco-admin.wrap .icon-signout:before{content:""}#loco-admin.wrap .icon-signin:before{content:""}#loco-admin.wrap .icon-upload:before{content:""}#loco-admin.wrap .icon-twitter:before{content:"🐦"}#loco-admin.wrap .icon-facebook:before{content:""}#loco-admin.wrap .icon-github:before{content:""}#loco-admin.wrap .icon-feed:before{content:""}#loco-admin.wrap .icon-globe:before{content:""}#loco-admin.wrap .icon-wrench:before,#loco-admin.wrap .notice-debug>.has-icon:before,#loco-admin.wrap .notice-debug p>strong.has-icon:before,#loco-admin.wrap .panel-debug>.has-icon:before,#loco-admin.wrap .panel-debug p>strong.has-icon:before{content:""}#loco-admin.wrap .icon-group:before{content:""}#loco-admin.wrap .icon-cloud:before{content:""}#loco-admin.wrap .icon-copy:before{content:""}#loco-admin.wrap .icon-save:before{content:""}#loco-admin.wrap .icon-menu:before{content:""}#loco-admin.wrap .icon-table:before{content:""}#loco-admin.wrap .icon-caret-down:before{content:"▼"}#loco-admin.wrap .icon-caret-up:before{content:"▲"}#loco-admin.wrap .icon-caret-right:before{content:"▶"}#loco-admin.wrap .icon-mail:before{content:""}#loco-admin.wrap .icon-cloud-upload:before{content:""}#loco-admin.wrap .icon-file:before{content:""}#loco-admin.wrap .icon-circle-white:before{content:"⚬"}#loco-admin.wrap .icon-circle-black:before{content:"●"}#loco-admin.wrap .icon-eraser:before{content:""}#loco-admin.wrap .icon-unlock:before{content:""}#loco-admin.wrap .icon-apple:before{content:""}#loco-admin.wrap .icon-android:before{content:""}#loco-admin.wrap .icon-robot:before{content:"🤖"}#loco-admin.wrap .icon-back:before{content:"⬅"}#loco-admin.wrap .icon-next:before{content:"➔"}#loco-admin.wrap .icon-arrow-up:before{content:"⬆"}#loco-admin.wrap .icon-arrow-down:before{content:"⬇"}#loco-admin.wrap .icon-confirm:before{content:"!"}#loco-admin.wrap .icon-visible:before{content:"⏿"}#loco-admin.wrap .icon-hidden:before{content:"*"}#loco-admin.wrap .icon-calendar:before{content:""}#loco-admin.wrap .icon-ccard:before{content:""}#loco-admin.wrap .icon-caret-left:before{content:"◀"}#loco-admin.wrap .icon-pro:before{content:"⚡"}#loco-admin.wrap .icon-bell:before{content:""}#loco-admin.wrap .icon-code:before{content:""}#loco-admin.wrap .icon-privacy:before{content:"🛡"}#loco-admin.wrap .icon-hellip:before{content:"…"}#loco-admin.wrap .icon-vellip:before{content:"⁞"}#loco-admin.wrap .icon-collapse:before{content:""}#loco-admin.wrap .icon-expand:before{content:""}#loco-admin.wrap .icon-wordpress:before{content:""}#loco-admin.wrap .icon-restore:before{content:""}#loco-admin.wrap .icon-pilcrow:before{content:"¶"}#loco-admin.wrap .icon,#loco-admin.wrap .has-icon:before,#loco-admin.wrap .has-dashicon:before{speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#loco-admin.wrap .icon,#loco-admin.wrap .has-icon:before{font-family:"loco"}#loco-admin.wrap .has-dashicon:before{font-family:"dashicons";font-size:1.4em}#loco-admin.wrap .has-icon:before,#loco-admin.wrap .has-dashicon:before,#loco-admin.wrap .has-icon>span{display:inline-block;vertical-align:middle}#loco-admin.wrap .only-icon{text-align:center}#loco-admin.wrap .only-icon:before{text-align:center;padding:0}#loco-admin.wrap .only-icon span{display:none}#loco-admin.wrap a,#loco-admin.wrap .wp-core-ui .button-link{text-decoration:none}#loco-admin.wrap h3:hover>a.loco-anchor{color:#ccc}#loco-admin.wrap h3:hover>a.loco-anchor:before{content:"¶"}#loco-admin.wrap .has-lang>span{display:inline-block;vertical-align:middle}#loco-admin.wrap a.has-lang:hover>span:last-child{text-decoration:underline}#loco-admin.wrap h1 ul,#loco-admin.wrap h1 li{margin:0;padding:0}#loco-admin.wrap h1 li{display:inline-block}#loco-admin.wrap h1 li:after{content:"/";color:#999;text-shadow:none;display:inline-block}#loco-admin.wrap h1 li:last-child:after{content:""}#loco-admin.wrap h1 li:last-child a{color:inherit;pointer-events:none;cursor:auto}#loco-admin.wrap h2 span{color:#999;font-weight:normal}#loco-admin.wrap h2 .loco-meta,#loco-admin.wrap h3 .loco-meta{color:inherit;font-size:14px;font-weight:normal;vertical-align:middle}#loco-admin.wrap .wp-list-table td:first-child .icon{width:16px;display:inline-block;text-align:center}#loco-admin.wrap .wp-list-table td{white-space:nowrap}#loco-admin.wrap .wp-list-table td>a,#loco-admin.wrap .wp-list-table td>time{display:inline-block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-o-text-overflow:ellipsis;-ms-text-overflow:ellipsis}#loco-admin.wrap .wp-list-table th.loco-sort{cursor:pointer}#loco-admin.wrap .wp-list-table th.loco-sort:hover{color:#000}#loco-admin.wrap .wp-list-table th.loco-sort.loco-asc:after,#loco-admin.wrap .wp-list-table th.loco-sort.loco-desc:after{padding:0 0 0 4px;color:#999}#loco-admin.wrap .wp-list-table th.loco-sort.loco-desc:after{content:"▼"}#loco-admin.wrap .wp-list-table th.loco-sort.loco-asc:after{content:"▲"}#loco-admin.wrap .wp-list-table td.has-row-actions{overflow:visible}#loco-admin.wrap .wp-list-table tfoot td{font-size:12px;color:#666}#loco-admin.wrap form.loco-filter{display:block;margin-bottom:5px}#loco-admin.wrap form.loco-filter .icon-filter{color:#999}#loco-admin.wrap nav.above-list-table{margin-bottom:1em}#loco-admin.wrap nav.above-list-table a{display:inline-block;margin-right:1em}#loco-admin.wrap .wp-core-ui button.inverted,#loco-admin.wrap .wp-core-ui button.inverted:hover,#loco-admin.wrap .po-fuzzy button.icon-cloud{background:-moz-linear-gradient(top, #cccccc 0%, #e0e0e0 30%, #fefefe 100%);background:-webkit-linear-gradient(top, #cccccc 0%, #e0e0e0 30%, #fefefe 100%);background:linear-gradient(to bottom, #cccccc 0%, #e0e0e0 30%, #fefefe 100%);-webkit-box-shadow:0 1px 0 #fff;-moz-box-shadow:0 1px 0 #fff;box-shadow:0 1px 0 #fff;border-color:gray}#loco-admin.wrap .wp-core-ui button:active,#loco-admin.wrap .wp-core-ui button.inverted:active{border-color:#5b9dd9}#loco-admin.wrap .loco-clearable{display:inline-block;vertical-align:middle;position:relative}#loco-admin.wrap .loco-clearable a.clear,#loco-admin.wrap .auto-comp-wrap a.clear{top:0;right:0;outline:none;margin:0;border:solid 1px rgba(0,0,0,0);padding:5px 5px;position:absolute;font-size:1em;line-height:inherit}#loco-admin.wrap .loco-clearable a.clear:before,#loco-admin.wrap .auto-comp-wrap a.clear:before{vertical-align:middle;padding:0;color:#999}#loco-admin.wrap .loco-clearable a.clear:hover:before,#loco-admin.wrap .auto-comp-wrap a.clear:hover:before{color:#333}#loco-admin.wrap .loco-clearable a.clear span,#loco-admin.wrap .auto-comp-wrap a.clear span{display:none}#loco-admin.wrap .loco-clearable ::-ms-clear,#loco-admin.wrap .auto-comp-wrap ::-ms-clear{display:none}#loco-admin.wrap div.progress{color:#000;background:rgba(0,0,0,0) !important}#loco-admin.wrap div.progress *{height:100%;overflow:hidden;white-space:nowrap}#loco-admin.wrap div.progress .t{background-color:#ddd;border:1px solid #ccc}#loco-admin.wrap div.progress .t .bar{float:left;clear:none;background-color:#3db63d}#loco-admin.wrap div.progress .t .bar.f{background-color:#bd2c00}#loco-admin.wrap div.progress .l{display:block}#loco-admin.wrap td div.progress .t{border:none}#loco-admin.wrap td div.progress .l{display:none}#loco-admin.wrap code.path{color:#333;margin:0;padding:0;display:inline;background:rgba(0,0,0,0)}#loco-admin.wrap .loco-danger{color:#d54e21}#loco-admin.wrap .notice,#loco-admin.wrap .panel{border:1px solid #ddd;border-left-width:4px;background:#fff;position:relative;margin:1em 0;padding:1px 12px;box-shadow:0 1px 1px rgba(0,0,0,.04)}#loco-admin.wrap .notice p,#loco-admin.wrap .panel p{margin:.5em 0;padding:2px}#loco-admin.wrap .notice-success,#loco-admin.wrap .panel-success{border-color:#00a32a}#loco-admin.wrap .notice-success>.has-icon,#loco-admin.wrap .notice-success p>strong.has-icon,#loco-admin.wrap .panel-success>.has-icon,#loco-admin.wrap .panel-success p>strong.has-icon{color:#000}#loco-admin.wrap .notice-warning,#loco-admin.wrap .panel-warning{border-color:orange}#loco-admin.wrap .notice-error,#loco-admin.wrap .panel-error{border-color:#dc3232}#loco-admin.wrap .notice-debug,#loco-admin.wrap .panel-debug{border-color:#00a0d2}#loco-admin.wrap .notice-info,#loco-admin.wrap .panel-info{border-color:#72aee6}#loco-admin.wrap .notice-danger,#loco-admin.wrap .panel-danger{border-color:#dc3232}#loco-admin.wrap .notice-locked,#loco-admin.wrap .panel-locked{border-color:orange}#loco-admin.wrap .notice>.has-icon:before,#loco-admin.wrap .notice p>strong.has-icon:before,#loco-admin.wrap .panel>.has-icon:before,#loco-admin.wrap .panel p>strong.has-icon:before{padding-right:6px}#loco-admin.wrap .notice>p>a.button.has-icon:before,#loco-admin.wrap .panel>p>a.button.has-icon:before{width:1.2em}#loco-admin.wrap .notice>p>em,#loco-admin.wrap .panel>p>em{color:#d54e21;font-style:normal}#loco-admin.wrap .notice>p>em a,#loco-admin.wrap .panel>p>em a{color:inherit}#loco-admin.wrap .notice>p a:hover,#loco-admin.wrap .notice>nav a:hover,#loco-admin.wrap .panel>p a:hover,#loco-admin.wrap .panel>nav a:hover{text-decoration:underline}#loco-admin.wrap .notice>p a.button,#loco-admin.wrap .notice>nav a.button,#loco-admin.wrap .panel>p a.button,#loco-admin.wrap .panel>nav a.button{text-decoration:inherit}#loco-admin.wrap .notice>p:not(:first-child)>strong.has-icon:first-child,#loco-admin.wrap .panel>p:not(:first-child)>strong.has-icon:first-child{visibility:hidden}#loco-admin.wrap .notice.has-nav,#loco-admin.wrap .panel.has-nav{display:flex;flex-direction:row}#loco-admin.wrap .notice.has-nav p,#loco-admin.wrap .notice.has-nav nav,#loco-admin.wrap .panel.has-nav p,#loco-admin.wrap .panel.has-nav nav{line-height:22px;flex-grow:1}#loco-admin.wrap .notice.has-nav nav,#loco-admin.wrap .panel.has-nav nav{text-align:right;padding:2px;margin:.5em 0}#loco-admin.wrap .notice.has-nav.is-dismissible,#loco-admin.wrap .panel.has-nav.is-dismissible{padding-right:38px}#loco-admin.wrap .notice.has-nav a,#loco-admin.wrap .panel.has-nav a{white-space:nowrap}#loco-admin.wrap .notice.has-nav nav>span,#loco-admin.wrap .panel.has-nav nav>span{color:#666}#loco-admin.wrap .notice>h3>span,#loco-admin.wrap .panel>h3>span{display:inline-block;vertical-align:middle}#loco-admin.wrap ul.problems li{font-style:italic}#loco-admin.wrap label{position:relative}#loco-admin.wrap label.for-disabled,#loco-admin.wrap label.for-disabled>input{cursor:default !important}#loco-admin.wrap input.regular-text,#loco-admin.wrap textarea.regular-text{width:25em}#loco-admin.wrap .button-link{padding:0 10px 1px}#loco-admin.wrap .button-danger{background:#ba0000;border-color:#900 #600 #600;-webkit-box-shadow:0 1px 0 #600;-moz-box-shadow:0 1px 0 #600;box-shadow:0 1px 0 #600;color:#fff;text-shadow:0 -1px 1px #600,1px 0 1px #600,0 1px 1px #600,-1px 0 1px #600}#loco-admin.wrap .button-success{background:#00b500;border-color:#090 #2e892e #2e892e;-webkit-box-shadow:0 1px 0 #2e892e;-moz-box-shadow:0 1px 0 #2e892e;box-shadow:0 1px 0 #2e892e;color:#fff;text-shadow:0 -1px 1px #2e892e,1px 0 1px #2e892e,0 1px 1px #2e892e,-1px 0 1px #2e892e}#loco-admin.wrap .button-success:hover{background:#3db63d}#loco-admin.wrap .button-danger[disabled],#loco-admin.wrap .button-success[disabled]{text-shadow:none !important}#loco-admin.wrap form button.loco-loading.button-large{padding-left:0}#loco-admin.wrap form button.loco-loading.button-large:before{width:16px;height:16px;margin:0 4px;content:" ";font-size:16px;line-height:1;display:inline-block;vertical-align:sub}#loco-admin.wrap ::placeholder{color:#ccc}#loco-admin.wrap ::-webkit-input-placeholder{color:#ccc}#loco-admin.wrap :-moz-placeholder{color:#ccc;opacity:1}#loco-admin.wrap ::-moz-placeholder{color:#ccc;opacity:1}#loco-admin.wrap :-ms-input-placeholder{color:#ccc}#loco-admin.wrap .ielt10 .placeheld{color:#ccc}#loco-admin.wrap a.has-raquo:after{content:" »"}#loco-admin.wrap a.has-laquo:before{content:"« "}#loco-admin.wrap span.inline-spinner{display:inline-block;min-width:16px;min-height:16px;background:rgba(0,0,0,0) url(../img/spin-modal.gif?v=2.6.11) 0 0 no-repeat}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){#loco-admin.wrap span.inline-spinner{background-size:100%;background-image:url(../img/spin-modal@2x.gif?v=2.6.11)}}.js #wpbody-content>.notice{display:none}.js #wpbody-content>.notice.inline{display:block}.js #loco-admin.wrap .jshide,.no-js #loco-admin.wrap .jsonly{display:none !important}#loco-admin.wrap .invis{visibility:hidden}.form-table tr td{vertical-align:top}.form-table tr.compact td{padding-top:0}.form-table tr th a.icon-help{display:none}.loco-modal{min-width:50%;min-height:100px}.loco-modal-wide{min-width:90% !important}.loco-modal-no-close .ui-dialog-titlebar-close{display:none}.loco-modal .ui-dialog-titlebar-close{overflow:hidden}.loco-modal.request-filesystem-credentials-dialog{top:15% !important;max-height:85% !important}.loco-modal.request-filesystem-credentials-dialog>.ui-dialog-content{background:inherit}.loco-modal.request-filesystem-credentials-dialog .ftp-password>label>em:last-child{display:none}.ui-dialog-content>div.loco-loading{height:100%;background:rgba(0,0,0,0) url(../img/spin-modal.gif?v=2.6.11) center 20px no-repeat}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.ui-dialog-content>div.loco-loading{background-size:16px;background-image:url(../img/spin-modal.gif?v=2.6.11@2x.gif?v=2.6.11)}}#loco-po-ref ol li{color:#aaa;margin:0;white-space:pre;padding:0 0 0 1em;font:normal 12px/17px Consolas,Monaco,monospace;background:rgba(0,0,0,0);border-left:1px solid #eee}#loco-po-ref ol li code{margin:0;padding:0;display:inline;background:inherit}#loco-po-ref ol li.highlighted{color:#666;background-color:#f8eec7}#loco-po-ref ol li.highlighted code.T_CONSTANT_ENCAPSED_STRING{color:#c931c7}#loco-credit>*{vertical-align:middle}#loco-credit>a{display:inline-block;position:relative;overflow:hidden;background:rgba(0,0,0,0) url(../img/logo-foot.gif?v=2.6.11) 0 0 no-repeat;height:30px;width:100px;text-indent:200px;-webkit-transition-duration:0s;transition-duration:0s}#loco-credit>a:hover{background-position:0 -35px}#loco-content{position:relative}#footer-upgrade span:before{color:#ccc;content:" | ";display:inline;padding-left:.5em;padding-right:.5em}#footer-upgrade span:first-child:before{content:"";display:none}.loco-clearfix:after{content:".";display:block;clear:both;visibility:hidden;line-height:0;height:0}dl.debug dt{font-weight:bold}dl.debug dt,dl.debug dd{white-space:pre}.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(0,115,170,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#0073aa}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}.wp-core-ui .button-primary.loco-loading[disabled]{color:#66c6e4 !important;background:#008ec2 !important;border-color:#008ec2 !important}.wp-core-ui .button-primary.loco-loading[disabled]:before{background:rgba(0,0,0,0) url(../img/skins/fresh/spin-primary-button.gif?v=2.6.11) 0 0 no-repeat !important}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.wp-core-ui .button-primary.loco-loading[disabled]:before{background-size:16px !important;background-image:url(../img/skins/fresh/spin-primary-button@2x.gif?v=2.6.11) !important}}pub/css/fileinfo.css000064400000001637147206622240010443 0ustar00#loco-admin.wrap .panel-info nav,#loco-admin.wrap .notice-info nav{display:block;position:absolute;right:0;top:0;font-size:1.3em;padding:1em}#loco-admin.wrap .panel-info nav a,#loco-admin.wrap .notice-info nav a{color:#666;margin-left:10px}#loco-admin.wrap .panel-info nav a:hover,#loco-admin.wrap .notice-info nav a:hover{color:#000;text-decoration:none}#loco-admin.wrap .panel-info dl,#loco-admin.wrap .notice-info dl{margin-top:0;display:inline-block}#loco-admin.wrap .panel-info dl dt,#loco-admin.wrap .panel-info dl dd,#loco-admin.wrap .notice-info dl dt,#loco-admin.wrap .notice-info dl dd{line-height:1.4em}#loco-admin.wrap .panel-info dl dt,#loco-admin.wrap .notice-info dl dt{font-weight:bold;color:#555}#loco-admin.wrap .panel-info dl dd,#loco-admin.wrap .notice-info dl dd{margin-left:0;margin-bottom:.8em}#loco-admin.wrap .panel-info dl div.progress .l,#loco-admin.wrap .notice-info dl div.progress .l{display:none}pub/css/editor.css000064400000053151147206622240010134 0ustar00#loco-editor{border:solid 1px #ccc}#loco-editor ._ajax_loader_f2{background-image:url(../img/ajax-loader-f2.gif?v=2.6.11);background-repeat:no-repeat;min-height:16px}#loco-editor ._ajax_loader_f2x4{background:rgba(0,0,0,0) url(../img/ajax-loader-f2-x4.gif?v=2.6.11) 0 0 no-repeat;min-height:75px}#loco-editor ._ajax_loader_cc{background-image:url(../img/ajax-loader-cc.gif?v=2.6.11);background-repeat:no-repeat;min-height:16px}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){#loco-editor ._ajax_loader_f2{background-image:url(../img/ajax-loader-f2-x2.gif?v=2.6.11);background-size:16px}#loco-editor ._ajax_loader_cc{background-image:url(../img/ajax-loader-cc-x2.gif?v=2.6.11);background-size:16px}}#loco-editor ._green_glow_inner,#loco-editor .is-editable>.wg-content>textarea:focus,#loco-editor .is-editable>.wg-content.has-focus .ace_scroller,#loco-editor .is-editable>.wg-content.has-focus .wysihtml-editor,#loco-editor .is-editable>.wg-content.has-focus .mce-content-body{-webkit-box-shadow:inset 0 0 10px 0 #3db63d;-moz-box-shadow:inset 0 0 10px 0 #3db63d;box-shadow:inset 0 0 10px 0 #3db63d}#loco-editor ._green_glow_outer{-webkit-box-shadow:0 0 .5em 0 #3db63d;-moz-box-shadow:0 0 .5em 0 #3db63d;box-shadow:0 0 .5em 0 #3db63d}#loco-editor .loco-font,#loco-editor .is-table .wg-thead .wg-sortable>header:after{font-family:"loco";speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#loco-editor div.ta-mirror{position:absolute;top:0;border:solid 1px rgba(0,0,0,0);color:rgba(0,0,0,0);padding:.4em .6em;pointer-events:none}#loco-editor div.ta-mirror span{position:relative}#loco-editor div.ta-mirror span:after{color:#ccc;white-space:pre;display:inline-block;position:absolute;top:0;line-height:normal}#loco-editor div.ta-mirror .crlf:after{content:"¬"}#loco-editor div.ta-mirror .eof:after{content:"¶"}#loco-editor div.ta-mirror .x20:after{content:"·";color:#aaa}#loco-editor div.ta-mirror .x9:after{content:"⟶"}#loco-editor div.ta-mirror,#loco-editor .has-mirror textarea{white-space:pre-wrap;word-wrap:break-word;word-spacing:0px}#loco-editor .has-mirror ::placeholder{color:rgba(0,0,0,0)}#loco-editor .has-mirror ::-webkit-input-placeholder{color:rgba(0,0,0,0)}#loco-editor .has-mirror :-moz-placeholder{color:rgba(0,0,0,0);opacity:1}#loco-editor .has-mirror ::-moz-placeholder{color:rgba(0,0,0,0);opacity:1}#loco-editor .has-mirror :-ms-input-placeholder{color:rgba(0,0,0,0)}#loco-editor .has-mirror .ielt10 .placeheld{color:rgba(0,0,0,0)}#loco-editor div.ta-mirror{left:0;text-align:left}#loco-editor div.ta-mirror span:after{left:0}#loco-editor div.ta-mirror .eol:after{padding-left:.2em}#loco-editor [dir=RTL] div.ta-mirror{left:auto;right:0;text-align:right}#loco-editor [dir=RTL] div.ta-mirror span:after{right:0}#loco-editor [dir=RTL] div.ta-mirror .eol:after{padding-left:0;padding-right:.2em}#loco-editor .has-proxy>textarea{display:none !important}#loco-editor .has-proxy .ace_editor{height:100%;font-size:13px !important;line-height:1.4 !important}#loco-editor .has-proxy .ace_editor .ace_marker-layer .ace_bracket{display:none}#loco-editor .has-proxy .ace_print-margin{display:none}#loco-editor .has-proxy .wg-overlay,#loco-editor .has-mirror .wg-overlay,#loco-editor .has-overlay>textarea{display:none !important}#loco-editor .ace_printf{color:#b90690;background-color:#edf1be}#loco-editor .ace_locked{color:gray}#loco-editor .ace_icu-quoted{color:gray}#loco-editor .ace_icu{color:#697eb9}#loco-editor .ace_icu.ace_name{color:#b90600}#loco-editor .resizer{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0 0 6px 0;background:#f0f0f0 url(../img/wg/splity.png?v=2.6.11) center bottom no-repeat;border:1px solid #ddd;overflow:hidden;cursor:move;cursor:row-resize;cursor:s-resize}#loco-editor .resizer>*{height:100%;border:none}#loco-editor .glossarized{white-space:pre-wrap;word-wrap:break-word}#loco-editor .glossarized .term{color:inherit;text-decoration:underline dashed;text-underline-offset:3px;cursor:help}#loco-editor ._ajax_loader_f2{background-image:url(../img/ajax-loader-f2.gif?v=2.6.11);background-repeat:no-repeat;min-height:16px}#loco-editor ._ajax_loader_f2x4{background:rgba(0,0,0,0) url(../img/ajax-loader-f2-x4.gif?v=2.6.11) 0 0 no-repeat;min-height:75px}#loco-editor ._ajax_loader_cc{background-image:url(../img/ajax-loader-cc.gif?v=2.6.11);background-repeat:no-repeat;min-height:16px}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){#loco-editor ._ajax_loader_f2{background-image:url(../img/ajax-loader-f2-x2.gif?v=2.6.11);background-size:16px}#loco-editor ._ajax_loader_cc{background-image:url(../img/ajax-loader-cc-x2.gif?v=2.6.11);background-size:16px}}#loco-editor ._green_glow_inner,#loco-editor .is-editable>.wg-content>textarea:focus,#loco-editor .is-editable>.wg-content.has-focus .ace_scroller,#loco-editor .is-editable>.wg-content.has-focus .wysihtml-editor,#loco-editor .is-editable>.wg-content.has-focus .mce-content-body{-webkit-box-shadow:inset 0 0 10px 0 #3db63d;-moz-box-shadow:inset 0 0 10px 0 #3db63d;box-shadow:inset 0 0 10px 0 #3db63d}#loco-editor ._green_glow_outer{-webkit-box-shadow:0 0 .5em 0 #3db63d;-moz-box-shadow:0 0 .5em 0 #3db63d;box-shadow:0 0 .5em 0 #3db63d}#loco-editor .loco-font,#loco-editor .is-table .wg-thead .wg-sortable>header:after{font-family:"loco";speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#loco-editor .wg-cell,#loco-editor .wg-cell>div{clear:both;position:relative;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;outline:none}#loco-editor .wg-cell{left:0;top:0;padding:0;margin:0;overflow:hidden}#loco-editor .wg-split{background:#eee}#loco-editor .wg-body:after{content:".";display:block;clear:both;visibility:hidden;line-height:0;height:0}#loco-editor .wg-split-x>div>.not-first>*{margin-left:6px}#loco-editor .wg-split-y>div>.not-first>*{margin-top:6px}#loco-editor .wg-split>div>.has-title .wg-content{margin-top:0}#loco-editor .wg-split-x>div>.wg-cell{float:left;clear:none;height:100%}#loco-editor .wg-split-x>div{cursor:move;cursor:ew-resize;cursor:col-resize}#loco-editor .wg-split>div>.not-first:before{display:block;position:absolute;overflow:hidden;content:" "}#loco-editor .wg-split-x>div>.not-first:before{width:6px;height:100%;background:rgba(0,0,0,0) url(../img/wg/splitx.png?v=2.6.11) center center no-repeat}#loco-editor .wg-split-y>div{cursor:move;cursor:ns-resize;cursor:row-resize}#loco-editor .wg-split-y>div>.not-first:before{height:6px;width:100%;background:rgba(0,0,0,0) url(../img/wg/splity.png?v=2.6.11) center center no-repeat}#loco-editor .wg-split>div.locked{cursor:default}#loco-editor .wg-split-x>div.locked>.not-first>*{margin-left:0px}#loco-editor .wg-split-y>div.locked>.not-first>*{margin-top:0px}#loco-editor .wg-split>div.locked>.not-first:before{display:none}#loco-editor .has-title>header{background:#e2e2e2;cursor:default !important;margin:0;white-space:nowrap}#loco-editor .wg-content{background:#fff;cursor:default;padding:4px 6px;overflow:hidden;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}#loco-editor .wg-toolbar{top:0;right:0;margin-top:6px;position:absolute}#loco-editor .wg-toolbar>span{color:#fff;background:#000;display:block;padding:0 6px;cursor:default !important}#loco-editor .wg-toolbar>span:active{color:#000;background-color:#fff}#loco-editor .wg-content,#loco-editor .wg-tbody>div{overflow:scroll;overflow-x:hidden;overflow-y:auto}#loco-editor .is-table{background-color:#fff}#loco-editor .is-table .wg-thead .has-title>header{font-weight:normal;background:rgba(0,0,0,0);padding:4px 0;margin:0 0 0 10px}#loco-editor .is-table .wg-thead>div>.not-first:before{background-position:center center}#loco-editor .is-table .wg-thead{background:#e2e2e2;border-bottom:solid 1px #ccc}#loco-editor .is-table .wg-tbody{background-image:url(data:image/gif;base64,R0lGODlhAQAsAIABAPz8/P///yH5BAEAAAEALAAAAAABACwAAAIIjI+pAe0PIygAOw==);position:relative}#loco-editor .is-table .wg-cols>div{float:left;clear:none}#loco-editor .is-table .wg-cols>div>div{white-space:nowrap;line-height:1.7em;padding-left:10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-o-text-overflow:ellipsis;-ms-text-overflow:ellipsis;background-color:#fff}#loco-editor .is-table .wg-cols>div>div:nth-child(even){background-color:#f7f7f7}#loco-editor .is-table .wg-cols>div>div.selected{background-color:#3db63d;color:#fff}#loco-editor .is-table .wg-cols>div>div.selected::selection{background-color:#fff;color:#000}#loco-editor .is-table .wg-cols>div:first-child>div{padding-left:4px}#loco-editor .is-table .wg-dead{clear:both}#loco-editor .is-table .wg-thead .wg-sortable>header{cursor:pointer !important}#loco-editor .is-table .wg-thead .wg-sortable>header:after{padding:0 0 0 5px;color:#999}#loco-editor .is-table .wg-thead .wg-sortable.wg-asc>header:after{content:"▲"}#loco-editor .is-table .wg-thead .wg-sortable.wg-desc>header:after{content:"▼"}#loco-editor .is-table .wg-thead .wg-sortable:hover>header:after{color:#000}#loco-editor .is-field>.wg-content{cursor:text;padding:0;line-height:normal;overflow:hidden;overflow-y:hidden}#loco-editor .is-field>.wg-content>div,#loco-editor .is-field>.wg-content>textarea{font-size:14px;line-height:1.4;border:1px solid #ddd;width:100%;height:100%;padding:8px 10px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:0;-moz-border-radius:0;-ms-border-radius:0;-o-border-radius:0;border-radius:0}#loco-editor .is-field>.wg-content textarea{resize:none;overflow:auto;display:block}#loco-editor .is-field>.wg-content>.ace_editor{padding:0;border-width:0}#loco-editor .is-field>.wg-content>div[contenteditable]{overflow:scroll;overflow-x:hidden;overflow-y:auto}#loco-editor .is-field>.wg-content>div.mce-content-body{position:relative}#loco-editor .is-readonly>.wg-content{cursor:default}#loco-editor .is-readonly>.wg-content>div,#loco-editor .is-readonly>.wg-content>textarea,#loco-editor .is-readonly>.wg-content>textarea[readonly]{background:#f8f8f8;text-shadow:0 1px #fff;color:inherit}#loco-editor .is-readonly>.wg-content .ace_scroller{cursor:default;background:#f8f8f8}#loco-editor .is-readonly>.wg-content .ace_cursor-layer{display:none}#loco-editor .is-readonly>.wg-content>textarea[readonly]{cursor:default}#loco-editor .is-editable>.wg-content[dir=RTL] .ace_editor .ace_line{direction:ltr;unicode-bidi:bidi-override}#loco-editor .wg-split-x>nav.wg-tabs{white-space:nowrap;text-align:center;cursor:default}#loco-editor .wg-split-x>nav.wg-tabs>a{display:inline-block;padding:.5em 1em;margin-top:.2em;color:#000;text-decoration:none;-webkit-border-radius:.2em .2em 0 0;-moz-border-radius:.2em .2em 0 0;-ms-border-radius:.2em .2em 0 0;-o-border-radius:.2em .2em 0 0;border-radius:.2em .2em 0 0;background-color:#f3f7fd}#loco-editor .wg-split-x>nav.wg-tabs>a:hover{background-color:#fff}#loco-editor .wg-split-x>nav.wg-tabs>a.active{background-color:#3db63d;color:#fff}#loco-editor .wg-split>div>.has-nav>.wg-body{margin-top:0px}#loco-editor .wg-dead{visibility:hidden}#loco-editor .is-field>.wg-content>.wg-count{display:block;position:absolute;right:10px;bottom:8px;padding:0;margin:0;width:auto;font-size:12px;height:12px;line-height:normal;text-align:right;white-space:nowrap;pointer-events:none;border:none;z-index:1;color:#ccc}#loco-editor .is-field>.wg-content>.wg-count.is-eq{color:#000}#loco-editor .is-field>.wg-content>.wg-count.is-gt{color:#bd2c00;font-weight:bold}@media all and (max-width: 768px){#loco-editor .wg-split-x>div>.not-first>*{margin-left:20px}#loco-editor .wg-split-y>div>.not-first>*{margin-top:20px}#loco-editor .wg-split-x>div>.not-first:before{width:20px}#loco-editor .wg-split-y>div>.not-first:before{height:20px}#loco-editor .is-table .wg-thead header{padding-left:20px}}#loco-editor .is-table .po-fuzzy{color:#b59829;font-weight:bold}#loco-editor .is-table .po-empty{color:#1f507a;font-weight:bold}#loco-editor .is-table .po-flagged{color:#bd2c00}#loco-editor .is-table .wg-cols>div:first-child>div:before{font-family:loco;vertical-align:inherit;display:inline-block;content:" ";width:1.3em;line-height:1}#loco-editor .is-table .wg-cols>div:first-child>div.po-fuzzy:before{content:""}#loco-editor .is-table .wg-cols>div:first-child>div.po-flagged:before{content:""}#loco-editor .is-table .wg-cols>div:first-child>div.po-comment:before{content:"";color:#999}#loco-editor .is-table .wg-cols>div:first-child>div.po-unsaved:before{content:"";color:#f1d040}#loco-editor .is-table .wg-cols>div:first-child>div.po-error:before{content:"";color:#bd2c00}#loco-editor .is-table .wg-cols>div:first-child>div.selected:before{color:#fff !important}#loco-editor .wg-cell>.meta{color:#333;margin:0 !important;padding:6px 10px;font-weight:normal;font-size:13px;line-height:1.4em;cursor:default !important}#loco-editor .wg-cell>.meta>p{display:block;white-space:pre-line;margin:0 0 .3em 0}#loco-editor .wg-cell>.meta>p.tags{line-height:1.8em;white-space:nowrap}#loco-editor .wg-cell>.meta>p.tags span{border:1px solid rgba(0,0,0,0)}#loco-editor .wg-cell>.meta>p.tags span:first-child{border-left:none}#loco-editor .wg-cell>.meta>p.tags mark{color:#999;background-color:#eee;border-radius:2px;border:1px solid silver;padding:2px 4px;margin-right:4px}#loco-editor .wg-cell>.meta>p.tags mark.ctxt{color:#fff;background-color:silver}#loco-editor .wg-cell>.meta .icon-warn{color:#bd2c00}#loco-editor .wg-cell>.meta .has-icon:before{padding-right:0;width:17px}#loco-editor .wg-cell>.meta code{font-size:12px}#loco-editor .is-table .wg-cols>div>div>mark{display:inline-block;vertical-align:text-bottom;font:inherit;font-weight:normal;color:#fff;border-radius:2px;font-size:90%;line-height:1;padding:.2em .3em;background-color:rgba(0,0,0,.25)}#loco-editor .is-table .wg-cols>div>div.selected.po-flagged{background-color:#bd2c00}#loco-editor .is-table .wg-cols>div>div.selected.po-fuzzy{background-color:#b59829}#loco-editor .is-table .wg-cols>div>div.selected.po-empty{background-color:#999}#loco-editor #po-target header nav{display:block;position:absolute;right:0px;top:0px;padding:2px}#loco-editor #po-target header nav button{margin-left:5px}#loco-editor #po-target header nav.po-empty .icon-cloud{display:none !important}#loco-editor #po-list .wg-content{padding:0}#loco-editor #po-source>.wg-body>.has-title>header{background:rgba(0,0,0,0);font-weight:normal;float:left;clear:none;min-width:3.4em}#loco-editor #po-source>.wg-body>.has-title>.wg-content{clear:none}#loco-editor .has-title>header{line-height:normal;padding:7px}#loco-editor .has-title>header .lang{margin-right:6px;margin-bottom:1px}#loco-editor .trg-rtl #po-list-tbody .wg-cols>div[for=po-list-col-target]>div,#loco-editor .src-rtl #po-list-tbody .wg-cols>div[for=po-list-col-source]>div{direction:rtl;padding-left:0;padding-right:10px;text-align:right}#loco-editor #po-source>.wg-body>.has-title>h2{min-width:4.5em}#loco-editor .is-table .wg-td{font-size:13px}#loco-editor header,#loco-editor nav{display:block;position:relative}#loco-editor-inner{min-height:600px;font-size:14px;clear:both}#loco-editor-inner>div.loco-loading{height:100px;background:rgba(0,0,0,0) url(../img/spin-editor-button.gif?v=2.6.11) center 20px no-repeat}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){#loco-editor-inner>div.loco-loading{background-size:16px;background-image:url(../img/spin-editor-button@x2.gif?v=2.6.11)}}#loco-editor-inner h2{color:#000;line-height:1}#loco-editor-inner .wg-cell>.meta code{padding:0;color:#0073aa;background:inherit;cursor:pointer}#loco-editor-inner .wg-cell>.meta code:hover{text-decoration:underline}#loco-admin.wrap #loco-editor>nav{font-size:14px;height:50px}#loco-admin.wrap #loco-editor>nav form{display:block;float:left;clear:none;padding:0;margin:0}#loco-admin.wrap #loco-editor>nav form.aux{float:right;margin-right:5px}#loco-admin.wrap #loco-editor>nav fieldset{display:block;position:relative;float:left;clear:none}#loco-admin.wrap #loco-editor>nav button,#loco-admin.wrap #loco-editor>nav input[type=text]{display:block;position:relative;float:left;clear:none;margin:5px 0 0 5px;height:36px;text-align:left;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}#loco-admin.wrap #loco-editor>nav input[type=text]{padding:0 10px;font-size:14px;line-height:normal}#loco-admin.wrap #loco-editor>nav .invalid input[type=text]:focus{border-color:#c00;-webkit-box-shadow:0 0 2px rgba(153,0,0,.5);-moz-box-shadow:0 0 2px rgba(153,0,0,.5);box-shadow:0 0 2px rgba(153,0,0,.5)}#loco-admin.wrap #loco-editor>nav .loco-clearable{padding:0 20px}#loco-admin.wrap #loco-editor>nav .loco-clearable.invalid a.clear:before{color:#c00}#loco-admin.wrap #loco-editor>nav .loco-clearable a.clear{right:25px;line-height:2;top:2px}#loco-admin.wrap #loco-editor>nav .loco-clearable a.clear:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}#loco-admin.wrap #loco-editor>nav .loco-clearable a.clear:hover:before{color:#c00}#loco-admin.wrap #loco-editor>nav .loco-clearable a.clear:active:before{color:#000}#loco-admin.wrap #loco-editor>nav button.only-icon{width:40px}#loco-admin.wrap form.aux{float:right;margin-right:5px}#loco-admin.wrap button.has-icon:before{width:16px;padding:0}#loco-admin.wrap button.has-icon.loco-loading:before{content:" ";height:16px;background:rgba(0,0,0,0) url(../img/spin-editor-button.gif?v=2.6.11) 0 0 no-repeat}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){#loco-admin.wrap button.has-icon.loco-loading:before{background-size:100%;background-image:url(../img/spin-editor-button@x2.gif?v=2.6.11)}}#loco-admin.wrap .button,#loco-admin.wrap input[type=text]{border-color:#aaa;color:#444}#loco-admin.wrap .button-link{color:#888;padding-right:5px}#loco-admin.wrap .button:hover,#loco-admin.wrap .button-link:hover{color:#000}#loco-admin.wrap .button-primary,#loco-admin.wrap .button-primary:hover{color:#fff}#loco-admin.wrap button.icon-translate:before{font-size:16px}.loco-modal .loco-api{position:relative;padding:10px;background:#f7f7f7;border:solid 1px #eee;font-size:14px;margin-bottom:16px}.loco-modal .loco-api p{padding:0;margin:0;font-size:inherit}.loco-modal .loco-api blockquote{font-weight:bold;margin:0;padding:10px 0}.loco-modal .loco-api .loco-api-credit{padding:0;display:block;font-size:12px;white-space:nowrap;position:absolute;right:10px;bottom:10px}.loco-modal .loco-api-loading{text-indent:20px;background:rgba(0,0,0,0) url(../img/spin-modal.gif?v=2.6.11) 10px center no-repeat}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.loco-modal .loco-api-loading{background-size:16px;background-image:url(../img/spin-modal@2x.gif?v=2.6.11)}}.loco-modal .loco-alert p{margin-bottom:2em;font-size:14px}.loco-modal .loco-alert nav{display:block;position:relative;margin:1em 0}.loco-modal .loco-alert nav a{display:inline-block;margin-right:1em;padding:10px}.loco-modal .loco-api>select{width:100%;max-width:100%}#loco-auto{display:none;min-width:50%;min-height:300px;position:relative}#loco-auto form blockquote{margin:0;padding:1em 0;font-size:14px}.loco-api-deepl .loco-api-credit a,.loco-api-google .loco-api-credit a,.loco-api-microsoft .loco-api-credit a{height:20px;text-indent:-999px;text-align:left;display:inline-block;vertical-align:top;overflow:hidden}.loco-api-deepl .loco-api-credit a{width:50px;background:rgba(0,0,0,0) url(../img/api/deepl.png?v=2.6.11) 0 0 no-repeat}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.loco-api-deepl .loco-api-credit a{background-size:100%;background-image:url(../img/api/deepl@2x.png?v=2.6.11)}}.loco-api-google .loco-api-credit a{width:50px;background:rgba(0,0,0,0) url(../img/api/google.png?v=2.6.11) 0 0 no-repeat}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.loco-api-google .loco-api-credit a{background-size:100%;background-image:url(../img/api/google@2x.png?v=2.6.11)}}.loco-api-microsoft .loco-api-credit a{width:76px;background-image:url(../img/api/microsoft.png?v=2.6.11)}@media only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx){.loco-api-microsoft .loco-api-credit a{background-size:100%;background-image:url(../img/api/microsoft@2x.png?v=2.6.11)}}pub/font/loco.svg000064400000240704147206622240007771 0ustar00 Generated by IcoMoon pub/font/loco.eot000064400000064404147206622240007762 0ustar00ilhLP locoRegularVersion 1.0loco 0OS/2o`cmapk=gaspglyfvFM]head(xbc6hheac$hmtxdlocaęemaxpf nameM6fbposthL 3 @@@ 444 l@,!*?BIPZu{ & ^!9!##%%%%%& &&&''+  $+.06s!G& *?BIPZu{ & ^!9!##%%%%%& &&&''+ "&-03s!G&W7#߳ނda\YK }uؐ  /-,+*)(&][qD D ; !**??BBIIPP ZZ uu {{  & & ^ ^!9!9!!####%%%%%%%%%%& & &&&&&&''"''$++% )-/0 E"$M&+P-.V00X36Y]assbcd!!eGGfghi&&j  k&&lm  nopq797979n-B4767632#"'&'&5;27674'&+";276=4'&+";;edxwee;;;;eewxde;;e i ~ mmwee;;;;eewxde;;;;edxcmm,8Qhp47676327632#"'&547&'&'&537&'&547%32765476327654'&#"76767&'7#7 X34    >NCQ$67=F;CI  2"# 33HnL DDkgA@$336%.b76OABnLe%54'&+";27654'&'&#"32767632;276547676767676765#"'&'&54767632nn 0/2I K  $n   ;;eewxde;;;;edxwee;;mm2++y 9'  !"&   nxde;;;;edxwee;;;;eew@(>54'.'&#!!27>7654&32+#32 F./55/.FDe*<<)f,>>"T/5/.FF./5FtFK55KK55K #3!53#5@@@@@@KUaq.'".'.'>706'4&/.+""3267>73267>7>'4&'>7>72.506>7>7%#"&'>32'.#!"3!2654&#5#!"&5463!23J -#8  /     ,8 M2;:,d"  ," #1  K *3Y'0  JP! !//!!/!E   s  I)  $lF ?3 9 RX  8 83f*# 40K #7 5 !/!!//!`!P `   #';DY3##3#;##3#;##3#3#;265#53'.#!"3!2654&#5#!"&5463!23```````````````````  P! !//!!/!E   @@@@@@@@   @!/!!//!`!P `  $/%##3353#"&'.53!>7>=!#3  ;7ST7A  /# H*a*H#/ &M546;5#"+32;5#"&=4&'>51!54&+532;#"+5326=467.51. Kk.  .kK .p. Kk.  .kK .Bh .hkKh .h. hKkh. h&CC&h .hkKh .h. hKkh. h&CC&3476763!2#"+"'&5#+"'&5&'&'&522E@  > R > T8H&$Z_EC *n H !ECQ,%#"/#"/&54?'&54?6327632NNNNNNNN   % 7% 7Vgggg33 33%)>5476;2+"'&5!5476;2+"'&5!5476;2+"'&5nn%nn$nnmmmmmm(<75476;2+"'&5476;2+"'&55476;2+"'&nnnnnnnn7654'.'&"'.'&547>7632j]^((((^]jj]^((((^]jPEFiiFEPPEFiiFE((^]jj]^((((^]jj]^((iFEPPEFiiFEPPEFi7C"327>7654'.'&"'.'&547>76324632#"&j]^((((^]jj]^((((^]jPEFiiFEPPEFiiFEpPPppPPp((^]jj]^((((^]jj]^((iFEPPEFiiFEPPEFiPppPPppn1"32767654'&'&##"'&'&54767632UGH)**)HGUTHG****GHT;;eewxde;;;;edxwee;;**GHTUGH)**)HGUTHG**xde;;;;edxwee;;;;eew ' ` @ n34/&#"'&#"32765#"'&'&54767632 4   4  6 ;;eewxde;;;;edxwee;; 3 4  7 \xde;;;;edxwee;;;;eewI%54763!'&54?632#"/&54?!"'&5+t+nnI+ +n%47632!2#!#"'&5%t+n+t+I,u%47632#"/+"'&5#"/&5tt*I+t+n+%4?632476;27632#"'&5*I*+m+u !!!'!'%7!@@@@@@ #/!!!!!!4632#"&4632#"&4632#"&K55KK55KK55KK55KK55KK55K@5KK55KK5KK55KK5KK55KK )%!!!!!!'#5#53#575#53#535#535#5@@@@2@<2@@@@@@ !!!!!!!!!!@@@@ !!!!!!!!!!%@@@@ @r67>'&7#"&/.546?>327.#"326?>''.#"7.546?>32#"&'326?64@ '< '   c  A19..c9:*#Ac9:*#A   c  A19. <' '  c   A.-c*u.Ac*u.A  c   A-2eimquy}#"&/.546?>327.#"326?>''.#"7.546?>32#"&'326?64''773#3#'3#3#   c  A19..c9:*#Ac9:*#A   c  A19...i@@.@@  c   A--d*u.Ac*u.A  c   A--.@.)@n)>%#!"'&=4763!2#!"'&=4763!2#!"'&=4763!2n  $  $  $ I I $I I %I I L#!"'&5;2767676767+"'&'&'&'&'&'&'&'&54763!2%%!M "!!" a *4    4ba# ,J%!:%%9  G~-))$    $DD**$,& +!54'&#"#!"'&5476;54763232$*+=<++LKiiLKn<++++?yVV32Q#*+=+$<;M!=Y%4'&#"3276534'&#"327657#!"'&=4763!32?!2#"'&76;476;232     I M!,-!N            N NE  !@\%4'&#"3276534'&#"327657#!"'&=476;;276732++"'&5#"'&7632     I #"        n      r  %1Tm27654'&#"327654'&#"2#"'&=476+#"'&=##"'&5'#"'&5!'!4767'&7663276#"'&=47632    9w+O* =%$$%>))7<=6)    i }89CC98KKK(=#"'&#"#"'&54763232763267670JI41%#/.WUTA@b)<;8:(D6.%('3$'+*eHGpSR%$'5AG89#++$ V==n,E54'&+54'&+"#";;276=327657#"'&'&54767632  I   I  ;;eewxde;;;;edxwee;;I   I   %xde;;;;edxwee;;;;eewn-54'&#!"3!27657#"'&'&54767632 I  ;;eewxde;;;;edxwee;;I I %xde;;;;edxwee;;;;eewH+"'&5&547632#"'&'&'&#"#"'&547676323276767632%%$I{X"$$((*m   !i=65H$$ %*,*%LB  S  E!  %(<MU+"'&5476;23+"'&5476;2+"'&5476;2!3!276765!'&'#+#!"'&5#"'&=476;7676;232%$$%%%%I7&%&7( (III[  fCU$0"#!"/ $``n)=W7!5!!34'&/&'&##!"'&=#354763!254'&+";276#!"'&54763!2IJ IInnn I$  '2:T#!"'&=4763!25#!"'&=4763!2!#"'&=!3&/&'#!"'&54763!2InnIn$n $$%%$ &#"'%#"'&547'&547%6321 1    E    **:#"''&'1&765&?1761676767676767&'&5476767632EDvv(+q%   Z34)(ED__hvvDEdTT11d'  2KKVJDD1111TUc:t#"'#"'&'&5456?1761766767676767&'&547676321"'&'&'#"'3276767654'%66]]m13GX G()65]]mm]]66)(G XG31s!\UTCG&' I++ODC'( 3  )< Mi =B ~ .))= Nj =D I 1,4'&#"32764'&#"327654'&#"32765#"/+"'&/&'#"'&5476767&/&'&=476?67&'&5476763267676;27632#"'&'#"'#"'&547&'&=4767&54767676326326?2#"'&'#"'#"'&547&'&=4767&54767676326326?2++<=++++=<++X   "" B j CS WY   "# Aj CR WnU F  E UU   E UU F  E UU   E U<++++<=+**+Ij&""3 =X 3L  j '!"3 >W 3L   P A)  )A P  @  )(@  IP A(  (A P  @  ((@  ns2#"'&'&54767627676367676&767676?"'&'4#&'&'&'&'&'&'4'&'&'&#"#&#"##"6'&6'4'34'&'&'&'&'&'&'&&'&2'&65656747676#76767632376''&'&&'&'&#"6'&'6'&#&3&'&''#"'"'&76&'&'7676367&&'&'&'"74&'&3"70176'&'&'&'2#21327676767765'0'&'&5&743256'&'&'&''"'"'67674767656'&##&'&'&547674'67672723767&'&#7670'&77236736745&5'=&'&7674767676'27"'&''676776'676756547676?6#76'6'&'23276'&67&#&'&'4'&'&'&'&'&'&'&"#"0&'"'&'&#&76'&#&'&5wee;;;;eewxde;;;;ed         S3                  ]vS    n;;eewxde;;;;edxwee;;     -Q                  X   b(?'334#"32765'#5'763244I=+  5%b__I44=I  6n%_^IK\m#"'&5432327#!"'&54767676767676323276767632#"'&547632#"'&547632+&'654'32767632#"'&547632S];L/ G"#!'%.d**E E** #&''&'&# ++=<++++<=++@AZ[A@@A[ZA@I /L;]/%'!#"GI++<=++++=<++F- OCE'(('E!"   "!=++++=<++++<[@@@@[[@AA@[-FCO \=++++=<++++%5F%#!"'&54767676767676323276767632#"'&547632%**E F)* #&&&'&&# A@[[@@@@[[@AE'(('E!"   "![@@@@[[@AA@[b)4'&#"3276#"'&'&=476;2bg%$%%h=)E4'&#"3276#"'&'&=476;23#"'&'654'&' bg%$  g%$$%%%h hI %9#5##37#34'&#!"3!276#!"'&54763!2%nmܒImI%m%%%n$nIII%%&C74'&#"3276%#"/&547%#"'&5476326767632   p{<++8k CDPjKKKKj!$% o+*## n  z>8++&M0/KKjiLK  a= )>Sg|%54'&+";2765=4'&+";276554'&+";276554'&+";276554'&+";27654'&+";276554'&+";27654'&+";276=4'&+";2767#!"'&54763!2%$$%%I&%%&mmnnmmnnnnmmnnnnnn&&m&%/S7#"'&547632+"'&'&'&'&'&=476;+"'&5&'&'&'&'&'&=476; -. .- % M  ]] \ST@A''% R 33QRno{ jk==. .. t ]]  N '&B@TS[ {onRQ33 R ==kjI1A2#"'&54763%2#!"'&5476;76763!2327654'&#"%D0000DD1001D<++++<<++++< % niKLLKijKKKK701DD0000DD10++=<++++<=++MMmKLijKKKKjiLK R7!5!!5#"'&=!4'&#"327653+#!"'&=#"'&=476;4763!232[   I !-$W $-! In\%  [[-! 7 W !-'*->I2#!"'&=!"'&5476?676;26;3375#+!54767#+! '"ɫp% "I yǵ5n3L+"'&=476;5476;24'&'&#"32767653#"'&'&54767632%**GHTUGH)**)HGUTHG**;;eewxde;;;;edxwee;;%THG****GHTUGH)**)HGUxde;;;;edxwee;;;;eew)A%54'&+";2765'4'&+";2767#!"'&'&767632Inn ~ j    mm$$ $$% 3###535476;#"$aa11qQ y)עbh56 Qnt'&=4'67676767654'6'&&#"&'&'&#"'&'&'&/"32?'&'&54767632nTT! -6895 ! - !     ST;;edxwee;;rs, y7&%1E04A  A40E1%%'     ,srwee;;;;eewn.[#"'&'#"'&54763!232767676;2#!"'&54?&#"+"'&=676327632_%tuTNN=J   N)338LCB( n  OTsMBB( q%vuSOO=J  [__ :J   N&%&A :   ON%%A 9 `_ 9I nG#!"'&?&#"32767672#"'&'&'&547676763276n  OTs<66''''66doxde;nId47667632#"'&'&56?633276767654'&'&'&#"#!"'&55476;5476;2+"'&5K=NOTYQQ;;####;;QQYbYY>O *<=D;66(''(66;843(N  %%% I9 ##;;QQYYQR:;##)*KO6''66<;66('&O %#4dm#"'#373!"'&54763!2#7!2#!"'&547635#5##3#27673&767676?"'&'67673&'3&1tQ0[(, ,y- m6z,  ,,  ,>E#$$"12,-23"&&#I)q( l ,z,  ,llh ,, ,y, ;uu;#&&    &%#h&BB&p ,LX.++"7>=#5!26764'2#"&546326=4&'&"3!";546"&54632p 1.PQ5+?@*3f=(CA.,->?,5p0D'/J  8/IO)/.>^8PA,,4  3.Q<12g@i8@,,< 5.Q75=\@0=b5P;#"'327&'&'327&'&=&'&547&5476326767'6,,=>VVbe<0/@**',&Eccr66MP6?7<658(JJJDD45SO#$8 32C)(02+U34M66; !B$ 7)!3#!!3#@@@QQ@@$A%'>54'.'&#"32672?6&'47>7632#"'.'&5Z;;BA::VZ;;B,P$)0K<((-.)*?<((--*)?$T-A<;YU::AB;>NCQ$67=F;CI  2"# 33HnL DDkgA@$336%.b76OABnLe%54'&+";27654'&'&#"32767632;276547676767676765#"'&'&54767632nn 0/2I K  $n   ;;eewxde;;;;edxwee;;mm2++y 9'  !"&   nxde;;;;edxwee;;;;eew@(>54'.'&#!!27>7654&32+#32 F./55/.FDe*<<)f,>>"T/5/.FF./5FtFK55KK55K #3!53#5@@@@@@KUaq.'".'.'>706'4&/.+""3267>73267>7>'4&'>7>72.506>7>7%#"&'>32'.#!"3!2654&#5#!"&5463!23J -#8  /     ,8 M2;:,d"  ," #1  K *3Y'0  JP! !//!!/!E   s  I)  $lF ?3 9 RX  8 83f*# 40K #7 5 !/!!//!`!P `   #';DY3##3#;##3#;##3#3#;265#53'.#!"3!2654&#5#!"&5463!23```````````````````  P! !//!!/!E   @@@@@@@@   @!/!!//!`!P `  $/%##3353#"&'.53!>7>=!#3  ;7ST7A  /# H*a*H#/ &M546;5#"+32;5#"&=4&'>51!54&+532;#"+5326=467.51. Kk.  .kK .p. Kk.  .kK .Bh .hkKh .h. hKkh. h&CC&h .hkKh .h. hKkh. h&CC&3476763!2#"+"'&5#+"'&5&'&'&522E@  > R > T8H&$Z_EC *n H !ECQ,%#"/#"/&54?'&54?6327632NNNNNNNN   % 7% 7Vgggg33 33%)>5476;2+"'&5!5476;2+"'&5!5476;2+"'&5nn%nn$nnmmmmmm(<75476;2+"'&5476;2+"'&55476;2+"'&nnnnnnnn7654'.'&"'.'&547>7632j]^((((^]jj]^((((^]jPEFiiFEPPEFiiFE((^]jj]^((((^]jj]^((iFEPPEFiiFEPPEFi7C"327>7654'.'&"'.'&547>76324632#"&j]^((((^]jj]^((((^]jPEFiiFEPPEFiiFEpPPppPPp((^]jj]^((((^]jj]^((iFEPPEFiiFEPPEFiPppPPppn1"32767654'&'&##"'&'&54767632UGH)**)HGUTHG****GHT;;eewxde;;;;edxwee;;**GHTUGH)**)HGUTHG**xde;;;;edxwee;;;;eew ' ` @ n34/&#"'&#"32765#"'&'&54767632 4   4  6 ;;eewxde;;;;edxwee;; 3 4  7 \xde;;;;edxwee;;;;eewI%54763!'&54?632#"/&54?!"'&5+t+nnI+ +n%47632!2#!#"'&5%t+n+t+I,u%47632#"/+"'&5#"/&5tt*I+t+n+%4?632476;27632#"'&5*I*+m+u !!!'!'%7!@@@@@@ #/!!!!!!4632#"&4632#"&4632#"&K55KK55KK55KK55KK55KK55K@5KK55KK5KK55KK5KK55KK )%!!!!!!'#5#53#575#53#535#535#5@@@@2@<2@@@@@@ !!!!!!!!!!@@@@ !!!!!!!!!!%@@@@ @r67>'&7#"&/.546?>327.#"326?>''.#"7.546?>32#"&'326?64@ '< '   c  A19..c9:*#Ac9:*#A   c  A19. <' '  c   A.-c*u.Ac*u.A  c   A-2eimquy}#"&/.546?>327.#"326?>''.#"7.546?>32#"&'326?64''773#3#'3#3#   c  A19..c9:*#Ac9:*#A   c  A19...i@@.@@  c   A--d*u.Ac*u.A  c   A--.@.)@n)>%#!"'&=4763!2#!"'&=4763!2#!"'&=4763!2n  $  $  $ I I $I I %I I L#!"'&5;2767676767+"'&'&'&'&'&'&'&'&54763!2%%!M "!!" a *4    4ba# ,J%!:%%9  G~-))$    $DD**$,& +!54'&#"#!"'&5476;54763232$*+=<++LKiiLKn<++++?yVV32Q#*+=+$<;M!=Y%4'&#"3276534'&#"327657#!"'&=4763!32?!2#"'&76;476;232     I M!,-!N            N NE  !@\%4'&#"3276534'&#"327657#!"'&=476;;276732++"'&5#"'&7632     I #"        n      r  %1Tm27654'&#"327654'&#"2#"'&=476+#"'&=##"'&5'#"'&5!'!4767'&7663276#"'&=47632    9w+O* =%$$%>))7<=6)    i }89CC98KKK(=#"'&#"#"'&54763232763267670JI41%#/.WUTA@b)<;8:(D6.%('3$'+*eHGpSR%$'5AG89#++$ V==n,E54'&+54'&+"#";;276=327657#"'&'&54767632  I   I  ;;eewxde;;;;edxwee;;I   I   %xde;;;;edxwee;;;;eewn-54'&#!"3!27657#"'&'&54767632 I  ;;eewxde;;;;edxwee;;I I %xde;;;;edxwee;;;;eewH+"'&5&547632#"'&'&'&#"#"'&547676323276767632%%$I{X"$$((*m   !i=65H$$ %*,*%LB  S  E!  %(<MU+"'&5476;23+"'&5476;2+"'&5476;2!3!276765!'&'#+#!"'&5#"'&=476;7676;232%$$%%%%I7&%&7( (III[  fCU$0"#!"/ $``n)=W7!5!!34'&/&'&##!"'&=#354763!254'&+";276#!"'&54763!2IJ IInnn I$  '2:T#!"'&=4763!25#!"'&=4763!2!#"'&=!3&/&'#!"'&54763!2InnIn$n $$%%$ &#"'%#"'&547'&547%6321 1    E    **:#"''&'1&765&?1761676767676767&'&5476767632EDvv(+q%   Z34)(ED__hvvDEdTT11d'  2KKVJDD1111TUc:t#"'#"'&'&5456?1761766767676767&'&547676321"'&'&'#"'3276767654'%66]]m13GX G()65]]mm]]66)(G XG31s!\UTCG&' I++ODC'( 3  )< Mi =B ~ .))= Nj =D I 1,4'&#"32764'&#"327654'&#"32765#"/+"'&/&'#"'&5476767&/&'&=476?67&'&5476763267676;27632#"'&'#"'#"'&547&'&=4767&54767676326326?2#"'&'#"'#"'&547&'&=4767&54767676326326?2++<=++++=<++X   "" B j CS WY   "# Aj CR WnU F  E UU   E UU F  E UU   E U<++++<=+**+Ij&""3 =X 3L  j '!"3 >W 3L   P A)  )A P  @  )(@  IP A(  (A P  @  ((@  ns2#"'&'&54767627676367676&767676?"'&'4#&'&'&'&'&'&'4'&'&'&#"#&#"##"6'&6'4'34'&'&'&'&'&'&'&&'&2'&65656747676#76767632376''&'&&'&'&#"6'&'6'&#&3&'&''#"'"'&76&'&'7676367&&'&'&'"74&'&3"70176'&'&'&'2#21327676767765'0'&'&5&743256'&'&'&''"'"'67674767656'&##&'&'&547674'67672723767&'&#7670'&77236736745&5'=&'&7674767676'27"'&''676776'676756547676?6#76'6'&'23276'&67&#&'&'4'&'&'&'&'&'&'&"#"0&'"'&'&#&76'&#&'&5wee;;;;eewxde;;;;ed         S3                  ]vS    n;;eewxde;;;;edxwee;;     -Q                  X   b(?'334#"32765'#5'763244I=+  5%b__I44=I  6n%_^IK\m#"'&5432327#!"'&54767676767676323276767632#"'&547632#"'&547632+&'654'32767632#"'&547632S];L/ G"#!'%.d**E E** #&''&'&# ++=<++++<=++@AZ[A@@A[ZA@I /L;]/%'!#"GI++<=++++=<++F- OCE'(('E!"   "!=++++=<++++<[@@@@[[@AA@[-FCO \=++++=<++++%5F%#!"'&54767676767676323276767632#"'&547632%**E F)* #&&&'&&# A@[[@@@@[[@AE'(('E!"   "![@@@@[[@AA@[b)4'&#"3276#"'&'&=476;2bg%$%%h=)E4'&#"3276#"'&'&=476;23#"'&'654'&' bg%$  g%$$%%%h hI %9#5##37#34'&#!"3!276#!"'&54763!2%nmܒImI%m%%%n$nIII%%&C74'&#"3276%#"/&547%#"'&5476326767632   p{<++8k CDPjKKKKj!$% o+*## n  z>8++&M0/KKjiLK  a= )>Sg|%54'&+";2765=4'&+";276554'&+";276554'&+";276554'&+";27654'&+";276554'&+";27654'&+";276=4'&+";2767#!"'&54763!2%$$%%I&%%&mmnnmmnnnnmmnnnnnn&&m&%/S7#"'&547632+"'&'&'&'&'&=476;+"'&5&'&'&'&'&'&=476; -. .- % M  ]] \ST@A''% R 33QRno{ jk==. .. t ]]  N '&B@TS[ {onRQ33 R ==kjI1A2#"'&54763%2#!"'&5476;76763!2327654'&#"%D0000DD1001D<++++<<++++< % niKLLKijKKKK701DD0000DD10++=<++++<=++MMmKLijKKKKjiLK R7!5!!5#"'&=!4'&#"327653+#!"'&=#"'&=476;4763!232[   I !-$W $-! In\%  [[-! 7 W !-'*->I2#!"'&=!"'&5476?676;26;3375#+!54767#+! '"ɫp% "I yǵ5n3L+"'&=476;5476;24'&'&#"32767653#"'&'&54767632%**GHTUGH)**)HGUTHG**;;eewxde;;;;edxwee;;%THG****GHTUGH)**)HGUxde;;;;edxwee;;;;eew)A%54'&+";2765'4'&+";2767#!"'&'&767632Inn ~ j    mm$$ $$% 3###535476;#"$aa11qQ y)עbh56 Qnt'&=4'67676767654'6'&&#"&'&'&#"'&'&'&/"32?'&'&54767632nTT! -6895 ! - !     ST;;edxwee;;rs, y7&%1E04A  A40E1%%'     ,srwee;;;;eewn.[#"'&'#"'&54763!232767676;2#!"'&54?&#"+"'&=676327632_%tuTNN=J   N)338LCB( n  OTsMBB( q%vuSOO=J  [__ :J   N&%&A :   ON%%A 9 `_ 9I nG#!"'&?&#"32767672#"'&'&'&547676763276n  OTs<66''''66doxde;nId47667632#"'&'&56?633276767654'&'&'&#"#!"'&55476;5476;2+"'&5K=NOTYQQ;;####;;QQYbYY>O *<=D;66(''(66;843(N  %%% I9 ##;;QQYYQR:;##)*KO6''66<;66('&O %#4dm#"'#373!"'&54763!2#7!2#!"'&547635#5##3#27673&767676?"'&'67673&'3&1tQ0[(, ,y- m6z,  ,,  ,>E#$$"12,-23"&&#I)q( l ,z,  ,llh ,, ,y, ;uu;#&&    &%#h&BB&p ,LX.++"7>=#5!26764'2#"&546326=4&'&"3!";546"&54632p 1.PQ5+?@*3f=(CA.,->?,5p0D'/J  8/IO)/.>^8PA,,4  3.Q<12g@i8@,,< 5.Q75=\@0=b5P;#"'327&'&'327&'&=&'&547&5476326767'6,,=>VVbe<0/@**',&Eccr66MP6?7<658(JJJDD45SO#$8 32C)(02+U34M66; !B$ 7)!3#!!3#@@@QQ@@$A%'>54'.'&#"32672?6&'47>7632#"'.'&5Z;;BA::VZ;;B,P$)0K<((-.)*?<((--*)?$T-A<;YU::AB;>NCQ$67=F;CI  2"# 33HnL DDkgA@$336%.b76OABnLe%54'&+";27654'&'&#"32767632;276547676767676765#"'&'&54767632nn 0/2I K  $n   ;;eewxde;;;;edxwee;;mm2++y 9'  !"&   nxde;;;;edxwee;;;;eew@(>54'.'&#!!27>7654&32+#32 F./55/.FDe*<<)f,>>"T/5/.FF./5FtFK55KK55K #3!53#5@@@@@@KUaq.'".'.'>706'4&/.+""3267>73267>7>'4&'>7>72.506>7>7%#"&'>32'.#!"3!2654&#5#!"&5463!23J -#8  /     ,8 M2;:,d"  ," #1  K *3Y'0  JP! !//!!/!E   s  I)  $lF ?3 9 RX  8 83f*# 40K #7 5 !/!!//!`!P `   #';DY3##3#;##3#;##3#3#;265#53'.#!"3!2654&#5#!"&5463!23```````````````````  P! !//!!/!E   @@@@@@@@   @!/!!//!`!P `  $/%##3353#"&'.53!>7>=!#3  ;7ST7A  /# H*a*H#/ &M546;5#"+32;5#"&=4&'>51!54&+532;#"+5326=467.51. Kk.  .kK .p. Kk.  .kK .Bh .hkKh .h. hKkh. h&CC&h .hkKh .h. hKkh. h&CC&3476763!2#"+"'&5#+"'&5&'&'&522E@  > R > T8H&$Z_EC *n H !ECQ,%#"/#"/&54?'&54?6327632NNNNNNNN   % 7% 7Vgggg33 33%)>5476;2+"'&5!5476;2+"'&5!5476;2+"'&5nn%nn$nnmmmmmm(<75476;2+"'&5476;2+"'&55476;2+"'&nnnnnnnn7654'.'&"'.'&547>7632j]^((((^]jj]^((((^]jPEFiiFEPPEFiiFE((^]jj]^((((^]jj]^((iFEPPEFiiFEPPEFi7C"327>7654'.'&"'.'&547>76324632#"&j]^((((^]jj]^((((^]jPEFiiFEPPEFiiFEpPPppPPp((^]jj]^((((^]jj]^((iFEPPEFiiFEPPEFiPppPPppn1"32767654'&'&##"'&'&54767632UGH)**)HGUTHG****GHT;;eewxde;;;;edxwee;;**GHTUGH)**)HGUTHG**xde;;;;edxwee;;;;eew ' ` @ n34/&#"'&#"32765#"'&'&54767632 4   4  6 ;;eewxde;;;;edxwee;; 3 4  7 \xde;;;;edxwee;;;;eewI%54763!'&54?632#"/&54?!"'&5+t+nnI+ +n%47632!2#!#"'&5%t+n+t+I,u%47632#"/+"'&5#"/&5tt*I+t+n+%4?632476;27632#"'&5*I*+m+u !!!'!'%7!@@@@@@ #/!!!!!!4632#"&4632#"&4632#"&K55KK55KK55KK55KK55KK55K@5KK55KK5KK55KK5KK55KK )%!!!!!!'#5#53#575#53#535#535#5@@@@2@<2@@@@@@ !!!!!!!!!!@@@@ !!!!!!!!!!%@@@@ @r67>'&7#"&/.546?>327.#"326?>''.#"7.546?>32#"&'326?64@ '< '   c  A19..c9:*#Ac9:*#A   c  A19. <' '  c   A.-c*u.Ac*u.A  c   A-2eimquy}#"&/.546?>327.#"326?>''.#"7.546?>32#"&'326?64''773#3#'3#3#   c  A19..c9:*#Ac9:*#A   c  A19...i@@.@@  c   A--d*u.Ac*u.A  c   A--.@.)@n)>%#!"'&=4763!2#!"'&=4763!2#!"'&=4763!2n  $  $  $ I I $I I %I I L#!"'&5;2767676767+"'&'&'&'&'&'&'&'&54763!2%%!M "!!" a *4    4ba# ,J%!:%%9  G~-))$    $DD**$,& +!54'&#"#!"'&5476;54763232$*+=<++LKiiLKn<++++?yVV32Q#*+=+$<;M!=Y%4'&#"3276534'&#"327657#!"'&=4763!32?!2#"'&76;476;232     I M!,-!N            N NE  !@\%4'&#"3276534'&#"327657#!"'&=476;;276732++"'&5#"'&7632     I #"        n      r  %1Tm27654'&#"327654'&#"2#"'&=476+#"'&=##"'&5'#"'&5!'!4767'&7663276#"'&=47632    9w+O* =%$$%>))7<=6)    i }89CC98KKK(=#"'&#"#"'&54763232763267670JI41%#/.WUTA@b)<;8:(D6.%('3$'+*eHGpSR%$'5AG89#++$ V==n,E54'&+54'&+"#";;276=327657#"'&'&54767632  I   I  ;;eewxde;;;;edxwee;;I   I   %xde;;;;edxwee;;;;eewn-54'&#!"3!27657#"'&'&54767632 I  ;;eewxde;;;;edxwee;;I I %xde;;;;edxwee;;;;eewH+"'&5&547632#"'&'&'&#"#"'&547676323276767632%%$I{X"$$((*m   !i=65H$$ %*,*%LB  S  E!  %(<MU+"'&5476;23+"'&5476;2+"'&5476;2!3!276765!'&'#+#!"'&5#"'&=476;7676;232%$$%%%%I7&%&7( (III[  fCU$0"#!"/ $``n)=W7!5!!34'&/&'&##!"'&=#354763!254'&+";276#!"'&54763!2IJ IInnn I$  '2:T#!"'&=4763!25#!"'&=4763!2!#"'&=!3&/&'#!"'&54763!2InnIn$n $$%%$ &#"'%#"'&547'&547%6321 1    E    **:#"''&'1&765&?1761676767676767&'&5476767632EDvv(+q%   Z34)(ED__hvvDEdTT11d'  2KKVJDD1111TUc:t#"'#"'&'&5456?1761766767676767&'&547676321"'&'&'#"'3276767654'%66]]m13GX G()65]]mm]]66)(G XG31s!\UTCG&' I++ODC'( 3  )< Mi =B ~ .))= Nj =D I 1,4'&#"32764'&#"327654'&#"32765#"/+"'&/&'#"'&5476767&/&'&=476?67&'&5476763267676;27632#"'&'#"'#"'&547&'&=4767&54767676326326?2#"'&'#"'#"'&547&'&=4767&54767676326326?2++<=++++=<++X   "" B j CS WY   "# Aj CR WnU F  E UU   E UU F  E UU   E U<++++<=+**+Ij&""3 =X 3L  j '!"3 >W 3L   P A)  )A P  @  )(@  IP A(  (A P  @  ((@  ns2#"'&'&54767627676367676&767676?"'&'4#&'&'&'&'&'&'4'&'&'&#"#&#"##"6'&6'4'34'&'&'&'&'&'&'&&'&2'&65656747676#76767632376''&'&&'&'&#"6'&'6'&#&3&'&''#"'"'&76&'&'7676367&&'&'&'"74&'&3"70176'&'&'&'2#21327676767765'0'&'&5&743256'&'&'&''"'"'67674767656'&##&'&'&547674'67672723767&'&#7670'&77236736745&5'=&'&7674767676'27"'&''676776'676756547676?6#76'6'&'23276'&67&#&'&'4'&'&'&'&'&'&'&"#"0&'"'&'&#&76'&#&'&5wee;;;;eewxde;;;;ed         S3                  ]vS    n;;eewxde;;;;edxwee;;     -Q                  X   b(?'334#"32765'#5'763244I=+  5%b__I44=I  6n%_^IK\m#"'&5432327#!"'&54767676767676323276767632#"'&547632#"'&547632+&'654'32767632#"'&547632S];L/ G"#!'%.d**E E** #&''&'&# ++=<++++<=++@AZ[A@@A[ZA@I /L;]/%'!#"GI++<=++++=<++F- OCE'(('E!"   "!=++++=<++++<[@@@@[[@AA@[-FCO \=++++=<++++%5F%#!"'&54767676767676323276767632#"'&547632%**E F)* #&&&'&&# A@[[@@@@[[@AE'(('E!"   "![@@@@[[@AA@[b)4'&#"3276#"'&'&=476;2bg%$%%h=)E4'&#"3276#"'&'&=476;23#"'&'654'&' bg%$  g%$$%%%h hI %9#5##37#34'&#!"3!276#!"'&54763!2%nmܒImI%m%%%n$nIII%%&C74'&#"3276%#"/&547%#"'&5476326767632   p{<++8k CDPjKKKKj!$% o+*## n  z>8++&M0/KKjiLK  a= )>Sg|%54'&+";2765=4'&+";276554'&+";276554'&+";276554'&+";27654'&+";276554'&+";27654'&+";276=4'&+";2767#!"'&54763!2%$$%%I&%%&mmnnmmnnnnmmnnnnnn&&m&%/S7#"'&547632+"'&'&'&'&'&=476;+"'&5&'&'&'&'&'&=476; -. .- % M  ]] \ST@A''% R 33QRno{ jk==. .. t ]]  N '&B@TS[ {onRQ33 R ==kjI1A2#"'&54763%2#!"'&5476;76763!2327654'&#"%D0000DD1001D<++++<<++++< % niKLLKijKKKK701DD0000DD10++=<++++<=++MMmKLijKKKKjiLK R7!5!!5#"'&=!4'&#"327653+#!"'&=#"'&=476;4763!232[   I !-$W $-! In\%  [[-! 7 W !-'*->I2#!"'&=!"'&5476?676;26;3375#+!54767#+! '"ɫp% "I yǵ5n3L+"'&=476;5476;24'&'&#"32767653#"'&'&54767632%**GHTUGH)**)HGUTHG**;;eewxde;;;;edxwee;;%THG****GHTUGH)**)HGUxde;;;;edxwee;;;;eew)A%54'&+";2765'4'&+";2767#!"'&'&767632Inn ~ j    mm$$ $$% 3###535476;#"$aa11qQ y)עbh56 Qnt'&=4'67676767654'6'&&#"&'&'&#"'&'&'&/"32?'&'&54767632nTT! -6895 ! - !     ST;;edxwee;;rs, y7&%1E04A  A40E1%%'     ,srwee;;;;eewn.[#"'&'#"'&54763!232767676;2#!"'&54?&#"+"'&=676327632_%tuTNN=J   N)338LCB( n  OTsMBB( q%vuSOO=J  [__ :J   N&%&A :   ON%%A 9 `_ 9I nG#!"'&?&#"32767672#"'&'&'&547676763276n  OTs<66''''66doxde;nId47667632#"'&'&56?633276767654'&'&'&#"#!"'&55476;5476;2+"'&5K=NOTYQQ;;####;;QQYbYY>O *<=D;66(''(66;843(N  %%% I9 ##;;QQYYQR:;##)*KO6''66<;66('&O %#4dm#"'#373!"'&54763!2#7!2#!"'&547635#5##3#27673&767676?"'&'67673&'3&1tQ0[(, ,y- m6z,  ,,  ,>E#$$"12,-23"&&#I)q( l ,z,  ,llh ,, ,y, ;uu;#&&    &%#h&BB&p ,LX.++"7>=#5!26764'2#"&546326=4&'&"3!";546"&54632p 1.PQ5+?@*3f=(CA.,->?,5p0D'/J  8/IO)/.>^8PA,,4  3.Q<12g@i8@,,< 5.Q75=\@0=b5P;#"'327&'&'327&'&=&'&547&5476326767'6,,=>VVbe<0/@**',&Eccr66MP6?7<658(JJJDD45SO#$8 32C)(02+U34M66; !B$ 7)!3#!!3#@@@QQ@@$A%'>54'.'&#"32672?6&'47>7632#"'.'&5Z;;BA::VZ;;B,P$)0K<((-.)*?<((--*)?$T-A<;YU::AB;܌TrܔTn4Vl쬺$J\zDb|$F\vD^|4Rl$B䬶\r<^t,JdzLjBܔTr+ $B"B FM*B )X,$ "}"&C*oJ !JB&"},, + *&,LXF*A!,>܌\r܌Tnl4V䬺\z$J䄖줮\vtLf$F\rl4R$B䔢<^dz,J䌚쬶|BTrtrB,) d/%FL&$B"W/" #W/-,#,-(-/#EEC(WDڜA!,>܌Trtܔ\rtܔTnl4V$J\z줲tDb$F\v4Rl$B䤮\rtD^,Jdz쬺|LjBܜTr܌Trtܔ\rܔTr܌\r܌Tn4Vt쬺$J\zDb䤮$FD^t4Rl$B䜮\v<^,JdzLj䬶BܔTr܌\rx`BN<0k{N0XۖŁw6-P iAP|y"Aـ `(p̒l/ A.8WP!m aZS2B6 F~;wh"F f\Z%a1 L>TAb0r wc ,0x ^y UD!, >܌\r܌Tnl쬺4V\z$J䤮|Db䜪\v|$F\rtl4R$B䔢tD^dz,J䬶LjBTr# @ "Fi)%-l1Adphx FL^0X'45<:V UsЙIj`gҤJH@  Y'݊Mn𼡐.EBU+BD޶mm HīU m 7bؤBXa.*x 0t'րY`%PBqfyH n# 3YZNxm#MqZ]7*UH yQnzka qq"{FaKue(D!, >܌Tr܌TrܔTrtm:C硞@ҼysK=H@#UD`>,[=' l@ae He6lȨX*7r?( lPlCE:5  걠qM{t|Hk"4{rK,@ndm!3h&rqa CqtPbKd^(8.+W:A* kxCG%E!U{ A tV~v_A!, >܌TrPpgB$pQ0P>#CZE#Q8Ӆg`8%!~F1Pu"{`0Pi6[ (w~C$^F QЀC$VD!, >܌Trt܌Trtk4l8Ja0 ),la0BXQa[8%!/th",b4J4 O踰A B [A%f6jo`AicAl 7 wrq\Fx{%pC%G);1w}BaBh !ql!, >ܔTr0D&P\ݵ 7  UT|щ7TU`I8Z!, >܌\rܔTrt,bHhx>0){'>qGbZHD {uTb>D8B*^lGTe"3Y>GB #h{2b8c>='&l"*3,t"R VC383> $Luu* .w@  x0T.d!`)Dg+>68aK6ܕP8O|v54h%HJX<  P8HD'i Hk aqVkbX-|DpPׁ ATTv g(M`;d_0&L/`YJ0a 8@i 4e1$LC<^ᣏ[|R/nA t!ܽObAC"<"Cǜ"<E(P~  *'?A;pub/img/skins/ectoplasm/spin-primary-button.gif000064400000011025147206622240015662 0ustar00GIF89a<ΜlܜTļŒ֬D䤲dΤ|ʔTܤdִTD|\ƌLҤڴDΤt\̔L줲lʜԼƔҬڼ:! NETSCAPE2.0!,@EF q84J,UC1J)΃V0RV,_"K&KC"lB(W,T!&aLlm,+"$$ &($ " V$%%  %(%L,݇՘&FBA!,<ΤdԴ|޼T|tƌ̔Dڴ޼dlܼŒ\̔DΤ„TČDlܴ|ƔLڼĬtҤ\:@i >)` O $@M*  JD@4i `>!BmB FL!!B&V({| "  C(l '' $B ('F$(A!,<Μtܜ\޼„D֬|ʔd̜TΤ|ļʔTִDt䤮dŒLҤڴD䜮\Lʜl|ƔҬڼ:p58#aȄpT@ BBi$ CSRp2"m<UGFEpZF*"B"$$!(FL K+' #&X+!(" "C!!"''Em&!!+ ͷ̿&%  +հXF +CA!,<ΜlܜT„޼D֬|䴺|䤲dʜT|ܼŒ̔TִDҤtdĔLܼƌڴDΤt\LlƔڼ:pT'#aLPB BR`#X,e(t)&!" B   FL*#B%"Xe ()))X) "C$ $& #  %"$ $$´+T&$̾&TX+dXFFCA!,<ΜlܜT޼„D|̴ִ|dʜTҤ|̼ŒTDt䤮dĔLƌDΤtܜ\ĔLڼԤlҬƔ:0%N$#e4=0$%` ) xd Cj 8P(0LQi0`6(&B$ b)FL' BPV#  &Vc"C$"&&EC n" )E""ՆL)եFFCA!,<ҤtԜ\ļŒD|ڴ̜T|ʜTDִtܤlļƌLD֬dLڼԬ|̼Ɣ: cddDAb ;$ }L¡$ YORct<Bc! Np!!B $BFLHK%PV{L"C     D$  "VD %%%A!,<ҤlԜ\„޼D|ڴʔ|dTִ|ʔ̔TD֬tܤdŒĔLDҬt\LڼʜlܼƔ̉:0U 'ch$@)M21!h%&,@RG :LY@/c-%%F)%##"BC'$B!KW L(B'd%!&)m!  !WϹ)&%mFKdBA!,<ΜlܜT„ڴD|ִʔΤ|dT|ŒĔTDtܤd޼LҤƌDΤt\ڼLʜ̤lҬƔ:"#iܤú0TX(JA.0L+瑩GXX$F-, B$C%BKWW-!##BkS[C)(()$$ *+!-$   &(Ţ-)Ћ!BA!,<ҤtԜT„D޼ڴʔܤdTִ|ŒTD֬dLܼƌDҬ|ܜ\LڼʜlƔ:,#fȤLH&2b 5 :xgA^HiR3yhmO1gb$N &KDc}C$BVG&"YV#E$C$RG"!  " &!!U & ! $   %  UV &'!͑'#A!,<ΤlԜ\޼„D|֬䤲d޼ʜT|ܼŒT̔DҤ|dLܼƌDt\LڼlҬƔ:0(gȴP C"0( B̃1D CTAz`"3j1CKRW$_B''&W) ((C"E) % (~)y$ (# ")&" #]!##W) ґ$KKjCA!,<ΜtܜT޼„֬L|ʜlʔҤ|d̼ŒִTDt䜮\ĔLƌڴDΤ\Ҭ|̔TƔڼ:@PNq8<*ʩxB0) @PH= lF^׃`0 FX<vIB`C K&WQ% $ $KC E* ) ((  ! c! )'C& #cW&!* ϑ*PA!,<ΜlܜT„̔D֬|d|ʔTΤ|줮dŒTִDt䜮\̔LҤƌڴDt䜪\LlҬƔڼ:p%(q8L&G @1RĂfRP$E0H~?0`)V+y#K#('KC#BW+#!p#&&xaL&"SD($ R&)& +) *)) ^&##)" * (ӗaFECA;pub/img/skins/ectoplasm/spin-primary-button@2x.gif000064400000020710147206622240016235 0ustar00GIF89a <Μt޼TŒ֬D䴾|ԤdʔΤ޼T|dִT̔Dt\ƌLʔҤڴDΤܜ\LlĬ|ƔʜҬڼ:! NETSCAPE2.0!, pH$H@qZaP Gfqd)XcR\NVm,`(^t;]&zk+BbU1#wY{&n 1sU'fyZD-~fiDW/n #Y) J(~%/TvJ#wz1 %*)#Q'%.(%+id''ڕw1&" $[f10n@5$2{"ŋ,<خG 04P!REcwd/1Lف#93F Ȑ("ӧE A-&R010`T& Z';B[VtrZR0"_ -&BV,WY%&J\|@ x4ncCZV`($ˀB,l-ʤ ZlhVP@zJ]|D! lFL[U/>5pŞ QCya`#ĉ#NK%e IJl(@5x Rl8B%w,#D{!PȽAi WV-EVXQw[T4a`rsPup4(PusܢP@WAhŋNPEn(ɅTELpKxo1H( ,ЫC 0a(Y=,PX9&(P!ktlr^k#(H1t4E!LA $N@Vs1:'*{<*x)L,YƈcȦ b7qE!pYPxfZD^0\!, <Μl޼T„֬D|ʔ̤dΤ|ܼʔT|ĤdŒִTDt\LҤļƌڴDΤtܜ\LʜԤlҬ̼Ɣڼ:@pH$@/lj( `bVd;b"JHmTSdao1!BGIK'{'~B(ppGv"z*[(sC)"oJ%+C,DR2,u#/)B%+.B'$,K_"H 01. 20-0'$p*',$.#),-eC(6%e)1PPXDpPA*6-dD %epbX  PbÅ d4 B`hQrP)SC]ӈSRҫLGh0,,48!n90BhBb%RHjˈ"XL+dܶnݖRK8A_U{3yB%(S0 ,Zg[ 8+5з |j@-}2T0H0_r@E28$N!GhСFH@4KӞ6"*)^qBk 6][ `da-8, v#qA(;W ^@⢐ |zp… W$ZP E`[K=‚m [Hg\:mBp^Wcex9/p6@Cg^I`($@Mo5D}CPA^!, <Μl޼T„֬D|ʔΤ|̤dT|ܼŒִTDt̤dLҤܼƌڴDΤtĜ\LʜԤlҬƔڼ:@pH$\H@q6JpEaRs (MZ1) :vhR8&*:Qe=\)&(BHV0 ",|e( jGU[!rC#cV%,g"0D%+Q_SU!)x"B, B&+++&Jc#y,) //)J!#g%!Z]+%#WFXx (N}H y-LhDd|5$Ȅ$XrUkIst8oJ ~%qhNM@ AJEDПjR8DٕQ4# քmN5[" O&r[QZa R%zS /b/yְC,Sx]&%q 3݊Ww l6aOF} Z,@q Jw T`I==S8RvB.?C7T+D0[!, <ΜlԜT޼„֬D|䤲dʜΤ|̼ʔT|dļŒִTDt\LҤļƌڴDΤtܜ\LlҬ̼Ɣڼ:pH$lKxPh,DY)X7 %=Ru,)BeW3*j #m}3%r3,fW#'\X3I)j1#%CD*Rrr*GV B h%K*d+, & Kɑ,**1 .Ղ*űE%', .& X3Fω}\W x fbN4$hTA 1EĉTbER5Ҥ2d@B& 5b&>q٨gMH=)T"\#$ؠbC xX,b0``Ԅ6M*.gX,S`B2UQ؆h!„"Dm6/BX"CF0݄KDł0B!2Rn:{Pam1B !c։\Jc 2G=&:/XbCDΜx~@H!p-p pNp BBV#w{p!, <Μl޼T„֬씢D|ʜΤ|޼lʔT|dŒִT̔Dt\LҤļƌڴDΤtܜ\LҬƔڼ:pH$Z$H@qVJa)(PdÔTvx1\QaIÕ;ZJ+u}x3^^%3qUt,ZV3zXbd+, BDP{%qf,C1mn%Jm^Hg'1 1 J{O^+%)+%1ݦOn3%# # DD+1##[E8 qbT. j0B!{+"J\A 0f41f4p`/xlA L(rQK1a|aLZBHC) !!>հ&կ,,U4@5U( P+`! F"ہc$ZH&!BÂʈO0/A_ "6 P!T8Ɗ( peNRf \,%&wi|!PYąC)1dxS` !p 4PDZuNh!, <Μl޼T„֬D|ʔ䤲dΤ|޼ʔT|ܤdŒִT̔Dt\LҤļƌڴDΤtܜ\LʜlҬƔڼ:@pH$~( l0Hi !TR)\(vMZ7 ra$\jx4}jGItg[DbPU]gWPFQenTHf.p& BmI$KmUn. &KT$v.+ ZPI)$We(+ &D٠E)(r[ N]m!"^P0B4*t6Qb*2j2F aɒɓ*R1@RD,ڒтPiZV  "bAX45bՄ@FT:c,&0Dx 1j ZXXPʇ32h0[j\=',X"ƈ30`if!".4  &lX鄘<` B,HR+h`Q. %b6I1`4PxPH0cp{0ȴ ltT7HQMx!VA!, <Μt޼T„֬D̴|dΤ޼ʜT|ܤdŒִT̔Dt\LҤļƌڴDΤܜ\LԴlܬ|ҬƔڼ:pH$r"lЄHc" !CSR)$ۡ7i11Ҕq$٬czZGIPw[%DrPU]ldexBxflTH|I~q*~KkUo*K,&Y"c"*10%W&1 $+E"ɕD) u%%E# *C! TOmU0`ᠡC jE H0A?-$X0E l\ȓ0GyA.^1a Eԡ A]/^H`H!)/Nb ,&oH X^ OȂ`QT0„XB 4(A,X8abdhqjf!H   |]x&ER0B840DcDTњC^H@0|ـ&D@끂)cdxkw dPҽ`M6 ,kc|BXx &y0\b@ C}0VA!, <Μl޼T„֬Dʔ|̤dΤʔT|ĤdŒִTD|ܜ\LҤļƌڴDΤtܜ\LʜԤlҬ̼Ɣڼ:pH$~$ lU1"IͲHٚ)†iIXԨC;RduXx({/5Tb*TwN(D1{T$ubCD4 % B){aeTVIX K{)(uK$vB2- -2/l.W u$HRP-#-,hB*CL$Yvia@* AaDÇ2Eifb C Ё6~~0rLHg#_S0!pBƛ4/&,x 2B4 L`߇Y6EUE&8eC" 42,{a Ɗ kbM+ 3J<`@jxn"!+^X` gH*L%b?XP 7N$'$bqA1Έ GXb6;>ϸ𶈊˯-,<8Fà alh`|P\ ՞G? ;pub/img/skins/fresh/spin-primary-button.gif000064400000011104147206622240015000 0ustar00GIF89aĄD$̤d4Tt Č< Č,̴<\|ĄLl4t,̼\! NETSCAPE2.0!,@Eg48QrBH0Re"$b:A6 *$z "V,}UF, KL*)"W,*(_WmF , * &$ ', +'+ '  & *& W'KKECA!,ĄD$dܤT4 t\< ČL,<̼lܬT4t\܌L,̼4* h'0lAfLOsp T>RB3 P2 B }~F'CB!X'%}LoF%## '"#oCț' FBA!,ĄD$̤dT4̔ t\< ČL,<̜|ČT4̔\L,̼|@PEceD0(L1&G4IP9L$BBxR8B$""*W( B)W*''$ FL*$ *$' *&& C ̞ &* ʞ'F* A!,ĄD$dܤTĔ4tܴ ČL\ ČL,Ĝ<|ܼĄDlܬTt\,̜<Լ@ #aȬG00Cbbpt0I $` El&pIaB%"B+$ d,%W"BW, } %W! pFL#(#&( S'(C*֓W*K(F,CA!,ĄD$dܤTĔ4 ČLtܴ\ ČL,<ĄDlܬTԜ|ܼ\,<@u#a ] BRUF- #W\K-###W-*%,, *  L l-(G$$k($b$!*+ Ș-Ԇ(K紹A;pub/img/skins/fresh/spin-primary-button@2x.gif000064400000020612147206622240015356 0ustar00GIF89a ĄDd$̤ĔTt4̴| Č\< ČLĜ|<̼ĄLl,̬Tt4|\̼! NETSCAPE2.0!, pH$HH@q"T19DEJu8I$ P(`<ء`slZiX(0顡}HJeehy/ll)/rfX-DrbcWB%t,m+rB ,J+l,(`ue/B#,,#.l# Q, Bf/",Leh7 .HpA !8XqZUX,Nb(pX\ NSᤁ+ŏ?0\yV%_kzaD)(>SX&I-B+h1l+CxeYyD!, ĄD$dܤTĔ4tܴ ČL\<| ČL,Ĝ<|ܼĄDlܬTԔ4t\,pH$$H@q& qQ )RD9\RFbM,v 9 QB1z8r@rbU3 y1/{B!SU'Z0D&~ b"y1 qBD,^B- B+$J2!'b ,x2J 2-J#/"+ +& -Y &,VC0 &(- G ~T8%>J w6iE&hXE( X hF `Ls7oLϟ>mz>9PThΡF,A Vr@ @ps XTl@M`A5LP6tY XMI 6p 0V@@ "l C &^H09>T4hmPA )\XB?̸ !Tyig^V@!T_Z]dP=z2`:w?D!tm BC^r_\M7Lt!A!, ĄDd$̤Tt4̔| ČL\< ČL,̜|<̼ĄDlܬTt4|\,̜̼pH$:lP"{Ȗ„e`4*.N;lXŎ\x{IW3/. .{BpTH0 \ 1Dp#V.rB"[C R3pGI2.B/B/#KpV%J3&K *"#o+//  #  XO;a@~Jp+FA+(M\.daQeK,g*r)s@-Aѣ z9 T(ѝ:r腫X/(bbۡ P'F1Z\x,UhmX P(L9 @'MoCǝѡ /XX}[3:3r | Dp e !@TT2o? .3l͈}GufvB~] )P T(Ν[zeрM8"uҳA,2`,lʄ Ҏ 1m,Hq`A&bؠݿ*v`/]%$ B]< {7l`yg* p`B"uZZH ˷0ŕ+DpnI j-1(5VV / ʻo"B0\LW\pWD([!, ĄDd$̤TĜt4̔| ČL\ ČL,|<̼ĄDlܬTԜt|\,<Լ@pH$LH@qVJ!i:*,v!RJX$z(\R&cJ-xxj{0) 0GI"ZrC n$ w 0 D+.B)$SH( B ..-Jn&&J . --wY.+Ve" H2$eW@@ 1TMDI I H"P `X@f- ܩ }@'=cxD4 С<<)xp!ٳSb T4hI5 PaO9 $*`"Ⱙ?$(' XWrOR8D*~-D*aD'0̭0I6wX#x BrbE cQ%ܳ`}BE% ֔~ Q$`wH}.gBLkm \BD.!, ĄD$̤dT4t Č\< ČL,<̼|ĄlT4t\L,̜̼@pH$ ('l-\'TS$Tvz*bTi=t acK xxj|2ojTJxZ!V2o s %qB!C0 R !g*(B. 0BP2oI *K0%%K00(. ( Y - .V!ذ#"ؓ|(aͻa(с0 @HC(UCD Ȝ)e6HIS K%cy QB@Poa>ĂkPdu@@SV tqe   B bkC4s6caUVH^ခe2(n$`dL0!  A\ 0B)r,X01Y R@7%e"pYNLxlA10c`P`$!p A Wٝ%At [A!, ĄD$dܤT4tܔ ČL\<| ČL,<ĄDlܬT4t\|,̜pH$*H@qJ"DgOs a^(Axkz#5NAW3 v((kyY_3G$ +)["W_0Sdu+ "ND0`Q"{SshC*k_YP3^nG)(J*Jo{00 .h+$*03OE)% * C|EEڳoIy*2,-t[AC YDs dLPHmBUA9 #!vCЁ9}ch!, ĄD$̤dT4tܔ Č\<| ČL,<|ܼĄDԬlT4t\,̜̼@pH$H@q$0U #fR$ @zFaņȄǪ0A%eNJ#bhx40{-4SGSS ZHD{/bugCYB4 {qc4G%( J'{fB(3*l#tStvuQ(((V G4 &([b 4RqG5X\`񁅡28сbD,2G*PBC4Ve>7*Y!8lYJdK0% C2Q LxPI )*# gI0A !\A C)P0Fh;act h)2g@B(@  X}2W @J  V N,QC $H `ud2A铼}'pBv JKP[z1OmtB@A 0D Mu @ oĢ@ȡ$?)A;pub/img/skins/blue/spin-primary-button.gif000064400000011113147206622240014620 0ustar00GIF89aܚ$Δ\Dܢ4|֤TtDܞ4ҤTܦDʌڴܞ,ҜlLܦ<ƌڬܚ,ΜdLܢ<Ƅ֬\tۙ%! NETSCAPE2.0!,0E @8c"5Az0&HGCdBЀ=$V)j~LLC#L%%%$W)}k " W!JJB#&& !$&#!V ' !$  )#'&)߫CA!,ܚ$ΔTܦE3s\ "=LFA BEZ" B %%"B FLB&W'!!bW W! F!$ '  &&#!&$#ў֘!A!,ܚ$Δ\D޴|ܢ4֤tTDܞ4lʌܦDڴܞ,ҜdTƌܦ<ڬܚ,ΜdL޼Ƅܢ<֬t\ۙ%@&#aLLǤRa J$ JPH Pi*Lj! BB VB% V( '{" FL (%(  %#  #  & #н& Ϝ&(#bVD CA!,ܚ$Δ\D޼|ܢ4֬tTܞ4ҤlTʌܦDܞ,ҜdLƌܦ<ܚ,ΜdLƄܢ<ڴt\ۙ%@"d$QǤa `̓)PDpM$ )" $ B&VBPV|# w V&#!FL $s& ! E%C!!Lх!FA!,ܚ$ҜdD|ܢ4޼ڬtʌܞ4\ܦDܞ,֤dTƌܦ<ܚ,LƄܢ<ڴtʔ֬lۙ%pd>eȤT2$a 7dťa$8NnF }:Ɂd!B BLHBV# V" FL  # d  ! D  " #Ӆ֙FF#A!,ܚ$Ҝ\D|ܢ4޴ڬtTDܞ4֤lTʌܦDܞ,dLƌܦ<ܚ,ҤdLƄܢ<ڴt\֬ۙ% #h$ B|69 #t‘l( Bb4' $eB " XB X'~KX!! FL f'$! %'E   f  ʜӏ& ٜF֛'A!,ܚ$Δ\Dڬ|ܢ4tTDܞ4֬l޼ʌܦDܞ,ҜdT޴ƌܦ<ܚ,ΜdLڴƄܢ #f)% FL%P) X)tL!! C% f# '&&'(a}) & ) $ #X)KCA!,ܚ$ҜdDܢ4|޴ڬTDʌܞ4֬tܦDܞ,֤Tܦ<ƌܚ,ҤlLܢ<Ƅڴ\ʔtۙ%@Д*"c|HH#` ' *t( - `"L$d.<Å!7&b&BZViBY KV pV"$C$G#FB!!|% #  !"&  !$ %% V&xV& &"&A!,ܚ$Μ\D|޴ܢ4T֤tʌܞ4TܦDܞ,ҜlLƌܦ<ܚ,dLƄ޼ܢ<\ڴtʔҤۙ%@Ф"dlDEB1H* dP8Nse&\"KxFL {B%%$X&    C e#  !"&  ! uɘ &! ؘ&$KA!,ܚ$ΔdD޴ܢ4|֤t\Dܞ4ҤTܦDʌڴܞ,ҜdLܦ<ƌڬܚ,ΜLܢ<Ƅ֬tlۙ%@" cglD(FЉBD 0BHg[8S$2"bhL K( { "X (XQ %'C'T( G##^  'e' C ƙ%( ՙ PA!,ܚ$Δ\D|ܢ4֤tTDܞ4ҤlTʌܦDڴܞ,ҜdLƌܦ<ڬܚ,ΜdLƄܢ<֬t\ۙ%0& cg Й `LIFs,F5 8PL 0Jc2itqW) RwK)"C$"X) qq$((z%X F&FB&U% ( ) # $%$#  ԘK!A;pub/img/skins/blue/spin-primary-button@2x.gif000064400000021120147206622240015171 0ustar00GIF89a ܚ$ΔdD޴|֤ܢ4Ttʌ֤ܞ4ҤTܦDڴܞ,ҜdLƌܦ<ڬܚ,ΜL޼Ƅܢ<\tʔ֬lۙ%! NETSCAPE2.0!, pH$HPq"RaP #fb`)JC LVmh:iR8WLꡥ8sfij| -"c&gZD|UghCYB"-"|+HvCV'" "(- |",T& wB$ m$'u&# fJ&"" "+D&gB#![vZ@!8j"Dȅ")ȃ1aQ&B 6 (S\J"@M 'Z*eRJ4:%P|0!ݞqB MHl'1᪅EĄfM0N DR@"h5( :@E@RP (`a@BX!C 3@ Q&J 2.xP i{ Cp[8ѫ`B %!bEp=B!h0r c+ }X :7|-!, ܚ$ΔdD޴ܢ4֤|TDܞ4ҤtTܦDڴʌܞ,ҜLܦ<ڬƌܚ,ΜlL޼ܢ<֬Ƅ\tۙ%@pH$HPq>19KPbIH s,HءbL X!bԊSa.vvyBll (X D|#'CQC!)B\|" C "."-# `!& p.(m*  Q"E%䶕B$ԺbԪT_E m4(3P` ,HC~ NDƒ.-xh\&f>(y*)-HPr GX;  !D ϰDl`b | K- @ hua ^@P80 T7o@舁7 I!\ ąDVAZ6 ;Ń@yA,DPME*84">!ADn{5 vCna|] m.)B$E*Oc+B(9P5wq7D}iDzE!, ܚ$Δ\D޴|ܢ4֤t\D֤ܞ4lTʌܦDڴܞ,ҜdLƌܦ<ڬܚ,ΜdL޼Ƅܢ!"]!„49 +KC.ׯ `]Oдu# &H! B,0j:A+!AC=𕋯)XΠt/YB工8*Qٝ sRU]\ qg  8By!M Er!A!, ܚ$Δ\D޴|ܢ4֤tTʌDܞ4ҤlTܦDڴܞ,ҜdLƌܦ<ڬܚ,ΜdL޼Ƅܢ<֬t\ʔۙ%pH$F*H@qRa©(Us Ҡ.!i&#e;\.A8^I=t/&BGI-$#h,k|-'pkUV#,#['D oGU "y sB ZCQ-o*SH, B" $B$+*JcU&y - J&!! !$h*! Z)* WCN$Kb"z0ܷ08," 6@"-E"=Qa bLB8D@}d@ѣFM-E]ӁҧMJU] '][ R@s֚BBVM0<0TąWB }2a WWcOq]v-xPrs PPA]j[8Q2*n斶=V@y[`0׌ P~}+?.0{nȂq J{ 'E̦ ` &^`ٔVey='"Ey x18DDxdC!, ܚ$Δ\D޴|ܢ4֬tTʌDܞ4ҤlTܦDܞ,ҜdLƌܦ<ڬܚ,ΜdL޼Ƅܢ (]4SBItPNHB* . >(0M"LL '6mrB F0F‚E (HPo ;,I,\0T;_8CӶ;|pp63õܳO"`aBf׭&B{8ZFN#SA%pK4d-h/oT_ t@u1[h@e}}E!, ܚ$Δ\D޴|ܢ4֤t\Dܞ4lTʌܦD֬ܞ,ҤdLƌܦ<ڬܚ,ҜdL޼Ƅܢ2d!hX"Q2 Ȑ!R\"*4#ϑ-@P%0H16PBM6KIu=:B^eNlYņ(b*4 LLCu#:Ɠ 1Ä IP !*XE4HPB_1:8]O=O b8@y Lj  b\ pH  0!Vݹ6A tl fCD+Q1D\A!, ܚ$Δ\D޴|ܢ4֤t\ʌD֤ܞ4lTܦDڴܞ,ҜdLƌܦ<ڬܚ,ΜdL޼Ƅܢc^ZF,E'lRЕȄ+, (0yjA=rx o!6H-VPXݡſr )V%HcAÀVS Y8(KHNہ Z`1(itWoa58x`A:O@ f PT 7 * dĞB:ȆA!, ܚ$Δ\D޴|ܢ4֤tTʌD֤ܞ4ҤlTܦDڴܞ,ҜdLƌܦ<ڬܚ,ΜdL޼Ƅܢ) & ,9Q@BjP0YYE(8a@Uٲ Q1m۷#LL L  "J0k[$u;!ê0X@ Db C0|5 M p"0` :#fPEh8  'f@BrcQqj,Bb\q)!;y؇0< CF$.Z/ PreT Pqp!, ܚ$Δ\D޴ܢ4֤|TtD֤ܞ4ҤTܦDʌڴܞ,ҜlLܦ<ƌڬܚ,ΜdL޼ܢ<Ƅ\t֬ۙ%pH$r lMА#ip‡ɴLPЊĂ&)W(= zzqS rdKgU[{-$~)-Te &d\D~#IIVj-B-`nsVGTCGR~ GH Kz- ) #n+ x t[& *XC&GIU-%]V0 =wCOT8qC#DHD@T/˗.Q̰Q Rܼb@5섉ӄHJ @H ! %l' l9p` !4 CE80UkHm߶J`8{ E3É.h+B (B!@)ڶb[LP‚ qb‰\[֔ qD.H,p t_Hv ,[`Уw؈ xAȂ՝ @[D*w, δBa_bnePA;pub/img/skins/light/spin-primary-button.gif000064400000011133147206622240015002 0ustar00GIF89aD$dĤ4\tT4 L,tL,l̴<| D$l\<| ! NETSCAPE2.0!,@Ѕ'c,LCa  LaxQ(T3@ 0> #$%BTF.%KL+V.-)i- ClF" .*n U$!&  %- . !.CA!,D$dĤT4T4t L,L,l̴\ D$dĬ B($BFL#K-"Vc  & C,-$&%,!, $,!-!)$EEC)͜-!bVlFFCA!,D$dĤ\4t̔Tt L,lLUQ+C H()kl %CX4&%B ) c.  V B%+V.  +PV!!!$FL$ cn .* ! $'& D 'i *'V$. ڛ-J.-CA!,D$dT4t̜4 L,t\ĔL,l̼T<|̤ D$dTĜ<| u0 fȼPD@ 0& š$0F8P>&Jt BLbYx6rB#B FL+BKW~ ("L'%Cc&%,#  '-&E"  $ )W&-*),F--A!,D$dĤTt̜4 l\ČL,l̬T|< D$dĬTĜ|< @cfȌ"@J ƒJ@$d% !q8J!Dr8  L(sB +FLP,KX!LX"$+ , (n  ( ,$'#( B!'  #X',Z X,_,,A!,D$d4t̜T4 ,L,t<| L$l\<| 00&a4t4G@21H&h0ipH5Xۡ|#"i F) BC $BW tL&(W$((""'$& )%! P%% W) W)BA!,D$dĤ4TtT4 L,t<L,l\ļ *)E! F*1K@)V,|hF,#KLx!W,")q)({ C(l,!!GR &%,   +#   cW&,%(%"KaA;pub/img/skins/light/spin-primary-button@2x.gif000064400000020703147206622240015357 0ustar00GIF89a D$dĤ4Tt̔4 ,<L,l̬\|̜ /Pd;%pl^x{؁WdK,xx{B&o%GI[&DpTVCRC B0_ot  C0,+ +Kp H R&r1* Ӽo,$)01)  E C @p( s&\!@v mِo&]8! AC(lXr h9C1,٬@cLIhr0 tH*U׋1 P(6mXIel1(eV r¸"Bo&H$`!D@H%8H,@Ddk:X, t<|~ك E ) <,iB ؍1Ãb! QCA˙4-B&_]zNG[햂id0G](Dl N!, D$dĤT4tT4 L,l̬<L,l̬\| 3I@(@c@ &XL 5NIsf )l@3'Ξ{4Xʔ&9 A:U!dJh)ML`!FV!Ɋ$=Fܿ/sm%#KqXIP8o!jbrxUcx k N&-sp+@   @  HġtE]b Z'KӴ/Z~B&$4pr`>(Mp> G~v&`cpi *"`7z ]蠀Z!, D$dĤܔ4TtT4t̼ ,l<L,l̴\| dP4$-_8(O)E 6-fK >.^P"E6tɳ@Db",*M!BP$R$bR M0OUIHPl(Pap7|@A&4`|Q]F$LXA'%`||A$L$|I r_ذ"|a٘$FM\k+&.5 8ȫ 9/I2|RPmQ$ĀM}lvvfp 4DD~E!, D$dĤT4Tt4 L,t̬<L,l̬\ ] \سC|FݿfAKTX!"ÊvW @ NP]T\ `-L' %%9Y ƅaw%  y];heG0L 'Rv= bcP6W/nڽiu/gٲ ]G 6DhUDZXvF8D!, D$dĤT4t̴4 L,l̬\L,l̬T<|̼ D$dĤTt̴< pH$.HH@qaIR ZfcRpB],) fRg&0c)iz5*n}5bUwZpC%mT\ 5\D Q5},40x)B3-'BJ, b0U5$" !'  - 5# E! 3VYv ҉a\Iu,T@C;=*аILЈ `ʜ!&͛,0`O&n@3@NԨ@yU2!0 YLѠW̚fkCUYݵ&0XA T8FP4ZpF%0cN3x; %jU!Wk`jm)i%[.MX@ p$BJj0ă~i`oSh$ļc+@xd1Gi1`!Bo A!, D$dĤT4t̔T4 L,t<L,l̬\| \(vAP$`ȇ(?'C0_I O޼.9ӦӢ[v*邒UJ>@CZU:@d0A& [x-@t CJ(pq -$kC7 9a` 5A'0p0,XE  v-Ϳ5ض€^Xx|oz)Pl8tct(TǴc5klH;M>!Epm}YPY _ (@Wyq `D!, D$dĤܔT4t̔T4 L,l<L,l̬\|̼ x ]+ >_!s"vC0@ =Mp$  $8=Á>wSg|C{(0g# mZ䀁it 7?jU&PCC!, D$dĤT4t̴T4 L,t̴<L,l̬\|̼ `D#ٴQ9L-.gƓ]r!n` }D!@ 8 dWoTm%E!, D$dĤT4Tt4 L,<L,l̴\| N@`5Fhժ"j&4TkR [u%b0hm;!U؛aBFk@GE (8+P6iFZ  DB@"8 @Ć%Q&⯇}k,BslAY9 (G PpYJ l y5CͱE!, D$dĔ4TԴtT4 ,<L,l̜\| `.0WdDLMtPC j ! ,8*ap9l (h!0` A\X,4"@!cjbC 8( ` ACТA 8(ËHaĖ'+z4ǐXeiw: :{)XNtLb l̤l(Pe7­[ !, D$dĤ4tTĴ4t L,lL,l̬<|\ļ D$dĬTĴ<| @pH$^Pl/Ѓ$,) E^gR)$=pj.BB*jȭ^Ks(iy6n/6HTI+[(D.n'VsteB061nfcC5I&Kn1H(gB 0#m5+s6"WCIRC .M(C '0XCB0 tA2 |ÈUq#o`dBɓf|ik!3bL1-$QL顧O;6B0#l؊0b-XˉMbPAW,8d kAaد `6H4QnV<*k% ̪€cJ` ! 6j ,b q RWu@+2xRSe @ڃ rAQaU͆d1%1yzM<[;pub/img/skins/midnight/spin-primary-button.gif000064400000011200147206622240015471 0ustar00GIF89a.$요f\J<|>4ZT쎄쪤vl6,RD좜얌nd슄¼F<2$잔ND|B4b\쒌~t:,VLjdJD^T쎌ztRL즜얔nl2,잜솄B<:4.#! NETSCAPE2.0!,V@5lVc15UTSXHڠLƄdJq/1"2B4~F5, W35'xW5 zz.j$$ LM $5$-530) )%  0()  04( # ##5& 3۝)LA!,.$요b\J4VL6,nd|RD좜F<2$잔jdND|B4^T:,vl쎄f\JD~tZTrl슄RL2,잜B<:4.#D*+5l.!ZBAN>s \NH"99J#D"krML($#B+!F/'+X- Bw#X/.||+ 'MnJ' JB{! -#../ &!) -''. ) / LX/A!,.$요f\J<|>4ZTvl쪤쎄6,RDnd좜F<얔2$ND슄B4b\~t쒌:,VLrl잜jdJD|^Tzt쎌RLnl즜2,B<:4.#@6Zce 9Fdâ` I1X M m* GP,a c 5< B%//*4&1V ,B ++!V4")3$ nLF2)FB   )$# 2$4#,,)($ʺ4 !   4, 24CA!,.$요f\J<|>4쪤vlZT쎄6,좜ndRD슄F<2$잔ND|B4~tb\얌:,jdJDzt^T쒌rl2,잜|B<:4.#0F #f 4ZT쎄vl¼6,즤RD좜nd슄F<얌2$잔ND|B4b\~t:,VLjdJD^T쒌ztRLrl얔2,잜솄B<:4.#@`@LGe81>-@CA"Gz0Fpqy IA , 3B$$43"/W B"We PW/!L%0 4 +FB,  3#!,,4E#2"#Ld# 2Fړ 4A!,.$잜f\J<|>4ZT쎄6,rlRD슄F<얌2$쪤ndND|B4b\:,zt좜jdJD^T쒌vlRL얔2,솄B<:4.#0vX dD$RIRq1LN!f %6@jq  8D\ b2}(  B-'$d1 %FL,BW1~W##C& 0! 0 1" +" ͞1!  J11A!,.$좜f\J<|>4vlZT쎄6,ndRL슄F<얌2$쪤ND|B4~tb\쒌:,rl즜jdJDzt^T쎌nlVL2,|B<:4.#@ kȴ D BCA s*P:%a>Ap*&&,B&(/ XBX0 (("KX)FL e 0$! 0"#'&eB,,Ο/v0ݟ FK 0A!,.$요f\J<|>4vlZT쎄6,nd좜VLF<얌2$잔ND|B4b\:,rljdJD~t^T쒌¼nl얔2,잜솄B<:4.#@PfȜlFb 9R @(" v!Z<  /h|/B+%, BFL#B.V0 r KV"V#/pDcB'.)!#-*0,&" B  ʜ&0$]/00A!,.$좜f\J<|>4ZT6,vl쎄RL얌2$ndNDF4ZT6,vlRD쎄즜F<2$ndND슄B4b\:,~tVL얌좜JD|^TztRL쪤2,rlB<:4얔.#@ bԀ4PB BN @A'40t#dL'T dB K0iFL$.B)X0'" %# .&C& ++))eB'/((#0&!*( Q*.͝!"0'ܝK$A!,.$요jdJ<|>4ZT쎄쪤6,vlRD¼좜슄|F<얌2$잔rlNDB4b\:,~tVL|ndJD^T쎌ztRL즤얔2,잜B<:4솄.#@7&#f>8  Ԙ*e6f;d@ Ca$V2 (3B6K8*0 X-8"X8&%%%#  6 C% 3-8- &'' - )d  4$5.46)̛" 8$A!,.$요b\J<~t>4VL쪤rl6,슄RD좜ndF<2$ND솄B4^Tzt:,쒌잜f\JD|ZTvl쎄RL즤nl2,B<:4.#0$!@5l62c1p5L6\;d H01O UJH,À4L "(V1!~BLM..F1/W1$$*q  0 W F+1! 'k!   (!''!1('(('_. ((dW  ۛLد1A;pub/img/skins/midnight/spin-primary-button@2x.gif000064400000021146147206622240016055 0ustar00GIF89a .$요f\J<|>4ZTrl쎄6,즤RDndF<얔2$좜ND슄B4b\zt쒌:,VL잜jdJD솄^Tvl쎌쪤RL¼nl2,B<:4.#! NETSCAPE2.0!, pH$^Jqn(P kfJp2*#62i'&cRx8MN408c7 'fiz7*} 7rUwZD}0bH2 vV7YB!3 W},2'5C wY",i}Ty7)"+$m& ufwQѼ31D24 7/6%KC;"$5:j D! ]4BDy'MF %nL(tD$p$qVlS̚wjXt(8P@WT"Z^l ^~PScۊAAZ^[(KwmWlxg0`,X4/:ɂ2Ew0$C `&`p4J7@pjCg<3n[M waET>Jr> &j9`6& 2`D>fACtxBb{BPuD!, .$요f\J<|>4쎄ZT쪤¼vl6,RD좜슄얌nl|F<2$잔NDB4쒌b\~t:,VL|jdJD쎌^TztRL즤얔rl2,잜B<:4솄.#pH$zdHMq~Z19,XipH79ULءry x©Lž`b wwhz?;mhr/X*D }r91,CQC0%B=7lr%2C.#+J }`:2?<**? 0# +l" .365w)*Q #68#)D+0=B4@ar e= @4p|` $8!C1E$$h`dI3Ç/?,0 fM TfG .X!"!}4T SV3X9#C 4`"hDz thniL@$P{7?"*I͐PHv#HȐ"=2@X 4쪤rl¼슄VL6,좜jd|RLF<2$잔ND|B4zt쒌^T:,ndf\JD|vl쎄ZT즤솄2,잜B<:4nl.#pH$rLHKq41bR W撶|:*Hzp@s&TJ xx{B-obI5ZD~0t&4 &6C#D 7B"(S&")B+1 B J~(c.349395n!// 21x%Y).%D $ˁ!aXa" Qpp5qAU`1Œ H"5B0&ȹ"'J'Ϝ9}pS=uTЫCo@YhX 炁56xx]$X I7o 84 "5`ж-6^Hpb ',;5yA+^!UR4.5%G"Zqx x! ZoX::%=E^qq~ Y%@7v ECVD!, .$요f\J<|>4쪤ZT쎄rl6,좜RD¼얌nd슄F<2$잔ND|B4b\쒌zt:,즜VLjdJD^T쎌vlRL얔nl2,잜솄B<:4즤.#@pH$^KqBy)Os9 :a;t A8M cX=ytTV:.y|B(9H30 [7D!ouV&%sB3D Q:"p#U/!B&-.B.4JH/J(-(:+34o .5z"4-*:8!!*Dh a^&-ܾ2MTlP  @jy:mq`E7p V/]nXDpꬠ' *(ZbѣFm]С|"=TO:u` 'nXP˶ X0aB\-#Oܽ{@pB/,,pp@7θq1'wX 8>ẗ,rI Hv\0D!p*.(M`v%ltؕ|B" pޅ5` u^sJyNfs!T7] c ]cCGD!, .$요f\J<|>4ZTvl6,즜RD쎄nd좜F<2$잔ND슄B4b\~t¼:,VL얌rljdJD솄^TztRLnl2,잜B<:4얔.#@pH$RlAJ+'S&HJR)-*vg6*T*zXYScK5w'{B~~sJwZD$nT) C D  B 7~+ )$BB'/K+7Iw5  -8,'(/// +6# 86%$ 7"VB#,a ׂaaɋu 4Ys `˗JHcC 7s ϞhA(Q?}M4hQ?Y(X j*ў.<K6$lfAz99C˕Z}KaF)" ^؅>S,}ƒ 0[>q e p c`s Xp5 CpZܙC 8sCrq0xV9,T,5j!P@RY[;M_l"˭j?KǴ3q@q8p4p MU&!, .$잜f\J<|>4ZTvl쎄6,RDnd슄즜|¼F<2$NDB4b\~t얔:,VLrl|좜jdJD^Tzt쒌RLnl즤2,B<:4솄.#@pH$JJH@q6aP ,VcRs}Z]HpXW[h U:ffiy:^^ir%vZ pC -,6b4C6`C Q: m/ 4B5(B "J/-%% %:(Ҏ:&&,|,(1.f/Y +E7").+BPB؇.DqAb#a/N!< hР &+8ɁK5:thΞH{T耧B󐎦Pk K!BXfA Mܺfܹ2Z(bC P$ w É1V Š5flȰ@4pՇǶ0g4bȍuCL=Ԋ#dFBn1N +:mc8y^P cb '0W!xƗ]MUȁQ )pDhׅBD!, .$요f\J<|>4쪤ZTvl쎄6,좜RDnd¼F<얌2$잔ND슄B4b\~t:,즜VLrljdJD솄^Tzt쒌RLnl얔2,잜B<:4즤.#pH$H@q:\19\Kk  MCvX[ᱥ!xzy-J3x{B7o jSux#XqC o%,2.x39D.Q#o&Gb1B !9+J , \ t% 29-Q ++ 7%n7W&/E7,/)֬!@L)l%,V O595X,XA@3L,PcK&h @ 8c0s &f6Ѣ>}TQ`D cgϟ?2!zԨ†ဢ JX,7R_LL 481,O&ʺ*" u1xAa Oa 29,%0j)@6i-^p9/lԦ@uZ15kƏ\ `pFUdaˮ :Gr-(O}E+fNsX! aA!, .$요f\J<|>4쪤ZT쎄vl¼6,좜RDnd슄|F<얔2$잔NDB4b\쒌~t:,즜VLrl|jdJD^T쎌ztRLnl2,잜B<:4즤솄.#pH$ H@q:X19qK"H2Ǜ, `T6V-&1zi r2(b?; 11 i{?m\%?Ga2w X9QC~4(4 B9C;\ms9w1 B"B,,p!0BD?UD!, .$요f\J<|>4ZT쎄쪤vl¼6,RD좜nd얌슄F<2$잔ND|B4b\쒌~t:,VLrljdJD^T쎌ztRL즤nl얔2,잜|B<:4.#@pH$>H@qbJaR#؇U: N;txePcJw `z<)o<1tw Z7qCX d,w&BD|7$bcwC n)X;J$})w3J -- J$$ /:- -YO^Vfd - CѽTg 톞 0kn>P DpQ`e7QdM8nse9o)&5q*@S PuH;Hd #? lX(B49![#l0p6D>74xvD LXXpexP hhxB *0@] 4R1u#`ࡣa-t0q AÇGĐp!)TA4h'#(&iqBٵ &y(P}xHn &i0. 7B0Bj Qt4ZT쎄쪤vl6,RD¼좜nd슄F<얔2$ND|B4b\쒌~t:,VL잜jdJD^T쎌ztRL즤rl2,솄B<:4.#@pH$VNl/$Hq %cEPq^Q=]Hé?C[bK!uuix8qiGI/u*[4oCbcb*}y4GH7THf*C `l''7 K KbHf* }PqqE2 C'WL62"B!4x*(7 9`  DA!22jԈ Ǐe\,Bɓ+:(P +`Trex>ܩ>@m`aЁ )-r .NÆ-F ƒ TID !r5"D,PkA.Ԃ"2BU`CbRF 2M0cB "x\ǁsI44쪤ZTvl쎄6,좜RD¼nd슄F<2$잔ND|B4b\~t얔:,즜VLrljdJD^Tzt쒌RLnl2,잜|B<:4즤.#@pH$RPl-$Hu %Cʑ"Pvب,SR9n-IGK rZy:fUZvJbH`N#Dcd!|oCB03*!r!C 3+p^c(c#/p& ^ 4,#WB47, :,'3)E8,d\2(y2rB ,xѪH:@젂C:<gM ?aɓ&1$ r䡒(Ozd)c'Ǎ3Cpج@! ߚ LUu ȁ * .& g1tx1FW8lpPㆇ ^2hEY0gA *pA8J0N e mN)Ju C4쎄쪤ZTvl6,좜RDnd슄얌|F<2$잔NDB4쒌b\~t:,즜VLrl|jdJD쎌^TztRLnl얔2,잜B<:4즤솄.#pH$^Vlw$,#tlxŬVv)!yEW涱;d? U+zB&}!?GtGG[+D=}:T p"!B  E"VJ" VzzttV  V!D B!!î"E ǬDK!׌KA!,Ĭ¬䜺ܴμԴʴƴ¬̬ƬμܔʼƴҼ܆ptB0pFGƱLfhfY~BD&rlP©>@ сl& ʠ +_B" `#UB U# \    U!pK ]!! #E!E  ىKEJUD܊KA!,̬¬䜺μܔʴ¬ƴƬ䜶Ԍ̜ҼܔʼƴԌ@PJ.pPD*, TKBQPD ±< qb @x B yE" WQBOW KnW" ! B` n! Sʲ "B  W!E ΪI" "A!,̬¬μܴʴČԴƴ¬μԬƬҼԜ씶ƴĆ@ BpYT@ h,A"H2L4dHoEatr B d W IJWW CKkd ECac cD  eB  ĤdФ A!,Ĭ¬䜺μԴʴ¬̬Ƭ줺Ҽ܌̔ƴ줾܌'~e1Ʀ Qmp~KA,d E`  L(f< QPx\-)6٘͘P#m+ QB Oiu j_ = 4%  %" t7t%a"!!,̬¬μԴʴ¬ČƴμԬƬ윶ҼԜܤƴ쌮Ć@b6BpT@\ idϦg1pʆQ187e1` )k O XV uB|V  K C~ ~B   H E ͊ OO A!,̬¬μԴʴ¬ƴ̬Ƭμ܌̜ܴʼƴҼ@P4p(Ne)\ Vc`*C3\3EWT"qB   !JK! ECu f "y" "`̂!"hCA!,Ĭ¬윶μܔʴ̬Ƭμ䔶ʼƴҼ02pTBGbL q1ymW CPPyBO!U! Cx !B_   !  Py U !͌!x!yA!,Ĥ¬윶μʴ¬μ̜ƬҼԔʼԜƴĆD!cȄ:D2K<&Dd$$AR“ (@PA8'E)xM"ń# C(ƍ.a0g Y /H`ST a7pAy~\ !, Ĭ¬䜺ܴμԴʴ¬ƴ̬Ƭμ̜䔶ʼƴҼ܆@pH$Dl\ ' ,RE; (AHU |=)tHvyy|BppuKZD&! $DRC&^pu$#CV%Kcv wp #&$L$BeN$E2F;|NHH+FX8BLc"Qm*qe &TFǐ#_dӯXJ~HC>0`" @h 20bj 0i,]h%$5S“* H(og5q.P( 1Ai ,mل48-D@l{jV1$X: +"e"HDT uI©# paQwzBmmrtwYDn %r wpB XCtdr$ C"%JnG#wb " B  $"X! D   c@WC""D*˄&4d)>=DED!@~0a(`e8͚.WFp)3 ͛6]lg@mb )S4jpä\ *IV\rdW`8[JE0|Y:)ՆB@܅ 6/n=Pׁ4r! ~f2*k3VmPA{n*Y Jwm|AGpP}/ 9H.{C4|Ɂ<>!, Ĭ¬䜺μܔԴʴ¬ƴ̬Ƭμ䜶Ԍ̜ܔʼƴҼԌ@pH$. l10-X"Zaz=6vhD2R6zsHJ${Bpjt![#jCo$yrB aB!oTH#gCBBnUB !j!o#$!!E  D Hq܀ -YS[ ? ' Ne$TဦM4șe-j 燝;:9T(O`! ]:p n@]NF||ef`EG w [e$\\0N`^ jh|`gIpxX1I\K$RHa3bCODdS%)uD -#tgw&\lgqBt X$D \&T# oB  Q& zq|BB  BlkQB g tκE$ #^C$ X@*-ׄOX|+ K !A"2f$pC;$)N~ ^ƤRIX:)&τ?q6)'&%q@&4t 6I AL2`,!vek6DjC\6%O %*~XQ0# IȋaHYr%p2x`q*4i$.vD[H RMj +u!J/#_fP܄؁o``u"q$!, Ĭ¬䜺ܴμ̔ʴ¬Ԍƴμ̬Ƭ䜶ԌҼ̜Ԕƴ܌ĆpH$*$Iq$f8#i"F.%By`"0(!B2£I)|l&zbSBuIBxh{'~"haU'w"YpC]'S'e'D!B]""& wB%!~r'   %']  e X!D%  $ V )[ Q\7EpU cCMJ4a mSdeK>|, ܩ3޼ܓgE4 J@UV*BP@ʯZd +͆ &tf# ᦃ3sZHV@k.;F\0@n  $0"h=M)*Mti0ȵwydϾv!HpJT@᥉cQ€M<Ӥ%qvHy>l_Nxֺd!, Ĭ¬䜺ܴμԴʴ¬ƴ̬ƬμԌ̜䔶ƴҼԌpH$.Oq$2:$,M9 R>ެFKOx0W`H B!f$ydm`bBY D|}%SsC$o\|[b vCi%[} Q ң}f!Xҽ$D!% j<)%5 5hׄDD$"THCDEjJh'Į}J(G:wC)SJ۷<%O4iAO69`HP+& 4tUUP[N#&Fu5`A Kt0R9pi/%6X, L0!080D!c)҃A*|y04 R %@P (6* %d,1(Mc0yCx[tFThÄt-zSಪyw!O!, Ĭ¬䜺ܴμ̔ʴ¬Ԍƴ̬ƬμԌ̜ԔʼƴҼ܌@pH$rDȢrI y2'G3ZO#r#{vUl(o_B"rWDxkOGIcrmB'NU[Q_#CVik{kk"B'%#BZx\'' %%(NjjY\EH!CȦVLJiW%LNTwCdigA!D@SL#a'Lm͖%&P23Sk6ңGIp:9 "4u Tf @PwD%a@Bg0<}Q Z)M4tB bValkՖE@P|;H@q P:! 6Pa ލ]D`E0`L4XXL"ѥ  Bdf A1!JC xҹDFLBn_T;yGx\!, Ĭ¬䜺ܼμԴʴ¬ƴ̬ƬҼ̜䔶ʼƴČ܆@pH$:ǢrIsx 1#z a{bG^M۹XMݎڄs HixuSa ^J PROlHxp B G$j&NHY ~a^!wgRy }{ !! k[U gp ! VNhYdD]e$ !EgW L$ CsBR b" D FD4 3`BEb\0G٨d!B$ l@"MiB$ Bم9 $HH "8L 4H ju!(J 05?! &-€ !!S%bP,a"x0A"B,*$,81! @=%HX@. )$0a@_*`hhC[ .dl 8La8oOhP"BJ 0d)g8C4/D H!, Ĭ¬䜶ܴμԔʴƴ̬Ƭ윺μܔʼƴ윺Ҽ܆@pH$"8ǢrI sH0U8NM $qbG+rrM"9°.|koS]^WD$RmxgH}&uCSNwgQ[k" [&^" &!k $$u"D] ""&%e qD xeɁW$~&J3Q`Lث#B\0~6`Fb,CFAdY)MĄH$ &PB$0ѻ+F\Ȃ={N#TLHZA酢BpaW4S JDA .Dڵ*Una0bB ( b*MB ,=LCi4|Yt $dȀ@̺  / -[ HPU1?Y9@%Jn"9o{ݎda` Ct+z8v&bKMj?d !, Ĭ¬䜺μܔ̴ʴČƴ¬μ̬ƬԌҼ윺ܔʼƴԌĆpH$>Ǣrid@,=E2 @T!y``z>N4|>E$a4yO?fu'k#'nmQHXrDk{'%RHBWB%B%\j G fkQb'!j% qS`f  JЗH'SsK"tLq`g SB$``@YcX8P&@и 3v6bcD,f\d6AY<[RA t!^`Bx| "L" @J0z3 l 3Th`A 0 6H`1mL: BqEHctbA.!!B}oBWFdxW#|}aW""E L#"}x#  " W#Ӛ ##A!,lʴ̮ĢԾ|̪Զ̦ƴĚ|tμ̲Ħ¬Ğ|tʼ̪Ժ̲Ħ´Ğm@ЄD#aT@$a` $M:̓!2 OPHRp8 eqd$ Bb&V" B#PV`V%!FL&!%% % !k  V&! !  !CA!,lʴ̮ĢԾ|ԶμԲ̪ƴĚ|tμ̲¬Ğ|tʼ̦¬Ժ̪̲Ğm@tDhȜG`40#S`S!D`PBX XP*!B! e$ WBPW!W$ qL! FB# # #n#n ͧ# D#٧FFA!,lʴ̮ĢԾļ|Զμ̪ƴԲ¬Ě|tμ̲̦¬ԺĞ|tʼĦԾԶ̪̲Ğm@&&iM(Qa0 csHr% )J ⓔ@6'E2gt@ #B%~ (WB%%W($$ ! #FL&  (  #''E'$Cٝʇ'WF쏾A!,lμ̮ĢԾԼ|Զ̪ƴԲ¬Ě|tμ̲̦¬ԺĞ|tĦԾԶ̪̲Ğm@tfȬ8:r0g&N4r H6ю> Pa0`w#tB }c& #FLh%BW& }!WC" c%%#% D% ! &%% cÆ $D&A!,lμ̮ĢԾ̼|Զ̪ԲƴĚ|t̲̦¬ԺĞ|tĦԶ̪̲´Ğm@P cl*!@a  p( <8g!ȥT0 Bb"FL P"V" !!!KV Ci!EC!a "E B!b ֛nFKZCA!,lʴ̮ĢԾ|Զ̪Ě|tμ̲̦ƴĞ|tʼĦ¬Ժ̪̲Ğm@P@ aȬ|G1En2̆EDMCE(6O!`+ B "FLB!L!p!KW C S"[C "E cB! W"" R"P"A!,lμ̮ĢԾ|Զ̪ƴĚ|ț¬ԺĞ|t̲Ħ¬Զ̪Ğm@Pt" ctXQ0H2Etx6Lfthm HX6N[yKnFLBZL  r!W Bc G   " x! W""թc" ~"A!,lʼ̮ĢԾ̼|Զ̪ƴ̦¬Ě|tμԲĦ¬ԺĞ|tμ̲ԾԶ̪ĦĞm@Д0%dȔ`% "0h&rftxJiD+!PslD6$|nFL B W& $KLc""| k#!& ! ^G# #! &uϑK& A!,lʴ̮ĢԾļ|Զ̪μƴԲ̦¬Ě|tμ̲ĦĞ|tʼԾԺ̪´̲ĦĞmp6*#gȜl&+ B#Ap*0@X!fZ4؊<ų(K+'yC'b $ XQ&  & B"+)* & + $ U (()%eX +ӕ+P'FBA!,lʴ̮ĢԾ|̪μԶƴĚ|ț¬Ğ|tʼ̲ĦԾ̪ԺĞmDHeȔL:% p ^HTM$Rw \@J9 0$ t4W%mHF%KLxX q    CTG      d# %#"Ř # % ]ԘKbA;pub/img/skins/coffee/spin-primary-button@2x.gif000064400000021104147206622240015473 0ustar00GIF89a lʴ̮ĢԾ̼|Զ̪ƴμ¬Բ̦Ě|tμ̲Ħ¬ԺĞ|tʼԾԶ̪´̲ĦĞm! NETSCAPE2.0!, pH$NqP )fpcR&Pu88BqUx0s&! c*vVy^|+rvZD $^)r*id+'PB|$!UfY+fV"'!i$''T*if+)(&""uŃ*Q'#+ă+ ϸD:(֍\("LH@GM82a<-a(ʳy('h"H@&M)Zt@xg4t@S6@W%ZX@ _ j@ Pn^A0aUr(DyA4Aߚ@KQٷVCFT0bÇ $H`b,) Th`BTLfgV`Y0[A@w(2>aP.`JHb2WXh@AP%<8^z$l@A I% W%Lr)7`} 1 jHA!, lʴ̮ĢԾļ|Զ̪ƴμ¬Բ̦Ě|tμ̲Ħ¬ԺĞ|tʼԾԶ̪´̲ĦĞm@pH$BB6y X1@_@ "[~Koa` PT FHNH0s&8sȰ v@7qI4NF~5DmNH[ڐX'di*_`~X !h3r)" :8B4Rul LHA| 00(A!@Jč`',(Hb2@ĤFc0X)` $` _/a DP"+`0" h@As.'(:(a@y 0z`- ѭRZu^` W~Y7зEG (0@!, lʴ̮ĢԾ|Զ̪μԲ̦ƴĚ|tμ̲Ħ¬Ğ|tʼ¬Ժ̪̲ĦĞm@pH$<l'8`HU QPᑡ" a;<5Po4i:4$M=QsIKyk|*(%pkTV$[D( cJ&CD!"B!Uxa*B'!! K& I$%K%%*! %#''y$ ZP!&#D\0p^ l+L {¼ t @Q N0Y@B͛:8`N 2٤ذahQ|)NPXgj & agC! (`-"bfQvULxA& Xe3j NA LXK(Qn۰&(Xmb 6 [k 1vL01 'Dw &8;\"MVxvpkK/8jB ڛ,"%cQCC7GȶS]7M y A!, lʴ̮ĢԾļ|Զ̪ƴμ¬Բ̦Ě|tμ̲Ħ¬ԺĞ|tʼԾԶ̪´̲ĦĞmpH$6l)QJJ['9mVaEU*Hԁb2D8.) qhM+B#Hwz~Bqq d*+(z[sC+ufCZC '-q"#d%a-'$-   KpH% K$-#&q )$"!",GAqspA %ke| !\aAP8ⓓ."8BM`HGUTܩB>M@i H"ӧpT)XB(hUŠ`K?0"X  6 &P/'"NpPLSDA 23{h„ׂCc RA]!{'RK+@+%LðC"!"&Ё [HXPa* "CŃ QbU P+`D˗&JhZd7oHPbCϞ!,Hy\ShH>P3ӞN^^P'ŃҪ9!Y& t7+Y8pH U! {R8!+&tPA2r(bb):s@NH>f/%8>@6Q<0mw |vMk{^$l%H3qQUF>c_^6&lfBym!ZmGD!, lμ̮ĢԾļ|Զ̪μԲ̦ƴĚ|t̲Ħ¬ԺĞ|t¬Զ̪̲ĦĞmpH$l%ЀTI'']HCj$Bv\iU a jcKw!jy)on)Ig!ZqCo"bd'g) D$ 'B %%w!&B$(B&" &K'^TH !V(K"& "^"((&Y &$DZR H _O[PLf┓ ?CV@ /+\QˆJ IB@O@6BTFzXS@U =8}tꚢ&@V"xuBj"X @(:9@†CMҢ@@I-'((AJ, {X< (&# hA})#!td񢰍W RؠB!"X Ѷ+ &ł<m%>L Z '` _B%C/10]A&I~1M^B0Օ6D lG_ zQDL!, lʴ̮ĢԾ|Զ̪μԲ̦ƴĚ|tμ̲Ħ¬ԺĞ|tʼ¬Զ̪̲ĦĞm@pH$j0 l'&TM'@p$LҪtv()iJP,U JbX %B HKzgj~en %jtJ{ %ZrC%I*D"Rpn)Gg"B& (B " R onzV((R &"& ))g (Y)%O @^0P(  |CA H ("`uBj@͚6ȹSL'N8@НHutQE(Mq?Z+~Α%&HpLxsBpy`g#'&0d0~0ĬI9nH $0`LHz@(=D*Rԝ[RHyE&AW ARe `Y8T&xPN2ץ߮BmY'4މ8Ny q&pL? ҽ7Wi!, lʴ̮ĢԾ|Զ̪ƴμ̦¬Ě|tμ̲ĦԺĞ|tʼ¬Զ̪´̲ĦĞmpH$ $lAP,/|PHAVJ*4ӓ(h *SHJ/'il~/`r/dU$i\ X/.q*dv""B [CR G*B.tr*zKq% (!./ ..E *B@ >'.Ա !@ fC5 $X9HQ&0 GF/dZy"1yP>hA&͂0]hl3?D@ f# ?mЉ%x" hQ" VD1m w⭐ 0AR* AkC\$H1~x i $hB(HkK s <ѡ)Rz!_1bFpXA Z(Hp@ashwiB(PyȕX0`D v`vĀ(@ hf荠 6wvCTxQ`~Op!, lʴ̮ԾĢ|Զƴ¬̪ԲĚ|tμ̲¬̦ԺĞ|tʼԾĦԶ´̪̲ĞmpH$&l(y :)JP Ax+|1TP#{|BtJ-# ({-~~aVh![RBQe((k-,D}Qm,_&th(C, ^K~_T!K %#KYopP!#  o~`B&%D_ȆB"]tb\ 5l.b#A"diB Y$$F-Lae bbàM-@Pϙ[jrMG"(5$D- vibE[,+0t]hd>`4]ծP`BqS\p`M>P! i" 8p8@bÀ VxB\W:n a<`@ć)R BʼnZC|V AĂ t q ZBl% ,H ut^ۄ@#C00Bx0ʵtD(-nZ5E!, lʼ̮ĢԾ̼|Զ̪ƴ¬μԲ̦Ě|t̲Ħ¬ԺĞ|tμԾԶ̪´̲ĦĞmpH$rXG@qJɊ@ARGΡ#,DVءrȭɑ%Rz(GbHJw j{a,_TvzMVWaPSb x p-Y|bQ,^Gx' j]a  'JH") GE'D,Et[{)'k\{-($T` JAA/Fp $`1#ƍ dI!8e rrr  R4Z#Fس (tD kXxPA $IAW\lR  +4p8VdXMj8 Pa l(t$@0ŀ$(`E+( `*{E(iP -Rx k "T"aLpޠa ө {Ɠ9 j*ւDPX<!, lʴ̮ĢԾļ|Զ̪μƴԲ̦¬Ě|tμ̲Ħ¬ԺĞ|tʼԾԶ̪̲ĦĞmpH$~\G@qJa銾*PAfO$ 'dD'GBL:xQiw a.YTOTvMD a}|c]GPN_B#SGHG &_)#"J]|W& +J*% y XTfY-,+EO +B #VK}K .w+L !wWziC 9 @a & JC ئA8 !, lʼ̮ĢԾ|Զ̪ƴԲ̦¬Ě|tμ̲ĦԺĞ|tԾԶ̪´̲ĦĞmpH$nTG@qJ(!R 'f]VXAtCNTHzq_>BbJTj{~"+sUzM*D&~bdwGV++)Y~cUY+Gj#J~GHTB#%" nXTeO˸ ) B*+(#EK8pA5sa= C -FXqBD!{#*t| b/+`'ϒ'M¥K:HP* JPQ&8!‡ZDbaN$@nS"a *B)5!PKX@j?|"6SH0@ ('@CArz3(Bu2SIH@PY8 JSBvu Ș 5O0A{pH' F: >8D~C;pub/img/skins/sunrise/spin-primary-button.gif000064400000011037147206622240015366 0ustar00GIF89an$五ܖ\ԂD|ּƬܢtz4ԎT䲌¤ܞlԊLv,쾜ܚl侜ܚdԆD䮄ʬ|~<䶌r,亜ܖd䪄ܦtz<ܒ\ԊTv4ԆLʴ䶔m#! NETSCAPE2.0!,EN g<Ǥ` !P (qRE9u H5V)'!<!"`-}~K-)C|V-j# y}BklD,+FB +(& %&&`$ *)      -$ԚF-kCA!,n$五ܒ\ԂDܢtδz4ԊLʬܚd|v,܎T쾜ܖ\ԆD|~<ԎTr,亜ܦtҼz<ԊTʴܞl䮄v4ܖdԆLm#D4!i,L@HBӄ@H Qp LD(A2pid!0 r#B}B" X#BxX'| bX"L'"Fx" &&##" % oC""#'Ӛ&'BA!,n$亜ܖ\ԂDδ䮄ܢtz4ƤԎTܞlԊLv,ܚlʴ¤ܚdԆD䶔|~<ʬr,쾜ܖdҼ䲌ܦtz<Ƭܒ\ԊTv4ԆLm#0 #a\t&P9I A"K2tɤAH@<0Y4! B##!)X&(BX)%X FL%%F  %) $ m% $"( "%E)FA!,n$五ܖ\ԂD|δz4ƤܢtԎT䲌v,¤ܞl쾜ܚdԊT~<ʴ|܎T䶌r,侜ܖdԆD䮄Ҽz<Ƭܦtv4ܒ\䶔m#0U,#aXNG00CcH"&)U$, @z`L%G|Q(c)&%B nW"B#W)#%vv SFL$)$$' $ǰ)  !!' ޳WDWFF A!,n$五ܖ\ԂD|δԎTz4ܢtƬ䲌ԊLv,¤ܞl쾜ܚdԆD䮄܎T~<|䶌r,侜ܖd䪄Ҽz<ܦtʴԊTv4ԆLܒ\䶔m#p#`hd2&m)qc$   FLBW$ W C c  D ls "Ś $ њFKA!,n$五ܖ\ԂD|ʬz4ܦtԎT䲌Ҽܞlv,Ƭܚl쾜ܚdԊL䮄δ~<܎T䶌ּr,亜ܖd䪄ʴz<|v4ԊTܒ\䶔m#@P aȼHFP0E#I6&"(]B@8(J/ᄊF*%  d$CP*LrC!Wtd ) !mr$  (*$&) e $ W*&*$ *PA!,n$쾜ܖ\ԂD|z4ʬܢtԎT䲌v,ܞlƬܚlԊTδ܎T䶌r,¤ܚdԆL䮄~<ʴܦtv4ܒ\䶔m#t#cclt@rT PT OiǑ\%l;Of%tTFLP%L uC#C nG" GS $  % !  X%% GA!,n$亜ܖ\ԂD|δԎTz4ܢtƤԊLv,䶌쾜ܞlԆD܎T~<|r,侜ܚd䮄Ҽz<ܦtʴԊTv4䶔ԆLܒ\m#0,#ftXHG 0T h,L$HѴ1"Zɋ H$'\w~$IW(P) W)# ~C EF' "FB#y" &w! )  p$! )'!K)A!,n$五ܖdԂD|δz4ƬԎTܢt䲌v,¤ԊLּ쾜ܞlԆD䮄~<ʬ܎T|䶌r,侜ܚd䪄z<ܦtv4ԆLʴܒ\䶔m#@,glXJG d =iP"@6Tt%H LBdq }"+}C'}&V, + %%B ),'FB'$I)$' b*&(C% bV$$,]!|,( ,,A!,n$亜ܖ\ԂD|z4ƤܢtԎT䲌v,ܞlԊLʴ¤ܚlԆD䮄~<ʬ|܎Tr,쾜ܚd䪄z<Ƭܦt䶔v4ԆLܒ\m#pU0#gȴ4$cb  0M* 9 rb0-H U+H{K+!C[  V+  $ **& Wk+ FBj" * b&"& # bV + ԗ K+A;pub/img/skins/sunrise/spin-primary-button@2x.gif000064400000020542147206622240015741 0ustar00GIF89a n$五ܖdԂDʬ䪄ԎTz4¤ܢtҼԊLv,쾜ܞl䲌侜ܚdԆDδ܎T~<ּr,亜ʴ䮄z<Ƭ|ԊTv4䶔ܚlԆLܒ\m#! NETSCAPE2.0!, @pH$H@q*P19Q ϡ%Z,C&APHň ]G ZlрCIF uSY2H,2Fzg%ɛ-P)*쉍N\a')%EbP!@-bA ²UDD `@"UbM`\A@A@Ϫ Sj`(Q5-e(D`. %h b. C(<ʀd XїaBN<DI.PءB MHp-` t@CF-[@'(Q <-9!yrQ ~k ЀB' \:I7xB!, n$五ܖ\ԂDδ|ƬԎTz4ܢtּ䲌¤ԊLv,쾜ܞl侜ܚlԆD䮄ʬ܎T~<|䶌r,亜ܖdҼ䪄z<ܦtԊTv4ԆLʴܒ\䶔m#@pH$XlѐTU'S` #JBvK)!!Bzpc,rK xx{B~T%ZD ~t ( CuB+B~G/,C"u +&K~+H#V")/B&Ľ-n12 2- g!$  +3+E+/Χ40+@+"gK QW=/ X 4ǎ(B/(eʘ;77c|ә賃03(S& d0a5QB0j1܋OTL8;bc4[쭐tjppE&",K̝;IṔ[5B+ )dNB 2Xȥɂ4h5=#'<ᛂep(n *pƋmjұA&IhMy*:R ޯY CcGD}h!, n$五ܖ\ԂDδ|Ƥܢtz4ԎT¤ܞl䲌v,쾜ܚlԊLʴ侜ܚd䮄ʬ|~<܎Tr,亜ܖdԆLҼ䪄Ƭܦtz<䶔v4ԊTܒ\m#pH$jFH@q 3R x cU8Vv~J ZJVz> rcJg++${B%oGI!Z D%0% Td +-C D(1-%n"# (B1"Jn#T0J.01!$.$g/-'1 " VC  8h5 2[ ` Qxa@ `WA.$b%6P%5IH@ @HPS[&E**T')"E׮,Ъ->:aA-'`: (Dp @xG ۽E$ DJJo)0Ak&o x{[,)z1#B@ 0u a ή;4iی:VazK3܅m0@%~ ]َv1 H@ea1A!, n$五ܖ\ԂD|ʬܢtz4ԎTҼƬܞl䶌v,쾜ܚlԊL侜ܚdԆD䮄δ|~<܎Tּr,亜ܖd䪄ʴܦtz<䶔v4ԆLܒ\m#@pH$Hl!A<;D(PJȐB̢%qB(Ie.--B&ppk(VW{-\) Dp U C^D R.(o $ B ".KV|-R "" K%p* )"[!((*XC΂RAB?&a0C`*F &ȵ叉T0頀E˗!Lx2M;6bvX%piXѳXKiXqq0I+>=nKKPbgY[.cU/"Gy7[D A!, n$侜ܖ\ԂD|ҼܢtԎTz4ʬܞlԊL䲌v,Ƭܚl쾜ܚdԆD䮄ּ|܎T~<δr,ܖd䪄ܦtz<ʴԊT䶔v4¤ԆLܒ\m#pH$blA/а,/ H`01y]INvH>0j:c=d|1rtK' j|1 jsV)'%[ D oGH]'qB aC/ (R1 #-J, !B .B KoU V1.! !&%_-.. /-WC+ .$ЧdpW ,"L`\<D ֬0 -Z\# Xņ/pl(F:@gΣ 6)20iӛH\AS T'(0P@W 8{DB5QN-E^'i ^@ Xx+ b. 5`dM&Ç18 w߸K#8]JBtT+\,-}lzư6x1 ?dX) "xEt-?Ƌ)@dwd D[E!, n$五ܖ\ԂDδ|ƤԎTz4ܢt䲌¤ܞlԊLּv,쾜ܚlʴ侜ܚdԆD䮄ʬ܎T~<䶌r,亜ܖdҼ䪄Ƭz<ܦtԊTv4ԆLܒ\䶔m#@pH$.4H@q Jᱡz}xX&2ivӧ"D8D/$Ljqv0% [Y,R@m Q0@, `; p{ q~*B0T`P#hpo604 +rP!(u(L` bf*i `z WhyлBK@X]I; |ϞB, o 眅I.[!, n$五ܖ\ԂDδ|ƤܢtԎTz4ܞlԊLּ䲌v,¤ܚlʴ쾜ܚdԆDʬ|܎T~<䶌r,亜ܖdҼ䮄Ƭܦtz<ԊTv4ԆLܒ\䶔m#pH$:blIJg pe]ãB! 51VRU8Å[B*g g xln!3bJ#v[!W3^rd BD*.eI1!vB&jm3Y}c K& KoY. .0v*!&Z]{E!1,)ͦMd\xMD P   "9{ D)"Z7@)BK zyIDH ngrqAiD(H$D/^hc`5j50,XذBJtB" `|3L\E NeZ (X` 2(aX cBEJT^!BF1J|0@6B!,"@ ]&H: ׯM`>bb^$GhA !)h p x`laM~x@u2 a %E!, n$五ܖ\ԂDδ|ƬԎTz4ܢt¤ԊLּ䲌v,쾜ܞl侜ܚdԆD䮄ʬ܎T~<|r,亜ܖdҼ䪄z<ܦtԊT䶔v4ԆLʴܒ\m#pH$rG@qJ!ȉNBf((jC@NQ9B/c_gv1 {go, /dZD]aom~/V'D',bTC_jT1l] dJ~  1oGHs  1\yE ilZ,-VNGZL /E$H0T)vÇ'쐡 B|(QPCVԸDG;(. .[hX!H@% 08BBB= ( hHaTz1 h S8(ԩ+DAkP)V\0 .pa֬ g608B_hD4SjC2p .Ƞć .\hpA f{rh.X@PA5`G$(^#i v`/tzYa)oxL-D]T~zV\D(+ xHzC) /g+$#Jlr){ J rj/_.-B\S DVKpE'tK)t|C "  s^H`p! Nh4厒)ά҂- Bh&QTEm-'j=33)vk&jjuT'vmj:K#s޼:Izdj{ic% %K(ctb K$rԕI 8+Yb_߾'O&pTqj=>zM571y}\ qoYX:rC\iH% IbqdlNL'&y^<|*Ӟ'u|ONéDQgSd82WQ^4\HoK ~L*{2L'<-2$L,1('&Y,t2̴C$<PU'1a]hJ(y2\J(|0\\"Uˆ.!$k;crk;w2i^ ^w+wFcSwb{c ~Yl5 c0zXn?5󩧨2߽yWp<+wۈ̚Vn "pLETd^Ř@* 06OjPEd|(H egXB@Bwslw$O0OЄlNJH;%%CZ0!4!>ȞOP -%]e={Y'ިiUq΄r'B@ /vEZӻnNؼŸZ/nDtՉÇ'Q5ֿ?~u&vc;}DlȲ>9!'z9@Ɂ~Ng,9''}rr fxN>_'Q}e>Ig">as|&rI e))9ޏ-K>5Bq+O24tb8^ub8j}4qaĝGDI /9QNʉ߮ЋF(0#J-FdˣAN%|^(ȅ jvqTbX7JXŚDS-MD!/Zt4+M٪}c%0(pAD8qP"wqjM;x//qCKwoo?\ʿĒRyݾԣP@ũJ(fdם{9ބ%~Tsxsٺ3tYic%l{A VīU=B{7LEUī eI6 iPvf"J"4Nl*B0 f >};Ӳ 0u*03o]xr'91bLpEH\.p!!?fhOT=RKZsQOϢzJu +։*I {8kΞ2< aώv3/l栞.d JlB–mTh Zr0B͢ ";3^H2{v0`SڄٳٛȄٳ=<THV2!m MGFυI´.qh+ HM;v\:GVbb{3v5Lz%r*pydT 90;y&l@ʩ<|H%DEc2kkc7LjH&P0T~A ‹!  %o%7e/fhoʪBD?PѳDo^d7e/Uq1G;bkzHxyjy'LϚrOt=[#5&kBMl,ix ⯟D WĊLĔp. X/6ťe[iG|:t:yHoNۻyWoNI7ZѤ5ޘy&pE~> IMG`Saω!1Pڧ'9811hX[YZZ_s0q9sV21R 2!بndRœ-/s|H & F12?)gs_"X-:eUᐨTLE ٔ@ aSE9f9Seǎ$*簉*$ hxF/5$0-I؈:e[i ̑7TD>tQieۊnSFГRW$!YT2  YHhQg,?&Od:t=5OL"Y:Qt|,}C.ɋA]+򁒟HFEE#N4WddElS 'Wnxg@+ d@Kۮnִ$_]t1C[xL``v! QaQC"-M"GO7+B825!y!1 X0@Ob{XP023f(t6iG, |x?@ 3p"@D#xǐD˲D2  G+"&"㥌ȿ@66T²]W㗁ۂGqjY0,eW2^V"7%x&;p|t&mS¦cb_>p^wn%8[ wjqZ\ĥ{;7 }/߾_P*ѿcp//lz?(GMQSܔ +"D 82~tTy&Z+c]_ [Q`h*AD^Úüp$$(ʰ$ (rM9= `Yq0 ,MPڻ"$:lA܁&Rũ_4 xͯ5L?;`=X±,$ icm K!G xғR[ ;*"-qD{}Y6S=P" leԎHR 2¦gMILAD 9Pn!;is녖 t+ J;(2aǎ zΎGVW#/W<)M0@U4'*,AٺUؠ @"1U^û4HM%EZ4 ,N_*1 Y4 D7[XoΑ^'x$NXB|sY/E'6U?ǜ:-b_ekuŀ}zqJ l`k1*"e>d*%ofVɄT^J FVqE<6nyL_CJ:d.}`68dHB*J˺0ͨU8fW&9"`('\@5^`-p/*$mkxX'<Ǖ8@xb$HJXb ' =xZ?̑Ly_Eࡾ2$\!*}K)̜_.JxvUxYF:Z}ޠC, gy C8!:T^U[[eީe&@Lwʣx%_IJe\u>$޾.G\/y} TPP*bI7' Q\:ٍx/sK[)_sP"\UjWy-;8JBeح~IкaZ",dhf)j"*)^%bEeQy\ _};f`y{iSTV6RG{hE"1>b[uY5C}[DBЕ$[iPdՅp2/l\D<$G$)ct@0 ܁*]ǨBM!o< xD"< >"RwAR@}}7:eCk.'"@3UIJID2@'"\ůg{k쇣~5 O,ETV$i%+SR?.b@sb" x-:'$T%p8MGSFh|DY4…plcƮ1uR;iO䁴 "D8B)J8 $XTđebGQЅ3sc8.lC@ 1v`<&lg%tfڠNO{Y rW 4I%s=$dI#RAkwXU11UN1N{ƩD;Ut`I#dT sQ=`W^y4xfQI\> H3@yI@έp38873Af9pB 9|2o ۴s= aAOKKٳPe|o+>#b{nE%QId=K&Nq$ΟĜ,\F;~&랲е88DI^4pڸ)mG7":#yZ}m41Æu=6lzfڨs$rř3ki9odNNNs!5Y[nIn%4n7qb f|@3^lM'Jjȉpϻ;%( Ӆj65~gqz/*3%K:+/·b11 )p@ /- kVijWuې|Q:~7x4ȡDPP۝ l1&K퀳=MYӡ. PbH_S|͢Ph遀APxVx/;>q@ʁW0R8@0:J|S^e@D't.g[wyzۺ<^q?=tx.܁ o+VEtAi1 )E=NܺQ1k 4n@ܬH5N4hD02u1f5bIn%vqT|=j y'ڎ9y,?@B֩3SO*#0A @q"b<"q<BadzI,DCO-xKC-OoCCR{\.{ א'tZ]}T `G",G4qD >ظ`Jƍaܦf:Ĉ[."#^&>32rx)u .T:tCxE3+СCA &tЫ|ժuúE5 IAMQ /())WT8JPQNQIQNNIMNN PA|bI"JC?rΟɁRp k M*vs+nn4>V?6ބf@ Hn$Zv LLYBa3Jf1he.2DĨQoΈ0Ć߿zCUn<]F5_cpYMeѻ_y.N??FmQ`7}()vaZPr6thδiSs6 e-bjE4J^(>0pՈxn p݁:PRf zӂ& !Vkd'P'!p um D8>?e-%֯[ʦMlK8|nM/o^koV-"?_cv۶oZdDxcQXpbPxX+x ,g 8GRAYue {% C$L82dV7Dp|9¯8!;խ'?a5D\ff~q,24wAL{滃{K>x `w i|KKa+rs+!%e(**h|R(bBP X BM#VVXU?;ni ƨ»"ꉨ~Dx]7?QA,^di;)DE$CĈ.bjE]<&(xOVbŊ[Ј+@"XpS۷  "nn[D$4PPq{{D AOJ#$qq0ɉ8E)y~4aY jB 3_ uIAH"B :թ`u*X ZgB.M8#Fna( GE() 2Z| joD]M?R*+s"%"#LwV#DD1tFUW ʄmcљC8@;#U~&.rk|j!*)_`ضg޸ 3y(06Xص+C+6*:." x$>T hCi%mAZ!ED$._EOo"%D~aE@B4$X#HIZ̛ҽ %+)v\Ępĕ'9qt/p4 NSi7k2 r`/wg"`> 0ԍ҂&:)&|3f`W>O_B97A⛉C3 ̈́kEu'%9r!V^MHZ)#7q@x#$QlV"V8ZBfL`Bܑ,G>Ēy"yp\3vtd(sƛQ \\ +3{8fsE\Z]XJ s7[sbU$#G Z?{;9y7lĹ%%g1fsⰻw71&J32`=:P7 0csT0ؼlƌeXoƌŋ73o@` A( PU&N]~[.(EOLwqAO}pqiǑ"t-&ht(!. qѤ qњ`Th*h48=&^W1w:0k6Ks~{n(:}zh(O!ƹQDGFPJ8sz `pf@Vc<( "kFQ?_O&}Ni rnN%!!2A80 ni˫])=% {oat͝0oT /h4 beQ`YTpYD.ca7Iw1/Gwq׍wtA.Q] HJOE7BDT=LkYagL;v;kRl ygY` jUC;})zqB+JAjTRVv jѪUFS,GÓ(F0 uip uX !Rd)QAQ>y+NJcg1&!Eq>)PqK__ظP9> E}:nABOI8#K(80SrLDhu aI̻"+g+!+mۮL7+{X,uʹAΉ$tq Qytj=/f1H8sIBu.lgRjvt> :K-]:g)(ugCAĜgR`4. #kVzKB&ZtjҤoͩ}'u7 3>v|m6bC!BK fH:i ۓVqPZҚ5K\h,mfHff>,@un`9+P] ^i:ЩLAw!Dui1u)@NkEl -a\JW,}cbQaL;SGL˖% hh E()ru+ Gά'9&!%YBIq@(P4n)%G (6SӒ R2ĔN*/΋h:8@@R/ @/ o@lu0r$ Lz:(7/n|?=_+!y`T f"SuXeb*?D`F(<= kgCP*;Yw?"װ, 6YYܰ^>Hvb8'.^܈gA$'.Y山'靛ʃ>0~97DxR (m)=%KsҾGAz}tڨ>(NuP#snT q;ɚm>88J`tSs%ݷQŻu 3<O^ȇ''jO>KcQ6ҰoIb2B5},S B7@#ٱ[ kv+62e/.^ť`n:UrP2u'vyIX H XP4$c1Ymg_a"*:r3B!(d}sAgv"])s'j ?f!U*|ظWH} Pޔܫ Wnċq,F2MM108z% @&`^R^Sh(AV!Bh-P0#UEO@}U层(]5Vȸj'Uix n*dgK"2´nGGk;ru QH֛&6Vi4 O D(; q@'P<Gmմ8-ЭZrTV} [p8z*~P`**SrcN̏w6")ₐ}L[v=ĥiã @\ Sv;QvCBXe1*#ِsxxvdĈ/\¢2_X].#5q]ID6E{;ZIriE.k$%ADIkT Ljw{RRۓh*Q1@G&:6L|8o|H8Eh苷}YD@D1ch1ՉGp2"PfIENDB`pub/img/logo-foot.gif000064400000003223147206622240010507 0ustar00GIF89a4EAQo4kӌ:5eMu.Vuϥд0Ϙ6HH.Q&L @>߫3a!,4E@pH,Ȥh@˨Yl kzR(I32HB>bBO Mxw } j4 vD\iHi r W 66]C6M6yC va#rMPV, 6a uuCOapegi4pe5ofM6 e,vp4P= Hz@Ѫ7! ,@ Lu4ңHŁ 0PK JM3Å KPTD. O(bM-0%< jA(]_+{=P 6d\ -ԔBI/~-@1Rg0V \!ba}͵1p)kc2NSNjXQ 2$*Z ęˇE*F1u\Q?,IH=oX' m/׫-o0{ c hNzF(d5[|s}Z#PCE5pvj%%B_h] DrPH|)&aT#h$(4`EL’D0dH!ղւ ]E<E4;@w {LEjoGrG[LYc!*` @p*mzڗqX?Á<t+sjX:~I6!l)P3v74bU9I@h`,6BH 6CQ A#p\` 9APp3v D9̉ x 0G%AnVzC `p< q`"=б]"n0ªm@j8(oAs 8sjs/;) ;pub/img/flags-32.png000064400000103402147206622240010137 0ustar00PNG  IHDR 2PLTE+  %i#5(}'f 9):'FP e4l-}X`gK90&;\5#E02:')*Q-)>l,*.;0A qfi"ET@/9"6bdR H7W^T-dRB# >–%"bX"**TZ!AEEbDm4ݍ&6FScM9c\̝;wν3I]e. `%ڕ԰rI~r\hruhL4fYrڧ(ŸTR.6'cQ) *2FIU(Ch߅v e3 eXr]"AZ&RոQ'P1`tLN~w# iRL"Ԁb5wFu@4@;Пa ue%뮀i Yc=bUZr PTB١ P.PQ uuY~ejl:H.d(Z_  \]z b^V/2+4l0!+U.ah6ɪC,1_&1jD/P,"@lFe (<;D:#I)0lPI 4pFzW 3 kr jԕ64LO"r|1 q@u'0 wD&Th%+BMj"K=Bҁ9O].r]|.b4"U\ԋtM*.oO1%o >,*f ":PiPy*L{wF>1uhIҋE `).:@jvkiT f9>8+gvIiP<Z-Yjbx%nƽX M4֒ Xep||9,і<I$x$6>MrPK#{ @?F5(/&(f[Phwefvvffv * A@jv^v6ucBԐP(P[|vءCLco9믿nB(vп{ QC:E8|)ЃLr7wpgV$%FT2w7ȭHe4L=t5xpq߇u?^:ງبsx][W8w\Pam U?Mշj!0@Pk/hɶ edCG[T{=EEz#,{N?#H>w"`. #@Q 9HMxV{審҇?;rT;ap􍑏獌x#62?(=!G%MeuAʆ#S3ݖpա4Lޞ>hmpN&ɸuz)%=[R`UV =" <X)8t[=cU reaRNYhkH#ꐦ]PՁ$aeI1v2u]"& 2gNLE+1 'cA8pµ G›pmL5`|0CD LOo1jQr6V_0CT1nK6JO p g`H$6Ѭe)aH|bs+U֭Aj]zՍ=-+BDz!89(Tg@ ma)I?0}:Ev]#$9{KKNJL /ɃJE򪵫(@NOKAꀦu.;yzI_9`.oi.o [/vVg([ @BHKԅc=9pnOQI v4RP:֯g^ lShnhH  (aiBf@B9fSYėëQĪU"JP6 /H$h fd7tJ#tinJ@^9AWқGv$?.ewN1tBw.LXu꽩wUftx΅]' $doWBH $PwB2T-(@%e*g|݉oϳD&]'93Wy$ Ʌ zMA]QS P)E䳏JB QJB 0' )P$H FBOgeOnW,0__Xo'>S_^<,X! q X@(ɞ𘄱UAx2! ٓ_}E"a=kx"lsZ6׿?Q/ghEf͞Iaw^z9zG`Þe߳K{ˉ졉=@x_˗uuT ^hXvc!n a#fZK]^J7toHÙ lA {-S vd 06֋α1%(s##Wr&g^ f.wͺyS9c*L@<64jkE}c3/;wnxͱ-=ظj"z0%\Ӵep3w~2>66غlᥒ-prź{Ż:m>/*MEGlPr4J[T/O5#{kX"[U`0u}  W@hV„C()PIaDz8Ȳ*)R29t7 DfKBUΤ0.`S:MbR;"w߲DHW-~ ' b|p1W]03PU21?5`Pթ͚ l3Vz>=RQ&]ض=!dd}-3fY:Sߴ5OVXdf67f8ݐQXGƦ,Y־yetdtltlj+ڧ@UX#ԵK>r0 Pw(88Ma_C)! ,SbPUo&fR.]_yY89 5 4xu9`85;?I\y0РK)hT)e~:dtI{{{c|_?O9Ƿ_>"*퓌#$77hۊP{*hGpI{.P>=5C[`֭R ƘΓIc>yMј{ sfsϬS]ts:E NwN:ixmOh(iSҀ&( l"@J>ҤdJ-p.4uu@?MAhM 1KADp%8\RPp08h*)F 0`R !K $ #=QjKð:yi 'u-sړўp%kEWu}FɊ?W^6(BG?9z$=rTcq:qHhM }eo_LAǟ%(/lG$?\YiE%WC>ح+t=X:$Okko8sΙ=gd3;ggStHV^xq)br{i l\]y rk>ͷ>i*u RakzĔ|z"|e.@FswC_5r"0'#qdSJujj=6dbbrwwiJVjaW;;_q@Xh7K햽0WNҊ6S΅ҏ2,X66!k}^PPK|U4`8`YxÆ_=6a6ɇڃZ= PkYjq2ZnN +uxi,2_5Ĥ ,_.z{]@=psEu@kk5+wD t;4hsg !ٛy ;$^p.QX@hcPA"Ը9~@30>w| 7N`ߋ}Xt$30A] |S?:]ŀO =NGE}t1uͳ5ǞSq @& x6AV!Y܋ >XIppM}|Ӕ:f,o? /T:[:ۑ#29ZvRw_#dq`ܺVz{d?OվrQOcѾhtT]-J<;0 D#u>b]=t),S}:)tma`!n_JݲEѡa!>Jt{=|PuΡ:[Or`nE¸a82ӑ6q0v0?M șZhGn1XBF{sQ+u'FV6ai B;w ek߅h -2)(YN?aM'0C}@;f;wQ;@-Fnh؈*q;`tc:@ւVЙx+v nDb3ЂΪX 4Ztjnf&EC,w foH_q(J uC%D?6 g\; k3rM  3rיFkAqt&3-) g22(K$E@ԅW֡p#q@1}k.Aγ8p üd^gh .lY#)_xqeR=8#P*=HˑH$Dqz~"u8/ku\VVPF"ϟ "5r"xU 3 ]73Pr3/X))M>cRDtC2 СzhtBoxDשI򃒸ezD}neTP==F@_Tޓ푽C/Ǧo>z !@)&'ƇDq5g Iӊi5 W 4I7 44.~Dh<X5D -OI q*HESaKdr>0GoM4;|%ܡI%a.2Zۀa@0 dщ_A+;|OKjg۵) :zJb@ Mcj`f$@i7CD?$#/(1[71tj_$!5GNZMQ]7[Z"Wy ,-O㍍-mH[`!t‚m`6-nmVԁXcP9 O3G hc^ gb9&s aC7hp9Vx́r ce {I@wr"xWqb+пd%b6o Z*** 9P>Wn 0Co1QvՓ@DD(T }n@kUC AdHo4V>!yT=>ixO}LOVԻs̀{3P7u ~G,<[xb:cEݑݻֵ0cU'Zo7fEЃ:UhcaQσF硇vR&pe3Y%Ij5_S{3:0n6ygF,t/s;=-l)4 ^8N ˾y*+WZ,c5N)(6u(v\nP,uW6u fpyťygQ.fw6!0Iy.{jbPp:d(( p63U(r t`$m`^^%˩4lNl,Y0L4=Sr%cꁦׯ PR `*Xe57F/`, zY?M+* IAme]]Fjap9,@XB`q^/TWӦP_CW_(CyE~PzMpx̘pAfP9{?<`LXi1>xUT٫@1sqo)#[l=^f끨Ҏo6 5-Nc;,9A}d:pڀZ7>$$**qf)S#]]JMq o  MZy׸Fq.9i9o(qek+ʋpY&.A. @'OqZAh#ś _p; -vVN}${?Mgغt&@UU}}U!GP2lhb+WLC 3oW>2O,V,vaP84lz1ՖriK/miY[$i]2pP0KrN*u(?ϓ'}{fz/}~w'yIzqO0rщ+Zv@>{乡QQtOĀ(mrO*l6XrO=P22i-@k<P[ʹ{FLV# 9F5KeFBg8MAF$2⊤)&'y$΃dnǝ!@H**ʲNH(@BB`kܿd g "D d_+w(33(^H ~;g&Z֌y y`~DD0h7a7GEI{a(cfby n<L ɾ!BW] jtWA= Aξᴾ@w5'XCauĉ!A\?A pu~cyz3'N /??8Ec`j}҇֗(} Vh|e'8Ŗ }̰]b`!Ѝvehk%ArL/t3Cd$j9bֶNf2:bk1`!َEHl ^7ؼ"]ݕf "Ī<0#w"E!bbj pĝ@GaJ0#\xÌ0r]h;)RD;ӄz"@#^ pzM xb5E)ݣSSe[dK0#()0@6&d vMP\!tqK1/Jb +9!M \3yBv;b , /]*#е充/ABS56&NR9MLY7[j.tB6 @]BIrg.`4 l:A=5`KWdyT'`:]ny2PRSW5yԦ#jG7 l NOfCf;YL7WB {0iͻX]z՞G@xۋepE/F2 1 f oE \(Elĩf+lxć[|ˆOwsz X~d7vvx@@C ]IM <y N,& $>X`.9]A 1@h: {qg,RtgR)>ktIO?m2T-?&JƧUp~.mK,D;{~_Sw? f_|ƷA+6{qm6C@[ |590bñ@":0SS ?NKJ_! NM}/c6\]uwQcũw=z|`jpQ p:*|} h 52IeDyksx"=PFjk[`+ /X t9 ' @!^ @ A<ֶY&&.S9)h>E?_&/7&IVk"XީjIMd<UyYxa]XަJ[wVV7`ap\#|@~w_G ATC("PȏJy"+RjP׋P8R)p,J=nVhX" +,$@:[k JyRTЉģ=.;Rm1Z"B~Cl+ t#zďIX*7r#Tc Tg4"PS@ƺv1w { 5 !ׂ$_LsuC%Nu@ý%ɛ=b/;>nWTW+̀j_< c` Pc@/"oϞyj *GpCoG=;|fFE&M}>@LվǠf4OTAĩf8To3{o;{'0 XY!`ڤJu ^cBY 1 IG삜FV  qAqF..ؘA$dgO{ͺ3>̷w 8's ߰5؅]p oz $x{`=/pC8$a?~p]l~6 5kGa>~q v3bl8é=7X=} ]' 7 vB|:n8A|v 0ܺo또VJ,YEJ$cG7S㍈E#<=? $EY ϶#~~h엺 m(Ix> gh0q('\Yp<ظi38KEzJ 6)fBEml|17Amq4ܧ&<XI]V&-(q'F3x,4`zK,AA< y b +|̥%bk6%Wr'Yk33u60Mr 4n^f Iq 3Y'6] 0l.D BאQjd< ~Vix/RZѴVL[CÜ?rdlD֠dc~FNP@<"rHK#|Yx}_.S\Y @5Ya$p^UsPg5T%x^{Wg= rt'd`Tks]Oo_pUKP-X,hbe_ɍ#?jjnMSž vVuT$ͶiD*$zltB/9AH4[x&@ltűnn MVK 8%PEĞb\kG2ii."+nGO?pCDb0R f@G"Ý<7MaJ? nYghnAvՂm_ڃ\ ]>0,{ Mֲ:ʜML)&b+?H =W=ml백,% xX#)~b$\DcH!wc,/8S,Qr#/r$F12 5-lP RkXNdf˜{a 5+ LA}X AܴU ]e-mqA"tv=7 <|PtMzN'how:sϓ%f40;3'RUk4?mg:T\ןIۙ/K0GQ.mg:_?Aq3GoXgÛo}}?}p8O:PKgUR{yQ;UhSQ]vKVլ~U}*t2¼ An:6kQ\E&1! qrUSY8C ɏtl<!dzM:DXdz3 K';f7[ |!i|!ilmMpw~C+:~*y:ރVt =!{ <8bS־~{t)tO='PkƻO=8)㿛v=QI=% vDXidB$ATwYiDGa*S5LL;{9'A=g|s35k ;4`N@8]C@` b $(yralAp v"FwEu+kP; d^f:У=M@11U"ք: L(5!!Ѷ\GP —b:޳%>.zqCe]>,^ ZAԕJZN@tjY"UI #KC خKޒm,]L빫ƯZ,h׀^j 0Y5ԡpl|1xͿ\= Ǟo 68䧉sp;`ibU`D@zh)FXf׀?mv9i !> lavp [mۍ7^dog?}",G׏j\AjLAcVAj@ 8 3Y˜fgP*j $G9r1M 偼=ъ9ĞNq3T!< yAFw2.? @4e@5h=%t,.ø2<I2+#q|(op\͇s*@p7ݺdd2P 7eÙLf$!n[zk@f͌R47gG0Rsi9|sEVl¿U=2)[بs~`d$/?e0ݔ W(Ɵ[%&,󑫨T0 W\JH@ࠋItn}}Ww}[ȿ]wQ~Go+Nbh}>OU\ps^9o.OswT2aby7_pQ{<{VSU{< wIg|]R\bL'e oRvo>ˌ:Mr;ß.x܅DyߥX80nw9(]ڜxo;Ex-#U;9 DzWCLq;Ǩs|\Tm,D ųgk/m  UVPP;ס}t9}=eOmO:18E;]==upc'h6n@y qC!5OlǾp.N[\$.93^29%vlJT u8J*h8ǸMyp lQ% FXiƍ2maV(K ?K@/ pm(~/п M ƃЇ^OQS48̳h|!VXz>x/|uy5)Q^2_&.2+I3mPm{ X'TtaJ>P{k.hYxD `9U)w!1GN BWU .lŪ#Fb D $ՠݨ4nnKqy"pbжhжoen}ہ};t._ psl*65g>VR xntdt='wKZGKPQE$j)) Q )FE77J$}j7'Ξ'J ׾M;^z]{ʺP[`EZMzfW)) 9(M=ejWx'#\C(p=^I%`L1`iWB_GЗ 1i^,p{-uƵk >m9>'h Hл _rthݧ7R` mb;AE}0)q8I.W\@C72Pj3zg%gO3/8c N} 򊬠RX,\M2eiŁun0pL_uw}ݍDbǁD7߻։Rf  c۵ @:e )-E&YnyYMJ` J^` P @q曂,JVj^]٨$q`ӥP"6_A ' = 0L֬x@(*VG^p7aPjiNB$RR rEb^"~tvC9."b}#bMN!^HErƒ]y+:+L8bd@tr Q<=VlL WigeU [MARSc p. Up.@!YU)o+ 8z)VB&HP#;%$ 6(&]1]$,a4-DYJdypl/>, K5GHj@Q| #7摊5diW\iP9=2PqBJ|% .]׺Ły ˼Pi\'B4/Xr> ]gxY.RH^y[m|Xm>0th+w'~՚YNJ xbӬORKw!J+ 5+fE @uivSLZ霥W^^C`A Pf-;2fhh d mnrhm ˪ŏ?~j&kb;g) 6:CF0\.gUpRfspMa (\A&S7氇!؂ʆ3 r-ЊQww<";Z?t/?}U|Ӟ;~R_`7W!f)\y]uϿo @;QշH$"7LKdʲRɨ"IZ\Ix̲ǘGB$4PӉR`y$n>J@ i9OU).E@ fdZӖÀ3T8Trj2ЂV e᫯cY6p 3-X7%pk 5U[;ell|l<ƭٌ1j-AT>$aSZ'<̠ڴ5\2`A),:I\S$EA 1{qa!E->RےM!ƺD#bWT1P O()TD%*T+ESQ4ҀQK"$(c|3v޹wu>vvoKdG>;7bSk3<72! l= ]H _,!axt'$̣5"Uq2,4auCpвDé/(w@Xgp 6pХq\b᧝tx΁ B p 92Av6^E@.|Ep دnB7ˤnL[$px"I0օ[oy:3:qx~9..s>7#u#QBquN :X]]0AuXup쪂88HmH`-Mxx'*C!'h@ǬX?'tMG[0gKd7| Ht 8Dz}N A?7 'w-[E7"-X'#/Op>Hk􉄄 \wSmjTTXNN9#24&X:GD@p#vHi:輅*  Nq7`&A S>0 .{f\}O/ WM>'\\P{dziux脫1}B6p_gNf'|&ЙIȁ,7 !SG$#mEE42hQ9X<|Qm`jߦTtU]va Umm:trbSxE2vk_&#~7v=Ҫ==_xD-@߾C6 p^hU"ϲ[s EOvBW9(ٶ~y?D~ ݵR8ッY̋CUmA2Y,GZdHח/".o?ztvp$.fY$@~ ,XQ~.oHA=Ms655 "~qh:`6 EsΛD,vcE"n@WP AA]h4a=`c 4znD&] Pbmm<0;U۔ߔ_DB~-|wzmRXX{<ӐwLOHw'C%'cb~<<O݄,1zDzr?8"08r~f!dY`ل,4C}'O:=~FùǏ?<,CW' úyTUUWa ?&7o\\>"D0% yoH!!!n@ ZCVq>W`F~KlWz0>y(tWqBOF}ϪvxwRsO~"Մx(}[aXʝG~ ts a婸k4˖Np봝Gk J’6`#TJO&R2q`b2 2qj[SrP bQnЩ5ze gX->UtӄąZ% d0K}T0XgN`KeE買_} t ]"W7{c6<62+UHHȴ4qQ~~Qt`?h4] 48 -- 8Q.QB<$`h@@@? 7Y7  %5v m_w@ F@#r4&rL}㐦pxƅVXh[ś5P,q&훽hVNadk\ 0Kmlfda+l`5mR,Y}E@0%IhxVH"TK4b B9iD@L# _?Q?'Qˀp#h]wg&ܚp/=玠1n  p_ñM锡xύAݑ[ܰ1A;zvқYv/Bqm@(`!#P:EEΞUrژ`_XǤ!mtFh:@`c|/BCNnj~jsptyh!7e8<xٺa-]/ p =8v%=܁ 쭰P-ub j;ϟܚp} ;v\0ϗ}'8(@HO =)*[Q0 \#D`>D x_*_ʀAiE Zc7tD[ P̚5qdn(o0tO3ɓcQCDi"8iXt'ӾrڑHG 1(}Q:6-B114c!5u rT3AMm2zg41u4Aj@0<ؙ/#M.ME - ܰh43B ~{:+1ЩgZ៕aGa8SIc`O?CLiDLwj,`! 4J{4 {4 {ؤE@p{9p')Eʧ)_(RX/cx^Ɨ`$nHlH+jnޭOP쮬L!vE,(ʒ5RLI[֧CSRVNVx ?IV˦MhѺɶd-ۧ^Z{k){*/g\;ڵkek7b{n]kY He]VB^ۣ572Z¹nnž|}|Jn/.:ƞٴuSJlGZ._=9h~믿A"lr>~7 ~".|Eem:dX^#{k0kr7e ]ӣj=`_-.4Ez|c`ME!ۜM=2zنl2(ScWA}E_R9=zT0 ޕ(Qۂ{E>9o}ɥ|uo! -nvSh`ݺAA)07n[wF3s\tV;*^Zj랞o,0LaXdX c=bz?"݉msFg|NzH ̞mdeI m`vOKYYz󳲠"hhȚ,hh``BB ygO8 p(..@\ܹqq =4xqE\J,@z-lTq/L` {?hjlTp4m.]mmԷrjNO&ZܺȀBϴ.8w']0ddTsA V#Gtl_1[pQKmu+QЅAjVZ/VJ~߹gA-a|`'`ׂ@]$!fq0ד`&oD|l>Ih"C9v3ڴ(Eɏ/|LH;L08qfh2W=MX DT ZT`0;(8<~fX>@uQK ;u (M^v=5 YiÃgLt_ԇ0/ ~ 09@!jBuxg"b ߹fi Ǝ0ֶ  5(^ 9Ch?GE0/A ?OOlZkX2cnI=>e]Q֭{L*9֭~N|U?zߛ/4xѮ YtqcXx]F޸M[O>Y$`}c?'NlR`p#c'~Z0v'iy'h;@WVX q)xğ{cx8gݬ Qfr!:yiz(Ym 0uMKDji(`$f4S2uQ 4S%i&B} XDFz)f"-0^,YiL6S"aY,Q3pgU6]dU;< <0!2                   `34a|UzP7R(MfX5a Aȁ=^ڄa " &^u ?H÷gH;INA{ Rǫ:G*9,L3ZsRbW`[@jl^fٲ"TVQV"sUnnLbEWXX#W/`rA'@\pN+_XT5up7p,q (Bb'}s+*: 2-(4֭oB#Ev#aY-́G#M7?-tF wߊD"[j?C-Y?X([(RHkhhr9GFE:u\cZ  oM ko0B1@L1_.d:ZlUx]зZB1"3sĈUza))ř)rSGHկ! 8yyɩ /5,35OT䦦殒RRN`GeWԷrدʉHх|_ӳ)iӟ%a Q@u߳]VPtݺ+  |W9(Qs'ڤTu|}~P[`& 5 c&4 J<Oa:?lL uh+0:Ç~_(--.]c^}bma %w=|'"gqM]$gq֝&Ol/zLG##Cmp| 5y@gvk|l<|QM܅Kg@- e Ŵ͊yvxQP$Xc WPd]@&G4\x8`iC(79KW+Ļxb΁p Ul8xv.|; uU=SNNyrT)@ߗ S驯Nj Q?T7b%5͕% 2Rnۺ 1mL\\zJKn 22L"!;K@T_1wj 7/ L$2x0厘=h;"0BwWEшs9=@ m>),'[,vܸP _(ųv>We5apxx'*C!'h@ǬX?'tMGx ?x=H[X`:gYD~E~n?w출E.q §c`: ؜ӸM9rupn[Mŗz%9g[ZTϛp3#(UqCugu΂gs.츰m98pp_V ZO/=WwUۗ*Ν{n ir@T !|nCGjzdž y@CHkC" Λ"0DFSoQ/zv[>@^7ё{O7ze:@2s@23z"=7"WH:W@6 d8$) N1 l8p .lxb*N*.TEKVfkIfi[⋚@ě4E6l"P >[ ^So+ݖd pl `[{TUi;ח?̆$"P *% $8$ -i uؼbi + Ci۶9oN\,|O?I ־&+2p*%@K}%)^-(×M̗&RjVN?-r:jԣ/ tpd74d3DFAZB|nگ_d]]vB uuFcf"fd ǥWU*vj,(hU:SZ? Oؘ_0w//!999g@^wGMh*V8[ qT 9(,f8@͕4Լ{|把JM9k MJ YX @AȻ5pb9ݻi9k|^C+0 k@psazfA^n |)/ڏ4/Z3_hМad@]iD3]Z3uZ&A =&& <XA.3#5 Pu>\ ]iws@] S@] H) qĝw H)VF4 %bW|@Upw9Lap/W TXpK 9-6[kJgp#( KӖh w)7myKMXXg' ) kkU su)ł MWuW]RԶ]_?6Ѓjߘ~=.`z.1 5P{ 50u/{6?穖6`}b}@ ʍmR8)Dn@50GV]O\( x쏂#\ajU9<{KqLQK;8Bdis"᯦%fVwsQ|7 D~ ,RyEkVT؂4"RTm-.)JčV#(AUjh) #jFQs;wg{+gvvv9w 2tN W8YSąjcg Xܻ1v\s$M5zL e6ȹB6 5: EtO{wy`O +}Fbl!Ey"ޏ_,_,~1[$Y8@ gYD@"B/" 1 Y8 "B/ga< $0@,(|2BU>mff=a,1c[d5̮CFwk`f; h^ٳD"Z$+Wή-9Ε.+/@6[(ǔ-VHXBdQd=ᶪn\^̆7|̤,RRvONO>'=86ƞ|“Ov=xfJGm P ?`nl[9(#)|#}Qؓ0G+ <( _XeTY$)6v-,\PR18󮽻0oxƘfgEA nbCT]!mF[jL~`<OҪH#} 4B'@-"#}K_/Wf9žSQ 4Ak FD#"j1⦈n\U#iU]@Lh]us! z~SYۭ_0%_eESh&K&id@4 Ԣ)MEM"AI9A ȁ0G4UVJQY*E9hE HoN4;̙wA8p J2;^/QB#5:@Fȥs KGbZ܋&A^-4. S  0>3b;?*3.tIO 3 dL sΆ2߁EWR9s T0s] 0^@&HޯƁgd (V|z: UU6FZr=OqEBʼ?wos8+*="*(p>UayMNÁ 92 385f3 ?B^jv:אm ݣ+'ϱS#&@4 `1eb6b R"n!I҄ЦhF6̠o,kr99j 2IPWvHmʁC}J n0GЄ)||MډƀSif@y$0"RQs]]dyG 8WK}  Хv*fЅ؃(g)Kf.b%p;r_/MU tT8$8+!A렭!!<} {?v _NPAS<!ʝ;> i=ӫcN=wR1AxjHÈ{ NC.İ8sS"7ĉ=XL8؋QP0#>9PaR'|9Rn}:q`rГ17)E*O~7K F䴋VWVXuj ܅+7*t]*JƋm2 }WS1QUU@H<*%TY޷蟪.`%P'UqNOtN;b9-/i-[8rGJމQ981zd`aU[>U7oUy%K6Jc6uW~ U՚~=L׈]Eޮ`WiM_u nt5t[n$)>kҶFrxH۔$P N<$qG+;8 (w *v˧AתBhhUu-HH\k+vK˄K"{o9 /lQeOkGEsf”ּfϫ>%H<}Rၑ-_ ڶɤ(vGeSYr k}:MS85)kxzѢ@VӕnO:q' Hr)p );"nWvs g査Y@0{)tleAc@x4-"mN?@J U)x 5PH<~zCX6OHԁR8Je1!+EB ;n!##4#LlZ)2R%䇬Myޒ(fSpMfIo&^Uys)j܈P u>4A>b0;ŵ0|$E.(6h#m·o͇0uý،9&>n9Sܬ8W;(/ .F@GP-r>Zh[eoa) ˁu!=x T=w'Jta#=XaD$е70b˰3YREl1q!9~ u*mM#&@I&P$ p@̐9܁v!ɇÆ'j~dTcHNv)FtEdVFڊ.{>\N\h|Atȷ>r\| x~`́#C~zItt=޾=LLHj۷= pr“'c#|Mk~An0t 8@z6ѡͲQsW6%Q{/("j CD7avEtq$̡k}WMzjbhB-{kZjl"Etu=lVXooy6e}@@]`.MZ "SRbZ\$2MkzQ  f0Cl8Xra-Ȓe1wXY  _Q7hY}##9ǏB£2nn}{t''a}GGF MvwC8?2::̈́*E#tF `J[]2l=Gc5u,OXlcl +# B wL`XfsZ,|h&# :d⸽`Dls A,`&w0~n81Ͳ@ܒ!vA܁x_`ճv@v=[wpNаgwAp 3JsAOߗ5hjرlEB㻿EhG0lzvWk}*Px]5==p/LԸb`\C, f`~c5Ȋ/reK]T}p1||\zHI+z0ʊw>[#-y(/"X-`(ϻ4(q'ӂ@޶((ѱw(Av<Wa{h#13%K:QP+-9KAyKyPPqZ2ČEeeUUtu&$'&: }sI0~C;6tJ$DrW7:XjLNY;M@BXwl d#!yMg 'E'HL u3w3!%19! dƄc?0 |>@ΔN0L팅))u?]RM~ߌ[x5HP c4^pfx܁ϋ#Nk5(Pk_*w xm=PT9 &mVŁ9}0Mp w魭H5w UK2~/*v&,9/;UmeIENDB`pub/img/api/microsoft.png000064400000001346147206622240011403 0ustar00PNG  IHDRL+PLTEssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssP"P"P"P"sssssssssssssssssssssP"P"P"P"sssP"U@tRNS! _PB¶0)zqXžq]]hIm7s}lWJHD@=85S;΍IDAT8r0AB &4;g3oI2} ׹wB^ t{&YzQ|ͱ@_"vLb֋  ͟ɴ#͡$r |NWR)i4`HSV2\:SL鼀zr81`8&qrۘ1Ւ.Uڒ3K.m>yz`7 7 ; 4|QpB;n} =3tPaC$.9:.x;؞ 5c3@"H!OKfnd-= >v1$V`ɱhtݷcQ~lQ l؟go> ~P6KIENDB`pub/img/api/deepl.png000064400000001234147206622240010463 0ustar00PNG  IHDR2PLTE%+F%!.+F%%+F%%%+F%%%%%%%%%%%%%%%+F'<+F+F%%+F%+F+F%%+F+F%+F%+F%+F%%7$6+F+F+F+F+F%+F+F+F%+F!;TzRgzBYn>Tj8Q%էurj|Vj}I^s8Of3Kb.Hc=tRNSfM &3ƺeS,ѥta]G7²{lia?<83̛kYC.%[IDAT(n@YzNz} YrDF^_75:j/1(tbkrrDFh.beQnO[=>$%Э\ %fiVKێDi*|~Q IJRdYc$3Q6jkYY8|*]TH!x(";nÜ4}-pɠV ǯNiGp22 /u++IENDB`pub/img/api/microsoft@2x.png000064400000002204147206622240011747 0ustar00PNG  IHDR(5fPLTEssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssP"sssP"sssssssssssssssssssssssssssssssssssssssssssssP" 3tRNS/6 2;W$#謒VQiWB$oa*ɗ}w FS2!aXIDATX햋ғ0IJ[UЫ^X0)8.Obzp1;Tȣ>2Pq;!J1`UB&VNt,XkLoQ aɹ[cd@k k V= `#A!Gc8@h=lplHCсypó*1<Xhv=ae1hE7fH66!XϫցI:cWd+J̷ֲMDo)u@qh.`$޽WC0.3ZX0S܃Da!#3tOʆ+Pk]ܦ" j̰Ns@Z1̊`|άH> LD+p|.D<G{.o1GlPC0&C0-@"_B !q*FLwnA@!en@BAKq{&om_oʅU&O,r|K$W 2:Kh0C VawJvp"Wk΂E=R4g0Nǂ  Pࡌ\Dc}X) xӛ@;`8 qނG-Y`Q8 0VbӮJd &,S0v2+1a]7iڜ"gpW:]7:Օt@p{OmM`ؤI"35;IENDB`pub/img/api/google.png000064400000001104147206622240010642 0ustar00PNG  IHDR2~PLTEuuuuuuxxxvvvvvvvvvvvvvvvvvvwwwxxxvvvvvvvvvvvvwwwuuuvvvvvvvvvvvvvvvwww~~~uuuvvvwwwvvvuuuwwwxxxuuuvvvxxxzzz|||vvvwwwuuuff)tRNSUYG9л2Ĝ=jA o+zw%}_eH4-LIDAT(͒n0DfU$K.S0\%|a#̅!.fAkUM+ۛ[NlE 2N =()i^Y@S=J"/6+ ~?R9O͆k/ahzѮ%d V-gIqEC' %xJnG}h?'IENDB`pub/img/api/deepl@2x.png000064400000002407147206622240011040 0ustar00PNG  IHDRd(?qPLTE% *'=%%%%+F%+F#3%%%+F%%%%%%%%%%'>#2+F%%%%+F+F%%%%%%%+F%%+F+F+F%+F+F%+F+F+F%+F%%+F+F%%%+F%%+F+F%+F+F+F%+F+F%+F%+F%%%+F+F+F+F+F%+F+F+F%.I;Rh9RQey*C[#=UѶǠizYl2LβĥH]qʪycu1Iaµ[tRNS"~VC8iЋ& |u=,Κk^JȹroVO32ëc+# ⹍ieRK:~yxLB'IDATHWSPCb "=n}O u&r $9_SO&1ƣƏc%/wyԈ.|m,*Nyߒwpr@a#99nl.%5kQ?F]CyYMjCېg* K/U9GȰA߰pX/WnN.083^T(a4KЈ,*Okjqى-&v[hTm+$ A9C1!3@0<@GeL C TC|"wJN]f"41sf": o9˿ ՓR,Y7KYlk4! 3y iq~Nf^9f, >qbwEez"fV6,HTa{"ǟ(X120lʢ ⾜4!dn zsB^T+\dc1`pl,c!rCμu!"CYrź WWC ]l,$ER.0!|!Gl(/U>!Krҕ1&i]S4@ic_k Q+պO#=}euAl%l@A>n[l<. DN?P%Xj%g@‹if=ZH_U<U3$IENDB`pub/img/api/google@2x.png000064400000001751147206622240011224 0ustar00PNG  IHDRd(?qPLTEuuuuuuvvvuuuvvvvvvvvv{{{vvvwwwwwwvvvvvvwwwwwwyyyvvvvvvxxxvvvwww|||vvvvvvvvvvvvuuuvvvwwwuuuvvvvvvwwwxxxxxx|||uuuuuuwwwuuuW7*tRNSˏvSL;3 q#DXي`~j-)fuIDATHUǖ0M" 'n+k=_v=M,-Tݪj@c K<&뚠 TI1&{>8C<>k,}~Q:P8\/`MZفj/|]t#LJ"p/$Dޙs퓵o.e-X/\~+e z"$>Ӳ78:rZ;u$R6@A cNuSUS?+6ZVL$c#/ICcf aa O|9gJ\ĎȞ_HHEVR}#P8CgRaC"SY Yb%I<6DHŅk(})񾹅EBՁ/a! ~s9,)p[[&V("CnXT!Zy>gyXԊg ɋ \NX[JhP)q#F D1g.AX$α?  W Tt&rbQ-#r;[we*$:pU2_b%+m4u;Wl!R4zHI^첁R偗sG=h)]+j"&Pț1CFy. ֞ԡ}E@/b+OQE`1\9D&6~]2>fH"A\?7<_"dI|'c.HcYvAqE!M5h/lCv9/N/~l><eIENDB`pub/img/spin-modal.gif000064400000011141147206622240010643 0ustar00GIF89aDFD䤢dfd$"$\Z\tvt424<:<\Z\윞@i.'#k,8Ǥa Y9)u,MP 1D(<\^\$&$LNLtrt켺424dfd\Z\DFDdbd,*,TRTtvt켾<:<@Y caȬ8$ ` =Q)`@LErLC.HQI1(AG\*&JRHRRe:D" !::_E&bXIrd.'! BC,#BsW.rrKW & &&+FL(pr&+ , -* .(+! #-"C$-W*.-. BA! +,DFD䤦ljlTVT,.,쬮|~|\^\trt464䬪lnl$&$\Z\424촲dbdp%A4i4Ǥa %"B8dH=ISfO0- ‘<\Z\쬮lnl,*,ܜ DFD쬪ljl$&$ܜ|~|\^\trt,.,@ , 8> +Ix !y0"reya0Lp0A4`Y- '.B$+S"qB- LCL  X0' /%~+ X$$JI   "%f,$(`**-˝0-lٝFְECA;pub/img/spin-modal@2x.gif000064400000021220147206622240011214 0ustar00GIF89a DFD䤢dfd$"$424tvtTVT쬪lnl,*,<:<! NETSCAPE2.0! 4, @pH$^HKqF4 P [fԠpVcRh@V--F tLɩJ%wij|n4bU2vZ$Ds% 1f2C2 D -B ^|!ew41V 0 3!Jn T g n -w%w1( !&.4f1Bѻ DEGDƂL50 d!AǏ @X&QF0`0T/a p1X3MH@i,&тիE,BB5$@q ]蠦 o=*Aڮk]b=wb Uhd!XbY(AW2+\H(Bfp3 BņQdx1AǠ"$- A!,^0x""@(.=G_X0zBG(u$ѓ z*%hg޳ pe`e'! Ay  }B5K|G(Z! 8, DBD䤢dbd$&$trtTRT464쬪ljl|z|\Z\ LJL<><  pH$HUqR19$K&I2W(P' $#9ZE=d$!rHt9/xx/|Bm5at -X4D.!$*sqB 1D2B2 m! s+&B"-B J( J79,(/m6%x)W#(5 )D4$( <~9Nxg1Ll6 ܫ w58aa E44dF/_f# 9@*BiᄅG-84ѤGd)QiJW &`XĀ hBێM -  | B F Ba^{'kQhPae  0Br-rdlEAؘDH1 F)f" 6(@S(D`1= HA_@ըE` Xʓn"㵈"[E\MP3% aR- aE9 1ZZA! :, DBDdbd$"$TRTtrt424LJLljl,*,\Z\|z|<:<DFDdfd$&$TVTtvt464LNLlnl,.,\^\|~|<><@pH$@[qa(/[sy0ᑪN8e;la0'D8 7=`j'tUe 2k|:"oo&:c 75["D3p51(90C)D%  B)#o'6 H y2B7 9 B 6Jo5H,.e&''J, 1**p8 - +y*:0!361>\"Bň2:lS@,%k0L  JpS :mIB F*0Vd8_̠H\ѣFӦ,X = ӧNJE$F:MY6Z0ҐBpUaE %ACG&"P BҀ1Pw `"M\OF1ႍ"jKC9+Y 7-@[Nww[Bʯ3Lc--ˌsVovJ\>|/5_ hhΈwJJg[J6^k:ܠ56@S#C % "|{ !1Ҙ~"A! 7, DFD䤢dfd$"$TVTtvt424LNL쬪\^\lnl|~|<:<LJL䤦,*,\Z\|z|TRT쬮dbdtrt<><pH$nJXq*\᱔P OriNRd%zkEvH-a Af zr BSc7$yx{B ^jGt11Z-D.n4G%\x,qB&D&)~T%&%B1 $B4 +Q5 H%0Q00J33/15($"y -#Y '.+VC! Fp~! t3a&+a0!$PtJK BQECF*XA 0L8aLs'OHwN(ГgD6=$`&L(VD{(p( !D۷EbiI@k\CEL R (0^3Ȅ s/^(g <LJLljl$&$ܼ\^\|~|464TRTtrt@pH$hHNq0!V)Ps@aR6m;@@ L8^|=A2tde(yy|B$okG+-[3D57*sB aC! _ 0/ % B*86## Jo H4J("e #25p (*yZ5#)0E1 n\AB<  DFD䤦dfd$&$tvt464쬮lnl|~|\^\@pH$HYqZa`) Ds)CHFZ1)L*v(% qq-pHt6w0)jz6 o6G,22,w ['qC##cV6ODQ/G,' 3B2 1B33 "J#/,T 61J- .(_113g0& -1/WC.T ! 0v,& xqKo*aO5b'B &D!beKRIA͚1rRS!Bd (O1vjSchQ! Mj h`+H \DP˶<6@LNK#Q]g|uGĂukT Ι_D L0XY08'J);`8a!5ag%2QiC •qh`yu\8؎!".vSRx18$dO!agC!"W h{oPa`  7c:E! :, DBD䤢dbd$&$trtTVT464LJL쬪ljl|z| 424\^\<>y\LAׯ$P pВ6`M`H RB0f1D,`Ih ,H@[WD–P i1L8ck:F+!lL8mXv CHu#ppQn4(EAt8gԑ8 Ćxpp/$0D| k/8 f p W|hhA! :, DBDdfd$"$TRTtvt424LJLlnl\Z\,*,|~|<:<DFDljl$&$TVT|z|464LNLtrt\^\@pH$l5xHuj)]MFV~5ӤUo!īK2##jy||+:cJ8[C^Y8 UVv:5D$5QB_ r1v(B8~:m_2K 5/ K .#K5+85$ 22 (g.Pn_5 WB 'WY|p(W.[ e=t)kg$P AbjyX@In]qK0cTGAH/+40CV 0+hXT  Bb[7nCp[k\`!z&6lhyAZ"8d%" ]-B,D1oY0p /qF6DB cm6q(  oH1@D! 5 Cb@7p'V(!҄-+ZaAute&v@{:4z'a0D}jp48mЂ:ps7ED!l! 8, DBDdbd$"$TRTtrt424LJLljl\Z\|z|,.,<><  DFDdfd$&$TVTtvt464LNLlnl\^\|~|@pH$dHZq^$J!AVEq"IsI{!_)vB"/BQ*6z(kqSTJ xg {BUUjr244-g6[jC^cGd p"F2PcH b B41$`8mH/s2J/6$$JS(H" (/(ZOnH2 WB(&&$ N~LC4񏉀d$ ҅5(=rE#D 1Bɓ&)tŃ0K<0cΤٱ˟\ @h5PSp57ah0ch`Ucņ`a jhMR7MƆ`a|aBcP +` Â8$daȈuqИsB1ôu>HYBTxܰpb(4؂"p|Opف 8CDǑmXrA 6P40 6, 0"X[! 6, DBDdbd,*,TRTtrtLJL쬪464\Z\|z|ljlDFD䤦424TVTtvtLNL쬮<><\^\|~|lnl@pH$\HXqZ"J!EqEmRs [!W) ADmUiÈ<"EmUZx.Zq.00ST|M)DucGd TSo60B0TH bdcT B6 3Z.uHX)J(Y%O `/) WBOPC E+E1(qLBMVp)Pa>mj)!R` eH#MBR/E$hp'@{-mƈ?Ȩ"lj7!k'F&*Nq6\İ; C6|0 A$^`(0m)Qac&1m\ r6, DBDdbd$"$TRTtrt424 LJLljl,*,\Z\|z|<:<DFDdfd$&$TVTtvt464  LNLlnl,.,\^\|~|@pH$rH^qla!) Vs A='rVA4Vz8rTrxJUUj{ ~;>Gu< Sj78e~.tTwc%J;n9,,5,<xZ% (f%xc W>23L,UTƒ‹g'1" BQE,}$I^()a.a aʊ"`ܱg|3滣%*耠"9ec FPe4qĀ֔  tzVkdXAk[Whh<ܜ464! NETSCAPE2.0! 5,V.J8VfcDx4d`&jR>HAD('r 1ab -(BnJ5! WL&x.W5 !j *M%0F%/5 +0*+"%   2**,.,)0) )5 "֙F5CA! 0,<><\^\씖LNL$&$ܔDFDlnl424\Z\tvt<:<DBDTRTLJLtrt464@V 3ln*BNpBjT,`4-Z<`t8(& B J0 $X Fe** X0',})M oF,m/ )0)#)/ ,')0/F'0CA! 1,DFDdbd$"$trtTVT䬪̄djl424LNL䤦|z|\^\촲  dfd$&$tvt\Z\쬮lnl<><̬\Z\pFbb$b 1Ad av L0% GQJ+q B!- B*V/B2PV' ( .V3% L  *  FB  3)/l3%V3"V FA! ,,|~|<><\Z\Ԍ$&$LNLtrtdbd􌊌䴲ܔ424TVTĤDFD\^\ԔTRTdfd<:<՘<#fP"@DR0ũx#&+QVRrid!Т@҇ . rsB$" B#WR B&W/ , % FL )d)/ . $ D .+B! W)/؛FF/A! /,|DBD\^\̬TRTlnl,.,Ĭ䜚ԼLJLdfd464DFDdbd$"$\Z\trt424̬윞܄ҼBcay(db %9*L(Sk$!($Eue DTh c/#&%%!*B#(#V,BV/) V#*FL *c#EC' !  '/- ,# )/ל F//A! .,|~|DFDܤdbd쌎\Z\trtTRT,*,|z|LJL䤦lnl􌒔Դ\^\tvť424@u*!#h$xEĪ0Ăf*yJKTasB<߄X  d.)!B W*P.  W.&""rrW m FL(p[C r "-& .  -')C % -W). כGBA! 1,|~|ܜDBD\^\djl,.,TVTĤtrt464DFDdbd$&$lnl424\Z\ܜ0#j dva EIyBL1\.rxF&; y *j1!.++%P FL0B V1YY/.V P*1,,)I*. +,1  ). '1-&&* / +1֗P1A! /,|~|DBDܜdfd쬮TRT424ĤtrtLNL$&$DFD䤢ljl\Z\464̬tvt,*,ua)1"DW$4:ir@6.Hͧ$hTL#)K ~C, B.!W/)- ' '-C-"a) +) ,  &-/.+ &(( &W $/ ӓ#K/"CA! 1,DFDܤdbd$"$촲trtTVT䬪424ljl|z|\^\ LJL䤦dfd$&$tvt\Z\쬮<><0VcilR"HBZL!&"jBǫt4 ,c-,*PL// X1 . **!" - C. %K($FB+ &   +c $' +'( ,01" &P11A! 3,DFDܜ$"$dfd촶<:<켺LJL䤢dfd$&$\Z\tvt464촺TRTljl,.,! NETSCAPE2.0! =, pH$Mq>19DD SIeE hTRiX([d$-xB, {'=re$X$,Dm%(r-fhWB+&=4<%*v!Bb"<< *J/`u= ιl9 5!$fı2;3:D!v܃=1<3< hKШ7BfISG$X`hC%&>hC=4hx0 RL)dXrDAq+C>oIH'(`:rTd 1b,B@hA 3d8"0Ya A `.XTh@@"*C- n(7p4ݠ0H32(a W@ZR)" Xd] 5zpA uL)~J\HCq<|Ĥdfd424TVTtvtLNL<:<  \^\DFDB94  9>1&:./4B% . %-..@2 =,8A#%.# *AA >bYk@Y"ߏ2iDBhNl@qARTb?$.1YÂ#Rzx`|g1r( 34 eJ)0!1BAٲ"8&ăoz0ݺƎPɷ@dh ۔<[]A| 3t,OAg+pS%k&i#Ȧ ? y!w 'm@ !^pHJUw< b >8PÝH R!j B:='H!]08=#H 1'!ǁl !@,>p) Syd B &#w ! q[B"j ±%+hc!! >, |DFDܤdbd$"$씒TVTtrt424LNLljl\^\ ,*,\Z\|z|<>$yy|B%pp$>SU-x [%D4-yrB 8D= "B"78G323.B)5B / W877H#J#>1 $94h0ZO/= 0E%* <&'DBnha \"Ë h)2Pdc Kt`bek M!rh@'DPJth>ӡNsӛ"E 4*BFׯ(RPEh^PBBh8`:A&60@ !u+AiTH>M@b:PHC |Uk{i@p)* a6Թ:huG\W.쀝" &&62p]Bj̛|!;Ś)o%^ eGe[Ȯ£Eqn)Ur&DCCC{58M )BIE! <, |~|DFDܜdbd$"$쬮424TVTtrtԴLNLljl,*,<:<\^\|z|ܼLJL䤢dfd$&$464\Z\tvtܴTRTlnl,.,<><@pH$Kq> 4(uQsٱ^)rpmZ g;ޞR% ?E=4<pH$^Jqrj!x) As 1)dve 'z=@R2BSd(zy|e_n9sV)4[ D5 cVC7D4B$ t /y5B-&(B4 4,1v2)2HvJ'40 ('$ +$9 5WCF@(#.v<0-(E*`|dQb  %$ȜIE9t8ag(q"!f$P:)РE()ӥKx*KbE@,ѐ o͈]WkL@ Q$r˅{E@I50{%X8`Eo `޻$`Bp: yLƒ 10#HW+ s }mRA-l0ŀ ȹ6\%ة{< н'&m)An ^D67D~\8 QmRVD! <, |~|DBDܜdbd$"$̬TRTtrt424ԴLJL\Z\ ljl,*,DFD䤢$&$ԬTVT|z|<:<ܼLNL\^\lnl@pH$.Kqha{(Js,j1)t2$v}!zme\:zrHV<w:3{en~,VU! "g j&lXNbɊ/_¤@vܹB|-QSQH":4(Q& t@:UO@}b-Ҥ*<`-3dX8q[E. @b @HԤ,]#8p xp@W`#˜!T2/ E/#H0 T<~!! BZ0Y%6v`\޽i0p0T,ڱ;\c– Eq[Y Z[xCŃ)"Yq"`\ٕ1E pM!En rhoѡr<`-VH[(m|t#8AVd9ڃQc<R {ו#0cC !0Qtpс aaHNl! E, |~|DBDܜdbd$&$̌쬮TRTtrt464ԄĤLJLljl\Z\tz|  䤢424<><܌̜DFD䜢dfd,*,ԔTVTtvt<:<܄̬LNLlnl\^\|z|E":"A='++%E# &+34&+!=&  #E&"'!ȹ "-= '6$0BQg'hT  c2h CJ,=xXU4F0hwF,`8D.qq F|6ؐ 2tȅJ p0 C,AͳZPmp% +3 +P_vy᭡0a Rk ! aexyАg*,eUЌ&BCcE5f̀ƥ}ez!"!BڨQ_6Ƃ+`?@oPlhA$  BꩧO0[(MuD B `Cx!<@L6w:JX@nDx=Ÿ/Lx$ !c0E2(B7#H$:8cK~I ! @, |~|DBDܜdbd$"$쌎TRT,24trt<:3 !>6:@!6.@>*!366:: !! *  ^"/:8""aᡯg<$+f q@ S쁯y=,D/&JTA¥~9ĈNDFC:b3Ml(kEB<0vіeౖ-^e2:⽛_7u0INa2; 1cB.pp.4P( ҤBF =T$6l#X1`0.l7ivQ(*PqvjbC4h0}MIv r C8  A`42উ Ў :G H/d`$z4d  _ BAz ˀ|,X#6 +7N$.  DPPc< 60<̬LNL424ܼ\^\lnl@""<  @" <- |@:R jʢׯ^a\]0DŽg't=m cq|8A.`EJܴRx<Hx!’CK@CS8F-XqG % ѢuX @(!0hލa ep  Bz D OkVaCl (+@EPM4h FЌcj#}1|l c|<0Bh]98@?C\&l0 L Qup;|$ @ H ԧ( - p&' $B /'^L' ;U0 H ! E, |~|DBDܜdbd$"$쬮TRTtrt424ĤdjlԴLJL,*,\Z\ |z|<:<̬ljl|DFD䤢dfd$&$TVTtvt464̤ܼLNL,.,\^\lnlE'$ "-(E )( (2E(Aж ݄C"E!(E݇CC>!nPV따hاoȀ `"$Y,hG bCR>@w PFKE p;KU-Q|."qcf`@WY8V$`f%VX6ڱ krխZkW-Ac ^6h# G.Ax Az"< U$l1Blfb8 QZ (2At,lxq?@(љ "̮B 7,aCg!w #k(Р? F@ "@~/BD4Bp8^a zB!\p%5l@"a.:fU: Dࢁ6—  (2J( -1TBE (W)R6f#;pub/js/min/poinit.js000064400000004413147206622240010400 0ustar00"use strict"; !function(z, w, d) { function p(a) { d(h).find("button.button-primary").each(function(c, b) { b.disabled = a; }); } function x() { var a = q && q.val(), c = a && a.isValid() && "zxx" !== a.lang; const b = r && r.val(); c = c && b; A(a); p(!0); c && (a = r.txt(), a !== t ? (t = a, u.path.value = t, y.listen(B).connect()) : p(!1)); } function B(a) { p(!a); } function A(a) { const c = d(h), b = a && a.toString("_") || "", g = b ? "zxx" === b ? "{locale}" : b : "{invalid}"; c.find("code.path span").each(function(n, e) { e.textContent = g; }); c.find("span.lang").each(function(n, e) { a && "zxx" !== a.lang ? (e.setAttribute("lang", a.lang), e.setAttribute("class", a.getIcon())) : (e.setAttribute("lang", ""), e.setAttribute("class", "lang nolang")); }); } function C(a) { (a = a && a.redirect) && location.assign(a); } let t = ""; const l = z.loco, u = w.getElementById("loco-fs"), h = w.getElementById("loco-poinit"), y = u && l.fs.init(u), q = function(a) { function c() { m[0].checked = !0; e(!0); } function b() { k.value || (k.value = g()); m[1].checked = !0; e(!1); } function g() { const f = d(m[0].checked ? v : k).serializeArray(); return f[0] && f[0].value || ""; } function n() { e(m[0].checked); return !0; } function e(f) { k.disabled = f; v.disabled = !f; D.className = f ? "disabled" : "active"; E.className = f ? "active" : "disabled"; x(); } const v = a["select-locale"], k = a["custom-locale"], m = a["use-selector"], E = d(v).on("focus", c).closest("fieldset").on("click", c)[0], D = d(k).on("focus", b).closest("fieldset").on("click", b)[0]; return { val: function() { var f = g(); return f ? l.locale.parse(f) : l.locale.clone({ lang: "zxx" }); }, init: function() { d(m).change(n); n(); l.watchtext(k, function() { d(k.form).triggerHandler("change"); }); } }; }(h), r = function() { function a(b) { var g; return (g = (g = d(c).serializeArray()[0]) && g.value || null) && h[b + "[" + g + "]"]; } const c = h["select-path"]; return { val: function() { const b = a("path"); return b && b.value; }, txt: function() { const b = a("path"); return b && d(b.parentNode).find("code.path").text(); } }; }(h); q.init(); d(h).on("change", x).on("submit", function(a) { a.preventDefault(); y.applyCreds(h); l.ajax.submit(a.target, C); return !1; }); }(window, document, window.jQuery);pub/js/min/potinit.js000064400000000534147206622240010564 0ustar00"use strict"; !function(c, b, e) { function f(a) { (a = a && a.redirect) && location.assign(a); } const d = c.loco; c = b.getElementById("loco-fs"); b = b.getElementById("loco-potinit"); e(b).on("submit", function(a) { a.preventDefault(); d.ajax.submit(a.target, f); return !1; }); c && d.fs.init(c).setForm(b); }(window, document, window.jQuery);pub/js/min/delete.js000064400000000244147206622240010336 0ustar00"use strict"; !function(c, a) { let b = a.getElementById("loco-fs"); a = a.getElementById("loco-del"); b && a && c.loco.fs.init(b).setForm(a); }(window, document);pub/js/min/editor.js000064400000035625147206622240010375 0ustar00"use strict"; !function(F, d) { function t(a) { return k.l10n._(a); } function G(a, b, c) { return k.l10n.n(a, b, c); } function y(a) { return a.format(0, ".", Ba); } function Ca(a) { k.ajax.post("sync", ja, function(b) { const c = []; var f = b.pot, g = b.po; const r = b.done || { add: [], del: [], fuz: [] }; var n = r.add.length; const u = r.del.length, z = r.fuz.length, B = r.trn || 0; C.clear().load(g); h.load(C); Y(h); if (n || u || z || B) { if (f ? c.push(v(t("Merged from %s"), f)) : c.push(t("Merged from source code")), n && c.push(v(G("%s new string added", "%s new strings added", n), y(n))), u && c.push(v(G("%s obsolete string removed", "%s obsolete strings removed", u), y(u))), z && c.push(v(G("%s string marked Fuzzy", "%s strings marked Fuzzy", z), y(z))), B && c.push(v(G("%s translation copied", "%s translations copied", B), y(B))), d(H).trigger("poUnsaved", []), R(), Da && F.console) { f = console; g = -1; for (n = r.add.length; ++g < n; ) f.log(" + " + String(r.add[g])); n = r.del.length; for (g = 0; g < n; g++) f.log(" - " + String(r.del[g])); n = r.fuz.length; for (g = 0; g < n; g++) f.log(" ~ " + String(r.fuz[g])); } } else f ? c.push(v(t("Strings up to date with %s"), f)) : c.push(t("Strings up to date with source code")); k.notices.success(c.join(". ")); d(H).trigger("poMerge", [ b ]); a && a(); }, a); } function Ea(a) { const b = a.currentTarget; a.stopImmediatePropagation(); b.disabled = !0; ka(); b.disabled = !1; } function ka() { const a = []; C.each(function(b, c) { h.validate(c) && a.push(c); }); k.notices.clear(); la(a); } function Y(a) { a.invalid && (la(a.invalid), a.invalid = null); } function la(a) { const b = a.length; if (0 === b) k.notices.success(t("No formatting errors detected")); else { const c = [ v(G("%s possible error detected", "%s possible errors detected", b), b), t("Check the translations marked with a warning sign") ]; k.notices.warn(c.join(". ")).slow(); } 0 < b && h.current(a[0]); } function Fa(a) { const b = a.id, c = k.apis, f = c.providers(); return c.create(a, f[b] || f._); } function ma() { for (var a = -1, b, c = [], f = K, g = f.length, r = String(Ga); ++a < g; ) try { b = f[a], null == b.src && (b.src = r), c.push(Fa(b)); } catch (n) { k.notices.error(String(n)); } return c; } function na(a) { function b(f) { Z = new Date().getTime(); K = f && f.apis || []; 0 === K.length ? N = aa("loco-apis-empty", f.html) : S = aa("loco-apis-batch", f.html); c.remove(); a(K); } if (T || oa) k.notices.error("APIs not available in current mode"); else if (null == K || 0 === K.length || 10 < Math.round((new Date().getTime() - Z) / 1e3)) { N && N.remove(); N = null; S && S.remove(); S = null; U && U.remove(); K = U = null; var c = d('
').dialog({ dialogClass: "loco-modal loco-modal-no-close", appendTo: "#loco-admin.wrap", title: "Loading..", modal: !0, autoOpen: !0, closeOnEscape: !1, resizable: !1, draggable: !1, position: pa, height: 200 }); k.ajax.get("apis", { locale: String(D) }, b); } else Z = new Date().getTime(), a(K); } function aa(a, b) { b = d(b); b.attr("id", a); b.dialog({ dialogClass: "loco-modal", appendTo: "#loco-admin.wrap", title: b.attr("title"), modal: !0, autoOpen: !1, closeOnEscape: !0, resizable: !1, draggable: !1, position: pa }); return b; } function qa() { na(function(a) { a.length ? Ha() : ra(); }); } function Ia(a) { a.preventDefault(); na(function(b) { b.length ? Ja() : ra(); }); return !1; } function ra() { N ? N.dialog("open") : k.notices.error("Logic error. Unconfigured API modal missing"); } function Ja() { function a(e) { a: { var q = d(e.api).val(); for (var O, L = X || (X = ma()), P = L.length, Q = -1; ++Q < P; ) if (O = L[Q], O.getId() === q) { q = O; break a; } k.notices.error("No " + q + " client"); q = void 0; } e = e.existing.checked; M.text("Calculating...."); g = k.apis.createJob(q); g.init(C, e); r = q.toString(); M.text(v(t("%s unique source strings."), y(g.length)) + " " + v(t("%s characters will be sent for translation."), y(g.chars))); E[0].disabled = g.length ? !1 : !0; n = null; } function b(e) { g && (B && e.fuzzy(0, !0), h.pasteMessage(e), e === h.active && h.setStatus(e), h.unsave(e, 0), u++, z && !e.valid() && (z = !1)); } function c(e, q) { e = q ? 100 * e / q : 0; M.text(v(t("Translation progress %s%%"), y(e))); } function f() { E.removeClass("loco-loading"); if (g && n) { var e = n.todo(); e && k.notices.warn(v(G("Translation job aborted with %s string remaining", "Translation job aborted with %s strings remaining", e), y(e))).slow(); e = []; const q = n.did(); q && e.push(v(G("%1$s string translated via %2$s", "%1$s strings translated via %2$s", q), y(q), r)); u ? e.push(v(G("%s string updated", "%s strings updated", u), y(u))) : q && e.push(t("Nothing needed updating")); e.length && k.notices.success(e.join(". ")).slow(); n = g = null; } u && (R(), h.rebuildSearch()); I && (I.off("dialogclose").dialog("close"), I = null); h.fire("poAuto"); z || ka(); } let g, r, n, u = 0, z = !0, B = !1, I = S.dialog("open"); const x = I.find("form"), E = x.find("button.button-primary"), M = d("#loco-job-progress"); E.removeClass("loco-loading"); E[0].disabled = !0; k.notices.clear(); x.off("submit change"); a(x[0]); x.on("change", function(e) { e = e.target; const q = e.name; "api" !== q && "existing" !== q || a(e.form); return !0; }).on("submit", function(e) { e.preventDefault(); E.addClass("loco-loading"); E[0].disabled = !0; u = 0; c(0); B = e.target.fuzzy.checked; n = g.dispatch().done(f).each(b).prog(c).stat(); }); I.off("dialogclose").on("dialogclose", function() { g.abort(); I = null; f(); }); } function Ha() { function a(l) { if (l.isDefaultPrevented()) return !1; var p = l.which; let m = -1; 49 <= p && 57 >= p ? m = p - 49 : 97 <= p && 105 >= p && (m = p - 97); return 0 <= m && 9 > m && (p = e && e.find("button.button-primary").eq(m)) && 1 === p.length ? (p.click(), l.preventDefault(), l.stopPropagation(), !1) : !0; } function b(l, p) { return function(m) { m.preventDefault(); m.stopPropagation(); g(); m = h.current(); const A = h.getTargetOffset(); m.translate(p, A); h.focus().reloadMessage(m); }; } function c(l, p, m, A) { let ba = A.getId(), ca = P[ba], sa = String(ca + 1), Ka = A.getUrl(), ta = t("Use this translation"); A = String(A); let ua = V && V[ba]; l = d('').attr("tabindex", String(1 + M + ca)).on("click", b(l, p)); l.attr("accesskey", sa); 1 < q.length && (ta += " (" + sa + ")"); l.text(ta); ua && ua.replaceWith(d('
').append(d('
Translated by
').append(d('').attr("href", Ka).text(A))).append(d("
").text(p || "FAILED")).append(l)); ++Q === O && (e && e.dialog("option", "title", t("Suggested translations") + " — " + m.label), M += Q, x.attr("disabled") && x.attr("disabled", !1)); 0 === ca && l.focus(); } function f(l) { const p = d('
').text("Calling " + l + " ..."); return V[l.getId()] = p; } function g(l) { e && null == l && e.dialog("close"); V = P = e = null; d(F).off("keydown", a); } function r(l) { return function(p, m, A) { L[l.getId()] = m; c(p, m, A, l); }; } function n(l) { L = va[l] || (va[l] = {}); let p = -1; for (;++p < O; ) { const m = q[p], A = m.getId(); e.append(f(m)); P[A] = p; L[A] ? c(l, L[A], D, m) : m.translate(l, D, r(m)); } } const u = h.current(); if (!u) return !1; var z = u.pluralized(); const B = z ? Math.min(h.getTargetOffset(), 1) : 0, I = 'lang="' + String(D) + '" dir="' + (D.isRTL() ? "RTL" : "LTR") + '"'; let x, E = u.source(null, B); z ? (x = d(''), u.eachSrc(function(l, p) { var m = h.t(); m = l ? m._x("Plural", "Editor") : m._x("Single", "Editor"); m = d("").attr("label", m); x.append(m.append(d("").attr("value", String(l)).text(p))); }), x.val(String(B)), x.on("change", function(l) { e.find("div.loco-api-result").remove(); V = {}; P = {}; Q = 0; E = u.source(null, l.target.selectedIndex); x.attr("disabled", "true"); n(E); })) : x = d('
').text(E); let M = 99, e = (U || (U = aa("loco-apis-hint", "
"))).html("").append(d('

Source text:

').append(x)).dialog("option", "title", t("Loading suggestions") + "...").off("dialogclose").on("dialogclose", g).dialog("open"); (z = u.translation(B)) && d('

Current translation:

').append(d("
").text(z)).append(d('').attr("tabindex", String(++M)).text(t("Keep this translation")).on("click", function(l) { l.preventDefault(); g(); })).appendTo(e); const q = X || (X = ma()), O = q.length; let L, P = {}, Q = 0, V = {}; n(E); d(F).on("keydown", a); return !0; } function La(a) { const b = new FormData(); for (const c in a) a.hasOwnProperty(c) && b.append(c, a[c]); return b; } function wa(a) { let b = d.extend({ locale: String(C.locale() || "") }, xa || {}); ya && ya.applyCreds(b); da ? (b = La(b), b.append("po", new Blob([ String(C) ], { type: "application/x-gettext" }), String(b.path).split("/").pop() || "untitled.po")) : b.data = String(C); k.ajax.post("save", b, function(c) { a && a(); h.save(!0); d("#loco-po-modified").text(c.datetime || "[datetime error]"); Y(h); }, a); } function Ma() { h.dirty && wa(); } function Na() { return t("Your changes will be lost if you continue without saving"); } function Oa(a) { function b() { a.disabled = !1; d(a).removeClass("loco-loading"); } h.on("poUnsaved", function() { a.disabled = !1; d(a).addClass("button-primary"); }).on("poSave", function() { a.disabled = !0; d(a).removeClass("button-primary"); }); xa = d.extend({ path: ea }, w.project || {}); d(a).on("click", function(c) { c.preventDefault(); a.disabled = !0; d(a).addClass("loco-loading"); wa(b); return !1; }); return !0; } function Pa(a) { const b = w.project; if (b) { var c = function() { a.disabled = !1; d(a).removeClass("loco-loading"); }; h.on("poUnsaved", function() { a.disabled = !0; }).on("poSave", function() { a.disabled = !1; }); ja = { bundle: b.bundle, domain: b.domain, type: T ? "pot" : "po", path: ea || "", sync: Qa || "", mode: Ra || "" }; d(a).on("click", function(f) { f.preventDefault(); a.disabled = !0; d(a).addClass("loco-loading"); Ca(c); return !1; }); a.disabled = !1; } return !0; } function Sa(a) { h.on("poUnsaved", function() { a.disabled = !0; }).on("poSave poAuto", function() { a.disabled = !1; }); d(a).on("click", Ia); a.disabled = !1; return !0; } function Ta(a) { d(a).on("click", Ea); a.disabled = !1; } function Ua(a) { a.disabled = !1; d(a).on("click", function(b) { b.preventDefault(); b = 1; var c, f = /(\d+)$/; for (c = "New message"; C.get(c); ) b = f.exec(c) ? Math.max(b, Number(RegExp.$1)) : b, c = "New message " + ++b; h.add(c); return !1; }); return !0; } function Va(a) { a.disabled = !1; d(a).on("click", function(b) { b.preventDefault(); h.del(); return !1; }); return !0; } function fa(a, b) { a.disabled = !1; d(a).on("click", function() { let c = ea; "archive" === b ? c = c.replace(/\.po$/, ".zip") : "binary" === b && (c = c.replace(/\.po$/, ".mo")); const f = a.form; f.path.value = c; f.source.value = C.toString(); return !0; }); return !0; } function ha(a) { a.preventDefault(); return !1; } function R() { var a = h.stats(), b = a.t, c = a.f, f = a.u; b = v(G("%s string", "%s strings", b), y(b)); var g = []; D && (b = v(t("%s%% translated"), a.p.replace("%", "")) + ", " + b, c && g.push(v(t("%s fuzzy"), y(c))), f && g.push(v(t("%s untranslated"), y(f))), g.length && (b += " (" + g.join(", ") + ")")); d("#loco-po-status").text(b); } function za(a, b) { a = b.getAttribute("data-loco"); const c = W[a]; c && c(b, a) || d(b).addClass("loco-noop"); } const k = F.loco, w = k && k.conf, H = document.getElementById("loco-editor-inner"); if (k && w && H) { var Da = !!w.WP_DEBUG, ia = k.po.ref && k.po.ref.init(k, w), ja = null, xa = null, da = w.multipart, Wa = k.l10n, v = k.string.sprintf, Ba = w.wpnum && w.wpnum.thousands_sep || ",", D = w.locale, C = k.po.init(D).wrap(w.powrap), T = !D, Ga = k.locale.clone(w.source || { lang: "en" }), Xa = document.getElementById("loco-actions"), ea = w.popath, Qa = w.potpath, Ra = w.syncmode, J = document.getElementById("loco-fs"), ya = J && k.fs.init(J), oa = w.readonly; J = !oa; var K, X, va = {}, U, S, N, Z = 0, pa = { my: "top", at: "top", of: "#loco-content" }; !da || F.FormData && F.Blob || (da = !1, k.notices.warn("Your browser doesn't support Ajax file uploads. Falling back to standard postdata")); ia || k.notices.warn("admin.js is out of date. Please empty your browser cache and reload the page."); var Aa = function() { var a, b = parseInt(d(H).css("min-height") || 0); return function() { for (var c = H, f = c.offsetTop || 0; (c = c.offsetParent) && c !== document.body; ) f += c.offsetTop || 0; c = Math.max(b, F.innerHeight - f - 20); a !== c && (H.style.height = String(c) + "px", a = c); }; }(); Aa(); d(F).resize(Aa); H.innerHTML = ""; var h = k.po.ed.init(H).localise(Wa); k.po.kbd.init(h).add("save", J ? Ma : ha).add("hint", D && J && qa || ha).enable("copy", "clear", "enter", "next", "prev", "fuzzy", "save", "invis", "hint"); var W = { save: J && Oa, sync: J && Pa, revert: function(a) { h.on("poUnsaved", function() { a.disabled = !1; }).on("poSave", function() { a.disabled = !0; }); d(a).on("click", function(b) { b.preventDefault(); location.reload(); return !1; }); return !0; }, invs: function(a) { var b = d(a); a.disabled = !1; h.on("poInvs", function(c, f) { b[f ? "addClass" : "removeClass"]("inverted"); }); b.on("click", function(c) { c.preventDefault(); h.setInvs(!h.getInvs()); return !1; }); k.tooltip.init(b); return !0; }, code: function(a) { var b = d(a); a.disabled = !1; b.on("click", function(c) { c.preventDefault(); c = !h.getMono(); b[c ? "addClass" : "removeClass"]("inverted"); h.setMono(c); return !1; }); k.tooltip.init(b); return !0; }, source: fa, binary: T ? null : fa, archive: T ? null : fa }; T ? (W.add = J && Ua, W.del = J && Va) : (W.auto = Sa, W.lint = Ta); d("#loco-editor > nav .button").each(za); d("#loco-content > form .button").each(za); d(Xa).on("submit", ha); (function(a) { function b(g) { d(a.parentNode)[g || null == g ? "removeClass" : "addClass"]("invalid"); } h.searchable(k.fulltext.init()); a.disabled = !1; var c = a.value = "", f = k.watchtext(a, function(g) { g = h.filter(g, !0); b(g); }); h.on("poFilter", function(g, r, n) { c = f.val(); f.val(r || ""); b(n); }).on("poMerge", function() { c && h.filter(c); }); })(document.getElementById("loco-search")); h.on("poUnsaved", function() { F.onbeforeunload = Na; }).on("poSave", function() { R(); F.onbeforeunload = null; }).on("poHint", qa).on("poUpdate", R).on("poMeta", function(a, b) { b = "CODE" === b.tagName ? b : b.getElementsByTagName("CODE")[0]; return b && ia ? (ia.load(b.textContent), a.preventDefault(), !1) : !0; }); C.load(w.podata); h.load(C); (D = h.targetLocale) ? D.isRTL() && d(H).addClass("trg-rtl") : h.unlock(); R(); Y(h); delete k.conf; } }(window, window.jQuery);pub/js/min/config.js000064400000002141147206622240010337 0ustar00"use strict"; !function(n, p, d) { function h(a, b) { let c = a.offsetTop; for (;(a = a.offsetParent) && a !== b; ) c += a.offsetTop; return c; } function k() { function a(e, f) { e = f.name.replace("[0]", l); d(f).attr("name", e).val(""); } var b = d("#loco-conf > div"); let c = b.eq(0).clone(); b = b.length; let l = "[" + b + "]"; c.attr("id", "loco-conf-" + b); c.find("input").each(a); c.find("textarea").each(a); c.find("h2").eq(0).html("New set (untitled)"); c.insertBefore("#loco-form-foot"); g(c.find("a.icon-del"), b); c.hide().slideDown(500); d("html, body").animate({ scrollTop: h(c[0]) }, 500); } function g(a, b) { return a.on("click", function(c) { c.preventDefault(); m(b); return !1; }); } function m(a) { var b = d("#loco-conf-" + a); b.find('input[name="conf[' + a + '][removed]"]').val("1"); b.slideUp(500, function() { d(this).hide().find("table").remove(); }); } d("#loco-conf > div").each(function(a, b) { g(d(b).find("a.icon-del"), a); }); d("#loco-add-butt").attr("disabled", !1).on("click", function(a) { a.preventDefault(); k(); return !1; }); }(window, document, window.jQuery);pub/js/min/podiff.js000064400000003516147206622240010350 0ustar00"use strict"; !function(h, g, d) { function p() { return d(e).removeClass("loading"); } function k(a) { return d(e).find("div.diff").html(a); } function x(a) { p(); return d('

').text(a).appendTo(k("")); } function E(a, b) { let c = b.getElementsByTagName("tr"), y = c.length; a = b.getAttribute("data-diff").split(/\D+/); b = a[0]; let q = a[1], F = a[2], G = a[3]; for (a = 0; a < y; a++) { var l = c[a].getElementsByTagName("td"); var m = l[0], z = b++; z <= q && d("").text(String(z)).prependTo(m); l = l[2]; m = F++; m <= G && d("").text(String(m)).prependTo(l); } } function H(a) { f && f.abort(); let b = A[a]; null != b ? (k(b), p()) : (k(""), d(e).addClass("loading"), f = r.ajax.post("diff", { lhs: t.paths[a], rhs: t.paths[a + 1] }, function(c, y, q) { q === f && (c && c.html ? (b = c.html, A[a] = b, k(b).find("tbody").each(E), p()) : x(c && c.error || "Unknown error")); }, function(c) { c === f && (f = null, x("Failed to generate diff")); })); } function I(a) { a.preventDefault(); u(n - 1); return !1; } function J(a) { a.preventDefault(); u(n + 1); return !1; } function u(a) { if (0 <= a && a <= v) { n = a; H(a); { a = n; let b = a + 1; B.disabled = a >= v; C.disabled = 0 >= a; w.addClass("jshide").removeClass("diff-meta-current"); w.eq(a).removeClass("jshide").addClass("diff-meta-current"); w.eq(b).removeClass("jshide"); } } } let f, A = [], r = h.loco, t = r.conf, n = 0, v = t.paths.length - 2, e = g.getElementById("loco-ui"); h = g.getElementById("loco-fs"); g = e.getElementsByTagName("form").item(0); let D = e.getElementsByTagName("button"), w = d(e).find("div.diff-meta"), B = D.item(0), C = D.item(1); h && g && r.fs.init(h).setForm(g); v && (d(B).on("click", J).parent().removeClass("jshide"), d(C).on("click", I).parent().removeClass("jshide")); u(0); }(window, document, window.jQuery);pub/js/min/head.js000064400000000245147206622240007776 0ustar00"use strict"; !function(c, a) { var b = a.getElementById("loco-fs"); a = a.getElementById("loco-main"); b && a && c.loco.fs.init(b).setForm(a); }(window, document);pub/js/min/upload.js000064400000002217147206622240010362 0ustar00"use strict"; !function(e, l, m) { function h(a) { const n = m(b).find("button.button-primary"); n.each(function(A, t) { t.disabled = a; }); return n; } function p() { h(!0).addClass("loco-loading"); } function f(a) { h(a).removeClass("loco-loading"); } function u(a) { f(!(a && c && d)); } function q() { b.path.value = c + "/" + d; p(); g.connect(); } function v(a) { d = String(b.f.value).split(/[\\\/]/).pop(); a = a.target || {}; if ("dir" === a.name && a.checked) { if ((a = a.value) && a !== c && (c = a, d)) { q(); return; } } else if ("f" === a.name && c) { q(); return; } h(!(c && d && g.authed())); } function w(a) { a.redirect ? (f(!0), e.location.assign(a.redirect)) : f(!1); } function x() { f(!1); } function y(a) { if (c && d && g.authed()) return z ? (a.preventDefault(), a = new FormData(b), p(), k.ajax.post("upload", a, w, x), !1) : !0; a.preventDefault(); return !1; } let g, c, d; const k = e.loco, z = (k && k.conf || {}).multipart && e.FormData && e.Blob, r = l.getElementById("loco-fs"), b = l.getElementById("loco-main"); r && b && (g = e.loco.fs.init(r).setForm(b).listen(u), m(b).change(v).submit(y)); }(window, document, window.jQuery);pub/js/min/poview.js000064400000003171147206622240010407 0ustar00"use strict"; !function(p, u, c) { const g = p.loco, C = g.po.ref.init(g, g.conf), h = u.getElementById("loco-po"); !function(d, e) { function b() { l.length && (v.push([ m, w ]), e.push(l), l = []); m = null; } function k(a) { return c('
    ').attr("start", a).appendTo(d); } function q(a) { x !== a && (c("#loco-content")[a ? "removeClass" : "addClass"]("loco-invalid"), x = a); } let m, w, l = [], v = [], x = !0, r = !1, t = c(d).find("li"); t.each(function(a, f) { f = c(f); f.find("span.po-none").length ? b() : (w = a, null == m && (m = a), a = f.find(".po-text").text(), "" !== a && (l = l.concat(a.replace(/\\[ntvfab\\"]/g, " ").split(" ")))); }); b(); g.watchtext(c(d.parentNode).find("form.loco-filter")[0].q, function(a) { if (a) { var f = e.find(a), y = -1, z = f.length, A; c("ol", d).remove(); if (z) { for (;++y < z; ) { var B = v[f[y]]; a = B[0]; for (A = k(a + 1); a <= B[1]; a++) A.append(t[a]); } q(!0); } else q(!1), k(0).append(c("
  1. ").text(g.l10n._("Nothing matches the text filter"))); r = !0; n(); } else r && (q(!0), r = !1, c("ol", d).remove(), k(1).append(t), n()); }); }(h, g.fulltext.init()); c(h).removeClass("loco-loading"); var n = function() { let d, e = h.clientHeight - 2; return function() { for (var b = h, k = b.offsetTop || 0; (b = b.offsetParent) && b !== u.body; ) k += b.offsetTop || 0; b = p.innerHeight - k - 20; d !== b && (h.style.height = b < e ? String(b) + "px" : "", d = b); }; }(); n(); c(p).resize(n); c(h).on("click", function(d) { const e = d.target; if (e.hasAttribute("href")) return d.preventDefault(), C.load(e.textContent), !1; }); }(window, document, window.jQuery);pub/js/min/debug.js000064400000000015147206622240010156 0ustar00"use strict";pub/js/min/move.js000064400000001162147206622240010042 0ustar00"use strict"; !function(k, e, f) { function g(a) { f(b).find("button.button-primary").each(function(r, l) { l.disabled = a; }); } function m(a) { g(!(a && c)); } function n(a) { a = a.target || {}; "dest" !== a.name || !a.checked && "text" !== a.type || (a = a.value) && a !== c && (c = a, g(!0), p !== a && (d.dest.value = a, h.connect())); } function q(a) { if (c) return !0; a.preventDefault(); return !1; } let h, c, d = e.getElementById("loco-fs"), b = e.getElementById("loco-main"), p = b.path.value; d && b && (h = k.loco.fs.init(d).setForm(b).listen(m), f(b).change(n).submit(q)); }(window, document, window.jQuery);pub/js/min/setup.js000064400000002722147206622240010237 0ustar00"use strict"; !function(r, t, b) { function u(a, f, k) { function e() { l("Failed to contact remote API"); c = null; } function g() { c && (clearTimeout(c), c = null); } var c = setTimeout(e, 3e3); l(""); b.ajax({ url: n.apiUrl + "/" + a + "/" + f + ".jsonp?version=" + encodeURIComponent(k), dataType: "jsonp", success: function(h) { if (c) { g(); const p = h && h.exact, v = h && h.status; p ? (d["json-content"].value = p, b("#loco-remote-empty").hide(), b("#loco-remote-found").show()) : 404 === v ? l("Sorry, we don't know a bundle by this name") : (q.notices.error(h.error || "Unknown server error"), e()); } }, error: function() { c && (g(), e()); }, cache: !0 }); return { abort: g }; } function l(a) { d["json-content"].value = ""; b("#loco-remote-empty").show().find("span").text(a); b("#loco-remote-found").hide().removeClass("jshide"); } var q = r.loco, n = q.conf || {}, m, d = t.getElementById("loco-remote"); b(d).find('button[type="button"]').on("click", function(a) { a.preventDefault(); m && m.abort(); m = u(d.vendor.value, d.slug.value, d.version.value); return !1; }); b(d).find('input[type="reset"]').on("click", function(a) { a.preventDefault(); l(""); return !1; }); b.ajax({ url: n.apiUrl + "/vendors.jsonp", dataType: "jsonp", success: function(a) { for (var f = -1, k, e, g = a.length, c = b(d.vendor).html(""); ++f < g; ) k = a[f][0], e = a[f][1], c.append(b("").attr("value", k).text(e)); }, cache: !0 }); }(window, document, window.jQuery);pub/js/min/admin.js000064400000364563147206622240010205 0ustar00"use strict"; (function(J, L, A, ka) { const B = function() { function w(C) { throw Error("Failed to require " + C); } const v = {}; return { register: function(C, y) { v[C] = y; }, require: function(C, y) { return v[C] || w(y); }, include: function(C, y, p) { return v[C] || (p ? w(y) : null); } }; }(); B.register("$1", function(w, v, C) { function y(p) { const t = typeof p; if ("string" === t) if (/[^ <>!=()%^&|?:n0-9]/.test(p)) console.error("Invalid plural: " + p); else return new Function("n", "return " + p); "function" !== t && (p = function(d) { return 1 != d; }); return p; } w.init = function(p) { function t(h, n, q) { return (h = f[h]) && h[q] ? h[q] : n || ""; } function d(h) { return t(h, h, 0); } function b(h, n) { return t(n + "" + h, h, 0); } function k(h, n, q) { q = Number(p(q)); isNaN(q) && (q = 0); return t(h, q ? n : h, q); } p = y(p); let f = {}; return { __: d, _x: b, _n: k, _: d, x: b, n: k, load: function(h) { f = h || {}; return this; }, pluraleq: function(h) { p = y(h); return this; } }; }; return w; }({}, J, L)); B.register("$2", function(w, v, C) { w.ie = function() { return !1; }; w.init = function() { return w; }; return w; }({}, J, L)); B.register("$3", function(w, v, C) { Number.prototype.format = function(y, p, t) { var d = Math.pow(10, y || 0); y = []; d = String(Math.round(d * this) / d); var b = d.split("."); d = b[0]; b = b[1]; let k = d.length; do { y.unshift(d.substring(k - 3, k)); } while (0 < (k -= 3)); d = y.join(t || ","); if (b) { { t = b; y = t.length; let f; for (;"0" === t.charAt(--y); ) f = y; f && (t = t.substring(0, f)); b = t; } b && (d += (p || ".") + b); } return d; }; Number.prototype.percent = function(y) { let p = 0, t = this && y ? this / y * 100 : 0; if (0 === t) return "0"; if (100 === t) return "100"; if (99 < t) t = Math.min(t, 99.9), y = t.format(++p); else if (.5 > t) { t = Math.max(t, 1e-4); do { y = t.format(++p); } while ("0" === y && 4 > p); y = y.substring(1); } else y = t.format(0); return y; }; return w; }({}, J, L)); B.register("$4", function(w, v, C) { Array.prototype.indexOf || (Array.prototype.indexOf = function(y) { if (null == this) throw new TypeError(); var p = Object(this), t = p.length >>> 0; if (0 === t) return -1; var d = 0; 1 < arguments.length && (d = Number(arguments[1]), d != d ? d = 0 : 0 != d && Infinity != d && -Infinity != d && (d = (0 < d || -1) * Math.floor(Math.abs(d)))); if (d >= t) return -1; for (d = 0 <= d ? d : Math.max(t - Math.abs(d), 0); d < t; d++) if (d in p && p[d] === y) return d; return -1; }); return w; }({}, J, L)); B.register("$5", function(w, v, C) { C = v.JSON; C || (C = { parse: A.parseJSON, stringify: null }, v.JSON = C); w.parse = C.parse; w.stringify = C.stringify; return w; }({}, J, L)); B.register("$6", function(w, v, C) { w.trim = function(y, p) { for (p || (p = " \n"); y && -1 !== p.indexOf(y.charAt(0)); ) y = y.substring(1); for (;y && -1 !== p.indexOf(y.slice(-1)); ) y = y.substring(0, y.length - 1); return y; }; w.sprintf = function(y) { return w.vsprintf(y, [].slice.call(arguments, 1)); }; w.vsprintf = function(y, p) { let t = 0; return y.replace(/%(?:([1-9][0-9]*)\$)?([sud%])/g, function(d, b, k) { if ("%" === k) return "%"; d = b ? p[Number(b) - 1] : p[t++]; return null != d ? String(d) : "s" === k ? "" : "0"; }); }; return w; }({}, J, L)); B.register("$27", function(w, v, C) { function y(p) { return function(t, d) { let b = t[p] || 0; for (;(t = t.offsetParent) && t !== (d || C.body); ) b += t[p] || 0; return b; }; } w.top = y("offsetTop"); w.left = y("offsetLeft"); w.el = function(p, t) { p = C.createElement(p || "div"); t && (p.className = t); return p; }; w.txt = function(p) { return C.createTextNode(p || ""); }; w.rect = function(p) { return p.getBoundingClientRect(); }; return w; }({}, J, L)); B.register("$7", function(w, v, C) { function y(m, g, l) { function r() { z(); x = setTimeout(g, l); } function z() { x && clearTimeout(x); x = 0; } let x = 0; r(); A(m).on("mouseenter", z).on("mouseleave", r); return { die: function() { z(); A(m).off("mouseenter mouseleave"); } }; } function p(m, g) { m.fadeTo(g, 0, function() { m.slideUp(g, function() { m.remove(); A(v).triggerHandler("resize"); }); }); return m; } function t(m, g) { function l(F) { q[D] = null; p(A(m), 250); x && x.die(); var H; if (H = F) F.stopPropagation(), F.preventDefault(), H = !1; return H; } function r(F) { x && x.die(); return x = y(m, l, F); } const z = A(m); let x, D, E, G = z.find("button"); 0 === G.length && (z.addClass("is-dismissible"), G = A(''); B.require("$12", "tooltip.js").init(f); return f; } function t(f) { return p("cloud").attr("title", f.labels[8] + " (Ctrl-U)").on("click", function(h) { h.preventDefault(); f.focus().fuzzy(!f.fuzzy()); }); } function d(f) { return p("robot").attr("title", f.labels[9] + " (Ctrl-J)").on("click", function(h) { h.preventDefault(); f.fire("poHint"); }); } function b(f, h) { return B.require("$6", "string.js").vsprintf(f, h); } w.init = function(f) { const h = new y(); f = h.setRootCell(f); var n = f.splity("po-list", "po-edit"); let q = n[0], u = n[1]; n = u.splitx("po-trans", "po-comment"); var c = n[0]; let a = n[1].header("Loading.."); n = c.splity("po-source", "po-target"); c = n[0].header("Loading.."); n = n[1].header("Loading.."); f.distribute([ .34 ]); u.distribute([ .8 ]); h.setListCell(q); h.setSourceCell(c); h.setTargetCell(n); h.commentCell = a; h.editable.source = !1; return h; }; v = y.prototype = B.require("$30", "base.js").extend(y); v.getListHeadings = function() { const f = this.t(), h = [ f._x("Source text", "Editor") ]; this.targetLocale && (h[1] = f._x("Translation", "Editor")); return h; }; v.getListColumns = function() { const f = { source: 0 }; this.targetLocale && (f.target = 1); return f; }; v.getListEntry = function(f) { const h = this.cellText, n = [ function() { let q, u = h(f.source() || ""), c = f.context(); return c ? (q = C.createElement("p"), q.appendChild(C.createElement("mark")).innerText = c, q.appendChild(C.createTextNode(" " + u)), q) : u; } ]; this.targetLocale && (n[1] = function() { return h(f.translation() || ""); }); return n; }; v.stats = function() { let f = this.po, h = f.length, n = 0, q = 0, u = 0; f.each(function(c, a) { a.fuzzy() ? u++ : a.translated() ? n++ : q++; }); return { t: h, p: n.percent(h) + "%", f: u, u: q }; }; v.unlock = function() { const f = this.targetLocale; this._unlocked || (this.editable = { source: !0, context: !0, target: !1 }, this.po && this.po.unlock(), this.contextCell = this.targetCell, delete this.targetCell, f && (this._unlocked = f, delete this.targetLocale, this.reload(), this.fire("poLock", [ !1 ])), this.active && this.loadMessage(this.active)); }; v.lock = function() { const f = this._unlocked; f && (this.targetLocale = f, delete this._unlocked, this.po && this.po.lock(f), this.editable = { source: !1, context: !1, target: !0 }, this.targetCell = this.contextCell, delete this.contextCell, this.reload(), this.fire("poLock", [ !0, f ]), this.active && this.loadMessage(this.active)); }; v.locked = function() { return !this._unlocked; }; v.setStatus = function(f) { let h = this.$tnav; if (null == f) h && (h.remove(), this.$tnav = null); else { h || (this.$tnav = h = A("").append(t(this)).append(d(this)).appendTo(this.targetCell.header())); var n = []; f.translated() ? f.fuzzy() && n.push("po-fuzzy") : n.push("po-empty"); h.attr("class", n.join(" ")); } }; v.getSorter = function() { function f(q, u) { const c = q.weight(), a = u.weight(); return c === a ? h(q, u) : c > a ? -1 : 1; } function h(q, u) { return q.hash().localeCompare(u.hash()); } const n = this; return function(q) { const u = n.po, c = n.locked() ? f : h; q.sort(function(a, e) { return c(u.row(a), u.row(e)); }); }; }; v.validate = function(f) { f.err = null; if (f.untranslated(0)) return 0; const h = []; let n = this.validateMessagePrintf(f, h); n && (f.err = h); return n; }; v.validateMessagePrintf = function(f, h) { const n = f.format(); if ("no-" === n.substring(0, 3)) return 0; const q = f.msgid(), u = f.msgidPlural(); null == k && (k = B.require("$31", "printf.js").init()); var c = k; if (!("" !== n || c.sniff(q) || "" !== u && c.sniff(u))) return 0; let a = 0, e = c.parse(q); u && e.valid && (e = c.parse(u, e)); if (!e.valid) return 0; let m = e.count; if (0 !== m || "" !== n) { var g = this; f.eachMsg(function(l, r) { h[l] = []; if ("" !== r) { r = c.parse(r); var z = r.count; l = h[l]; if (r.valid) if (z > m) l.push(b(g.t()._("Too many placeholders; source text formatting suggests a maximum of %s"), [ m ])), a++; else if (z < m && "" === u) l.push(b(g.t()._("Missing placeholders; source text formatting suggests at least %s"), [ m ])), a++; else { z = e.types; for (const x in r.types) for (const D in r.types[x]) if (null == z[x] || null == z[x][D]) { l.push(g.t()._("Mismatching placeholder type; check against source text formatting")); a++; return; } } else l.push(g.t()._("Possible syntax error in string formatting")), a++; } }); return a; } }; v.handle = {}; let k; return w; }({}, J, L)); B.register("$14", function(w, v, C) { const y = { copy: 66, clear: 75, save: 83, fuzzy: 85, next: 40, prev: 38, enter: 13, invis: 73, hint: 74 }, p = { 38: !0, 40: !0, 73: !0 }, t = { 66: function(d, b) { if (d = b.current()) d.normalize(), b.focus().pasteMessage(d); }, 75: function(d, b) { if (d = b.current()) d.untranslate(), b.focus().pasteMessage(d); }, 85: function(d, b) { b.focus().fuzzy(!b.fuzzy()); }, 13: function(d, b) { b.getFirstEditable() && b.next(1, !0, !0); }, 40: function(d, b) { d = d.shiftKey; b.next(1, d, d); }, 38: function(d, b) { d = d.shiftKey; b.next(-1, d, d); }, 73: function(d, b) { if (!d.shiftKey) return !1; b.setInvs(!b.getInvs()); } }; w.init = function(d, b) { function k(h) { if (h.isDefaultPrevented() || !h.metaKey && !h.ctrlKey) return !0; const n = h.which; if (!f[n]) return !0; const q = t[n]; if (!q || h.altKey || h.shiftKey && !p[n] || !1 === q(h, d)) return !0; h.stopPropagation(); h.preventDefault(); return !1; } const f = {}; A(b || v).on("keydown", k); return { add: function(h, n) { t[y[h]] = n; return this; }, enable: function() { for (const h in arguments) f[y[arguments[h]]] = !0; return this; }, disable: function() { A(b || v).off("keydown", k); d = b = null; for (const h in t) f[h] = !1; } }; }; return w; }({}, J, L)); B.register("$32", function(w, v, C) { function y() { this.reIndex([]); } w.init = function() { return new y(); }; v = y.prototype; v.reIndex = function(p) { const t = {}, d = p.length; let b = -1; for (;++b < d; ) t[p[b]] = b; this.keys = p; this.length = b; this.ords = t; }; v.key = function(p, t) { if (null == t) return this.keys[p]; const d = this.keys[p], b = this.ords[t]; if (t !== d) { if (null != b) throw Error("Clash with item at [" + b + "]"); this.keys[p] = t; delete this.ords[d]; this.ords[t] = p; } return p; }; v.indexOf = function(p) { p = this.ords[p]; return null == p ? -1 : p; }; v.add = function(p, t) { let d = this.ords[p]; null == d && (this.keys[this.length] = p, d = this.ords[p] = this.length++); this[d] = t; return d; }; v.get = function(p) { return this[this.ords[p]]; }; v.has = function(p) { return null != this.ords[p]; }; v.del = function(p) { this.cut(this.ords[p], 1); }; v.cut = function(p, t) { t = t || 1; const d = [].splice.call(this, p, t); this.keys.splice(p, t); this.reIndex(this.keys); return d; }; v.each = function(p) { const t = this.keys, d = this.length; let b = -1; for (;++b < d; ) p(t[b], this[b], b); return this; }; v.sort = function(p) { const t = this.length, d = this.keys, b = this.ords, k = []; let f = -1; for (;++f < t; ) k[f] = [ this[f], d[f] ]; k.sort(function(n, q) { return p(n[0], q[0]); }); for (f = 0; f < t; f++) { var h = k[f]; this[f] = h[0]; h = h[1]; d[f] = h; b[h] = f; } return this; }; v.join = function(p) { return [].join.call(this, p); }; return w; }({}, J, L)); B.register("$33", function(w, v, C) { function y(p, t) { var d = new RegExp("^.{0," + (p - 1) + "}[" + t + "]"), b = new RegExp("^[^" + t + "]+"); return function(k, f) { for (var h = k.length, n; h > p; ) { n = d.exec(k) || b.exec(k); if (null == n) break; n = n[0]; f.push(n); n = n.length; h -= n; k = k.substring(n); } 0 !== h && f.push(k); return f; }; } w.create = function(p) { function t(q) { return f[q] || "\\" + q; } var d = /(?:\r\n|[\r\n\v\f\u2028\u2029])/g, b = /[ \r\n]+/g, k = /[\t\v\f\x07\x08\\"]/g, f = { "\t": "\\t", "\v": "\\v", "\f": "\\f", "": "\\a", "\b": "\\b" }; if (null == p || isNaN(p = Number(p))) p = 79; if (0 < p) { var h = y(p - 3, " "); var n = y(p - 2, "-– \\.,:;\\?!\\)\\]\\}\\>"); } return { pair: function(q, u) { if (!u) return q + ' ""'; u = u.replace(k, t); var c = 0; u = u.replace(d, function() { c++; return "\\n\n"; }); if (!(c || p && p < u.length + q.length + 3)) return q + ' "' + u + '"'; q = [ q + ' "' ]; u = u.split("\n"); if (n) for (var a = -1, e = u.length; ++a < e; ) n(u[a], q); else q = q.concat(u); return q.join('"\n"') + '"'; }, prefix: function(q, u) { q = q.split(d); return u + q.join("\n" + u); }, refs: function(q) { q = q.replace(b, " ", q); h && (q = h(q, []).join("\n#: ")); return "#: " + q; } }; }; return w; }({}, J, L)); B.register("$46", function(w, v, C) { function y() { this.length = 0; } w.init = function() { return new y(); }; v = y.prototype; v.push = function(p) { this[this.length++] = p; return this; }; v.sort = function(p) { [].sort.call(this, p); return this; }; v.each = function(p) { for (var t = -1, d = this.length; ++t < d; ) p(t, this[t]); return this; }; return w; }({}, J, L)); B.register("$34", function(w, v, C) { function y() {} w.extend = function(p) { return p.prototype = new y(); }; v = y.prototype = B.require("$44", "abstract.js").init([ "add", "load" ]); v.row = function(p) { return this.rows[p]; }; v.lock = function(p) { return this.locale(p || { lang: "zxx", label: "Unknown", nplurals: 1, pluraleq: "n!=1" }); }; v.unlock = function() { const p = this.loc; this.loc = null; return p; }; v.locale = function(p) { null == p ? p = this.loc : this.loc = p = B.require("$43", "locale.js").cast(p); return p; }; v.each = function(p) { this.rows.each(p); return this; }; v.indexOf = function(p) { "object" !== typeof p && (p = this.get(p)); if (!p) return -1; null == p.idx && (p.idx = this.rows.indexOf(p.hash())); return p.idx; }; v.get = function(p) { return this.rows && this.rows.get(p); }; v.has = function(p) { return this.rows && this.rows.has(p); }; v.del = function(p) { p = this.indexOf(p); if (-1 !== p) { const t = this.rows.cut(p, 1); if (t && t.length) return this.length = this.rows.length, this.rows.each(function(d, b, k) { b.idx = k; }), p; } }; v.reIndex = function(p, t) { const d = p.hash(), b = this.indexOf(p), k = this.rows.indexOf(d); return k === b ? b : -1 !== k ? (t = (t || 0) + 1, p.source("Error, duplicate " + String(t) + ": " + p.source()), this.reIndex(p, t)) : this.rows.key(b, d); }; v.sort = function(p) { this.rows.sort(p); return this; }; v.export = function() { const p = this.rows, t = p.length, d = B.require("$46", "list.js").init(); let b = -1; for (;++b < t; ) d.push(p[b]); return d; }; return w; }({}, J, L)); B.register("$35", function(w, v, C) { function y(d, b, k) { if (null == k) return d[b] || ""; d[b] = k || ""; return d; } function p() { this._id = this.id = ""; } function t(d, b) { const k = d.length; let f = -1; for (;++f < k; ) b(f, d[f]); } w.extend = function(d) { return d.prototype = new p(); }; v = p.prototype; v.flag = function(d, b) { const k = this.flg || (this.flg = []); if (null != b) k[b] = d; else for (b = Math.max(k.length, this.src.length, this.msg.length); 0 !== b--; ) k[b] = d; return this; }; v.flagged = function(d) { return (this.flg || [])[d || 0] || 0; }; v.hasFlag = function() { const d = this.flg || []; let b = d.length; for (;0 !== b--; ) if (this.isFlag(d[b])) return !0; return !1; }; v.isFlag = function(d) { return 0 < d; }; v.flags = function() { const d = {}, b = [], k = this.flg || []; let f = k.length; for (;0 !== f--; ) { const h = k[f]; d[h] || (d[h] = !0, b.push(h)); } return b; }; v.flaggedAs = function(d, b) { const k = this.flg || []; if (null != b) return d === k[b] || 0; for (b = k.length; 0 !== b--; ) if (k[b] === d) return !0; return !1; }; v.fuzzy = function(d, b) { const k = this.flaggedAs(4, d); null != b && this.flag(b ? 4 : 0, d); return k; }; v.source = function(d, b) { if (null == d) return this.src[b || 0] || ""; this.src[b || 0] = d; return this; }; v.plural = function(d, b) { if (null == d) return this.src[b || 1] || ""; this.src[b || 1] = d || ""; return this; }; v.sourceForms = function() { return this.srcF; }; v.targetForms = function() { return this.msgF; }; v.each = function(d) { const b = this.src, k = this.msg, f = Math.max(b.length, k.length); let h = -1; for (;++h < f; ) d(h, b[h], k[h]); return this; }; v.eachSrc = function(d) { t(this.src, d); return this; }; v.eachMsg = function(d) { t(this.msg, d); return this; }; v.count = function() { return Math.max(this.src.length, this.msg.length); }; v.pluralized = function() { return 1 < this.src.length || 1 < this.msg.length; }; v.translate = function(d, b) { this.msg[b || 0] = d || ""; return this; }; v.untranslate = function(d) { if (null != d) this.msg[d] = ""; else { const b = this.msg, k = b.length; for (d = 0; d < k; d++) b[d] = ""; } return this; }; v.translation = function(d) { return this.msg[d || 0] || ""; }; v.errors = function(d) { return this.err && this.err[d || 0] || []; }; v.valid = function() { return null == this.err; }; v.translated = function(d) { if (null != d) return !!this.msg[d]; const b = this.msg, k = b.length; for (d = 0; d < k; d++) if (!b[d]) return !1; return !0; }; v.untranslated = function(d) { if (null != d) return !this.msg[d]; const b = this.msg, k = b.length; for (d = 0; d < k; d++) if (b[d]) return !1; return !0; }; v.comment = function(d) { return y(this, "cmt", d); }; v.notes = function(d) { return y(this, "xcmt", d); }; v.refs = function(d) { return y(this, "rf", d); }; v.format = function(d) { return y(this, "fmt", d); }; v.context = function(d) { return y(this, "ctx", d); }; v.tags = function() { return this.tg; }; v.getMax = function(d) { return (this.mx || [ 0 ])[d] || 0; }; v.toString = v.toText = function() { return this.src.concat(this.msg, [ this.id, this.ctx ]).join(" "); }; v.weight = function() { let d = 0; this.translation() || (d += 2); this.fuzzy() && (d += 1); return d; }; v.equals = function(d) { return this === d || this.hash() === d.hash(); }; v.hash = function() { return this.id; }; v.normalize = function() { let d = -1, b = this.msg.length; for (;++d < b; ) this.msg[d] = this.src[Math.min(d, 1)] || ""; }; v.disabled = function(d) { return !!(this.lck || [])[d || 0]; }; v.disable = function(d) { (this.lck || (this.lck = []))[d || 0] = !0; return this; }; v.saved = function(d) { const b = this.drt; if (null == b) return !0; if (null != d) return !b[d]; for (d = b.length; 0 !== d--; ) if (b[d]) return !1; return !0; }; v.unsave = function(d) { (this.drt || (this.drt = []))[d || 0] = !0; return this; }; v.save = function(d) { null == d ? this.drt = null : (this.drt || (this.drt = []))[d] = !1; return this; }; v.is = function(d) { return d && (d === this || d.idx === this.idx); }; v.isHTML = function(d) { if (null == d) return this.htm || !1; this.htm = d; }; v = null; return w; }({}, J, L)); B.register("$15", function(w, v, C) { function y(f) { return { "Project-Id-Version": "PACKAGE VERSION", "Report-Msgid-Bugs-To": "", "POT-Creation-Date": f || "", "PO-Revision-Date": f || "", "Last-Translator": "", "Language-Team": "", Language: "", "Plural-Forms": "", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=UTF-8", "Content-Transfer-Encoding": "8bit" }; } function p(f, h) { f = f || ""; h && (f += "\0" + h); return f; } function t(f) { const h = v.console; h && h.error && h.error(f.message || String(f)); } function d(f) { return B.require("$33", "format.js").create(f); } function b(f) { this.locale(f); this.clear(); this.head = y(this.now()); } function k(f, h) { this.src = [ f || "" ]; this.msg = [ h || "" ]; } w.create = function(f) { return new b(f); }; C = B.require("$34", "messages.js").extend(b); C.clear = function() { this.rows = B.require("$32", "collection.js").init(); this.length = 0; return this; }; C.now = function() { function f(a, e) { for (a = String(a); a.length < e; ) a = "0" + a; return a; } var h = new Date(); const n = h.getUTCFullYear(), q = h.getUTCMonth() + 1, u = h.getUTCDate(), c = h.getUTCHours(); h = h.getUTCMinutes(); return f(n, 4) + "-" + f(q, 2) + "-" + f(u, 2) + " " + f(c, 2) + ":" + f(h, 2) + "+0000"; }; C.header = function(f, h) { const n = this.head || (this.head = {}); if (null == h) return this.headers()[f] || ""; n[f] = h || ""; return this; }; C.headers = function(f) { const h = this.now(), n = this.head || (this.head = y(h)); if (null != f) { for (u in f) n[u] = f[u]; return this; } const q = this.locale(); f = {}; for (u in n) f[u] = String(n[u]); if (q) { f.Language = String(q) || "zxx"; f["Language-Team"] = q.label || f.Language; f["Plural-Forms"] = "nplurals=" + (q.nplurals || "2") + "; plural=" + (q.pluraleq || "n!=1") + ";"; var u = "PO-Revision-Date"; } else f.Language = "", f["Plural-Forms"] = "nplurals=INTEGER; plural=EXPRESSION;", f["PO-Revision-Date"] = "YEAR-MO-DA HO:MI+ZONE", u = "POT-Creation-Date"; f[u] || (f[u] = h); f["X-Generator"] = "Loco https://localise.biz/"; return f; }; C.get = function(f, h) { f = p(f, h); return this.rows.get(f); }; C.add = function(f, h) { f instanceof k || (f = new k(f)); h && f.context(h); h = f.hash(); this.rows.get(h) ? t("Duplicate message at index " + this.indexOf(f)) : (f.idx = this.rows.add(h, f), this.length = this.rows.length); return f; }; C.load = function(f) { let h = -1, n, q; var u; let c, a, e, m = (u = this.locale()) && u.nplurals || 2, g = []; for (;++h < f.length; ) n = f[h], null == n.parent ? (q = n.source || n.id, u = n.target || "", c = n.context, q || c ? (a = new k(q, u), a._id = n._id, c && a.context(c), n.flag && a.flag(n.flag, 0), n.comment && a.comment(n.comment), n.notes && a.notes(n.notes), n.refs && a.refs(n.refs), a.format(n.format), n.message = a, this.add(a), n.prev && n.prev[0] && (a.prev(n.prev[0].source, n.prev[0].context), n.prev[1] && a._src.push(n.prev[1].source || ""))) : 0 === h && "object" === typeof u && (this.head = u, this.headcmt = n.comment)) : g.push(n); for (h = -1; ++h < g.length; ) try { n = g[h]; q = n.source || n.id; a = f[n.parent] && f[n.parent].message; if (!a) throw Error("parent missing for plural " + q); e = n.plural; 1 === e && a.plural(q); e >= m || (n.flag && a.flag(n.flag, e), a.translate(n.target || "", e), n.format && !a.format() && a.format(n.format)); } catch (l) { t(l); } return this; }; C.wrap = function(f) { this.fmtr = d(f); return this; }; C.toString = function() { var f, h = this.locale(), n = [], q = [], u = this.headers(), c = !h, a = h && h.nplurals || 2, e = this.fmtr || d(); u[h ? "PO-Revision-Date" : "POT-Creation-Date"] = this.now(); for (f in u) q.push(f + ": " + u[f]); q = new k("", q.join("\n")); q.comment(this.headcmt || ""); c && q.fuzzy(0, !0); n.push(q.toString()); n.push(""); this.rows.each(function(m, g) { m && (n.push(g.cat(e, c, a)), n.push("")); }); return n.join("\n"); }; C = B.require("$35", "message.js").extend(k); C.msgid = function() { return this.src[0]; }; C.msgidPlural = function() { return this.src[1] || ""; }; C.prev = function(f, h) { this._src = [ f || "" ]; this._ctx = h; }; C.hash = function() { return p(this.source(), this.context()); }; C.toString = function() { return this.cat(d()); }; C.cat = function(f, h, n) { var q = [], u; (u = this.cmt) && q.push(f.prefix(u, "# ")); (u = this.xcmt) && q.push(f.prefix(u, "#. ")); var c = this.rf; if (u = this._id) c += (c ? " " : "") + "loco:" + u; c && /\S/.test(c) && q.push(f.refs(c)); !h && this.fuzzy() && q.push("#, fuzzy"); (u = this.fmt) && q.push("#, " + u + "-format"); (u = this._ctx) && q.push(f.prefix(f.pair("msgctxt", u), "#| ")); if (u = this._src) u[0] && q.push(f.prefix(f.pair("msgid", u[0]), "#| ")), u[1] && q.push(f.prefix(f.pair("msgid_plural", u[1]), "#| ")); (u = this.ctx) && q.push(f.pair("msgctxt", u)); q.push(f.pair("msgid", this.src[0])); if (null == this.src[1]) q.push(f.pair("msgstr", h ? "" : this.msg[0])); else for (c = -1, q.push(f.pair("msgid_plural", this.src[1])), u = this.msg || [ "", "" ], n = n || u.length; ++c < n; ) q.push(f.pair("msgstr[" + c + "]", h ? "" : u[c] || "")); return q.join("\n"); }; C.compare = function(f, h) { let n = this.weight(), q = f.weight(); if (n > q) return 1; if (n < q) return -1; if (h) { n = this.hash().toLowerCase(); q = f.hash().toLowerCase(); if (n < q) return 1; if (n > q) return -1; } return 0; }; C.copy = function() { let f = new k(), h, n; for (h in this) this.hasOwnProperty(h) && ((n = this[h]) && n.concat && (n = n.concat()), f[h] = n); return f; }; return w; }({}, J, L)); B.register("$17", function(w, v, C) { w.init = function(y, p) { function t() { return f || (f = A('
    ').dialog({ dialogClass: "loco-modal loco-modal-wide", modal: !0, autoOpen: !1, closeOnEscape: !0, resizable: !1, height: 500 })); } function d(h, n, q) { h = A("

    ").text(q); t().dialog("close").html("").dialog("option", "title", "Error").append(h).dialog("open"); } function b(h) { const n = h && h.code; if (n) { for (var q = n.length, u = A("
      ").attr("class", h.type), c = -1; ++c < q; ) A("
    1. ").html(n[c]).appendTo(u); 0 !== h.line && u.find("li").eq(h.line - 1).attr("class", "highlighted"); t().dialog("close").html("").dialog("option", "title", h.path + ":" + h.line).append(u).dialog("open"); } } function k(h) { h = h.target; const n = A(h).find("li.highlighted")[0]; h.scrollTop = Math.max(0, (n && n.offsetTop || 0) - Math.floor(h.clientHeight / 2)); } let f; return { load: function(h) { t().html('
      ').dialog("option", "title", "Loading..").off("dialogopen").dialog("open").on("dialogopen", k); h = A.extend({ ref: h, path: p.popath }, p.project || {}); y.ajax.post("fsReference", h, b, d); } }; }; return w; }({}, J, L)); B.register("$18", function(w, v, C) { function y() { this.inf = {}; } function p() { const b = C.createElement("p"), k = /&(#\d+|#x[0-9a-f]|[a-z]+);/i, f = /<[a-z]+\s/i; let h, n; return { sniff: function(q) { if (q === h) return n; h = q; if (k.test(q) || f.test(q)) if (b.innerHTML = q, b.textContent !== q) return n = !0; return n = !1; } }; } w.create = function(b, k) { k && "function" === typeof k.create || console.error("module.create is not callable"); k = k.create(y); k.init(b); return k; }; const t = y.prototype; t.init = function(b) { this.inf = b || {}; return this; }; t.param = function(b) { return this.inf[b] || ""; }; t.key = function() { return this.param("key") || ""; }; t.getId = function() { return this.param("id") || "none"; }; t.getUrl = function() { return this.param("url") || ""; }; t.toString = function() { return this.param("name") || this.getId(); }; t.getSrc = function() { return this.param("src") || "en"; }; t.setSrc = function(b) { this.inf.src = this.mapLang(b, this.getLangMap()); }; t.stderr = function(b) { const k = (v.loco || {}).notices; k && k.error && k.error(String(this) + ": " + String(b)); }; t.xhrError = function(b, k, f) { try { const h = b.responseText, n = h && v.JSON.parse(h); f = n && this.parseError(n) || f; } catch (h) {} return f || this.httpError(b); }; t.httpError = function(b) { return (b = b && b.status) && 200 !== b ? "Responded status " + b : "Unknown error"; }; t.parseError = function(b) { return b && b.error || ""; }; t.mapLang = function(b, k) { const f = String(b).replace("_", "-").toLowerCase(); var h = b.lang; k = k[f] || k[h] || []; b = k.length; if (0 === b) return h; if (1 < b) for (h = -1; ++h < b; ) { const n = k[h]; if (n === f) return n; } return k[0]; }; t.getLangMap = function() { return {}; }; t.maxChr = function() { return 0; }; t.fixURL = function(b) { b = b.split("://", 2); 1 === b.length && b.unshift("https"); return b[0] + "://" + b[1].replace(/\/{2,}/g, "/"); }; t.translate = function(b, k, f) { return this.batch([ b ], k, this.isHtml(b), f); }; t.verify = function(b) { return this.translate("OK", { lang: "fr", toString: function() { return "fr"; } }, function(k, f) { b(f && "OK" === k); }); }; t.hash = function() { return this.key(); }; t._call = function(b) { const k = this; k.state = null; b.cache = !0; b.dataType = "json"; b.error = function(f, h, n) { k.stderr(k.xhrError(f, h, n)); }; return k.abortable(A.ajax(b)); }; t.abortable = function(b) { const k = this; b.always(function() { k.$r === b && (k.$r = null); }); return k.$r = b; }; t.abort = function() { const b = this.$r; b && b.abort(); }; t.isHtml = function(b) { return (d || (d = p())).sniff(b); }; let d; return w; }({}, J, L)); B.register("$19", function(w, v, C) { function y(p) { this.api = p; this.chars = 0; } w.create = function(p) { return new y(p); }; v = y.prototype; v.init = function(p, t) { function d(g) { let l = { length: 0, html: g.html, sources: [] }; h.push(l); return n[g.html ? 1 : 0] = l; } function b(g, l) { let r = g.source(null, l); if (r && (g.untranslated(l) || t)) if (l = f[r]) l.push(g); else { l = r.length; var z = k.isHtml(r); z = n[z ? 1 : 0]; var x = z.sources; if (e && l > e) c++; else { if (z.length + l > a || 50 === x.length) z = d(z), x = z.sources; x.push(r); f[r] = [ g ]; z.length += l; q += l; u += 1; } } } const k = this.api, f = {}, h = []; let n = [], q = 0, u = 0, c = 0, a = 1e4, e = k.maxChr(); e && (a = Math.min(a, e)); d({ html: !1 }); d({ html: !0 }); const m = p.locale(); p.each(1 < m.nplurals ? function(g, l) { b(l, 0); b(l, 1); } : function(g, l) { b(l, 0); }); n = []; this.map = f; this.chars = q; this.length = u; this.batches = h; this.locale = m; c && k.stderr("Strings over " + a + " characters long will be skipped"); }; v.abort = function() { this.state = "abort"; return this; }; v.dispatch = function() { function p(x, D) { function E(R, I, P) { D !== P && (x === I || 1 < R && G.source(null, 1) === x) && (G.translate(D, R), O++, g++); } if (!t()) return !1; if (!D) return !0; let G, F = u[x] || [], H = F.length, M = -1, O; for (e++; ++M < H; ) if (G = F[M]) O = 0, G.each(E), O && h("each", [ G ]); return !0; } function t() { return "abort" === n.state ? (q && (q.abort(), f()), !1) : !0; } function d() { let x = c.shift(), D; x ? (D = x.sources) && D.length ? q.batch(D, a, x.html, p).fail(b).always(k) : k() : f(); } function b() { n.abort(); f(); } function k() { m++; h("prog", [ m, r ]); t() && d(); } function f() { q = c = null; h("done"); } function h(x, D) { x = z[x] || []; let E = x.length; for (;0 <= --E; ) x[E].apply(null, D); } let n = this, q = n.api, u = n.map, c = n.batches || [], a = n.locale, e = 0, m = 0, g = 0, l = n.length, r = c.length, z = { done: [], each: [], prog: [] }; n.state = ""; d(); return { done: function(x) { z.done.push(x); return this; }, each: function(x) { z.each.push(x); return this; }, prog: function(x) { z.prog.push(x); return this; }, stat: function() { return { todo: function() { return Math.max(l - e, 0); }, did: function() { return e; } }; } }; }; return w; }({}, J, L)); B.register("$20", function(w, v, C) { function y() {} w.create = function(p) { (y.prototype = new p()).batch = function(t, d, b, k) { function f(q) { const u = t.length; let c = -1; for (;++c < u && !1 !== k(t[c], q[c], d); ); } const h = v.loco; b = { hook: this.getId(), type: b ? "html" : "text", locale: String(d), source: this.getSrc(), sources: t }; const n = A.Deferred(); this.abortable(h.ajax.post("apis", b, function(q) { f(q && q.targets || []); n.resolve(); }, function() { f([]); n.reject(); })); return n.promise(); }; return new y(); }; return w; }({}, J, L)); B.register("$37", { pt: [ "pt", "pt-pt", "pt-br" ], en: [ "en", "en-gb", "en-us" ] }); B.register("$21", function(w, v, C) { function y() {} w.create = function(p) { p = y.prototype = new p(); p.toString = function() { return "DeepL Translator"; }; p.parseError = function(t) { const d = (t = t.message) && /^Wrong endpoint\. Use (https?:\/\/[-.a-z]+)/.exec(t); d && this.base(this.key()) === d[1] && (t = "Only the v2 API is supported"); return t; }; p.base = function(t) { let d = this.param("api"); d ? d = this.fixURL(d) : (d = "https://api", ":fx" === t.substring(t.length - 3) && (d += "-free"), d += ".deepl.com"); return d; }; p.getLangMap = function() { return B.require("$37", "deepl.json"); }; p.verify = function(t) { const d = this.key(), b = this.base(d); return this._call({ url: b + "/v2/usage", data: { auth_key: d } }).done(function(k) { const f = k && k.character_limit; k = k && k.character_count; t(!0, f && f <= k ? "OK, but quota exceeded" : ""); }).fail(function() { t(!1); }); }; p.batch = function(t, d, b, k) { function f(m) { const g = t.length; let l = -1; for (;++l < g && !1 !== k(t[l], (m[l] || {}).text || "", d); ); } const h = this; b = h.key(); const n = h.base(b), q = h.getSrc().substring(0, 2), u = h.mapLang(d, h.getLangMap()), c = h.param("glossary_id") || "", a = h.param("preserve_formatting") || "1"; var e = { formal: "prefer_more", informal: "prefer_less" }; e = this.param("formality") || e[d.tone] || "default"; return h._call({ url: n + "/v2/translate", method: "POST", traditional: !0, data: { source_lang: q.toUpperCase(), target_lang: u.toUpperCase(), formality: e, preserve_formatting: a, glossary_id: c, auth_key: b, text: t } }).done(function(m, g, l) { m.translations ? f(m.translations) : (h.stderr(h.parseError(m) || h.httpError(l)), f([])); }).fail(function() { f([]); }); }; return new y(); }; return w; }({}, J, L)); B.register("$38", { zh: [ "zh", "zh-cn", "zh-tw" ], he: [ "iw" ], jv: [ "jw" ] }); B.register("$22", function(w, v, C) { function y() {} w.create = function(p) { p = y.prototype = new p(); p.toString = function() { return "Google Translate"; }; p.parseError = function(t) { if (t.error) { const d = [], b = t.error.errors || [], k = b.length; let f = -1; for (;++f < k; ) d.push(b[f].message || ""); return "Error " + t.error.code + ": " + d.join(";"); } return ""; }; p.getLangMap = function() { return B.require("$38", "google.json"); }; p.batch = function(t, d, b, k) { function f(u) { const c = t.length; let a = -1; for (;++a < c && !1 !== k(t[a], (u[a] || {}).translatedText || "", d); ); } const h = this, n = h.getSrc(); b = b ? "html" : "text"; const q = h.mapLang(d, h.getLangMap()); return h._call({ url: "https://translation.googleapis.com/language/translate/v2?source=" + n + "&target=" + q + "&format=" + b, method: "POST", traditional: !0, data: { key: h.key(), q: t } }).done(function(u, c, a) { u.data ? f(u.data.translations || []) : (h.stderr(h.parseError(u) || h.httpError(a)), f([])); }).fail(function() { f([]); }); }; return new y(); }; return w; }({}, J, L)); B.register("$39", { zh: [ "zh", "zh-cn", "zh-tw" ], pt: [ "pt", "pt-pt", "pt-br" ] }); B.register("$23", function(w, v, C) { function y() {} w.create = function(p) { p = y.prototype = new p(); p.parseError = function(t) { var d = t.details || {}; let b = d.message; d = d.texts; return b ? (d && d !== b && (b += "; " + d), b = b.replace(/https?:\/\/(?:[a-z]+\.)?lecto.ai[-\w\/?&=%.+~]*/, function(k) { k += -1 === k.indexOf("?") ? "?" : "&"; return k + "ref=loco"; }), "Error " + (t.status || "0") + ": " + b) : ""; }; p.maxChr = function() { return 1e3; }; p.getLangMap = function() { return B.require("$39", "lecto.json"); }; p.batch = function(t, d, b, k) { function f(u) { const c = t.length; let a = -1, e = (u[0] || { translated: [] }).translated || []; for (;++a < c && (u = e[a] || "", !1 !== k(t[a], u, d)); ); } const h = this; b = this.getSrc(); const n = h.param("api") || "https://api.lecto.ai", q = h.mapLang(d, h.getLangMap()); return h._call({ url: h.fixURL(n + "/v1/translate/text"), method: "POST", data: JSON.stringify({ to: [ q ], from: b, texts: t }), headers: { "Content-Type": "application/json; charset=UTF-8", "X-API-Key": h.key(), Accept: "application/json" } }).done(function(u, c, a) { u ? f(u.translations || []) : (h.stderr(h.parseError(u) || h.httpError(a)), f([])); }).fail(function() { f([]); }); }; return new y(); }; return w; }({}, J, L)); B.register("$40", { nn: [ "no" ], pt: [ "pt", "pt-pt" ], sr: [ "sr", "sr-cyrl", "sr-latn" ], "sr-rs": [ "sr-cyrl" ], tlh: [ "tlh-latn", "tlh-piqd" ], zh: [ "zh-hans", "zh-hant" ], "zh-cn": [ "zh-hans" ], "zh-hk": [ "zh-hans" ], "zh-sg": [ "zh-hans" ], "zh-tw": [ "zh-hant" ] }); B.register("$24", function(w, v, C) { function y() {} w.create = function(p) { p = y.prototype = new p(); p.toString = function() { return "Microsoft Translator text API"; }; p.parseError = function(t) { return t && t.error ? t.error.message : ""; }; p.maxChr = function() { return 1e4; }; p.getLangMap = function() { return B.require("$40", "ms.json"); }; p.region = function() { return this.param("region") || "global"; }; p.hash = function() { return this.key() + this.region(); }; p.batch = function(t, d, b, k) { function f(e) { let m = -1; for (var g; ++m < u && (g = e[m] || {}, g = g.translations || [], g = g[0] || {}, !1 !== k(t[m], g.text || "", d)); ); } let h = this, n = [], q = h.getSrc(), u = t.length, c = -1; b = b ? "html" : "plain"; let a = h.mapLang(d, h.getLangMap()); for (;++c < u; ) n.push({ Text: t[c] }); return h._call({ url: "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&from=" + q + "&to=" + a + "&textType=" + b, method: "POST", data: JSON.stringify(n), headers: { "Content-Type": "application/json; charset=UTF-8", "Ocp-Apim-Subscription-Key": this.key(), "Ocp-Apim-Subscription-Region": h.region() } }).done(function(e, m, g) { e && e.length ? f(e) : (h.stderr(h.parseError(e) || h.httpError(g)), f([])); }).fail(function() { f([]); }); }; return new y(); }; return w; }({}, J, L)); B.register("$25", function(w, v, C) { w.init = function(y) { function p() { O || (E.on("click", n), O = A('
      ').dialog({ dialogClass: "request-filesystem-credentials-dialog loco-modal", minWidth: 580, modal: !0, autoOpen: !1, closeOnEscape: !0 }).on("change", 'input[name="connection_type"]', function() { this.checked && A("#ssh-keys").toggleClass("hidden", "ssh" !== A(this).val()); })); return O; } function t() { F && (d(A(e)), F = !1); if (l && M) { var I = M, P = A(G); P.find("span.loco-msg").text(I); H || (P.removeClass("jshide").hide().fadeIn(500), H = !0); } else H && (d(A(G)), H = !1); } function d(I) { I.slideUp(250).fadeOut(250, function() { A(this).addClass("jshide"); }); } function b() { if (l) return O && O.dialog("close"), t(), A(y).find('button[type="submit"]').attr("disabled", !1), A(v).triggerHandler("resize"), a && a(!0), !0; z && O ? (F || (A(e).removeClass("jshide").hide().fadeIn(500), F = !0), H && (d(A(G)), H = !1)) : t(); A(y).find('input[type="submit"]').attr("disabled", !0); a && a(!1); return !1; } function k(I) { var P, X = R || {}; for (P in X) if (X.hasOwnProperty(P)) { var ha = X[P]; I[P] ? I[P].value = ha : A('').attr("name", P).appendTo(I).val(ha); } } function f(I) { I.preventDefault(); I = A(I.target).serializeArray(); c(I); g = !0; return !1; } function h(I) { I.preventDefault(); O.dialog("close"); return !1; } function n(I) { I.preventDefault(); O.dialog("open").find('input[name="connection_type"]').change(); return !1; } function q(I) { l = I.authed; m = I.method; A(e).find("span.loco-msg").text(I.message || "Something went wrong."); M = I.warning || ""; I.notice && r.notices.info(I.notice); if (l) "direct" !== m && (R = I.creds, k(y), g && I.success && r.notices.success(I.success)), b(); else if (I.reason) r.notices.info(I.reason); else if (I = I.prompt) { var P = p(); P.html(I).find("form").on("submit", f); P.dialog("option", "title", P.find("h2").remove().text()); P.find("button.cancel-button").show().on("click", h); P.find('input[type="submit"]').addClass("button-primary"); b(); A(v).triggerHandler("resize"); } else r.notices.error("Server didn't return credentials, nor a prompt for credentials"); } function u() { b(); } function c(I) { g = !1; r.ajax.setNonce("fsConnect", D).post("fsConnect", I, q, u); return I; } var a, e = y, m = null, g = !1, l = !1, r = v.loco, z = y.path.value, x = y.auth.value, D = y["loco-nonce"].value, E = A(e).find("button.button-primary"), G = C.getElementById(e.id + "-warn"), F = !1, H = !1, M = "", O; r.notices.convert(G).stick(); if (y.connection_type) { var R = {}; R.connection_type = y.connection_type.value; l = !0; } else z && x && c({ path: z, auth: x }); b(); return { applyCreds: function(I) { if (I.nodeType) k(I); else { var P, X = R || {}; for (P in X) X.hasOwnProperty(P) && (I[P] = X[P]); } return this; }, setForm: function(I) { y = I; b(); k(I); return this; }, connect: function() { z = y.path.value; x = y.auth.value; c(A(y).serializeArray()); return this; }, listen: function(I) { a = I; l && I(!0); return this; }, authed: function() { return l; } }; }; return w; }({}, J, L)); B.register("$41", function(w, v, C) { function y(b, k) { return function(f) { b.apply(f, k); return f; }; } function p(b) { return function(k, f) { k = k && k[b] || 0; f = f && f[b] || 0; return k === f ? 0 : k > f ? 1 : -1; }; } function t(b) { return function(k, f) { return (k && k[b] || "").localeCompare(f && f[b] || ""); }; } function d(b) { return function(k, f) { return -1 * b(k, f); }; } w.sort = function(b, k, f, h) { k = "n" === f ? p(k) : t(k); h && (k = d(k)); return y([].sort, [ k ])(b); }; return w; }({}, J, L)); B.register("$26", function(w, v, C) { w.init = function(y) { function p(e) { let m = -1; const g = e.length; for (A("tr", q).remove(); ++m < g; ) q.appendChild(e[m].$); } function t(e) { f = e ? c.find(e, d) : d.slice(0); n && (e = b[n], f = a(f, n, e.type, e.desc)); p(f); } let d = [], b = [], k = 0, f, h, n, q = y.getElementsByTagName("tbody")[0]; var u = y.getElementsByTagName("thead")[0]; let c = B.require("$10", "fulltext.js").init(), a = B.require("$41", "sort.js").sort; u && q && (A("th", u).each(function(e, m) { const g = m.getAttribute("data-sort-type"); g && (e = k, A(m).addClass("loco-sort").on("click", function(l) { l.preventDefault(); { l = e; let r = b[l], z = r.type, x = !(r.desc = !r.desc); f = a(f || d.slice(0), l, z, x); p(f); h && h.removeClass("loco-desc loco-asc"); h = A(r.$).addClass(x ? "loco-desc" : "loco-asc").removeClass(x ? "loco-asc" : "loco-desc"); n = l; } return !1; }), b[k] = { $: m, type: g }); m.hasAttribute("colspan") ? k += Number(m.getAttribute("colspan")) : k++; }), A("tr", q).each(function(e, m) { let g, l = [], r = { _: e, $: m }, z = m.getElementsByTagName("td"); for (g in b) { const x = z[g]; (m = x.textContent.replace(/(^\s+|\s+$)/g, "")) && l.push(m); x.hasAttribute("data-sort-value") && (m = x.getAttribute("data-sort-value")); switch (b[g].type) { case "n": m = Number(m); } r[g] = m; } d[e] = r; c.index(e, l); }), y = A('form.loco-filter input[type="text"]', y.parentNode), y.length && (y = y[0], u = A(y.form), 1 < d.length ? B.require("$11", "LocoTextListener.js").listen(y, t) : u.hide(), u.on("submit", function(e) { e.preventDefault(); return !1; }))); }; return w; }({}, J, L)); const U = J.loco || {}, ia = U.conf || { $v: [] }; J = B.require("$1", "t.js").init(); L = ia.wplang; U.version = function(w) { return ia.$v[w || 0] || "0"; }; B.require("$2", "html.js"); B.require("$3", "number.js"); B.require("$4", "array.js"); B.require("$5", "json.js"); U.l10n = J; J.load(ia.wpl10n); L && J.pluraleq(L.pluraleq); U.string = B.require("$6", "string.js"); U.notices = B.require("$7", "notices.js").init(J); U.ajax = B.require("$8", "ajax.js").init(ia).localise(J); U.locale = B.require("$9", "wplocale.js"); U.fulltext = B.require("$10", "fulltext.js"); U.watchtext = B.require("$11", "LocoTextListener.js").listen; U.tooltip = B.require("$12", "tooltip.js"); U.po = { ed: B.require("$13", "poedit.js"), kbd: B.require("$14", "hotkeys.js"), init: B.require("$15", "po.js").create, ace: B.require("$16", "ace.js").strf("php"), ref: B.require("$17", "refs.js") }; U.apis = B.require("$18", "client.js"); U.apis.createJob = B.require("$19", "job.js").create; U.apis.providers = function() { return { _: B.require("$20", "wordpress.js"), deepl: B.require("$21", "deepl.js"), google: B.require("$22", "google.js"), lecto: B.require("$23", "lecto.js"), microsoft: B.require("$24", "microsoft.js") }; }; U.fs = B.require("$25", "fsconn.js"); A("#loco-admin.wrap table.wp-list-table").each(function(w, v) { B.require("$26", "tables.js").init(v); }); U.validate = function(w) { w = /^\d+\.\d+\.\d+/.exec(w && w[0] || ""); if ("2.6.11" === (w && w[0])) return !0; U.notices.warn("admin.js is the wrong version (2.6.11). Please empty all relevant caches and reload this page."); return !1; }; })(window, document, window.jQuery);pub/js/min/system.js000064400000002401147206622240010415 0ustar00"use strict"; !function(d, c) { function k(a, b, e) { "success" !== b && (e = d.ajax.parse(d.ajax.strip(a.responseText))); c("#loco-ajax-check").text("FAILED: " + e).addClass("loco-danger"); } function g(a, b) { return c("#loco-api-" + a).text(b); } function m(a) { var b = a.getId(); a.key() ? a.verify(function(e) { e ? g(b, "OK ✓") : g(b, "FAILED").addClass("loco-danger"); }) : g(b, "No API key"); } var f = c("#loco-utf8-check")[0].textContent, h = d.conf; 1 === f.length && 10003 === f.charCodeAt(0) || d.notices.warn("This page has a problem rendering UTF-8").stick(); window.ajaxurl && c("#loco-ajax-url").text(window.ajaxurl); c("#loco-vers-jquery").text([ c.fn && c.fn.jquery || "unknown", "ui/" + (c.ui && c.ui.version || "none"), "migrate/" + (c.migrateVersion || "none") ].join("; ")); d.ajax.post("ping", { echo: "ΟΚ ✓" }, function(a, b, e) { a && a.ping ? c("#loco-ajax-check").text(a.ping) : k(e, b, a && a.error && a.error.message); }, k); f = h.apis; h = f.length; const l = d.apis.providers(); if (d.apis) { let a = -1; for (;++a < h; ) { const b = f[a], e = b.id; try { m(d.apis.create(b, l[e] || l._)); } catch (n) { g(e, String(n)); } } } else d.notices.error("admin.js is out of date. Please empty your browser cache."); }(window.loco, window.jQuery);tpl/debug/dump.php000064400000000707147206622240010120 0ustar00view('debug/dump',..) */ ?>
      $value ): if( '_' !== substr($prop,0,1) ):?>
      tpl/debug/xml.php000064400000000123147206622240007743 0ustar00
      e('xml')?>
      tpl/admin/list/bundles.php000064400000000253147206622240011560 0ustar00extend('../layout'); echo $this->render('../common/inc-table-filter'); echo $this->render('inc-table');tpl/admin/list/inc-table.php000064400000003062147206622240011763 0ustar00
      e('name')?> e('dflt')?> n('size')?>
      tpl/admin/list/locales.php000064400000003577147206622240011562 0ustar00extend('../layout'); echo $this->render('../common/inc-table-filter'); ?>
      lattr?>>e('lcode')?> e('lname')?> e('lcode')?> n('nfiles',0)?> e('used')?>
      tpl/admin/common/inc-po-header.php000064400000001201147206622240013046 0ustar00

      e('code')?> e('name')?> : date('modified')?>has('meta') && self::e( $meta->getProgressSummary() )?>

      tpl/admin/common/inc-table-filter.php000064400000000505147206622240013562 0ustar00
      tpl/admin/common/inc-fsconn.php000064400000005344147206622240012504 0ustar00has('fsLocked') ):?>

      : e('fsLocked')?>.

      has('fsDenied') ):?>

      : .

      :

      : .

      _e();?>
      extend('../layout'); echo $header; /* @var Loco_mvc_ViewParams $params */ /* @var Loco_mvc_ViewParams[] $debug */ if( $params->has('debug') && $debug ): foreach( $debug['notices'] as $type => $notes ): if($notes):?>

      escape($text);?>.

      :

      e('xml')?>
      has('credit') ):?>

      .

      extend('../layout'); ?>

      attr?>>e('code')?> e('name')?> : date('modified')?>

      $group ): $type = $types[$t];?>

      e('name')?>

      render('../common/inc-table-filter'); ?> type ):?>
      e('title')?> meta->printProgress()?> meta->getPercent()?>% n('todo')?> -- -- e('name')?> e('store')?>

      e('text')?>

      extend('../../layout'); ?>

      . .

      tpl/admin/bundle/setup/inc-nav.php000064400000001766147206622240013127 0ustar00has('notices') ):?>
        notices as $text ):?>
      • escape( $text )?>

      tpl/admin/bundle/setup/saved.php000064400000001623147206622240012666 0ustar00extend('../setup'); $this->start('header'); ?>

      .

      _e()?>
      tpl/admin/bundle/setup/none.php000064400000001060147206622240012516 0ustar00extend('../setup'); $this->start('header'); ?>

      . .

      render('inc-nav')?>
      tpl/admin/bundle/setup/author.php000064400000001125147206622240013063 0ustar00extend('../setup'); $this->start('header'); ?>

      . .

      render('inc-nav')?>
      tpl/admin/bundle/setup/partial.php000064400000001036147206622240013216 0ustar00extend('../setup'); $this->start('header'); ?>

      .

      render('inc-nav')?>
      tpl/admin/bundle/setup/meta.php000064400000001207147206622240012510 0ustar00extend('../setup'); $this->start('header'); ?>

      . .

      render('inc-nav')?>
      tpl/admin/bundle/setup/conf.php000064400000006347147206622240012521 0ustar00extend('../setup'); $this->start('header'); /*if( $params->has('jsonFields') ): Remote JSON config is scrapped

      _e()?>
      has('xmlFields') ):?>

      :

      _e()?>
      has('autoFields') ):?>

      Auto setup

      We can make some guesses about how this bundle is set up, but we can't guarantee they'll be right.

      This is not recommended unless you're a developer able to make manual changes afterwards.

      _e()?>
      lcode; $ispo = (bool) $lc;?>
      lattr?>>e('lcode')?> e('lname')?> meta->printProgress()?> meta->getPercent()?>% n('total')?> n('todo')?> -- -- -- -- e('name')?> e('store');?>
      extend('../layout'); ?>

      .

      tpl/admin/bundle/inc-po-links.php000064400000000552147206622240012727 0ustar00 tpl/admin/bundle/view.php000064400000005066147206622240011403 0ustar00extend('../layout'); /* @var Loco_mvc_ViewParams[] $projects */ /* @var Loco_mvc_ViewParams[] $unknown */ if( $projects ): foreach( $projects as $p ): ?>
      name === $p->short ):?>

      e('name')?>

      e('name')?> (e('short')?>)

      render('inc-po-links', [ 'nav' => $p->nav ] ); echo $this->render('inc-po-table', [ 'pairs' => $p->po, 'domain' => $p->domain, 'installed'=>$p->installed, 'warnings'=>$p->warnings ] ); ?>

      . tag intact echo wp_kses( sprintf( __('Click the setup tab to complete the bundle configuration','loco-translate'), $tabs[1]->href ), ['a'=>['href'=>true]], ['http','https'] );?>.

      render('../common/inc-table-filter'); echo $this->render('inc-po-table', [ 'pairs' => $unknown, 'domain' => null ] )?>

      e('name')?> ()

      . setup tab to complete the bundle configuration','loco-translate'), $tabs[1]->href ), ['a'=>['href'=>true]], ['http','https'] );?>.

      render('inc-po-table', [ 'pairs' => $unknown, 'domain' => null ] )?>
      extend('../layout'); ?>
      $p ): $id = sprintf('loco-conf-%u',$i)?>
      remove name === $p->short ):?>

      e('name')?>

      e('name')?> (e('short')?>)

      :
      :

      . ()

      :
      :

      . ()

      . ()

      parent ):?>

      extend('../layout'); /* @var Loco_mvc_ViewParams $params */ /* @var Loco_mvc_ViewParams $prompt */ if( $params->has('prompt') ):?>

      e('title')?>. e('text')?>.

      e('subhead')?>

      e('summary')?>

      _e();?> $location ):?> has('sourceLocale') ):?>
      e('text')?>

      e('label')?>:

      disabled ):?>

      3. : e('text')?>

      tpl/admin/init/init-prompt.php000064400000002764147206622240012407 0ustar00extend('../layout'); $help = apply_filters('loco_external','https://localise.biz/wordpress/plugin/manual/templates'); /* @var Loco_mvc_ViewParams $params */ /* @var Loco_mvc_ViewParams $ext */ /* @var Loco_mvc_ViewParams $skip */ /* @var Loco_mvc_ViewParams $conf */ ?>

      has('pot') ):?>

      .

      .

      e('text')?> e('text')?>has('conf') ):?> e('text')?>

      tpl/admin/init/init-copy.php000064400000002022147206622240012023 0ustar00extend('../layout'); $help = apply_filters('loco_external','https://localise.biz/wordpress/plugin/manual/templates'); /* @var Loco_mvc_ViewParams $params */ /* @var Loco_mvc_ViewParams $ext */ /* @var Loco_mvc_ViewParams $skip */ /* @var Loco_mvc_ViewParams $copy */ ?> tpl/admin/init/init-pot.php000064400000004224147206622240011661 0ustar00extend('../layout'); $help = apply_filters('loco_external','https://localise.biz/wordpress/plugin/manual/templates'); ?>

      e('subhead')?>

      n('count')?> (f( 'size', __('%s on disk','loco-translate') );?>, f( 'largest', __('largest is %s','loco-translate') )?>)

      skip ):?>

      large )?>. Help

      e('relpath')?>

      $value ):?>

      tpl/admin/init/upload.php000064400000004553147206622240011407 0ustar00extend('../layout'); /* @var Loco_Locale $locale */ ?>
      _e();?>

      $location ):?>

      e('label')?>:

      {locale}', ''.$locale.'' ), ['code'=>[]] )?>

      tpl/admin/config/debug.php000064400000011237147206622240011510 0ustar00extend('../layout'); /* @var Loco_mvc_ViewParams $versions */ /* @var Loco_mvc_ViewParams $encoding */ ?>

      Versions

      $value ): if( $value ):?>
      :
      e($key)?>

      Unicode

      UTF-8 rendering:
      OK?> tick?>
      Multibyte support:
      e('mbstring')?>
      Site character set
      e('charset')?>

      Ajax

      Endpoint:
      /wp-admin/admin-ajax.php
      JSON decoding:
      json?>
      Ajax test result:

      Translation APIs

      e('name')?>:

      Limits

      $value ):?>
      escape($key)?>:
      e($key)?>

      Filesystem

      File mods disallowed:
      disabled?'Yes':'No'?>
      File mod safety level:
      e('fs_protect')?>
      $f ):?>
      escape($label)?>:
      e('path'); if( $f->writable ): echo ' ✓'; else:?>
      PHP open_basedir:

      Debug settings

      $value ):?>
      escape($key)?>:
      e($key)?>
      tpl/admin/config/settings.php000064400000034445147206622240012270 0ustar00extend('../layout'); $help_url = esc_html( apply_filters('loco_external','https://localise.biz/wordpress/plugin/manual/settings') ); /* @var Loco_data_Settings $opts */ /* @var Loco_data_Settings $dflt */ /* @var Loco_mvc_ViewParams $nonce */ /* @var Loco_mvc_ViewParams $verbose */ ?>
      (?)

      (?)

      (?)

      %

      (?)

      (?)

      (?)

      (?)

      tpl/admin/config/prefs.php000064400000004330147206622240011535 0ustar00extend('../layout'); /* @var Loco_data_Preferences $opts */ $help = apply_filters('loco_external','https://localise.biz/wordpress/plugin/manual/settings'); ?>

      tpl/admin/config/version.php000064400000006506147206622240012112 0ustar00extend('../layout'); // Loco Translate version: if( $params->has('update') ):?>

      .

      has('devel') ):?>

      has('phpupdate') ):?> has('wpupdate') ):?>

      extend('../layout'); /* @var Loco_mvc_ViewParams $ui */ /* @var Loco_mvc_ViewParams[] $apis */ /* @var Loco_mvc_ViewParams $nonce */ $help = apply_filters('loco_external','https://localise.biz/wordpress/plugin/manual/providers'); ?>
      e('name')?>
      e('api_key')?>

      https://www.deepl.com/translator

      e('name')?>
      e('api_key')?>

      https://cloud.google.com/translate

      e('name')?>
      e('api_key')?>

      https://lecto.ai/?ref=loco

      e('name')?>
      e('api_key')?>

      https://aka.ms/MicrosoftTranslator

      : . .

      tpl/admin/errors/no-tokenizer.php000064400000002402147206622240013107 0ustar00extend('../layout'); ?>

      Tokenizer extension to scan PHP source code for translatable strings','loco-translate'), ['a'=>['href'=>true,'target'=>true]], ['http','https'] );?>.

      tag intact sprintf( __('You can still translate any bundle that has a template','loco-translate'), $help ), ['a'=>['href'=>true,'target'=>true]], ['https'] );?>.

      tpl/admin/errors/no-backups.php000064400000001704147206622240012531 0ustar00extend('../layout'); $help = esc_url( apply_filters('loco_external','https://localise.biz/wordpress/plugin/manual/settings') ); ?>

      .

      |

      tpl/admin/errors/file-missing.php000064400000000731147206622240013054 0ustar00extend('../layout'); ?>

      :

      e('path')?>

      tpl/admin/errors/generic.php000064400000001622147206622240012102 0ustar00extend('../layout'); /* @var Loco_mvc_View $this */ /* @var Loco_error_Exception $error */ ?>

      getTitle() )?>

      getMessage() )?>

      has('file') && $file->line ):?>

      e('relpath')?>#e('line')?>

      has('trace') ): echo "\n"; endif; tpl/admin/errors/file-sec.php000064400000000602147206622240012152 0ustar00extend('../layout'); ?>

      : e('reason')?>

      tpl/admin/errors/no-locale.php000064400000000774147206622240012346 0ustar00extend('../layout'); ?>

      It may not be installed properly. See Installing WordPress in your language.

      tpl/admin/errors/file-isdir.php000064400000000721147206622240012514 0ustar00extend('../layout'); ?>

      :

      e('relpath')?>

      tpl/admin/debug.php000064400000000424147206622240010237 0ustar00extend('layout'); ?>

      Debug

      render('../debug/dump'); ?>
      tpl/admin/debug/debug-layout.php000064400000001611147206622240012637 0ustar00extend('../layout'); ?> has('header') and print $header; /* @var ArrayIterator|null $log */ if( $params->has('log') ):?>

      Trace log:

      If you're asking for help on the WordPress forum, please post the full text below.

      extend('debug-form'); $this->start('header'); /* @var Loco_mvc_ViewParams $result */ if( $result->translated ):?>

      Translation result:

      e('msgstr')?>

      This is the translation value returned from the e('callee')?> function.

      String found, but no translation returned

      The e('callee')?> function returned the same value as the source string. Either the string isn't translated, or the required translation file wasn't loaded.

      See below for the exact string matches we found in your translation files.

      matches as $g => $matched ): /* @var Loco_mvc_FileParams $group */ $group = $result->grouped[$g];?>

      e('type');?> translations:

      e('relpath') ?>e('msgstr');?>

      has('href') ):?>

      Edit PO

      extend('debug-layout'); $this->start('form'); // Translators: This file is intentionally in English only. /* @var Loco_mvc_ViewParams $form */ /* @var Loco_mvc_ViewParams $default */ ?>

      Enter the original string exactly. This field is mandatory.

      Context is optional. It's used to disambiguate duplicate strings. If in doubt, leave blank.

      Plural form is optional, and will only work if the string has been pluralized.

      Leaving empty will use the WordPress core (default) text domain.

      Enter a valid locale code. Your translation files must be suffixed exactly with this value.

      See the WordPress documentation for correct usage of the path argument.

      Simulate wp_set_script_translations with a relative script path, e.g. blocks/example.js.

      Options

      Lucky dip

      tpl/admin/layout.php000064400000006156147206622240010476 0ustar00
      has('breadcrumb') ):?>

      has('title') ):?>

      e('title')?>

      has('tabs') ):?>
      has('fsFields') ): echo $this->render('common/inc-fsconn'); endif?>
      _content;?>
      has('js') && $js instanceof Loco_mvc_ViewParams ):?>extend('view'); $this->start('source'); /* @var Loco_mvc_ViewParams $params */ /* @var string $phps */ echo $this->render('../common/inc-po-header');?>
      tpl/admin/file/edit-po.php000064400000003060147206622240011430 0ustar00extend('editor'); $this->start('header'); /* @var Loco_mvc_ViewParams $params */ ?>
      has('domain') ):?>
      _e();?>
      render('../common/inc-po-header'); tpl/admin/file/info-mo.php000064400000003026147206622240011435 0ustar00extend('info'); $this->start('header'); ?>

      e('code')?> e('name')?>

      :
      e('size')?>
      :
      date('mtime')?>
      :
      e('author')?> — date('potime')?>
      :
      getTotalSummary() )?>
      existent ):?>

      .

      extend('../layout'); echo $header; /* @var Loco_mvc_ViewParams $js */ /* @var Loco_mvc_ViewParams $ui */ /* @var Loco_mvc_ViewParams $params */ /* @var Loco_mvc_ViewParams $locale */ /* @var Loco_mvc_HiddenFields $dlFields */ ?>
      tpl/admin/file/info-other.php000064400000000535147206622240012145 0ustar00extend('info'); $this->start('header'); ?>

      e('error')?>

      tpl/admin/file/view-mo.php000064400000003736147206622240011464 0ustar00extend('view'); $this->start('source'); /* @var Loco_mvc_ViewParams $params */ /* @var string $bin */ ?>

      .

       126 ) {
                          $line[] = '.'; // <- unprintable
                      }
                      else {
                          $line[] = $params->escape($c); // <- printable
                      }
                      // wrap at cols, and print plain text
                      if( ++$i === $cols ){
                          echo '  ', implode('', $line ), "\n";
                          $line = [];
                          $i = 0;
                          $r++;
                      }
                  }
                  if( $line ){
                      if( $r ){
                         echo str_repeat( '   ', $cols - $i );
                      }
                      echo '  ', implode('', $line ), "\n";
                  }
                  ?>
      tpl/admin/file/diff.php000064400000004513147206622240011003 0ustar00extend('../layout'); $dfmt = _x( 'j M @ H:i', 'revision date short format', 'default' ); /* @var Loco_mvc_ViewParams $master */ ?>
      Current revision saved e('reltime')?>

      $file ):?>
      e('name')?>

      _e();?>
      tpl/admin/file/head.php000064400000002253147206622240010773 0ustar00extend('../layout'); ?>

      _e();?> $value ): if( preg_match('/^[-A-Za-z]+$/',$key) ):?>

      tpl/admin/file/move-po.php000064400000003112147206622240011447 0ustar00extend('move'); $this->start('source'); /* @var Loco_mvc_ViewParams $current */ /* @var Loco_mvc_ViewParams[] $locations */ ?>

      $location ):?>

      e('label')?>:

      active ):?>

      tpl/admin/file/info-po.php000064400000006566147206622240011454 0ustar00extend('info'); $this->start('header'); /* @var Loco_mvc_FileParams $file */ /* @var Loco_mvc_FileParams $locale */ /* @var Loco_gettext_Metadata $meta */ ?>

      e('code')?> e('name')?>

      :
      e('size')?>
      :
      :
      e('author')?> — date('potime')?>
      :
      getProgressSummary() )?>
      printProgress()?>
      existent ):?>

      .

      has('potfile') ): if( $potfile->synced ):?>

      name )?>.

      name )?>

      has('project') ):?>

      extend('../layout'); echo $source;tpl/admin/file/edit-pot.php000064400000001744147206622240011623 0ustar00extend('editor'); $this->start('header'); ?>
      _e();?>

      : : date('modified')?>

      Template files cannot be translated

      tpl/admin/file/delete.php000064400000002404147206622240011332 0ustar00extend('../layout'); ?>

      permanently delete the following file?','loco-translate')?>

      e('relpath')?>

      has('deps') ):?>

      e('warn')?>

      e('name')?>

      _e();?>
      tpl/admin/file/move-pot.php000064400000001130147206622240011631 0ustar00extend('move'); $this->start('source'); /* @var Loco_mvc_FileParams $file */ /* @var Loco_mvc_ViewParams $current */ ?>

      tpl/admin/file/view-pot.php000064400000001103147206622240011635 0ustar00extend('view'); $this->start('source'); ?>

      : : date('modified')?>getTotalSummary() )?>

      render('msgcat'); tpl/admin/file/view-po.php000064400000000235147206622240011456 0ustar00extend('view'); $this->start('source'); echo $this->render('../common/inc-po-header'); echo $this->render('msgcat'); tpl/admin/file/info.php000064400000011022147206622240011017 0ustar00extend('../layout'); echo $header; /* @var Loco_mvc_FileParams $file */ ?> existent ):?>

      e('relpath')?>

      writable ):?>

      type )?>

      .

      ls()?>

      .

      httpd )?>.

      ls()?>

      existent ):?>

      .

      e('relpath')?>

      writable ):?>

      httpd )?>.

      ls()?>

      deletable ):?>

      .

      .

      httpd )?>.

      ls()?>

      autoupdate ):?>

      .

      has('debug') ):?>

      Developer notes

      $raw ):?>

      e($prop)?>

      extend('../layout'); ?>
      _e(); echo $source?>

      ',$params->escape( $file->basename() ),'

      '; endforeach?>

      has('advanced') ):?>

      tpl/admin/file/info-pot.php000064400000003200147206622240011616 0ustar00extend('info'); $this->start('header'); /* @var Loco_mvc_FileParams $file */ /* @var Loco_mvc_ViewParams $params */ /* @var Loco_gettext_Metadata $meta */ /* @var int $words */ ?>

      :
      e('size')?>
      :
      :
      :
      getTotalSummary() )?> ()
      type && ! $params->isTemplate ):?>

      Unconventional file name

      Template files should have the extension ".pot".
      If this is intended to be a translation file it should end with a language code.

      render('../common/inc-table-filter');?>
        $line ):?>
      1. '; continue; } // may be a comment line if( '#' === substr($line,0,1) ){ // may be able to parse out references $symbol = (string) substr($line,1,1); if( '' !== $symbol ){ $line = substr($line,2); if( ':' === $symbol ){ echo '#:',preg_replace('/\\S+:\d+/', '\\0', $params->escape($line) ),''; } // parse out flags and formatting directives else if( ',' === $symbol ){ echo '#,',preg_replace('/[-a-z]+/', '\\0', $params->escape($line) ),''; } // else treat as normal comment even if empty else { echo '#',$symbol,'',$params->escape($line),''; } } // else probably an empty comment else { echo '',$params->escape($line),''; } continue; } // grab keyword if there is one before quoted string if( preg_match('/^(msg[_a-z0-9\\[\\]]+)(\\s+)/', $line, $r ) ){ echo '',$params->escape($r[1]),'',$params->escape($r[2]),''; $line = substr( $line, strlen($r[0]) ); } // remainder of line (or whole line) should be a quoted string if( preg_match('/^"(.*)"\s*$/', $line, $r ) ){ echo '"',$params->escape($r[1]),'"'; continue; } // else print whatever junk is left of line echo '',$params->escape($line),''; ?>
      tpl/admin/file/conf.php000064400000005112147206622240011014 0ustar00extend('../layout'); /* @var Loco_gettext_SyncOptions $conf */ /* @var Loco_mvc_ViewParams $params */ ?>

      _e();?>

      tpl/admin/help/tab-support.php000064400000002030147206622240012354 0ustar00

      If you have problems using Loco Translate, please try our help pages. There's a lot of information there to help you understand how it works and the most common pitfalls to avoid.

      To report a bug please start a new topic in the plugin support forum, but please check the FAQs for similar issues first. If you decide to submit a bug report please post enough relevant detail for us to reproduce your issue.

      tpl/admin/help/tab-init-po.php000064400000001063147206622240012224 0ustar00

      Adding a new language

      This screen is for adding new translation files on your server's file system.

      The location you choose is important. Some file locations can be overwritten by WordPress updates. See FAQ

      Full documentation

      tpl/admin/help/tab-bundle-view.php000064400000000357147206622240013073 0ustar00

      Bundle overview

      The Overview tab lists each set of available translations in the current bundle.

      If Loco Translate can't configure your bundle automatically, click the Setup tab to see your options.

      tpl/admin/help/tab-config-apis.php000064400000000774147206622240013054 0ustar00

      Loco Translate supports integration with several third party machine translation services. Each of these requires an account with the service provider and an API key for enabling access via the Loco Translate editor.

      tpl/admin/help/tab-config.php000064400000001001147206622240012102 0ustar00

      . .

      tpl/admin/help/tab-init-pot.php000064400000000626147206622240012414 0ustar00

      Creating a template

      This screen is for adding a new translations template for a set of translatable strings.

      Your language files use this template to ensure they all reference the same strings.

      Full documentation

      tpl/admin/help/tab-list-bundles.php000064400000000506147206622240013253 0ustar00

      Bundle listings

      This screen lists all bundles of the current type installed in your WordPress. They may not all be ready for translation, but compatible bundles will show at least one "set" of translatable strings.

      Clicking a bundle takes you to its translation management screen.

      tpl/admin/help/side-bar.php000064400000001356147206622240011574 0ustar00

      tpl/admin/help/tab-list-locales.php000064400000000454147206622240013243 0ustar00

      Installed languages

      This screen lists all the languages that are installed in your WordPress. For a language to show up here, you must have the WordPress core translation files installed.

      Clicking a language takes you to its translation management screen.

      tpl/admin/help/tab-file-info.php000064400000000520147206622240012512 0ustar00

      File information

      This screen shows technical information about the selected file. You may find it useful in debugging problems

      About filesystem access

      tpl/admin/help/tab-locale-view.php000064400000000514147206622240013054 0ustar00

      Installed language files

      This screen lists all files installed for the selected language.

      Only files from correctly configured bundles will show up here. If you don't see the files you expect, then locate the bundle in the Themes or Plugins section and ensure it's configured correctly.

      tpl/admin/help/tab-file-edit.php000064400000000511147206622240012504 0ustar00

      The translation editor

      This editor allows you to translate and save strings to your server's file system in the correct file format.

      Full documentation

      tpl/admin/help/tab-bundle-setup.php000064400000001104147206622240013250 0ustar00

      Bundle setup

      The Setup tab shows a summary of whether the bundle is configured for translation and where the configuration is stored.

      See our help pages for how to set up unconfigured bundles.

      Full documentation

      Help for bundle authors

      tpl/admin/help/tab-file-view.php000064400000000350147206622240012532 0ustar00

      Source view

      This screen shows translation files in their raw form. It's read-only because the file syntax is very easy to get wrong. Click the "Editor" tab to make changes safely.

      tpl/admin/help/tab-bundle-conf.php000064400000000631147206622240013041 0ustar00

      Advanced configuration

      The Advanced tab provides full, manual configuration of the bundle.

      This screen is designed for bundle developers, if you don't know what to enter try asking the author.

      Full documentation

      tpl/admin/help/tab-home.php000064400000000357147206622240011602 0ustar00

      Loco Translate home screen

      From the home screen you can access recently used items and your active theme. To translate other themes, plugins, or the WordPress core, use the subsection links in the side menu.

      tpl/admin/root.php000064400000004234147206622240010137 0ustar00extend('layout'); ?>

      attr?>>e('code')?> link ); if( $params->has('adminLocale') ): echo ' '; // translators: %s will be replaced with the full name of the user profile's admin language printf( esc_html( __('Your admin language is %s.','loco-translate') ), $adminLocale->link ); endif?>

      :

      render('list/inc-table', [ 'bundles' => $recent ] );?>

      render('list/inc-table', [ 'bundles' => [$theme] ] )?>

      :

      render('list/inc-table', [ 'bundles' => $plugins ] )?>

      tpl/ajax/modal-apis-batch.php000064400000003602147206622240012112 0ustar00

      Initializing...

      e('text'); ?> e('text'); ?>

      loco.php000064400000012305147206622240006217 0ustar00getMessage(), $e->getFile(), $e->getLine() ) ); } /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ catch( Throwable $e ){ trigger_error(sprintf('[Loco.fatal] %s in %s:%u',$e->getMessage(), $e->getFile(), $e->getLine() ) ); } src/output/Buffer.php000064400000007077147206622240010635 0ustar00output; } /** * @return Loco_output_Buffer */ public static function start(){ $buffer = new Loco_output_Buffer; return $buffer->open(); } /** * @internal * Ensure buffers closed if something terminates before we close gracefully */ public function __destruct(){ $this->close(); } /** * @return Loco_output_Buffer */ public function open(){ self::check(); if( ! ob_start() ){ throw new Loco_error_Exception('Failed to start output buffering'); } $this->ob_level = ob_get_level(); return $this; } /** * @return Loco_output_Buffer */ public function close(){ if( is_int($this->ob_level) ){ // collect output from our nested buffers $this->output = self::collect( $this->ob_level ); $this->ob_level = null; } return $this; } /** * Trash all open buffers, logging any junk output collected * @return void */ public function discard(){ $this->close(); if( '' !== $this->output ){ self::log_junk( $this->output ); $this->output = ''; } } /** * Collect output buffered to a given level * @param int $min highest buffer to flush, 0 being the root * @return string */ public static function collect( $min ){ $last = 0; $output = ''; while( $level = ob_get_level() ){ // @codeCoverageIgnoreStart if( $level === $last ){ throw new Loco_error_Exception('Failed to close output buffer'); } // @codeCoverageIgnoreEnd if( $level < $min ){ break; } // output is appended inside out: $output = ob_get_clean().$output; $last = $level; } return $output; } /** * Forcefully destroy all open buffers and log any bytes already buffered. * @return void */ public static function clear(){ $junk = self::collect(0); if( '' !== $junk ){ self::log_junk($junk); } } /** * Check output has not already been flushed. * @throws Loco_error_Exception */ public static function check(){ if( headers_sent($file,$line) && 'cli' !== PHP_SAPI ){ $file = str_replace( trailingslashit( loco_constant('ABSPATH') ), '', $file ); // translators: (1) is the name of a PHP script, (2) is a line number in the file throw new Loco_error_Exception( sprintf( __('Loco was interrupted by output from %1$s:%2$u','loco-translate'), $file?:'Unknown', $line ) ); } } /** * Debug collection of junk output * @param string $junk * @return void */ private static function log_junk( $junk ){ $bytes = strlen($junk); $message = sprintf("Cleared %s of buffered output", Loco_mvc_FileParams::renderBytes($bytes) ); Loco_error_AdminNotices::debug( $message ); do_action( 'loco_buffer_cleared', $junk ); } } src/output/DiffRenderer.php000064400000004471147206622240011756 0ustar00 true, 'leading_context_lines' => 1, 'trailing_context_lines' => 1, ] ); } /** * Render diff of two files, presumed to be PO or POT * @param Loco_fs_File Left hand file * @param Loco_fs_File Right hand file * @return string HTML table */ public function renderFiles( Loco_fs_File $lhs, Loco_fs_File $rhs ){ loco_require_lib('compiled/gettext.php'); // attempt to raise memory limit to WP_MAX_MEMORY_LIMIT if( function_exists('wp_raise_memory_limit') ){ wp_raise_memory_limit('loco'); } // like wp_text_diff but avoiding whitespace normalization // uses deprecated signature for 'auto' in case of old WordPress return $this->render( new Text_Diff( self::splitFile($lhs), self::splitFile($rhs) ) ); } /** * @param Loco_fs_File * @return string[] */ private static function splitFile( Loco_fs_File $file ){ $src = $file->getContents(); $src = Loco_gettext_Data::ensureUtf8($src); $arr = preg_split( '/\\r?\\n/', $src ); if( ! is_array($arr) ){ $f = new Loco_mvc_FileParams( [], $file ); throw new Loco_error_Exception('Failed to split '.$f->relpath.' ('.$f->size.')' ); } return $arr; } /** * {@inheritdoc} */ public function _startDiff() { return "\n"; } /** * {@inheritdoc} */ public function _endDiff() { return "
      \n"; } /** * {@inheritdoc} */ public function _startBlock( $header ) { return '\n"; } /** * {@inheritdoc} */ public function _endBlock() { return "\n"; } } src/package/Header.php000064400000006461147206622240010643 0ustar00wp = $header; } /** * @param string * @return string */ public function __get( $prop ){ $wp = $this->wp; // prefer "get" method to access raw properties (WP_Theme) if( is_object($wp) && method_exists($wp,'get') ){ $value = $wp->get($prop); if( is_string($value) && '' !== $value ){ return $value; } } // may have key directly, e.g. TextDomain in plugin array if( isset($wp[$prop]) ){ return $wp[$prop]; } // else header not defined, which is probably fine return ''; } /** * @param string * @param mixed * @codeCoverageIgnore */ public function __set( $prop, $value ){ throw new LogicException('Read only'); } /** * Get bundle author as linked text, just like the WordPress plugin list does * @return string escaped HTML */ public function getAuthorLink(){ if( ( $link = $this->AuthorURI ) || ( $link = $this->PluginURI ) || ( $link = $this->ThemeURI ) ){ $author = $this->Author or $author = $link; return ''.esc_html($author).''; } return ''; } /** * Get "name" by credit * @return string escaped HTML */ public function getAuthorCredit(){ if( $author = $this->Author ){ $author = esc_html( strip_tags($author) ); if( $link = $this->AuthorURI ){ $author = ''.$author.''; } } else { $author = __('Unknown author','loco-translate'); } // translators: Author credit: (1) Product name (2) version number, (3) author name. $html = wp_kses( sprintf( __('"%1$s" %2$s by %3$s','loco-translate'), $this->Name, $this->Version, $author ), ['a'=>['href'=>true,'target'=>true]], ['http','https'] ); $link = $this->PluginURI ?: $this->ThemeURI; if( $link ){ $html .= sprintf( ' — %s', esc_url($link), esc_html(__('Visit official site','loco-translate')) ); } return $html; } /** * Get hostname of vendor that hosts theme/plugin * @return string e.g. "wordpress.org" */ public function getVendorHost(){ $host = ''; if( ( $url = $this->PluginURI ) || ( $url = $this->ThemeURI ) ){ if( $host = parse_url($url,PHP_URL_HOST) ){ $bits = explode( '.', $host ); $host = implode( '.', array_slice($bits,-2) ); } } return $host; } }src/package/Inverter.php000064400000015177147206622240011255 0ustar00getFileFinder(); /* @var $project Loco_package_Project */ foreach( $bundle as $project ){ if( $file = $project->getPot() ){ // excluding all extensions in case POT is actually a PO/MO pair foreach( ['pot','po','mo'] as $ext ){ $file = $file->cloneExtension($ext); if( $path = realpath( $file->getPath() ) ){ $finder->exclude( $path ); } } } foreach( $project->findLocaleFiles('po') as $file ){ if( $path = realpath( $file->getPath() ) ){ $finder->exclude( $path ); } } foreach( $project->findLocaleFiles('mo') as $file ){ if( $path = realpath( $file->getPath() ) ){ $finder->exclude( $path ); } } } // Do a deep scan of all files that haven't been seen, or been excluded: // This will include files in global directories and inside the bundle. return $finder->setRecursive(true)->followLinks(false)->group('po','mo','pot')->exportGroups(); } /** * Compile anything found under bundle root that isn't configured in $known * @return Loco_package_Bundle */ public static function compile( Loco_package_Bundle $bundle ){ $found = self::export($bundle); // done with original bundle now $bundle = clone $bundle; $bundle->clear(); // first iteration groups found files into common locations that should hopefully indicate translation sets $groups = []; $templates = []; $localised = []; $root = $bundle->getDirectoryPath(); /* @var $list Loco_fs_FileList */ foreach( $found as $ext => $list ){ /* @var $file Loco_fs_LocaleFile */ foreach( $list as $file ){ // printf("Found: %s
      \n", $file ); // This file is NOT known to be part of a configured project $dir = $file->getParent(); $key = $dir->getRelativePath( $root ); // if( ! isset($groups[$key]) ){ $groups[$key] = $dir; $templates[$key] = []; $localised[$key] = []; } // template should define single set of translations unique by directory and file prefix if( 'pot' === $ext ){ $slug = $file->filename(); $templates[$key][$slug] = true; } // else ideally PO/MO files will correspond to a template by common prefix else { $file = new Loco_fs_LocaleFile( $file ); $slug = $file->getPrefix(); if( $file->getLocale()->isValid() ){ $localised[$key][$slug] = true; } // else could be some kind of non-standard template else { $slug = $file->filename(); $templates[$key][$slug] = true; } } } } unset($found); // next iteration matches collected files together into likely project sets $unique = []; /* @var $list Loco_fs_Directory */ foreach( $groups as $key => $dir ){ // pair up all projects that match templates neatly to prefixed files foreach( $templates[$key] as $slug => $bool ){ if( isset($localised[$key][$slug]) ){ //printf("Perfect match on domain '%s' in %s
      \n", $slug, $key ); $unique[$key][$slug] = $dir; // done with this perfectly matched set $templates[$key][$slug] = null; $localised[$key][$slug] = null; } } // pair up any unprefixed localised files if( isset($localised[$key]['']) ){ $slug = 'unknown'; // Match to first (hopefully only) template to establish a slug foreach( $templates[$key] as $_slug => $bool ){ if( $bool ){ $slug = $_slug; $templates[$key][$slug] = null; break; // <- not possible to know how multiple POTs might be paired up } } //printf("Pairing unprefixed files in %s to '%s'
      \n", $key, $slug ); $unique[$key][$slug] = $dir; // done with unprefixed localised files in this directory $localised[$key][''] = null; } // add any orphaned translations (those with no template matched) foreach( $localised[$key] as $slug => $bool ){ if( $bool ){ // printf("Picked up orphoned locales in %s as '%s'
      \n", $key, $slug ); $unique[$key][$slug] = $dir; } } // add any orphaned templates (those with no localised files matched) foreach( $templates[$key] as $slug => $bool ){ if( $bool ){ //printf("Picked up orphoned template in %s as '%s'
      \n", $key, $slug ); $unique[$key][$slug] = $dir; } } } unset( $groups, $localised, $templates ); // final iteration adds unique projects to bundle foreach( $unique as $key => $sets ){ foreach( $sets as $slug => $dir ){ $name = ucfirst( strtr( $slug, '-_', ' ' ) ); $domain = new Loco_package_TextDomain( $slug ); $project = $domain->createProject( $bundle, $name ); $project->addTargetDirectory($dir); $bundle->addProject($project); } // TODO how to prevent overlapping sets by adding each other's files to exclude lists } return $bundle; } }src/package/Bundle.php000064400000050434147206622240010663 0ustar00underThemeDirectory() ){ return Loco_package_Theme::fromFile($file); } else if( $file->underPluginDirectory() ){ return Loco_package_Plugin::fromFile($file); } else if( $file->underWordPressDirectory() && ! $file->underContentDirectory() ){ return Loco_package_Core::create(); } else { return null; } } /** * Construct from WordPress handle and friendly name * @param string $handle * @param string $name */ public function __construct( $handle, $name ){ parent::__construct(); $this->setHandle($handle)->setName($name); $this->xpaths = new Loco_fs_FileList; } /** * Re-fetch this bundle from its currently saved location * @return self */ public function reload(){ return call_user_func( [ get_class($this), 'create' ], $this->getSlug() ); } /** * Get ID that uniquely identifies bundle by its type and handle * @return string */ public function getId(){ $type = strtolower( $this->getType() ); return $type.'.'.$this->getHandle(); } /** * @return string */ public function __toString(){ return $this->name; } /** * @return bool */ public function isTheme(){ return false; } /** * Get parent bundle if possible. This can only be a theme. * @codeCoverageIgnore * @return Loco_package_Theme|null */ public function getParent(){ trigger_error( $this->getType().' bundles cannot have parents. Check isTheme first'); return null; } /** * @return bool */ public function isPlugin(){ return false; } /** * Get handle of bundle unique for its type, e.g. "twentyfifteen" or "loco-translate/loco.php" * @return string */ public function getHandle(){ return $this->handle; } /** * Attempt to get the vendor-specific slug, which may or may not be the same as the internal handle * @return string */ public function getSlug(){ if( $slug = $this->slug ){ return $slug; } // fall back to runtime handle return $this->getHandle(); } /** * Set friendly name of bundle * @return self */ public function setName( $name ){ $this->name = (string) $name; return $this; } /** * Set short name of bundle which may or may not match unique handle * @return self */ public function setSlug( $slug ){ $this->slug = (string) $slug; return $this; } /** * Set internal handle registered with WordPress for this bundle type * @return self */ public function setHandle( $handle ){ $this->handle = (string) $handle; return $this; } /** * Get friendly name of bundle, e.g. "Twenty Fifteen" or "Loco Translate" * @return string */ public function getName(){ return $this->name; } /** * Whether bundle root is currently known * @return bool */ public function hasDirectoryPath(){ return (bool) $this->root; } /** * Set root directory for bundle. e.g. theme or plugin directory * @return self */ public function setDirectoryPath( $path ){ $this->root = new Loco_fs_Directory( $path ); $this->root->normalize(); return $this; } /** * Get absolute path to root directory for bundle. e.g. theme or plugin directory * @return string */ public function getDirectoryPath(){ if( $this->root ){ return $this->root->getPath(); } // without a root directory return WordPress root return untrailingslashit(ABSPATH); } /** * @return string[] */ public function getVendorRoots(){ $dirs = []; $base = $this->getDirectoryPath(); foreach( ['node_modules','vendor'] as $f ){ $path = $base.'/'.$f; if( Loco_fs_File::is_readable($path) && is_dir($path) ){ $dirs[] = $path; } } return $dirs; } /** * Get file locations to exclude from all projects in bundle. These are effectively "hidden" * @return Loco_fs_FileList */ public function getExcludedLocations(){ return $this->xpaths; } /** * Add a path for excluding from all projects * @param Loco_fs_File|string $path * @return Loco_package_Bundle */ public function excludeLocation( $path ){ $this->xpaths->add( new Loco_fs_File($path) ); return $this; } /** * Create a file searcher from root location, excluding that which is excluded * @return Loco_fs_FileFinder */ public function getFileFinder(){ $root = $this->getDirectoryPath(); /*/ if bundle is symlinked it's resource files won't be matched properly if( is_link($root) && ( $real = realpath($root) ) ){ $root = $real; }*/ $finder = new Loco_fs_FileFinder( $root ); foreach( $this->xpaths as $path ){ $finder->exclude( (string) $path ); } return $finder; } /** * Get primary PHP source file containing bundle bootstrap code, if applicable * @return string */ public function getBootstrapPath(){ return $this->boot; } /** * Set primary PHP source file containing bundle bootstrap code, if applicable. * @param string $path to PHP file * @return Loco_package_Bundle */ public function setBootstrapPath( $path ){ $path = (string) $path; // sanity check this is a PHP file even if it doesn't exist if( '.php' !== substr($path,-4) ){ throw new Loco_error_Exception('Bootstrap file should end .php, got '.$path ); } $this->boot = $path; // base directory can be inferred from bootstrap path if( ! $this->hasDirectoryPath() ){ $this->setDirectoryPath( dirname($path) ); } return $this; } /** * Test whether bundle consists of a single file */ public function isSingleFile(){ return $this->solo; } /** * Add all projects defined in a TextDomain * @return self */ public function addDomain( Loco_package_TextDomain $domain ){ /* @var Loco_package_Project $proj */ foreach( $domain as $proj ){ $this->addProject($proj); } return $this; } /** * Add a translation project to bundle. * Note that this always adds without checking uniqueness. Call hasProject first if it could be a duplicate * @return self */ public function addProject( Loco_package_Project $project ){ // add global targets foreach( $this->getSystemTargets() as $path ){ $project->addSystemTargetDirectory( $path ); } // add global exclusions affecting source and target locations foreach( $this->xpaths as $path ){ $project->excludeLocation( $path ); } // projects must be unique by Text Domain and "slug" (used to prefix files) // however, I am not indexing them here on purpose so domain and slug may be added at any time. $this[] = $project; return $this; } /** * Export projects grouped by domain * @return array indexed by Text Domain name */ public function exportGrouped(){ $domains = []; /* @var $proj Loco_package_Project */ foreach( $this as $proj ){ $domain = $proj->getDomain(); $key = $domain->getName(); $domains[$key][] = $proj; } return $domains; } /** * Create a suitable Text Domain from bundle's name. * Note that internal handle may be a directory name differing entirely from the author's intention, hence the configured bundle name is slugged instead * @return Loco_package_TextDomain */ public function createDomain(){ $slug = sanitize_title( $this->name, $this->slug ); return new Loco_package_TextDomain( $slug ); } /** * Generate default configuration. * Adds a simple one domain, one project config * @param string|null $domainName optional Text Domain to use * @return Loco_package_Project */ public function createDefault( $domainName = null ){ if( is_null($domainName) ){ $domain = $this->createDomain(); } else { $domain = new Loco_package_TextDomain($domainName); } $project = $domain->createProject( $this, $this->name ); if( $this->solo ){ $project->addSourceFile( $this->getBootstrapPath() ); } else { $project->addSourceDirectory( $this->getDirectoryPath() ); } $this->addProject( $project ); return $project; } /** * Configure from custom saved option * @return bool whether configured via database option */ public function configureDb(){ $option = $this->getCustomConfig(); if( $option instanceof Loco_config_CustomSaved ){ $option->configure(); $this->saved = 'db'; return true; } return false; } /** * Configure from XML config * @return bool whether configured via static XML file */ public function configureXml(){ $xmlfile = $this->getConfigFile(); if( $xmlfile instanceof Loco_fs_File ){ $reader = new Loco_config_BundleReader($this); $reader->loadXml( $xmlfile ); $this->saved = 'file'; return true; } return false; } /** * Get XML configuration file used to define this bundle * @return Loco_fs_File */ public function getConfigFile(){ $base = $this->getDirectoryPath(); $file = new Loco_fs_File( $base.'/loco.xml' ); if( ! $file->exists() || ! loco_check_extension('dom') ){ return null; } return $file; } /** * Check whether bundle is manually configured, as opposed to guessed * @return string|false (file|db|meta|internal) */ public function isConfigured(){ return $this->saved; } /** * Do basic configuration from bundle meta data (file headers) * @param array $header tags from theme or plugin bootstrap file * @return bool whether configured via header tags */ public function configureMeta( array $header ){ if( isset($header['Name']) ){ $this->setName( $header['Name'] ); } if( isset($header['TextDomain']) && ( $slug = $header['TextDomain'] ) ){ $domain = new Loco_package_TextDomain($slug); $domain->setCanonical( true ); // use domain as bundle handle and slug if not set when constructed if( ! $this->handle ){ $this->handle = $slug; } if( ! $this->getSlug() ){ $this->setSlug( $slug ); } $project = $domain->createProject( $this, $this->name ); // May have declared DomainPath $base = $this->getDirectoryPath(); if( isset($header['DomainPath']) && ( $path = trim($header['DomainPath'],'/') ) ){ $project->addTargetDirectory( $base.'/'.$path ); } // else use standard language path if it exists else if( ! $this->solo ){ if( is_dir($base.'/languages') ) { $project->addTargetDirectory($base.'/languages'); } // else add bundle root by default else { $project->addTargetDirectory($base); } } // single file bundles can have only one source file if( $this->solo ){ $project->addSourceFile( $this->getBootstrapPath() ); } // else add bundle root as default source file location else { $project->addSourceDirectory( $base ); } // automatically block common vendor locations foreach( $this->getVendorRoots() as $root ){ $this->excludeLocation($root); } // default domain added $this->addProject($project); $this->saved = 'meta'; return true; } return false; } /** * Configure bundle from canonical sources. * Source order is "db","file","meta" where meta is the auto-config fallback. * No deep scanning is performed at this point * @param string $base * @param string[] $header tags from theme or plugin bootstrap file * @return self */ public function configure( $base, array $header ){ $this->setDirectoryPath( $base ); $this->configureDb() || $this->configureXml() || $this->configureMeta($header); do_action('loco_bundle_configured',$this); return $this; } /** * Get the custom config saved in WordPress DB for this bundle * @return Loco_config_CustomSaved|null */ public function getCustomConfig(){ $custom = new Loco_config_CustomSaved; if( $custom->setBundle($this)->fetch() ){ return $custom; } return null; } /** * Inherit another bundle. Used for child themes to display parent translations * @return self */ public function inherit( Loco_package_Bundle $parent ){ foreach( $parent as $project ){ if( ! $this->hasProject($project) ){ $this->addProject( $project ); } } return $this; } /** * Get unique translation project by text domain (and optionally slug) * TODO would prefer to avoid iteration, but slug can be changed at any time * @param string $domain * @param string|null $slug * @return Loco_package_Project */ public function getProject( $domain, $slug = null ){ if( is_null($slug) ){ $slug = $domain; } /* @var $project Loco_package_Project */ foreach( $this as $project ){ if( $project->getSlug() === $slug && $project->getDomain()->getName() === $domain ){ return $project; } } return null; } /** * @return Loco_package_Project|null */ public function getDefaultProject(){ $i = 0; /* @var Loco_package_Project $project */ foreach( $this as $project ){ if( $project->isDomainDefault() ){ return $project; } $i++; } // nothing is domain default, but if we only have one, then duh if( 1 === $i ){ return $project; } return null; } /** * Test if project already exists in bundle * @return bool */ public function hasProject( Loco_package_Project $project ){ return (bool) $this->getProject( $project->getDomain()->getName(), $project->getSlug() ); } /** * @return Loco_package_TextDomain[] */ public function getDomains(){ $domains = []; /* @var $project Loco_package_Project */ foreach( $this as $project ){ if( $domain = $project->getDomain() ){ $d = (string) $domain; if( ! isset($domains[$d]) ){ $domains[$d] = $domain; } } } return $domains; } /** * Get newest timestamp of all translation files (includes template, but exclude source files) * @return int */ public function getLastUpdated(){ // recent items is a convenient cache for checking last modified times $t = Loco_data_RecentItems::get()->hasBundle( $this->getId() ); // else have to scan targets across all projects if( 0 === $t ){ /* @var Loco_package_Project $project */ foreach( $this as $project ){ $t = max( $t, $project->getLastUpdated() ); } } return $t; } /** * Get project by ID * @param string $id identifier of the form [.] * @return Loco_package_Project */ public function getProjectById( $id ){ list( $domain, $slug ) = Loco_package_Project::splitId($id); return $this->getProject( $domain, $slug ); } /** * Reset bundle configuration, but keep metadata like name and slug. * Call this before applying a saved config, otherwise values will just be added on top. * @return self */ public function clear(){ $this->exchangeArray( [] ); $this->xpaths = new Loco_fs_FileList; $this->saved = false; return $this; } /** * @return array */ #[ReturnTypeWillChange] public function jsonSerialize(){ $writer = new Loco_config_BundleWriter( $this ); return $writer->toArray(); } /** * Create a copy of this bundle containing any files found that aren't currently configured * @return self */ public function invert(){ return Loco_package_Inverter::compile( $this ); } }src/package/TextDomain.php000064400000003032147206622240011516 0ustar00name = (string) $name; } /** * @internal */ public function __toString(){ return $this->name; } /** * Get name of Text Domain, e.g. "twentyfifteen" * @return string */ public function getName(){ return $this->name; } /** * Create a named project in a given bundle for this Text Domain * @param Loco_package_Bundle $bundle of which this is one set of translations * @param string $name * @return Loco_package_Project */ public function createProject( Loco_package_Bundle $bundle, $name ){ $proj = new Loco_package_Project( $bundle, $this, $name ); $this[] = $proj; return $proj; } /** * @param bool $bool * @return Loco_package_TextDomain */ public function setCanonical( $bool ){ $this->canonical = (bool) $bool; return $this; } /** * @return bool */ public function isCanonical(){ return $this->canonical; } } src/package/Plugin.php000064400000023523147206622240010707 0ustar00getHandle()); } /** * {@inheritdoc} */ public function getSlug(){ // TODO establish "official" slug somehow // Fallback to first handle component $slug = explode( '/', parent::getSlug(), 2 ); return current( $slug ); } /** * @return Loco_package_Plugin[] */ public static function getAll(){ $plugins = []; foreach( self::get_plugins() as $handle => $data ){ try { $plugins[] = Loco_package_Plugin::create($handle); } catch( Exception $e ){ // @codeCoverageIgnore } } return $plugins; } /** * Maintaining our own cache of full paths to available plugins, because get_mu_plugins doesn't get cached by WP * @return array[] */ public static function get_plugins(){ $cached = wp_cache_get('plugins','loco'); if( ! is_array($cached) ){ $cached = []; // regular plugins + mu plugins: $search = [ 'WP_PLUGIN_DIR' => 'get_plugins', 'WPMU_PLUGIN_DIR' => 'get_mu_plugins', ]; foreach( $search as $const => $getter ){ if( $list = call_user_func($getter) ){ $base = loco_constant($const); foreach( $list as $handle => $data ){ if( isset($cached[$handle]) ){ Loco_error_AdminNotices::debug( sprintf('Plugin conflict on %s', $handle) ); continue; } // WordPress 4.6 introduced TextDomain header fallback @37562 see https://core.trac.wordpress.org/changeset/37562/ // if we don't force the original text domain header we can't know if a bundle is misconfigured. This leads to silent errors. // this has a performance overhead, and also results in "unconfigured" messages that users may not have had in previous releases. /*/ TODO perhaps implement a plugin setting that forces original headers $file = new Loco_fs_File($base.'/'.$handle); if( $file->exists() ){ $map = array( 'TextDomain' => 'Text Domain' ); $raw = get_file_data( $file->getPath(), $map, 'plugin' ); $data['TextDomain'] = $raw['TextDomain']; }*/ // set resolved base directory before caching our copy of plugin data $data['basedir'] = $base; $cached[$handle] = $data; } } } $cached = apply_filters('loco_plugins_data', $cached ); uasort( $cached, '_sort_uname_callback' ); // Intended as in-memory cache so adding short expiry for object caching plugins that may persist it. // All actions that invoke `wp_clean_plugins_cache` should purge this. See Loco_hooks_AdminHooks wp_cache_set('plugins', $cached, 'loco', 3600 ); } return $cached; } /** * Get raw plugin data from WordPress registry, plus additional "basedir" field for resolving handle to actual file. * @param string $handle Relative file path used as handle e.g. loco-translate/loco.php * @return array */ public static function get_plugin( $handle ){ $search = self::get_plugins(); // plugin must be registered with WordPress if( isset($search[$handle]) ){ $data = $search[$handle]; } // else plugin is not known to WordPress else { $data = apply_filters( 'loco_missing_plugin', [], $handle ); } // plugin not valid if name absent from raw data if( empty($data['Name']) ){ return null; } // basedir is added by our get_plugins function, but filtered arrays could be broken if( ! array_key_exists('basedir',$data) ){ Loco_error_AdminNotices::debug( sprintf('"basedir" property required to resolve %s',$handle) ); return null; } return $data; } /** * {@inheritdoc} */ public function getHeaderInfo(){ $handle = $this->getHandle(); $data = self::get_plugin($handle); if( ! is_array($data) ){ // permitting direct file access if file exists (tests) $path = $this->getBootstrapPath(); if( $path && file_exists($path) ){ $data = get_plugin_data( $path, false, false ); } else { $data = []; } } return new Loco_package_Header( $data ); } /** * {@inheritdoc} */ public function getMetaTranslatable(){ return [ 'Name' => 'Name of the plugin', 'Description' => 'Description of the plugin', 'PluginURI' => 'URI of the plugin', 'Author' => 'Author of the plugin', 'AuthorURI' => 'Author URI of the plugin', // 'Tags' => 'Tags of the plugin', ]; } /** * {@inheritdoc} */ public function setHandle( $handle ){ // plugin handles are relative paths from plugin directory to bootstrap file // so plugin is single file if its handle has no directory prefix if( basename($handle) === $handle ){ $this->solo = true; } else { $this->solo = false; } return parent::setHandle($handle); } /** * {@inheritdoc} */ public function setDirectoryPath( $path ){ parent::setDirectoryPath($path); // plugin bootstrap file can be inferred from base directory + handle // e.g. if base is "/path/to/foo" and handle is "foo/bar.php" we can derive "/path/to/foo/bar.php" if( ! $this->getBootstrapPath() ){ $handle = $this->getHandle(); if( '' !== $handle ) { $file = new Loco_fs_File( basename($handle) ); $file->normalize( $path ); $this->setBootstrapPath( $file->getPath() ); } } return $this; } /** * Create plugin bundle definition from WordPress plugin data. * * @param string $handle plugin handle relative to plugin directory * @return Loco_package_Plugin */ public static function create( $handle ){ // plugin must be registered with at least a name and "basedir" $data = self::get_plugin($handle); if( ! $data ){ // translators: %s refers to the handle of a plugin, e.g. "loco-translate/loco.php" throw new Loco_error_Exception( sprintf( __('Plugin not found: %s','loco-translate'),$handle) ); } // lazy resolve of base directory from "basedir" property that we added $file = new Loco_fs_File( $handle ); $file->normalize( $data['basedir'] ); $base = $file->dirname(); // handle and name is enough data to construct empty bundle $bundle = new Loco_package_Plugin( $handle, $data['Name'] ); // check if listener heard the real text domain, but only use when none declared // This will not longer happen since WP 4.6 header fallback, but we could warn about it $listener = Loco_package_Listener::singleton(); if( $domain = $listener->getDomain($handle) ){ if( empty($data['TextDomain']) ){ $data['TextDomain'] = $domain; if( empty($data['DomainPath']) ){ $data['DomainPath'] = $listener->getDomainPath($domain); } } // ideally would only warn on certain pages, but unsure where to place this logic other than here // TODO possibly allow bundle to hold errors/warnings as part of its config. else if( $data['TextDomain'] !== $domain ){ Loco_error_AdminNotices::debug( sprintf("Plugin loaded text domain '%s' but WordPress knows it as '%s'",$domain, $data['TextDomain']) ); } } // do initial configuration of bundle from metadata $bundle->configure( $base, $data ); return $bundle; } /** * {@inheritDoc} */ public static function fromFile( Loco_fs_File $file ){ $find = $file->getPath(); foreach( self::get_plugins() as $handle => $data ){ $boot = new Loco_fs_File( $handle ); $boot->normalize( $data['basedir'] ); // single file plugins can only match if given file is the plugin file itself. if( basename($handle) === $handle ){ if( $boot->getPath() === $find ){ return self::create($handle); } } // else check file is under plugin root. else { $base = $boot->dirname(); $path = $base.substr( $find, strlen($base) ); if( $path === $find ){ return self::create($handle); } } } return null; } } src/package/Listener.php000064400000027415147206622240011242 0ustar00unhook(); self::$singleton = null; } } /** * Create a singleton listener that we can query from anywhere * @return Loco_package_Listener */ public static function create(){ self::destroy(); self::$singleton = new Loco_package_Listener; return self::$singleton->clear(); } /** * @return Loco_package_Listener */ public function clear(){ $this->buffer = []; $this->themes = []; $this->plugins = []; $this->domains = []; $this->domainPaths = []; $this->pluginHandles = null; $this->buffered = false; $this->globalPaths = []; foreach( ['WP_LANG_DIR'] as $name ){ if( $value = loco_constant($name) ){ $this->globalPaths[$value] = strlen($value); } } return $this; } /** * Early hook listening for active bundles loading their own text domains. */ public function on_load_textdomain( $domain, $mofile ){ // echo '
      Debug:',esc_html( json_encode(compact('domain','mofile'),JSON_UNESCAPED_SLASHES)),'
      '; $this->buffered = true; $this->buffer[$domain][] = $mofile; } /** * Get primary Text Domain that's uniquely assigned to a bundle * @param string theme or plugin relative path */ public function getDomain( $handle ){ $this->flush(); return isset($this->domains[$handle]) ? $this->domains[$handle] : ''; } /** * Get the default directory path where captured files of a given domain are held * @param string TextDomain * @return string relative path */ public function getDomainPath( $domain ){ $this->flush(); return isset($this->domainPaths[$domain]) ? $this->domainPaths[$domain] : ''; } /** * Utility: checks if a file path is under a given root * @return string subpath relative to given root */ private static function relative( $path, $root ){ $root = trailingslashit($root); $snip = strlen($root); // attempt unaltered path if( substr($path,0,$snip) === $root ){ return substr( $path, $snip ); } // attempt resolved in case symlinks along path $real = realpath($path); if( $real && $real !== $path && substr($real,0,$snip) === $root ){ return substr( $real, $snip ); } // path not under root return null; } /** * Check if given relative directory path the root of a known plugin * @param string relative plugin directory name, e.g. "foo/bar" * @return string relative plugin file handle, e.g. "foo/bar/baz.php" */ private function isPlugin( $check ){ if( ! $this->pluginHandles ){ $this->pluginHandles = []; foreach( Loco_package_Plugin::get_plugins() as $handle => $data ){ $this->pluginHandles[ dirname($handle) ] = $handle; // set default text domain because additional domains could be discovered before the canonical one if( isset($data['TextDomain']) && ( $domain = $data['TextDomain'] ) ){ $this->domains[$handle] = $domain; } } } if( ! array_key_exists($check, $this->pluginHandles) ){ return null; } return $this->pluginHandles[$check]; } /** * Convert a file path to a theme or plugin bundle * @return Loco_package_Bundle */ private function resolve( $path, $domain ){ $file = new Loco_fs_LocaleFile( $path ); // ignore suffix-only files when locale is invalid as locale code would be taken wrongly as slug, e.g. if you tried to load "english.po" if( $file->hasPrefixOnly() ){ return; } // no point looking at files in global directory as they tell us only the domain which we already know foreach( $this->globalPaths as $prefix => $length ){ if( substr($path,0,$length) === $prefix ){ return; } } // avoid infinite loops during bundle resolution $wasBuffered = $this->buffered; $this->buffered = false; // file prefix is *probably* the Text Domain, but can differ if load_textdomain called directly from bundle code $slug = $file->getPrefix() or $slug = $domain; $path = dirname($path); $bundle = null; while( true ){ // check if MO file lives inside a theme foreach( $GLOBALS['wp_theme_directories'] as $root ){ $relative = self::relative($path, $root); if( is_null($relative) ){ continue; } // theme's "stylesheet directory" must be immediately under this root // passed path could root of theme, or any directory below it, but we only need the top level $chunks = explode( '/', $relative, 2 ); $handle = current( $chunks ); if( ! $handle ){ continue; } $theme = new WP_Theme( $handle, $root ); if( ! $theme->exists() ){ continue; } $abspath = $root.'/'.$handle; // theme may have officially declared text domain if( $default = $theme->get('TextDomain') ){ $this->domains[$handle] = $default; } // else set current domain as default if not already set else if ( ! isset($this->domains[$handle]) ){ $this->domains[$handle] = $domain; } if( ! isset($this->domainPaths[$domain]) ){ $this->domainPaths[$domain] = self::relative( $path, $abspath ); } // theme bundle may already exist if( isset($this->themes[$handle]) ){ $bundle = $this->themes[$handle]; } // create default project for theme bundle else { $bundle = Loco_package_Theme::createFromTheme($theme); $this->themes[$handle] = $bundle; } // possibility that additional text domains are being added $project = $bundle->getProject($slug); if( ! $project ){ $project = new Loco_package_Project( $bundle, new Loco_package_TextDomain($domain), $slug ); $bundle->addProject( $project ); } // bundle was a theme, even if we couldn't configure it, so no point checking plugins break 2; } // check if MO file lives inside a plugin foreach( [ 'WP_PLUGIN_DIR', 'WPMU_PLUGIN_DIR' ] as $const ){ $root = loco_constant($const); $relative = self::relative($path, $root); if( is_null($relative) ){ continue; } // plugin *might* live directly under root $stack = []; foreach( explode( '/', dirname($relative) ) as $next ){ $stack[] = $next; $relbase = implode('/', $stack ); if( $handle = $this->isPlugin($relbase) ){ $abspath = $root.'/'.$handle; // set this as default domain if not already cached if( ! isset($this->domains[$handle]) ){ $this->domains[$handle] = $domain; } if( ! isset($this->domainPaths[$domain]) ){ $target = self::relative( $path, dirname($abspath) ); $this->domainPaths[$domain] = $target; } // plugin bundle may already exist if( isset($this->plugins[$handle]) ){ $bundle = $this->plugins[$handle]; } // create default project for plugin bundle (not necessarily the current text domain) else { $bundle = Loco_package_Plugin::create($handle); $this->plugins[$handle] = $bundle; } // add current domain as translation project if not already set // this avoids extra domains getting set before the default one if( ! $bundle->getProject($slug) ){ $project = new Loco_package_Project( $bundle, new Loco_package_TextDomain($domain), $slug ); $bundle->addProject( $project ); } break; } } } // failed to establish a bundle break; } $this->buffered = $wasBuffered; return $bundle; } /** * @internal * Resolve all currently buffered text domain paths */ private function flush(){ if( $this->buffered ){ foreach( $this->buffer as $domain => $paths ){ foreach( $paths as $path ){ try { if( $bundle = $this->resolve($path,$domain) ){ continue 2; } } catch( Loco_error_Exception $e ){ // silent errors for non-critical function } } } $this->buffer = []; $this->buffered = false; } } /** * @return array */ public function getThemes(){ $this->flush(); return $this->themes; } /** * @return array */ public function getPlugins(){ $this->flush(); return $this->plugins; } }src/package/Project.php000064400000062527147206622240011066 0ustar00.pot" * @var Loco_fs_File */ private $pot; /** * Whether POT file is protected from end-user update and sync operations. * @var bool */ private $potlock = false; /** * Construct project from its domain and a descriptive name * @param Loco_package_Bundle $bundle * @param Loco_package_TextDomain $domain * @param string $name */ public function __construct( Loco_package_Bundle $bundle, Loco_package_TextDomain $domain, $name ){ $this->setName($name); $this->bundle = $bundle; $this->domain = $domain; // take default slug from domain, avoiding wildcard $slug = $domain->getName(); if( '*' === $slug ){ $slug = ''; } $this->slug = $slug; // sources $this->sfiles = new Loco_fs_FileList; $this->spaths = new Loco_fs_FileList; $this->xspaths = new Loco_fs_FileList; // targets $this->dpaths = new Loco_fs_FileList; $this->gpaths = new Loco_fs_FileList; $this->xdpaths = new Loco_fs_FileList; // global $this->xgpaths = new Loco_fs_FileList; } /** * Split project ID into domain and slug. * null and "" are meaningfully different. "" means deliberately empty slug, whereas null means default * @param string [.] * @return string[] [ , ] */ public static function splitId( $id ){ $r = preg_split('/(?getSlug(); $domain = (string) $this->getDomain(); if( $slug === $domain ){ return $slug; } return addcslashes($domain,'.').'.'.addcslashes($slug,'.'); } /** * @return string */ public function __toString(){ return $this->name; } /** * Set friendly name of project * @param string $name * @return self */ public function setName( $name ){ $this->name = (string) $name; return $this; } /** * Set short name of project * @param string * @return Loco_package_Project */ public function setSlug( $slug ){ $this->slug = (string) $slug; return $this; } /** * Get friendly name of project, e.g. "Network Admin" * @return string */ public function getName(){ return $this->name; } /** * Get short name of project, e.g. "admin" * @return string */ public function getSlug(){ return $this->slug; } /** * @return Loco_package_TextDomain */ public function getDomain(){ return $this->domain; } /** * @return Loco_package_Bundle */ public function getBundle(){ return $this->bundle; } /** * Whether project is the default for its domain. * @return bool */ public function isDomainDefault(){ $slug = $this->getSlug(); $name = $this->getDomain()->getName(); // default if slug matches text domain. // else special case for Core "default" domain which has empty slug return $slug === $name || ( 'default' === $name && '' === $slug ) || 1 === count($this->bundle); } /** * Add a root path where translation files may live * @param string|Loco_fs_File $location * @return self */ public function addTargetDirectory( $location ){ $this->target = null; $this->dpaths->add( new Loco_fs_Directory($location) ); return $this; } /** * Add a global search path where translation files may live * @param string|Loco_fs_Directory $location * @return Loco_package_Project */ public function addSystemTargetDirectory( $location ){ $this->target = null; $this->gpaths->add( new Loco_fs_Directory($location) ); return $this; } /** * Get domain paths configured in project * @return Loco_fs_FileList */ public function getConfiguredTargets(){ return $this->dpaths; } /** * Get system paths added to project after configuration * @return Loco_fs_FileList */ public function getSystemTargets(){ return $this->gpaths; } /** * Get all target directory roots including global search paths * @return Loco_fs_FileList */ public function getDomainTargets(){ return $this->getTargetFinder()->getRootDirectories(); } /** * Lazy create all searchable domain paths including global directories * @return Loco_fs_FileFinder */ private function getTargetFinder(){ if( ! $this->target ){ $target = new Loco_fs_FileFinder; $target->setRecursive(false)->group('pot','po','mo'); foreach( $this->dpaths as $path ){ // TODO search need not be recursive if it was the configured DomainPath // currently no way to know at this point, so recursing by default. $target->addRoot( (string) $path, true ); } foreach( $this->gpaths as $path ){ $target->addRoot( (string) $path, false ); } $this->excludeTargets( $target ); $this->target = $target; } return $this->target; } /** * Utility excludes current exclude paths from target finder * @return void */ private function excludeTargets( Loco_fs_FileFinder $finder ){ foreach( $this->xdpaths as $file ){ if( $path = realpath( (string) $file ) ){ $finder->exclude( $path ); } } foreach( $this->xgpaths as $file ){ if( $path = realpath( (string) $file ) ){ $finder->exclude( $path ); } } } /** * Check if target file or directory is excluded * @return bool */ private function isTargetExcluded( Loco_fs_File $file ){ return $this->xgpaths->has($file) || $this->xdpaths->has($file); } /** * Add a path for excluding in a recursive target file search * @param string|Loco_fs_File $path * @return self */ public function excludeTargetPath( $path ){ $this->target = null; $this->xdpaths->add( new Loco_fs_File($path) ); return $this; } /** * Get all paths excluded when searching for targets * @return Loco_fs_FileList */ public function getConfiguredTargetsExcluded(){ return $this->xdpaths; } /** * Get first valid domain path * @return Loco_fs_Directory */ private function getSafeDomainPath(){ // use first configured domain path that exists foreach( $this->getConfiguredTargets() as $d ){ if( $d->exists() ){ return $d; } } // fallback to unconfigured, but possibly existent folders $base = $this->getBundle()->getDirectoryPath(); foreach( ['languages','language','lang','l10n','i18n'] as $d ){ $d = new Loco_fs_Directory($d); $d->normalize($base); if( $this->isTargetExcluded($d) ){ continue; } if( $d->exists() ){ return $d; } } // Give up and place in root return new Loco_fs_Directory($base); } /** * Lazy create all searchable source paths * @return Loco_fs_FileFinder */ public function getSourceFinder(){ if( ! $this->source ){ $source = new Loco_fs_FileFinder; $exts = $this->getSourceExtensions(); $source->setRecursive(true)->filterExtensions($exts); /* @var $file Loco_fs_File */ foreach( $this->spaths as $file ){ $path = realpath( (string) $file ); if( $path && is_dir($path) ){ $source->addRoot( $path, true ); } } $this->excludeSources( $source ); $this->source = $source; } return $this->source; } /** * @return string[] */ public function getSourceExtensions(){ // TODO source extensions should be moved from plugin settings to project settings $conf = Loco_data_Settings::get(); $exts = $conf->php_alias; $exts = array_merge( $exts, $conf->jsx_alias ); // ensure we always scan *.php and block.json files return array_merge( $exts, ['php','json'] ); } /** * Utility excludes current exclude paths from passed target finder * @return void */ private function excludeSources( Loco_fs_FileFinder $finder ){ foreach( [$this->xspaths,$this->xgpaths] as $list ){ foreach( $list as $file ){ $real = realpath( (string) $file ); if( is_string($real) && '' !== $real ){ $finder->exclude($real); } } } } /** * Add a root path where source files may live under for this project * @param string|Loco_fs_File $location * @return Loco_package_Project */ public function addSourceDirectory( $location ){ $this->source = null; $this->spaths->add( new Loco_fs_File($location) ); return $this; } /** * Add Explicit source file to project config * @param string|Loco_fs_File $path * @return Loco_package_Project */ public function addSourceFile( $path ){ $this->source = null; $this->sfiles->add( new Loco_fs_File($path) ); return $this; } /** * Add a file or directory as a source location * @param string|Loco_fs_File $path * @return Loco_package_Project */ public function addSourceLocation( $path ){ $file = new Loco_fs_File( $path ); if( $file->isDirectory() ){ $this->addSourceDirectory( $file ); } else { $this->addSourceFile( $file ); } return $this; } /** * Get all source directories and files defined in project * @return Loco_fs_FileList */ public function getConfiguredSources(){ $dynamic = $this->spaths->getArrayCopy(); $statics = $this->sfiles->getArrayCopy(); return new Loco_fs_FileList( array_merge( $dynamic, $statics ) ); } /** * Test if bundle has configured source files (even if they're excluded by other rules) * @return bool */ public function hasSourceFiles(){ return count( $this->sfiles ) || count( $this->spaths ); } /** * Add a path for excluding in source file search * @param string|Loco_fs_File $path * @return Loco_package_Project */ public function excludeSourcePath( $path ){ $this->source = null; $this->xspaths->add( new Loco_fs_File($path) ); return $this; } /** * Get all paths excluded when searching for sources * @return Loco_fs_FileList */ public function getConfiguredSourcesExcluded(){ return $this->xspaths; } /** * Add a globally excluded location affecting sources and targets * @param string|Loco_fs_File $path * @return Loco_package_Project */ public function excludeLocation( $path ){ $this->source = null; $this->target = null; $this->xgpaths->add( new Loco_fs_File($path) ); return $this; } /** * Check whether POT file is protected from end-user update and sync operations. * @return bool */ public function isPotLocked(){ return $this->potlock; } /** * Lock POT file to prevent end-user updates0 * @param bool $locked * @return Loco_package_Project */ public function setPotLock( $locked ){ $this->potlock = (bool) $locked; return $this; } /** * Get full path to template POT (file) whether it exists or nor * @return Loco_fs_File */ public function getPot(){ if( ! $this->pot ){ $slug = $this->getSlug(); $name = ( $slug ?: $this->getDomain()->getName() ).'.pot'; if( '.pot' !== $name ){ // find actual file under configured domain paths $targets = $this->getConfiguredTargets()->copy(); // always permit POT file in the bundle root (i.e. outside domain path) if( $this->isDomainDefault() && $this->bundle->hasDirectoryPath() ){ $root = $this->bundle->getDirectoryPath(); $targets->add( new Loco_fs_Directory($root) ); // look in alternative language directories if only root is configured if( 1 === count($targets) ){ foreach( ['languages','language','lang','l10n','i18n'] as $d ) { $alt = new Loco_fs_Directory($root.'/'.$d); if( ! $this->isTargetExcluded($alt) ){ $targets->add($alt); } } } } // pot check is for exact name and not recursive foreach( $targets as $dir ){ $file = new Loco_fs_File($name); $file->normalize( $dir->getPath() ); if( $file->exists() && ! $this->isTargetExcluded($file) ){ $this->pot = $file; break; } } } // fall back to a directory that exists, but where the POT may not if( ! $this->pot ){ $this->pot = new Loco_fs_File($name); $this->pot->normalize( (string) $this->getSafeDomainPath() ); } } return $this->pot; } /** * Force the use of a known POT file. This could be a PO file if necessary * @param Loco_fs_File $pot template POT file * @return Loco_package_Project */ public function setPot( Loco_fs_File $pot ){ $this->pot = $pot; return $this; } /** * Take a guess at most likely POT file under target locations * @return Loco_fs_File|null Existent file, or null */ public function guessPot(){ $slug = $this->getSlug(); if( ! is_string($slug) || '' === $slug ){ $slug = (string) $this->getDomain(); if( '' === $slug ){ $slug = 'default'; } } // search only inside bundle for template $finder = new Loco_fs_FileFinder; foreach( $this->dpaths as $path ){ $finder->addRoot( (string) $path, true ); } $this->excludeTargets($finder); $files = $finder->group('pot','po','mo')->exportGroups(); foreach( ['pot','po'] as $ext ){ /* @var $pot Loco_fs_File */ foreach( $files[$ext] as $pot ){ $name = $pot->filename(); // use exact match on project slug if found if( $slug === $name ){ return $pot; } // support unconventional "{slug}-en_US.{ext}" foreach( ['-en_US'=>6, '-en'=>3 ] as $tail => $len ){ if( $tail === substr($name,-$len) && $slug === substr($name,0,-$len) ){ return $pot; } } } } // Failed to find correctly named POT file, // but if a single POT file is found we'll use it. if( 1 === count($files['pot']) ){ return $files['pot'][0]; } // Either no POT files are found, or multiple are found. // if the project is the default in its domain, we can try aliases which may be PO if( $this->isDomainDefault() ){ $options = Loco_data_Settings::get(); if( $aliases = $options->pot_alias ){ $found = []; /* @var $pot Loco_fs_File */ foreach( $finder as $pot ){ $priority = array_search( $pot->basename(), $aliases, true ); if( false !== $priority ){ $found[$priority] = $pot; } } if( $found ){ ksort( $found ); return current($found); } } } // failed to guess POT file return null; } /** * Get all extractable PHP source files found under all source paths * @return Loco_fs_FileList */ public function findSourceFiles(){ $source = $this->getSourceFinder(); // augment file list from directories unless already done so $list = $this->sfiles->copy(); $crawled = $source->exportGroups(); foreach( $crawled as $ext => $files ){ /* @var Loco_fs_File $file */ foreach( $files as $file ){ $name = $file->filename(); // skip "{name}.min.{ext}" but only if "{name}.{ext}" exists if( '.min' === substr($name,-4) && file_exists( $file->dirname().'/'.substr($name,0,-4).'.'.$ext ) ){ continue; } // .json source files like block.json theme.json etc.. if( 'json' === $ext && 'block' !== $name && 'theme' !== $name ){ // arbitrarily named theme jsons, like onyx.json (twentytwentyfour) if( ! $this->getBundle()->isTheme() ){ continue; } // Skip JED. We will merge these in separately as needed if( preg_match('/-[0-9a-f]{32}]$/',$name ) ){ continue; } // Ok, treat as json schema file. May fail later... } $list->add($file); } } return $list; } /** * Get all translation files matching project prefix across target directories * @param string $ext File extension, usually "po" or "mo" * @return Loco_fs_LocaleFileList */ public function findLocaleFiles( $ext ){ $finder = $this->getTargetFinder(); $list = new Loco_fs_LocaleFileList; $files = $finder->exportGroups(); $prefix = $this->getSlug(); $domain = $this->domain->getName(); $default = $this->isDomainDefault(); $prefs = Loco_data_Preferences::get(); /* @var $file Loco_fs_File */ foreach( $files[$ext] as $file ){ $file = new Loco_fs_LocaleFile( $file ); // restrict locale by user preference if( $prefs && ! $prefs->has_locale( $file->getLocale() ) ){ continue; } // add file if prefix matches and has a suffix. locale will be validated later if( $file->getPrefix() === $prefix && $file->getSuffix() ){ $list->addLocalized( $file ); } // else in some cases a suffix-only file like "el.po" can match else if( $default && $file->hasSuffixOnly() ){ // theme files under their own directory if( $file->underThemeDirectory() ){ $list->addLocalized( $file ); } // check followed links if they were originally under theme dir else if( ( $link = $finder->getFollowed($file) ) && $link->underThemeDirectory() ){ $list->addLocalized( $file ); } // WordPress core "default" domain, default project else if( 'default' === $domain ){ $list->addLocalized( $file ); } } } return $list; } /** * @param string $ext File extension * @return Loco_fs_FileList */ public function findNotLocaleFiles( $ext ){ $list = new Loco_fs_LocaleFileList; $files = $this->getTargetFinder()->exportGroups(); /* @var $file Loco_fs_LocaleFile */ foreach( $files[$ext] as $file ){ $file = new Loco_fs_LocaleFile( $file ); // add file if it has no locale suffix and is inside the bundle if( $file->hasPrefixOnly() && ! $file->underGlobalDirectory() ){ $list->add( $file ); } } return $list; } /** * Initialize choice of PO file paths for a given locale * @param Loco_Locale $locale to initialize translation files for * @return Loco_fs_FileList */ public function initLocaleFiles( Loco_Locale $locale ){ $slug = $this->getSlug(); $domain = $this->domain->getName(); $default = $this->isDomainDefault(); $suffix = sprintf( '%s.po', $locale ); $prefix = $slug ? sprintf('%s-',$slug) : ''; $choice = new Loco_fs_FileList; /* @var Loco_fs_Directory $dir */ foreach( $this->getConfiguredTargets() as $dir ){ // theme files under their own directory normally have no file prefix if( $default && $dir->underThemeDirectory() ){ $path = $dir->getPath().'/'.$suffix; } // all other paths use configured prefix, which may be empty else { $path = $dir->getPath().'/'.$prefix.$suffix; } $choice->add( new Loco_fs_LocaleFile($path) ); } if( 'default' === $domain || '*' === $domain ){ $domain = ''; } /* @var Loco_fs_Directory $dir */ foreach( $this->getSystemTargets() as $dir ){ $path = $dir->getPath(); // themes and plugins under global locations will be loaded by domain, regardless of prefix if( ( '/themes' === substr($path,-7) || '/plugins' === substr($path,-8) ) && '' !== $domain ){ $path .= '/'.$domain.'-'.$suffix; } // all other paths (probably core) use the configured prefix, which may be empty else { $path .= '/'.$prefix.$suffix; } $choice->add( new Loco_fs_LocaleFile($path) ); } return $choice; } /** * Initialize a PO file path from required location * @return Loco_fs_LocaleFile * @throws Loco_error_Exception */ public function initLocaleFile( Loco_fs_Directory $dir, Loco_Locale $locale ){ $choice = $this->initLocaleFiles($locale); $pattern = '!^'.preg_quote($dir->getPath(),'!').'/[^/.]+\\.po$!'; /* @var Loco_fs_LocaleFile $file */ foreach( $choice as $file ){ if( preg_match($pattern,$file->getPath()) ){ return $file; } } throw new Loco_error_Exception('Unexpected file location: '.$dir ); } /** * Get newest timestamp of all translation files (includes template, but excludes source files) * @return int */ public function getLastUpdated(){ $t = 0; $file = $this->getPot(); if( $file && $file->exists() ){ $t = $file->modified(); } /* @var Loco_fs_File $file */ foreach( $this->findLocaleFiles('po') as $file ){ $t = max( $t, $file->modified() ); } return $t; } }src/package/Theme.php000064400000010706147206622240010512 0ustar00getHandle(); return trailingslashit(get_theme_root_uri($slug)).$slug.'/'; } /** * {@inheritdoc} */ public function getHeaderInfo(){ $root = dirname( $this->getDirectoryPath() ); $theme = new WP_Theme( $this->getSlug(), $root ); return new Loco_package_Header( $theme ); } /** * {@inheritdoc} */ public function getMetaTranslatable(){ return [ 'Name' => 'Name of the theme', 'Description' => 'Description of the theme', 'ThemeURI' => 'URI of the theme', 'Author' => 'Author of the theme', 'AuthorURI' => 'Author URI of the theme', // 'Tags' => 'Tags of the theme', ]; } /** * Get parent bundle if theme is a child * @return Loco_package_Theme|null */ public function getParent(){ return $this->parent; } /** * @return static[] */ public static function getAll(){ $themes = []; foreach( wp_get_themes(['errors'=>null]) as $theme ){ try { $themes[] = self::createFromTheme($theme); } catch( Exception $e ){ // @codeCoverageIgnore } } return $themes; } /** * Create theme bundle definition from WordPress theme handle * * @param string $slug Short name of theme, e.g. "twentyfifteen" * @param string $root Theme root if known * @return self */ public static function create( $slug, $root = '' ){ return self::createFromTheme( wp_get_theme( $slug, $root ) ); } /** * Create theme bundle definition from WordPress theme data * @return self */ public static function createFromTheme( WP_Theme $theme ){ $slug = $theme->get_stylesheet(); $base = $theme->get_stylesheet_directory(); $name = $theme->get('Name') or $name = $slug; if( ! $theme->exists() ){ throw new Loco_error_Exception('Theme not found: '.$name ); } $bundle = new Loco_package_Theme( $slug, $name ); // ideally theme has declared its TextDomain $domain = $theme->get('TextDomain') or // if not, we can see if the Domain listener has picked it up $domain = Loco_package_Listener::singleton()->getDomain($slug); // otherwise we won't try to guess as it results in silent problems when guess is wrong // ideally theme has declared its DomainPath. if not, we can see if the listener has picked it up // otherwise project will use theme root by default $target = $theme->get('DomainPath') ?: Loco_package_Listener::singleton()->getDomainPath($domain); $bundle->configure( $base, [ 'Name' => $name, 'TextDomain' => $domain, 'DomainPath' => $target, ] ); // parent theme inheritance: if( $parent = $theme->parent() ){ try { $bundle->parent = self::createFromTheme($parent); $bundle->inherit( $bundle->parent ); } catch( Loco_error_Exception $e ){ Loco_error_AdminNotices::add($e); } } return $bundle; } /** * {@inheritDoc} */ public static function fromFile( Loco_fs_File $file ){ $find = $file->getPath(); foreach( wp_get_themes( ['errors'=>null] ) as $theme ){ $base = $theme->get_stylesheet_directory(); $path = $base.substr( $find, strlen($base) ); if( $find === $path ){ return self::createFromTheme($theme); } } return null; } } src/package/Core.php000064400000012461147206622240010340 0ustar00 'default', 'DomainPath' => '/wp-content/languages/', // dummy author info for core components 'Name' => __('WordPress core','loco-translate'), 'Version' => $GLOBALS['wp_version'], 'Author' => __('The WordPress Team'), 'AuthorURI' => __('https://wordpress.org/'), ] ); } /** * {@inheritdoc} */ public function getMetaTranslatable(){ return []; } /** * {@inheritdoc} */ public function getType(){ return 'Core'; } /** * {@inheritdoc} * Core bundle doesn't need a handle, there is only one. */ public function getId(){ return 'core'; } /** * {@inheritDoc} */ public function getDirectoryUrl(){ return get_site_url(null,'/'); } /** * {@inheritdoc} * Core bundle is always configured */ public function isConfigured(){ $saved = parent::isConfigured() or $saved = 'internal'; return $saved; } /** * Manually define the core WordPress translations as a single bundle * Projects are those included in standard WordPress downloads: [default], "admin", "admin-network" and "continents-cities" * @return self */ public static function create(){ $rootDir = loco_constant('ABSPATH'); $langDir = loco_constant('WP_LANG_DIR'); $bundle = new Loco_package_Core('core', __('WordPress Core','loco-translate') ); $bundle->setDirectoryPath( $rootDir ); // Core config may be saved in DB, but not supporting bundled XML if( $bundle->configureDb() ){ return $bundle; } // front end, admin and network admin packages are all part of the "default" domain $domain = new Loco_package_TextDomain('default'); $domain->setCanonical( true ); // front end subset, has empty name in WP // full title is like "4.9.x - Development" but we don't know what version at this point list($x,$y) = explode('.',$GLOBALS['wp_version'],3); $project = $domain->createProject( $bundle, sprintf('%u.%u.x - Development',$x,$y) ); $project->setSlug('') ->setPot( new Loco_fs_File($langDir.'/wordpress.pot') ) ->addSourceDirectory( $rootDir) ->excludeSourcePath( $rootDir.'/wp-admin') ->excludeSourcePath( $rootDir.'/wp-content') ->excludeSourcePath( $rootDir.'/wp-includes/class-pop3.php') ->excludeSourcePath( $rootDir.'/wp-includes/js/codemirror') ->excludeSourcePath( $rootDir.'/wp-includes/js/crop') ->excludeSourcePath( $rootDir.'/wp-includes/js/imgareaselect') ->excludeSourcePath( $rootDir.'/wp-includes/js/jcrop') ->excludeSourcePath( $rootDir.'/wp-includes/js/jquery') ->excludeSourcePath( $rootDir.'/wp-includes/js/mediaelement') ->excludeSourcePath( $rootDir.'/wp-includes/js/plupload') ->excludeSourcePath( $rootDir.'/wp-includes/js/swfupload') ->excludeSourcePath( $rootDir.'/wp-includes/js/thickbox') ->excludeSourcePath( $rootDir.'/wp-includes/js/tw-sack.js') ; // "Administration" project (admin subset) $project = $domain->createProject( $bundle, 'Administration'); $project->setSlug('admin') ->setPot( new Loco_fs_File($langDir.'/admin.pot') ) ->addSourceDirectory( $rootDir.'/wp-admin' ) ->excludeSourcePath( $rootDir.'/wp-admin/js') ->excludeSourcePath( $rootDir.'/wp-admin/css') ->excludeSourcePath( $rootDir.'/wp-admin/network') ->excludeSourcePath( $rootDir.'/wp-admin/network.php') ->excludeSourcePath( $rootDir.'/wp-admin/includes/continents-cities.php') ; // "Network Admin" package (admin-network subset) $project = $domain->createProject($bundle, 'Network Admin'); $project->setSlug('admin-network') ->setPot( new Loco_fs_File($langDir.'/admin-network.pot') ) ->addSourceDirectory( $rootDir.'/wp-admin/network' ) ->addSourceFile( $rootDir.'/wp-admin/network.php' ) ; // end of "default" domain projects $bundle->addDomain( $domain ); // Continents & Cities is its own text domain) $domain = new Loco_package_TextDomain('continents-cities'); $project = $domain->createProject( $bundle, 'Continents & Cities'); $project->setPot( new Loco_fs_File( $langDir.'/continents-cities.pot') ) ->addSourceFile( $rootDir.'/wp-admin/includes/continents-cities.php' ) ; $bundle->addDomain( $domain ); return $bundle; } }src/package/Locale.php000064400000006240147206622240010645 0ustar00index = new ArrayObject; $this->match = []; if( $locale ){ $this->addLocale( $locale ); } } /** * Add another locale to search on * @param Loco_Locale * @return Loco_package_Locale */ public function addLocale( Loco_Locale $locale ){ if( $locale->isValid() ){ $sufx = (string) $locale.'.po'; $this->match[$sufx] = - strlen($sufx); } return $this; } /** * @param Loco_fs_File * @return Loco_package_Project|null */ public function getProject( Loco_fs_File $file ){ $path = $file->getPath(); if( isset($this->index[$path]) ){ return $this->index[$path]; } return null; } /** * @return Loco_package_Bundle[] */ public function getBundles(){ $bundles = $this->bundles; if( ! $bundles ){ $bundles = [ Loco_package_Core::create() ]; $bundles = array_merge( $bundles, Loco_package_Plugin::getAll() ); $bundles = array_merge( $bundles, Loco_package_Theme::getAll() ); $this->bundles = $bundles; } return $bundles; } /** * @return loco_fs_FileList */ public function findLocaleFiles(){ $index = $this->index; $suffixes = $this->match; $list = new Loco_fs_FileList; foreach( $this->getBundles() as $bundle ){ /* @var Loco_package_Project $project */ foreach( $bundle as $project ){ /* @var $file Loco_fs_File */ foreach( $project->findLocaleFiles('po') as $file ){ $path = $file->getPath(); foreach( $suffixes as $sufx => $snip ){ if( substr($path,$snip) === $sufx ){ $list->add( $file ); $index[$path] = $project; break; } } } } } return $list; } /** * @return loco_fs_FileList */ public function findTemplateFiles(){ $index = $this->index; $list = new Loco_fs_FileList; foreach( $this->getBundles() as $bundle ){ /* @var $project Loco_package_Project */ foreach( $bundle as $project ){ $file = $project->getPot(); if( $file && $file->exists() ){ $list->add( $file ); $path = $file->getPath(); $index[$path] = $project; } } } return $list; } } src/mvc/Controller.php000064400000007221147206622240010763 0ustar00exitForbidden(); } /** * Emulate permission denied screen as performed in wp-admin/admin.php */ protected function exitForbidden(){ do_action( 'admin_page_access_denied' ); wp_die( __( 'You do not have sufficient permissions to access this page.','default' ), 403 ); } // @codeCoverageIgnore /** * Set a nonce for the current page for when it submits a form * @return Loco_mvc_ViewParams */ public function setNonce( $action ){ $name = 'loco-nonce'; $value = wp_create_nonce( $action ); $nonce = new Loco_mvc_ViewParams( compact('name','value','action') ); $this->set('nonce', $nonce ); return $nonce; } /** * Check if a valid nonce has been sent in current request. * Fails if nonce is invalid, but returns false if not sent so scripts can exit accordingly. * @throws Loco_error_Exception * @param string $action action for passing to wp_verify_nonce * @return bool true if data has been posted and nonce is valid */ public function checkNonce( $action ){ $posted = false; $name = 'loco-nonce'; if( isset($_REQUEST[$name]) ){ $value = $_REQUEST[$name]; if( wp_verify_nonce( $value, $action ) ){ $posted = true; } else { throw new Loco_error_Exception('Failed security check for '.$name); } } return $posted; } /** * Filter callback for `translations_api' * Ensures silent failure of translations_api when network disabled, see $this->getAvailableCore */ public function filter_translations_api( $value = false ){ if( apply_filters('loco_allow_remote', true ) ){ return $value; } // returning error here has the safe effect as returning empty translations list return new WP_Error( -1, 'Translations API blocked by loco_allow_remote filter' ); } /** * Filter callback for `pre_http_request` * Ensures fatal error if we failed to handle offline mode earlier. */ public function filter_pre_http_request( $value = false ){ if( apply_filters('loco_allow_remote', true ) ){ return $value; } // little point returning WP_Error error because WordPress will just show "unexpected error" throw new Loco_error_Exception('HTTP request blocked by loco_allow_remote filter' ); } /** * number_format_i18n filter callback because our admin screens assume number_format_i18n() returns unescaped text, not HTML. * @param string * @return string */ public function filter_number_format_i18n( $formatted = '' ){ return html_entity_decode($formatted,ENT_NOQUOTES,'UTF-8'); } } src/mvc/FileParams.php000064400000012704147206622240010665 0ustar00= 1024 ){ $i++; $dp++; $n /= 1024; } $s = number_format( $n, $dp, '.', ',' ); // trim trailing zeros from decimal places $a = explode('.',$s); if( isset($a[1]) ){ $s = $a[0]; $d = trim($a[1],'0') and $s .= '.'.$d; } $units = [ ' bytes', ' KB', ' MB', ' GB', ' TB' ]; $s .= $units[$i]; return $s; } /** * @return Loco_mvc_FileParams */ public static function create( Loco_fs_File $file ) { return new Loco_mvc_FileParams( [], $file ); } /** * Override does lazy property initialization * @param array $props initial extra properties */ public function __construct( array $props, Loco_fs_File $file ){ parent::__construct( [ 'name' => '', 'path' => '', 'relpath' => '', 'reltime' => '', 'bytes' => 0, 'size' => '', 'imode' => '', 'smode' => '', 'owner' => '', 'group' => '', ] + $props ); $this->file = $file; } /** * {@inheritdoc} * Override to get live information from file object */ #[ReturnTypeWillChange] public function offsetGet( $prop ){ $getter = [ $this, '_get_'.$prop ]; if( is_callable($getter) ){ return call_user_func( $getter ); } return parent::offsetGet($prop); } /** * {@inheritdoc} * Override to ensure all properties populated */ #[ReturnTypeWillChange] public function getArrayCopy(){ $a = []; foreach( $this as $prop => $dflt ){ $a[$prop] = $this[$prop]; } return $a; } /** * @internal * @return string */ private function _get_name(){ return $this->file->basename(); } /** * @internal * @return string */ private function _get_path(){ return $this->file->getPath(); } /** * @internal * @return string */ private function _get_relpath(){ return $this->file->getRelativePath( loco_constant('WP_CONTENT_DIR') ); } /** * Using slightly modified version of WordPress's Human time differencing * + Added "Just now" when in the last 30 seconds * @internal * @return string */ private function _get_reltime(){ $time = $this->has('mtime') ? $this['mtime'] : $this->file->modified(); $time_diff = time() - $time; // use same time format as posts listing when in future or more than a day ago if( $time_diff < 0 || $time_diff >= 86400 ){ return Loco_mvc_ViewParams::date_i18n( $time, __('Y/m/d','default') ); } if( $time_diff < 30 ){ // translators: relative time when something happened in the last 30 seconds return __('Just now','loco-translate'); } // translators: %s: Human-readable time difference. return sprintf( __('%s ago','default'), human_time_diff($time) ); } /** * @internal * @return int */ private function _get_bytes(){ return $this->file->size(); } /** * @internal * @return string */ private function _get_size(){ return self::renderBytes( $this->_get_bytes() ); } /** * Get octal file mode * @internal * @return string */ private function _get_imode(){ $mode = new Loco_fs_FileMode( $this->file->mode() ); return (string) $mode; } /** * Get rwx file mode * @internal * @return string */ private function _get_smode(){ $mode = new Loco_fs_FileMode( $this->file->mode() ); return $mode->format(); } /** * Get file owner name * @internal * @return string */ private function _get_owner(){ if( ( $uid = $this->file->uid() ) && function_exists('posix_getpwuid') && ( $a = posix_getpwuid($uid) ) ){ return $a['name']; } return sprintf('%u',$uid); } /** * Get group owner name * @internal * @return string */ private function _get_group(){ if( ( $gid = $this->file->gid() ) && function_exists('posix_getpwuid') && ( $a = posix_getgrgid($gid) ) ){ return $a['name']; } return sprintf('%u',$gid); } /** * Print pseudo console line * @return string; */ public function ls(){ $this->e('smode'); echo ' '; $this->e('owner'); echo ':'; $this->e('group'); echo ' '; $this->e('relpath'); return ''; } }src/mvc/PostParams.php000064400000005154147206622240010734 0ustar00getArrayCopy(), false, '&' ); foreach( explode('&',$query) as $str ){ $serial[] = array_map( 'urldecode', explode( '=', $str, 2 ) ); } return $serial; } }src/mvc/ViewParams.php000064400000011771147206622240010723 0ustar00setTimestamp($u); return date_i18n( $f, $u + $d->getOffset() ); } /** * Wrapper for sprintf so we can handle PHP 8 exceptions * @param string $format * @return string */ public static function format( $format, array $args ){ try { return vsprintf($format,$args); } // Note that PHP8 will throw Error (not Exception), PHP 7 will trigger E_WARNING catch( Error $e ){ Loco_error_AdminNotices::warn( $e->getMessage().' in vsprintf('.var_export($format,true).')' ); return ''; } } /** * @internal * @param string $p property name * @return mixed */ public function __get( $p ){ return $this->offsetExists($p) ? $this->offsetGet($p) : null; } /** * Test if a property exists, even if null * @param string $p property name * @return bool */ public function has( $p ){ return $this->offsetExists($p); } /** * Print escaped property value * @param string $p property key * @return string empty string */ public function e( $p ){ $text = $this->__get($p); echo $this->escape( $text ); return ''; } /** * Print property as string date, including time * @param string $p property name * @param string $f date format * @return string empty string */ public function date( $p, $f = null ){ $u = (int) $this->__get($p); if( $u > 0 ){ echo $this->escape( self::date_i18n($u,$f) ); } return ''; } /** * Print property as a string-formatted number * @param string $p property name * @param int $dp optional decimal places * @return string empty string */ public function n( $p, $dp = 0 ){ // number_format_i18n is pre-escaped for HTML echo number_format_i18n( $this->__get($p), $dp ); return ''; } /** * Print property with passed formatting string * e.g. $params->f('name', 'My name is %s' ); * @param string $p property name * @param string $f formatting string * @return string empty string */ public function f( $p, $f = '%s' ){ echo $this->escape( self::format( $f, [$this->__get($p)] ) ); return ''; } /** * Print property value for JavaScript * @param string $p property name * @return string empty string */ public function j( $p ){ echo json_encode($this->__get($p) ); return ''; } /** * @return array */ #[ReturnTypeWillChange] public function jsonSerialize(){ return $this->getArrayCopy(); } /** * Fetch whole object as JSON * @return string */ public function exportJson(){ return json_encode( $this->jsonSerialize() ); } /** * Merge parameters into ours * @return Loco_mvc_ViewParams */ public function concat( ArrayObject $more ){ foreach( $more as $name => $value ){ $this[$name] = $value; } return $this; } /** * Debugging function * @codeCoverageIgnore */ public function dump(){ echo '
      ',$this->escape( json_encode( $this->getArrayCopy(),JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE ) ),'
      '; } /** * @param callable $callback * @return Loco_mvc_ViewParams */ public function sort( $callback ){ $raw = $this->getArrayCopy(); uasort( $raw, $callback ); $this->exchangeArray( $raw ); return $this; } }src/mvc/AjaxController.php000064400000004125147206622240011567 0ustar00auth(); $this->output = new ArrayObject; $this->input = new ArrayObject( $args ); // avoid fatal error if json extension is missing loco_check_extension('json'); } /** * Get posted data and validate nonce in the process * @return Loco_mvc_PostParams */ protected function validate(){ $route = $this->input['route']; if( ! $this->checkNonce($route) ){ throw new Loco_error_Exception( sprintf('Ajax %s action requires postdata with nonce',$route) ); } return Loco_mvc_PostParams::get(); } /** * {@inheritdoc} */ public function get( $prop ){ return isset($this->input[$prop]) ? $this->input[$prop] : null; } /** * {@inheritdoc} */ public function set( $prop, $value ){ $this->output[$prop] = $value; return $this; } /** * @return string JSON */ public function render(){ $data = [ 'data' => $this->output->getArrayCopy(), ]; // non-fatal notices deliberately not in "error" key $array = Loco_error_AdminNotices::destroy(); if( $array ){ $data['notices'] = $array; } return json_encode( $data ); } /** * Pretty json encode if PHP version allows * protected function json_encode( $data ){ $opts = 0; if( defined('JSON_PRETTY_PRINT') ){ $opts |= JSON_PRETTY_PRINT; } if( defined('JSON_UNESCAPED_SLASHES') ){ $opts |= JSON_UNESCAPED_SLASHES; } return json_encode( $data, $opts ); }*/ }src/mvc/AjaxRouter.php000064400000015465147206622240010735 0ustar00 $route, 'action' => 'loco_ajax', 'loco-nonce' => wp_create_nonce($route), ]; return admin_url('admin-ajax.php','relative').'?'.http_build_query($args); } /** * Create a new ajax router and starts buffering output immediately */ public function __construct(){ $this->buffer = Loco_output_Buffer::start(); parent::__construct(); } /** * "init" action callback. * early-ish hook that ensures controllers can initialize */ public function on_init(){ try { $class = self::routeToClass( $_REQUEST['route'] ); // autoloader will throw error if controller class doesn't exist $this->ctrl = new $class; $this->ctrl->_init( $_REQUEST ); // hook name compatible with AdminRouter, plus additional action for ajax hooks to set up do_action('loco_admin_init', $this->ctrl ); do_action('loco_ajax_init', $this->ctrl ); } catch( Loco_error_Exception $e ){ $this->ctrl = null; // throw $e; // <- debug } } /** * @param string $route * @return string */ private static function routeToClass( $route ){ $route = explode( '-', $route ); // convert route to class name, e.g. "foo-bar" => "Loco_ajax_foo_BarController" $key = count($route) - 1; $route[$key] = ucfirst( $route[$key] ); return 'Loco_ajax_'.implode('_',$route).'Controller'; } /** * Common ajax hook for all Loco admin JSON requests * Note that tests call renderAjax directly. * @codeCoverageIgnore */ public function on_wp_ajax_loco_json(){ $json = $this->renderAjax(); $this->exitScript( $json, [ 'Content-Type' => 'application/json; charset=UTF-8', ] ); } /** * Additional ajax hook for download actions that won't be JSON * Note that tests call renderDownload directly. * @codeCoverageIgnore */ public function on_wp_ajax_loco_download(){ $file = null; $ext = null; $data = $this->renderDownload(); if( is_string($data) ){ $path = ( $this->ctrl ? $this->ctrl->get('path') : '' ) or $path = 'error.json'; $file = new Loco_fs_File( $path ); $ext = $file->extension(); } else if( $data instanceof Exception ){ $data = sprintf('%s in %s:%u', $data->getMessage(), basename($data->getFile()), $data->getLine() ); } else { $data = (string) $data; } $mimes = [ 'po' => 'application/x-gettext', 'pot' => 'application/x-gettext', 'mo' => 'application/x-gettext-translation', 'php' => 'application/x-httpd-php-source', 'json' => 'application/json', 'zip' => 'application/zip', 'xml' => 'text/xml', ]; $headers = []; if( $file instanceof Loco_fs_File && isset($mimes[$ext]) ){ $headers['Content-Type'] = $mimes[$ext].'; charset=UTF-8'; $headers['Content-Disposition'] = 'attachment; filename='.$file->basename(); } else { $headers['Content-Type'] = 'text/plain; charset=UTF-8'; } $this->exitScript( $data, $headers ); } /** * Exit script before WordPress shutdown, avoids hijacking of exit via wp_die_ajax_handler. * Also gives us a final chance to check for output buffering problems. * @codeCoverageIgnore */ private function exitScript( $str, array $headers ){ try { do_action('loco_admin_shutdown'); Loco_output_Buffer::clear(); $this->buffer = null; Loco_output_Buffer::check(); $headers['Content-Length'] = strlen($str); foreach( $headers as $name => $value ){ header( $name.': '.$value ); } } catch( Exception $e ){ Loco_error_AdminNotices::add( Loco_error_Exception::convert($e) ); $str = $e->getMessage(); } echo $str; exit(0); } /** * Execute Ajax controller to render JSON response body * @return string */ public function renderAjax(){ try { // respond with deferred failure from initAjax if( ! $this->ctrl ){ $route = isset($_REQUEST['route']) ? $_REQUEST['route'] : ''; // translators: Fatal error where %s represents an unexpected value throw new Loco_error_Exception( sprintf( __('Ajax route not found: "%s"','loco-translate'), $route ) ); } // else execute controller to get json output $json = $this->ctrl->render(); if( is_null($json) || '' === $json ){ throw new Loco_error_Exception( __('Ajax controller returned empty JSON','loco-translate') ); } } catch( Loco_error_Exception $e ){ $json = json_encode( [ 'error' => $e->jsonSerialize(), 'notices' => Loco_error_AdminNotices::destroy() ] ); } catch( Exception $e ){ $e = Loco_error_Exception::convert($e); $json = json_encode( [ 'error' => $e->jsonSerialize(), 'notices' => Loco_error_AdminNotices::destroy() ] ); } $this->buffer->discard(); return $json; } /** * Execute ajax controller to render something other than JSON * @return string|Exception */ public function renderDownload(){ try { // respond with deferred failure from initAjax if( ! $this->ctrl ){ throw new Loco_error_Exception( __('Download action not found','loco-translate') ); } // else execute controller to get raw output $data = $this->ctrl->render(); if( is_null($data) || '' === $data ){ throw new Loco_error_Exception( __('Download controller returned empty output','loco-translate') ); } } catch( Exception $e ){ $data = $e; } $this->buffer->discard(); return $data; } }src/mvc/AdminController.php000064400000024377147206622240011747 0ustar00bench = microtime( true ); } $this->view = new Loco_mvc_View( $args ); $this->auth(); // check essential extensions on all pages so admin notices are shown loco_check_extension('json'); loco_check_extension('mbstring'); // add contextual help tabs to current screen if there are any if( $screen = get_current_screen() ){ try { $this->view->cd('/admin/help'); $tabs = $this->getHelpTabs(); // always append common help tabs $tabs[ __('Help & support','loco-translate') ] = $this->view->render('tab-support'); // set all tabs and common sidebar $i = 0; foreach( $tabs as $title => $content ){ $id = sprintf('loco-help-%u', $i++ ); $screen->add_help_tab( compact('id','title','content') ); } $screen->set_help_sidebar( $this->view->render('side-bar') ); $this->view->cd('/'); } // avoid critical errors rendering non-critical part of page catch( Loco_error_Exception $e ){ $this->view->cd('/'); Loco_error_AdminNotices::add( $e ); } } // helper properties for loading static resources $this->baseurl = plugins_url( '', loco_plugin_self() ); // add common admin page resources $this->enqueueStyle('admin', ['wp-jquery-ui-dialog'] ); // load colour scheme is user has non-default $skin = get_user_option('admin_color'); if( $skin && 'fresh' !== $skin ){ $this->enqueueStyle( 'skins/'.$skin ); } // core minimized admin.js loaded on all pages before any other Loco scripts $this->enqueueScript('admin', ['jquery-ui-dialog'] ); $this->init(); return $this; } /** * Post-construct initializer that may be overridden by child classes * @return void */ public function init(){ } /** * "admin_title" filter, modifies HTML document title if we've set one */ public function filter_admin_title( $admin_title, $title ){ if( $view_title = $this->get('title') ){ $admin_title = $view_title.' ‹ '.$admin_title; } return $admin_title; } /** * "admin_footer_text" filter, modifies admin footer only on Loco pages */ public function filter_admin_footer_text(){ $url = apply_filters('loco_external', 'https://localise.biz/'); return ''.sprintf( '%s Loco', esc_html(__('Loco Translate is powered by','loco-translate')), esc_url($url) ).''; } /** * "update_footer" filter, prints Loco version number in admin footer */ public function filter_update_footer( /*$text*/ ){ $html = sprintf( 'v%s', loco_plugin_version() ); if( $this->bench && ( $info = $this->get('_debug') ) ){ $html .= sprintf('%ss', number_format_i18n($info['time'],2) ); } return $html; } /** * "loco_external" filter callback, adds campaign identifier onto external links */ public function filter_loco_external( $url ){ $u = parse_url( $url ); if( isset($u['host']) && 'localise.biz' === $u['host'] ){ $query = http_build_query( [ 'utm_medium' => 'plugin', 'utm_campaign' => 'wp', 'utm_source' => 'admin', 'utm_content' => $this->get('_route') ] ); $url = 'https://localise.biz'.$u['path']; if( isset($u['query']) ){ $url .= '?'. $u['query'].'&'.$query; } else { $url .= '?'.$query; } if( isset($u['fragment']) ){ $url .= '#'.$u['fragment']; } } return $url; } /** * All admin screens must define help tabs, even if they return empty * @return array */ public function getHelpTabs(){ return []; } /** * {@inheritdoc} */ public function get( $prop ){ return $this->view->__get($prop); } /** * {@inheritdoc} */ public function set( $prop, $value ){ $this->view->set( $prop, $value ); return $this; } /** * Render template for echoing into admin screen * @param string $tpl template name * @param array $args template arguments * @return string */ public function view( $tpl, array $args = [] ){ /*if( ! $this->baseurl ){ throw new Loco_error_Debug('Did you mean to call $this->viewSnippet('.json_encode($tpl,JSON_UNESCAPED_SLASHES).') in '.get_class($this).'?'); }*/ $view = $this->view; foreach( $args as $prop => $value ){ $view->set( $prop, $value ); } // ensure JavaScript config present if any scripts are loaded if( $view->has('js') ) { $jsConf = $view->get( 'js' ); } else if( $this->scripts ){ $jsConf = new Loco_mvc_ViewParams; $this->set('js',$jsConf); } else { $jsConf = null; } if( $jsConf instanceof Loco_mvc_ViewParams ){ // ensure config has access to latest version information // we will use this to ensure scripts are not cached by browser, or hijacked by other plugins $jsConf->offsetSet('$v', [ loco_plugin_version(), $GLOBALS['wp_version']] ); $jsConf->offsetSet('$js', array_keys($this->scripts) ); $jsConf->offsetSet('WP_DEBUG', loco_debugging() ); // localize script if translations in memory if( is_textdomain_loaded('loco-translate') ){ $strings = new Loco_js_Strings; $jsConf->offsetSet('wpl10n',$strings->compile()); $strings->unhook(); unset( $strings ); // add currently loaded locale for passing plural equation into js. // note that plural rules come from our data, because MO is not trusted. $tag = apply_filters( 'plugin_locale', get_locale(), 'loco-translate' ); $jsConf->offsetSet('wplang', Loco_Locale::parse($tag) ); } // localized formatting from core translations global $wp_locale; if( is_object($wp_locale) && property_exists($wp_locale,'number_format') ){ $jsConf->offsetSet('wpnum', array_map([$this,'filter_number_format_i18n'],$wp_locale->number_format) ); } } // take benchmark for debugger to be rendered in footer if( $this->bench ){ $this->set('_debug', new Loco_mvc_ViewParams( [ 'time' => microtime(true) - $this->bench, ] ) ); } return $view->render( $tpl ); } /** * Shortcut to render template without full page arguments as per view * @param string $tpl * @return string */ public function viewSnippet( $tpl ){ return $this->view->render( $tpl ); } /** * Add CSS to head * @param string $name stem name of file, e.g "editor" * @param string[] $deps dependencies of this stylesheet * @return self */ public function enqueueStyle( $name, array $deps = [] ){ $base = $this->baseurl; if( ! $base ){ throw new Loco_error_Exception('Too early to enqueueStyle('.var_export($name,1).')'); } $id = 'loco-translate-'.strtr($name,'/','-'); // css always minified. sass in build env only $href = $base.'/pub/css/'.$name.'.css'; $vers = apply_filters( 'loco_static_version', loco_plugin_version(), $href ); wp_enqueue_style( $id, $href, $deps, $vers, 'all' ); return $this; } /** * Add JavaScript to footer * @param string $name stem name of file, e.g "editor" * @param string[] $deps dependencies of this script * @return string */ public function enqueueScript( $name, array $deps = [] ){ $base = $this->baseurl; if( ! $base ){ throw new Loco_error_Exception('Too early to enqueueScript('.json_encode($name).')'); } // use minimized javascript file. hook into script_loader_src to point at development source $href = $base.'/pub/js/min/'.$name.'.js'; $vers = apply_filters( 'loco_static_version', loco_plugin_version(), $href ); $id = 'loco-translate-'.strtr($name,'/','-'); wp_enqueue_script( $id, $href, $deps, $vers, true ); $this->scripts[$id] = $href; return $id; } /** * @param string $name * @return void */ public function dequeueScript( $name ){ $id = 'loco-translate-'.strtr($name,'/','-'); if( array_key_exists($id,$this->scripts) ){ wp_dequeue_script($id); unset($this->scripts[$id]); } } /** * @internal * @param string $tag * @param string $id * @return string */ public function filter_script_loader_tag( $tag, $id ) { if( array_key_exists($id,$this->scripts) ) { // Add element id for in-dom verification of expected scripts if( '',$tag,2); $tag = $foo."\n console.log({$json});".$bar; } return $tag; }*/ } src/js/Strings.php000064400000017502147206622240010123 0ustar00flush('loco-translate'); } } src/compat/MbstringExtension.php000064400000005557147206622240013032 0ustar00latin1 if( '' === $from_encoding || 'ISO-8859-1' === $from_encoding || 'cp1252' === $from_encoding ){ if( '' === $to_encoding || 'UTF-8' === $to_encoding || 'US-ASCII' === $to_encoding ){ if( function_exists('loco_fix_utf8') ) { return loco_fix_utf8( $str ); } } } trigger_error('Unable to convert from '.$from_encoding.' to '.$to_encoding.' without mbstring', E_USER_NOTICE ); } return $str; } public static function mb_strtolower( $str ){ return strtolower($str); } } // @codeCoverageIgnoreStart if( ! function_exists('mb_detect_encoding') ){ function mb_detect_encoding( $str = '', array $encoding_list = [], $strict = false ){ return Loco_compat_MbstringExtension::mb_detect_encoding( $str, $encoding_list, $strict ); } } if( ! function_exists('mb_list_encodings') ){ function mb_list_encodings(){ return Loco_compat_MbstringExtension::mb_list_encodings(); } } if( ! function_exists('mb_strlen') ){ function mb_strlen( $str, $encoding = null ){ return Loco_compat_MbstringExtension::mb_strlen( $str, $encoding ); } } if( ! function_exists('mb_convert_encoding') ){ function mb_convert_encoding( $str, $to_encoding, $from_encoding = null ){ return Loco_compat_MbstringExtension::mb_convert_encoding( $str, $to_encoding, $from_encoding ); } } if( ! function_exists('mb_encoding_aliases') ){ function mb_encoding_aliases(){ return false; } } if( ! function_exists('mb_strtolower') ){ function mb_strtolower( $str ){ return Loco_compat_MbstringExtension::mb_strtolower($str); } } src/compat/PosixExtension.php000064400000006400147206622240012333 0ustar00

      Error: '.implode('. ',$texts).'

      '; } } src/gettext/Metadata.php000064400000016126147206622240011263 0ustar00 total, 'p' => progress, 'f' => fuzzy ]; */ public static function stats( array $po ){ $t = $p = $f = 0; /* @var $r array */ foreach( $po as $i => $r ){ // skip header if( 0 === $i && empty($r['source']) && empty($r['context']) ){ continue; } // plural form // TODO how should plural forms affect stats? should all forms be complete before 100% can be achieved? should offsets add to total?? if( isset($r['parent']) && is_int($r['parent']) ){ continue; } // singular form $t++; if( '' !== $r['target'] ){ $p++; if( isset($r['flag']) /*&& LOCO_FLAG_FUZZY === $r['flag']*/ ){ $f++; } } } return compact('t','p','f'); } /** * {@inheritdoc} */ public function getKey(){ return 'po_'.md5( $this['rpath'] ); } /** * Load metadata from file, using cache if enabled. * Note that this does not throw exception, check "valid" key * @return Loco_gettext_Metadata */ public static function load( Loco_fs_File $po, $nocache = false ){ $bytes = $po->size(); $mtime = $po->modified(); // quick construct of a new metadata object. enough to query and validate cache $meta = new Loco_gettext_Metadata( [ 'rpath' => $po->getRelativePath( loco_constant('WP_CONTENT_DIR') ), ] ); // pull from cache if exists and has not been modified if( $nocache || ! $meta->fetch() || $bytes !== $meta['bytes'] || $mtime !== $meta['mtime'] ){ // not available from cache, or cache is invalidated $meta['bytes'] = $bytes; $meta['mtime'] = $mtime; // parse what is hopefully a PO file to get stats try { $data = Loco_gettext_Data::load($po)->getArrayCopy(); $meta['valid'] = true; $meta['stats'] = self::stats($data); } catch( Exception $e ){ $meta['valid'] = false; $meta['error'] = $e->getMessage(); } } // show cached debug notice as if file was being parsed else if( $meta->offsetExists('error') ){ Loco_error_AdminNotices::debug($meta['error'].': '.$meta['rpath']); } // persist on shutdown with a useful TTL and keepalive // Maximum lifespan: 10 days. Refreshed if accessed a day after being cached. $meta->setLifespan(864000)->keepAlive(86400)->persistLazily(); return $meta; } /** * Construct metadata from previously parsed PO data * @return Loco_gettext_Metadata */ public static function create( Loco_fs_File $file, Loco_gettext_Data $data ){ return new Loco_gettext_Metadata( [ 'valid' => true, 'bytes' => $file->size(), 'mtime' => $file->modified(), 'stats' => self::stats( $data->getArrayCopy() ), ] ); } /** * Get progress stats as simple array with keys, t=total, p=progress, f:flagged. * Note that untranslated strings are never flagged, hence "f" includes all in "p" * @return array in form ['t' => total, 'p' => progress, 'f' => fuzzy ]; */ public function getStats(){ if( isset($this['stats']) ){ return $this['stats']; } // fallback to empty stats return [ 't' => 0, 'p' => 0, 'f' => 0 ]; } /** * Get total number of messages, not including header and excluding plural forms * @return int */ public function getTotal(){ $stats = $this->getStats(); return $stats['t']; } /** * Get number of fuzzy messages, not including header * @return int */ public function countFuzzy(){ $stats = $this->getStats(); return $stats['f']; } /** * Get progress as a string percentage (minus % symbol) * @return string */ public function getPercent(){ $stats = $this->getStats(); $n = max( 0, $stats['p'] - $stats['f'] ); $t = max( $n, $stats['t'] ); return loco_string_percent( $n, $t ); } /** * Get number of strings either untranslated or fuzzy. * @return int */ public function countIncomplete(){ $stats = $this->getStats(); return max( 0, $stats['t'] - ( $stats['p'] - $stats['f'] ) ); } /** * Get number of strings completely untranslated (excludes fuzzy). * @return int */ public function countUntranslated(){ $stats = $this->getStats(); return max( 0, $stats['t'] - $stats['p'] ); } /** * Echo progress bar using compiled function * @return void */ public function printProgress(){ $stats = $this->getStats(); $flagged = $stats['f']; $translated = $stats['p']; $untranslated = $stats['t'] - $translated; loco_print_progress( $translated, $untranslated, $flagged ); } /** * Get wordy summary of total strings * @return string */ public function getTotalSummary(){ $total = $this->getTotal(); // translators: Where %s is any number of strings return sprintf( _n('%s string','%s strings',$total,'loco-translate'), number_format_i18n($total) ); } /** * Get wordy summary including translation stats * @return string */ public function getProgressSummary(){ $extra = []; // translators: Shows percentage translated at top of editor $stext = sprintf( __('%s%% translated','loco-translate'), $this->getPercent() ).', '.$this->getTotalSummary(); if( $num = $this->countFuzzy() ){ // translators: Shows number of fuzzy strings at top of editor $extra[] = sprintf( __('%s fuzzy','loco-translate'), number_format($num) ); } if( $num = $this->countUntranslated() ){ // translators: Shows number of untranslated strings at top of editor $extra[] = sprintf( __('%s untranslated','loco-translate'), number_format($num) ); } if( $extra ){ $stext .= ' ('.implode(', ', $extra).')'; } return $stext; } /** * @param bool $absolute * @return string */ public function getPath( $absolute ){ $path = $this['rpath']; if( $absolute && ! Loco_fs_File::abs($path) ){ $path = trailingslashit( loco_constant('WP_CONTENT_DIR') ).$path; } return $path; } } src/gettext/PhpCache.php000064400000003460147206622240011213 0ustar00headers = self::exportHeaders($po); $me->entries = self::exportEntries($po); // TODO support Loco_data_Settings::get()->php_pretty return $me->export(); } private static function exportHeaders( Loco_gettext_Data $po ){ $a = []; foreach( $po->getHeaders() as $key => $value ){ $a[ strtolower($key) ] = (string) $value; } return $a; } private static function exportEntries( Loco_gettext_Data $po ){ $a = []; $skip_fuzzy = ! Loco_data_Settings::get()->use_fuzzy; // $max = preg_match('/^nplurals=(\\d)/',$po->getHeaders()->offsetGet('plural-forms'),$r) ? $r[1] : 0; /* @var LocoPoMessage $message */ foreach( $po as $message ){ if( $skip_fuzzy && 4 === $message->__get('flag') ){ continue; } // Like JED, we must follow MO sparseness. Else empty strings will be merged on top of translations. // TODO what should we do about partial completion of pluralized messages? if( $message->translated() ) { $a[ $message->getKey() ] = implode( "\0", $message->exportSerial() ); } } return $a; } /*private function prettyExport() { return 'headers + ['messages'=>$this->entries],true) . ';' . PHP_EOL; }*/ } src/gettext/WordCount.php000064400000005567147206622240011476 0ustar00po = $po; } /** * @internal */ private function countField( $f ){ $n = 0; foreach( $this->po as $r ){ $n += self::simpleCount( $r[$f] ); } return $n; } /** * Default count function returns source words (msgid) in current file. * @return int */ #[ReturnTypeWillChange] public function count(){ $n = $this->sw; if( is_null($n) ){ $n = $this->countField('source'); $this->sw = $n; } return $n; } /** * Very simple word count, only suitable for latin characters, and biased toward English. * @param string * @return int */ public static function simpleCount( $str ){ $n = 0; if( is_string($str) && '' !== $str ){ // TODO should we strip PHP string formatting? // e.g. "Hello %s" currently counts as 2 words. // $str = preg_replace('/%(?:\\d+\\$)?(?:\'.|[-+0 ])*\\d*(?:\\.\\d+)?[suxXbcdeEfFgGo%]/', '', $str ); // Strip HTML (but only if open and close tags detected, else "< foo" would be stripped to nothing if( false !== strpos($str,'<') && false !== strpos($str,'>') ){ $str = strip_tags($str); } // always html-decode, else escaped punctuation will be counted as words $str = html_entity_decode( $str, ENT_QUOTES, 'UTF-8'); // Collapsing apostrophe'd words into single units: // Simplest way to handle ambiguity of "It's Tim's" (technically three words in English) $str = preg_replace('/(\\w+)\'(\\w)(\\W|$)/u', '\\1\\2\\3', $str ); // Combining floating numbers into single units // e.g. "£1.50" and "€1,50" should be one word each $str = preg_replace('/\\d[\\d,\\.]+/', '0', $str ); // count words by standard Unicode word boundaries $words = preg_split( '/\\W+/u', $str, -1, PREG_SPLIT_NO_EMPTY ); $n += count($words); /*/ TODO should we exclude some words (like numbers)? foreach( $words as $word ){ if( ! ctype_digit($word) ){ $n++; } }*/ } return $n; } } src/gettext/Extraction.php000064400000020060147206622240011653 0ustar00bundle = $bundle; $this->extracted = new LocoExtracted; $this->extracted->setDomain('default'); $default = $bundle->getDefaultProject(); if( $default instanceof Loco_package_Project ){ $domain = $default->getDomain()->getName(); // wildcard stands in for empty text domain, meaning unspecified or dynamic domains will be included. // note that strings intended to be in "default" domain must specify explicitly, or be included here too. if( '*' === $domain ){ $domain = ''; $this->extracted->setDomain(''); } // pull bundle's default metadata. these are translations that may not be encountered in files $extras = []; $header = $bundle->getHeaderInfo(); foreach( $bundle->getMetaTranslatable() as $prop => $notes ){ $text = $header->__get($prop); if( is_string($text) && '' !== $text ){ $extras[] = ['source'=>$text, 'notes'=>$notes ]; } } if( $extras ){ $this->extras[$domain] = $extras; } } } /** * @return self */ public function addProject( Loco_package_Project $project ){ $base = $this->bundle->getDirectoryPath(); $domain = (string) $project->getDomain(); // skip files larger than configured maximum $opts = Loco_data_Settings::get(); $max = wp_convert_hr_to_bytes( $opts->max_php_size ); // *attempt* to raise memory limit to WP_MAX_MEMORY_LIMIT if( function_exists('wp_raise_memory_limit') ){ wp_raise_memory_limit('loco'); } /* @var Loco_fs_File $file */ foreach( $project->findSourceFiles() as $file ){ $type = $opts->ext2type( $file->extension() ); $fileref = $file->getRelativePath($base); try { $extr = loco_wp_extractor( $type, $file->fullExtension() ); if( 'php' === $type || 'twig' === $type) { // skip large files for PHP, because token_get_all is hungry if( 0 !== $max ){ $size = $file->size(); $this->maxbytes = max( $this->maxbytes, $size ); if( $size > $max ){ $list = $this->skipped or $list = ( $this->skipped = new Loco_fs_FileList() ); $list->add( $file ); continue; } } // extract headers from theme files (templates and patterns) if( $project->getBundle()->isTheme() ){ $extr->headerize( [ 'Template Name' => ['notes'=>'Name of the template'], ], $domain ); if( preg_match('!^patterns/!', $fileref) ){ $extr->headerize([ 'Title' => ['context'=>'Pattern title'], 'Description' => ['context'=>'Pattern description'], ], $domain ); } } } // normally missing domains are treated as "default", but we'll make an exception for theme.json. else if( 'json' === $type && $project->getBundle()->isTheme() ){ $extr->setDomain($domain); } $this->extracted->extractSource( $extr, $file->getContents(), $fileref ); } catch( Exception $e ){ Loco_error_AdminNotices::debug('Error extracting '.$fileref.': '.$e->getMessage() ); } } return $this; } private function includeBlock( Loco_fs_File $file, $domain ) { $def = json_decode( $file->getContents(), true ); if( ! is_array($def) || ! array_key_exists('$schema',$def) ){ return; } // adding dummy line number for well-formed file reference, not currently pulling line number. $ref = $file->getRelativePath( $this->bundle->getDirectoryPath() ).':1'; foreach(['title','description','keywords'] as $key ){ if( ! array_key_exists($key,$def) ) { continue; } $msgctxt = 'block '.rtrim($key,'s'); foreach( (array) $def[$key] as $msgid ){ if( is_string($msgid) && '' !== $msgid ){ $str = new Loco_gettext_String($msgid,$msgctxt); $str->addFileReferences($ref); $this->addString($str,$domain); } } } } /** * Add metadata strings deferred from construction. Note this will alter domain counts * @return self */ public function includeMeta(){ foreach( $this->extras as $domain => $extras ){ foreach( $extras as $entry ){ $this->extracted->pushEntry($entry,$domain); } } $this->extras = []; return $this; } /** * Add a custom source string constructed from `new Loco_gettext_String(msgid,[msgctxt])` * @param Loco_gettext_String $string * @param string $domain Optional text domain, if not current bundle's default * @return void */ public function addString( Loco_gettext_String $string, $domain = '' ){ if( ! $domain ) { $default = $this->bundle->getDefaultProject(); $domain = (string) ( $default ? $default->getDomain() : $this->extracted->getDomain() ); } $index = $this->extracted->pushEntry( $string->exportSingular(), $domain ); if( $string->hasPlural() ){ $this->extracted->pushPlural( $string->exportPlural(), $index ); } } /** * Get number of unique strings across all domains extracted (excluding additional metadata) * @return array { default: x, myDomain: y } */ public function getDomainCounts(){ return $this->extracted->getDomainCounts(); } /** * Pull extracted data into POT, filtering out any unwanted domains * @param string $domain * @return Loco_gettext_Data */ public function getTemplate( $domain ){ do_action('loco_extracted_template', $this, $domain ); $data = new Loco_gettext_Data( $this->extracted->filter($domain) ); return $data->templatize( $domain ); } /** * Get total number of strings extracted from all domains, excluding additional metadata * @return int */ public function getTotal(){ return $this->extracted->count(); } /** * Get list of files skipped, or null if none were skipped * @return Loco_fs_FileList|null */ public function getSkipped(){ return $this->skipped; } /** * Get size in bytes of largest file encountered, even if skipped. * This is the value required of the max_php_size plugin setting to extract all files * @return int */ public function getMaxPhpSize(){ return $this->maxbytes; } } src/gettext/Compiler.php000064400000031270147206622240011312 0ustar00files = new Loco_fs_Siblings($pofile); $this->progress = new Loco_mvc_ViewParams( [ 'pobytes' => 0, 'mobytes' => 0, 'numjson' => 0, 'phbytes' => 0, ] ); // Connect compiler to the file system, if writing to disk for real if( ! $pofile instanceof Loco_fs_DummyFile ) { $this->fs = new Loco_api_WordPressFileSystem; } $this->done = new Loco_fs_FileList; } /** * @return Loco_fs_FileList */ public function writeAll( Loco_gettext_Data $po, Loco_package_Project $project = null ){ $this->writePo($po); $this->writeMo($po); if( $project ){ $this->writeJson($project,$po); } return $this->done; } /** * @return int bytes written to PO file * @throws Loco_error_WriteException */ public function writePo( Loco_gettext_Data $po ){ $file = $this->files->getSource(); // Perform PO file backup before overwriting an existing PO if( $file->exists() && $this->fs ){ $backups = new Loco_fs_Revisions($file); $backup = $backups->rotate($this->fs); // debug backup creation only under cli or ajax. too noisy printing on screen if( $backup && ( loco_doing_ajax() || 'cli' === PHP_SAPI ) && $backup->exists() ){ Loco_error_AdminNotices::debug( sprintf('Wrote backup: %s -> %s',$file->basename(),$backup->basename() ) ); } } $bytes = $this->writeFile( $file, $po->msgcat() ); $this->progress['pobytes'] = $bytes; return $bytes; } /** * @return int bytes written to MO file */ public function writeMo( Loco_gettext_Data $po ){ try { $mofile = $this->files->getBinary(); $bytes = $this->writeFile( $mofile, $po->msgfmt() ); } catch( Exception $e ){ Loco_error_AdminNotices::debug( $e->getMessage() ); Loco_error_AdminNotices::warn( __('PO file saved, but MO file compilation failed','loco-translate') ); $bytes = 0; } $this->progress['mobytes'] = $bytes; // write PHP cache, if WordPress >= 6.5 if( 0 !== $bytes ){ try { $this->progress['phbytes'] = $this->writePhp($po); } catch( Exception $e ){ Loco_error_AdminNotices::debug( $e->getMessage() ); } } return $bytes; } /** * @return int bytes written to .l10n.php file */ private function writePhp( Loco_gettext_Data $po ){ $phfile = $this->files->getCache(); if( $phfile && class_exists('WP_Translation_File_PHP',false) ){ return $this->writeFile( $phfile, Loco_gettext_PhpCache::render($po) ); } return 0; } /** * @param Loco_package_Project $project Translation set, required to resolve script paths * @param Loco_gettext_Data $po PO data to export * @return Loco_fs_FileList All json files created */ public function writeJson( Loco_package_Project $project, Loco_gettext_Data $po ){ $domain = $project->getDomain()->getName(); $pofile = $this->files->getSource(); $jsons = new Loco_fs_FileList; // Allow plugins to dictate a single JSON file to hold all script translations for a text domain // authors will additionally have to filter at runtime on load_script_translation_file $path = apply_filters('loco_compile_single_json', '', $pofile->getPath(), $domain ); if( is_string($path) && '' !== $path ){ $refs = $po->splitRefs( $this->getJsExtMap() ); if( array_key_exists('js',$refs) && $refs['js'] instanceof Loco_gettext_Data ){ $jsonfile = new Loco_fs_File($path); $json = $refs['js']->msgjed($domain,'*.js'); try { if( '' !== $json ){ $this->writeFile($jsonfile,$json); $jsons->add($jsonfile); } } catch( Loco_error_WriteException $e ){ Loco_error_AdminNotices::debug( $e->getMessage() ); // translators: %s refers to a JSON file which could not be compiled due to an error Loco_error_AdminNotices::warn( sprintf(__('JSON compilation failed for %s','loco-translate'),$jsonfile->basename()) ); } } } // continue as per default, generating multiple per-script JSON else { $buffer = []; $base_dir = $project->getBundle()->getDirectoryPath(); $extensions = array_keys( $this->getJsExtMap() ); $refsGrep = '\\.(?:'.implode('|',$extensions).')'; /* @var Loco_gettext_Data $fragment */ foreach( $po->exportRefs($refsGrep) as $ref => $fragment ){ $use = null; // Reference could be a js source file, or a minified version. We'll try .min.js first, then .js // Build systems may differ, but WordPress only supports these suffixes. See WP-CLI MakeJsonCommand. if( substr($ref,-7) === '.min.js' ) { $paths = [ $ref, substr($ref,-7).'.js' ]; } else { $paths = [ substr($ref,0,-3).'.min.js', $ref ]; } // Try .js and .min.js paths to check whether deployed script actually exists foreach( $paths as $path ){ // Hook into load_script_textdomain_relative_path like load_script_textdomain() does. $url = $project->getBundle()->getDirectoryUrl().$path; $path = apply_filters( 'load_script_textdomain_relative_path', $path, $url ); if( ! is_string($path) || '' === $path ){ continue; } // by default ignore js file that is not in deployed code $file = new Loco_fs_File($path); $file->normalize($base_dir); if( apply_filters('loco_compile_script_reference',$file->exists(),$path,$domain) ){ $use = $path; break; } } // if neither exists in the bundle, this is a source path that will never be resolved at runtime if( is_null($use) ){ Loco_error_AdminNotices::debug( sprintf('Skipping JSON for %s; script not found in bundle',$ref) ); } // add .js strings to buffer for this json and merge if already present else if( array_key_exists($use,$buffer) ){ $buffer[$use]->concat($fragment); } else { $buffer[$use] = $fragment; } } if( $buffer ){ // write all buffered fragments to their computed JSON paths foreach( $buffer as $ref => $fragment ) { $json = $fragment->msgjed($domain,$ref); if( '' === $json ){ Loco_error_AdminNotices::debug( sprintf('Skipping JSON for %s; no translations',$ref) ); continue; } try { $jsonfile = self::cloneJson($pofile,$ref,$domain); $this->writeFile( $jsonfile, $json ); $jsons->add($jsonfile); } catch( Loco_error_WriteException $e ){ Loco_error_AdminNotices::debug( $e->getMessage() ); // phpcs:ignore -- comment already applied to this string elsewhere Loco_error_AdminNotices::warn( sprintf(__('JSON compilation failed for %s','loco-translate'),$ref)); } } $buffer = null; } } // clean up redundant JSONs including if no JSONs were compiled if( Loco_data_Settings::get()->jed_clean ){ foreach( $this->files->getJsons($domain) as $path ){ $jsonfile = new Loco_fs_File($path); if( ! $jsons->has($jsonfile) ){ try { $jsonfile->unlink(); } catch( Loco_error_WriteException $e ){ Loco_error_AdminNotices::debug('Unable to remove redundant JSON: '.$e->getMessage() ); } } } } $this->progress['numjson'] = $jsons->count(); return $jsons; } /** * Clone localised file as a WordPress script translation file * @return Loco_fs_File */ private static function cloneJson( Loco_fs_File $pofile, $ref, $domain ){ $name = $pofile->filename(); // Theme author PO files have no text domain, but JSON files must always be prefixed if( $domain && 'default' !== $domain && preg_match('/^[a-z]{2,3}(?:_[a-z\\d_]+)?$/i',$name) ){ $name = $domain.'-'.$name; } // Hashable reference is always finally unminified, as per load_script_textdomain() if( is_string($ref) && '' !== $ref ){ $name .= '-'.self::hashRef($ref); } return $pofile->cloneBasename( $name.'.json' ); } /** * Hashable reference is always finally unminified, as per load_script_textdomain() * @param string $ref script path relative to plugin base * @return string */ private static function hashRef( $ref ){ if( substr($ref,-7) === '.min.js' ) { $ref = substr($ref,0,-7).'.js'; } return md5($ref); } /** * Fetch compilation summary and raise most relevant success message * @return Loco_mvc_ViewParams */ public function getSummary(){ $pofile = $this->files->getSource(); // Avoid calling this unless the initial PO save was successful if( ! $this->progress['pobytes'] ){ throw new LogicException('PO not saved'); } // Summary for localised file includes MO+JSONs $mobytes = $this->progress['mobytes']; $numjson = $this->progress['numjson']; if( $mobytes && $numjson ){ Loco_error_AdminNotices::success( __('PO file saved and MO/JSON files compiled','loco-translate') ); } else if( $mobytes ){ Loco_error_AdminNotices::success( __('PO file saved and MO file compiled','loco-translate') ); } else { // translators: Success notice where %s is a file extension, e.g. "PO" Loco_error_AdminNotices::success( sprintf(__('%s file saved','loco-translate'),strtoupper($pofile->extension())) ); } return $this->progress; } /** * Get all files written, not including backups. * @return Loco_fs_File[] */ public function getFilesWritten(){ return $this->done->getArrayCopy(); } /** * Obtain non-standard JavaScript file extensions. * @return string[] where keys are PCRE safe extensions, all mapped to "js" */ private function getJsExtMap(){ $map = ['js'=>'js','jsx'=>'js']; $exts = Loco_data_Settings::get()->jsx_alias; if( is_array($exts) && $exts ){ $exts = array_map( [__CLASS__,'pregQuote'], $exts); $map = array_fill_keys($exts,'js') + $map; } return $map; } /** * @internal */ private static function pregQuote( $value ){ return preg_quote($value,'/'); } /** * @param Loco_fs_File $file * @param string $data to write to given file * @return int bytes written */ public function writeFile( Loco_fs_File $file, $data ){ if( $this->fs ) { $this->fs->authorizeSave( $file ); } $bytes = $file->putContents($data); if( 0 !== $bytes ){ $this->done->add($file ); } return $bytes; } } src/gettext/Data.php000064400000036044147206622240010415 0ustar00extension() ), '~' ); if( 'po' === $ext || 'pot' === $ext || 'mo' === $ext || 'json' === $ext ){ return $ext; } // only observing the full `.l10n.php` extension as a translation format. if( 'php' === $ext && '.l10n.php' === substr($file->getPath(),-9) ){ return 'php'; } // translators: Error thrown when attempting to parse a file that is not a supported translation file format throw new Loco_error_Exception( sprintf( __('%s is not a Gettext file','loco-translate'), $file->basename() ) ); } /** * @return Loco_gettext_Data */ public static function load( Loco_fs_File $file, $type = null ){ if( is_null($type) ) { $type = self::ext($file); } $type = strtolower($type); // catch parse errors, so we can inform user of which file is bad try { if( 'po' === $type || 'pot' === $type ){ return self::fromSource( $file->getContents() ); } if( 'mo' === $type ){ return self::fromBinary( $file->getContents() ); } if( 'json' === $type ){ return self::fromJson( $file->getContents() ); } if( 'php' === $type ){ return self::fromPhp( $file->getPath() ); } throw new InvalidArgumentException('No parser for '.$type.' files'); } catch( Loco_error_ParseException $e ){ $path = $file->getRelativePath( loco_constant('WP_CONTENT_DIR') ); Loco_error_AdminNotices::debug( sprintf('Failed to parse %s as a %s file; %s',$path,strtoupper($type),$e->getMessage()) ); throw new Loco_error_ParseException( sprintf('Invalid %s file: %s',$type,basename($path)) ); } } /** * Like load but just pulls header, saving a full parse * @return LocoPoHeaders * @throws InvalidArgumentException */ public static function head( Loco_fs_File $file ){ $p = new LocoPoParser( $file->getContents() ); $p->parse(0); return $p->getHeader(); } /** * @param string $src PO source * @return Loco_gettext_Data */ public static function fromSource( $src ){ $p = new LocoPoParser($src); return new Loco_gettext_Data( $p->parse() ); } /** * @param string $bin MO bytes * @return Loco_gettext_Data */ public static function fromBinary( $bin ){ $p = new LocoMoParser($bin); return new Loco_gettext_Data( $p->parse() ); } /** * @param string $json Jed source * @return Loco_gettext_Data */ public static function fromJson( $json ){ $blob = json_decode( $json, true ); $p = new LocoJedParser( $blob['locale_data'] ); // note that headers outside of locale_data are won't be parsed out. we don't currently need them. return new Loco_gettext_Data( $p->parse() ); } /** * @param string $path PHP file path * @return Loco_gettext_Data */ public static function fromPhp( $path ){ $blob = include $path; if( ! is_array($blob) || ! array_key_exists('messages',$blob) ){ throw new Loco_error_ParseException('Invalid PHP translation file'); } // refactor PHP structure into JED format $p = new LocoMoPhpParser($blob); return new Loco_gettext_Data( $p->parse() ); } /** * Create a dummy/empty instance with minimum content to be a valid PO file. * @return Loco_gettext_Data */ public static function dummy(){ return new Loco_gettext_Data( [ ['source'=>'','target'=>'Language:'] ] ); } /** * Ensure PO source is UTF-8. * Required if we want PO code when we're not parsing it. e.g. source view * @param string $src PO source * @return string */ public static function ensureUtf8( $src ){ $src = loco_remove_bom($src,$cs); if( ! $cs ){ // read PO header, requiring partial parse try { $cs = LocoPoHeaders::fromSource($src)->getCharset(); } catch( Loco_error_ParseException $e ){ Loco_error_AdminNotices::debug( $e->getMessage() ); } } return loco_convert_utf8($src,$cs,false); } /** * Compile messages to binary MO format * @return string MO file source * @throws Loco_error_Exception */ public function msgfmt(){ if( 2 !== strlen("\xC2\xA3") ){ throw new Loco_error_Exception('Refusing to compile MO file. Please disable mbstring.func_overload'); // @codeCoverageIgnore } $mo = new LocoMo( $this, $this->getHeaders() ); $opts = Loco_data_Settings::get(); if( $opts->gen_hash ){ $mo->enableHash(); } if( $opts->use_fuzzy ){ $mo->useFuzzy(); } /*/ TODO optionally exclude .js strings if( $opts->purge_js ){ $mo->filter.... }*/ return $mo->compile(); } /** * Get final UTF-8 string for writing to file * @param bool $sort Whether to sort output, generally only for extracting strings * @return string */ public function msgcat( $sort = false ){ // set maximum line width, zero or >= 15 $this->wrap( Loco_data_Settings::get()->po_width ); // concat with default text sorting if specified $po = $this->render( $sort ? [ 'LocoPoIterator', 'compare' ] : null ); // Prepend byte order mark only if configured if( Loco_data_Settings::get()->po_utf8_bom ){ $po = "\xEF\xBB\xBF".$po; } return $po; } /** * Compile JED flavour JSON * @param string $domain text domain for JED metadata * @param string $source reference to file that uses included strings * @return string JSON source, or empty if JED file has no entries */ public function msgjed( $domain = 'messages', $source = '' ){ // note that JED is sparse, like MO. We won't write empty files. $data = $this->exportJed(); if( 1 >= count($data) ){ return ''; } $head = $this->getHeaders(); $head['domain'] = $domain; // Pretty formatting for debugging. Doing as per WordPress and always escaping Unicode. $json_options = 0; if( Loco_data_Settings::get()->jed_pretty ){ $json_options |= loco_constant('JSON_PRETTY_PRINT') | loco_constant('JSON_UNESCAPED_SLASHES'); // | loco_constant('JSON_UNESCAPED_UNICODE'); } // PO should have a date if localised properly return json_encode( [ 'translation-revision-date' => $head['PO-Revision-Date'], 'generator' => $head['X-Generator'], 'source' => $source, 'domain' => $domain, 'locale_data' => [ $domain => $data, ], ], $json_options ); } /** * @return array */ #[ReturnTypeWillChange] public function jsonSerialize(){ $po = $this->getArrayCopy(); // exporting headers non-scalar so js doesn't have to parse them try { $headers = $this->getHeaders(); if( count($headers) && '' === $po[0]['source'] ){ $po[0]['target'] = $headers->getArrayCopy(); } } // suppress header errors when serializing // @codeCoverageIgnoreStart catch( Exception $e ){ } // @codeCoverageIgnoreEnd return $po; } /** * Create a signature for use in comparing source strings between documents * @return string */ public function getSourceDigest(){ $data = $this->getHashes(); return md5( implode("\1",$data) ); } /** * @param Loco_Locale $locale * @param string[] $custom custom headers * @return Loco_gettext_Data */ public function localize( Loco_Locale $locale, array $custom = [] ){ $date = gmdate('Y-m-d H:i').'+0000'; // headers that must always be set if absent $defaults = [ 'Project-Id-Version' => '', 'Report-Msgid-Bugs-To' => '', 'POT-Creation-Date' => $date, ]; // headers that must always override when localizing $required = [ 'PO-Revision-Date' => $date, 'Last-Translator' => '', 'Language-Team' => $locale->getName(), 'Language' => (string) $locale, 'Plural-Forms' => $locale->getPluralFormsHeader(), 'MIME-Version' => '1.0', 'Content-Type' => 'text/plain; charset=UTF-8', 'Content-Transfer-Encoding' => '8bit', 'X-Generator' => 'Loco https://localise.biz/', 'X-Loco-Version' => sprintf('%s; wp-%s', loco_plugin_version(), $GLOBALS['wp_version'] ), ]; // Allow some existing headers to remain if PO was previously localized to the same language $headers = $this->getHeaders(); $previous = Loco_Locale::parse( $headers->trimmed('Language') ); if( $previous->lang === $locale->lang ){ $header = $headers->trimmed('Plural-Forms'); if( preg_match('/^\\s*nplurals\\s*=\\s*\\d+\\s*;\\s*plural\\s*=/', $header) ) { $required['Plural-Forms'] = $header; } if( $previous->region === $locale->region && $previous->variant === $locale->variant ){ unset( $required['Language-Team'] ); } } // set user's preferred Last-Translator credit if configured if( function_exists('get_current_user_id') && get_current_user_id() ){ $prefs = Loco_data_Preferences::get(); $credit = (string) $prefs->credit; if( '' === $credit ){ $credit = $prefs->default_credit(); } // filter credit with current username and email $user = wp_get_current_user(); $credit = apply_filters( 'loco_current_translator', $credit, $user->get('display_name'), $user->get('email') ); if( '' !== $credit ){ $required['Last-Translator'] = $credit; } } $headers = $this->applyHeaders($required,$defaults,$custom); // avoid non-empty POT placeholders that won't have been set from $defaults if( 'PACKAGE VERSION' === $headers['Project-Id-Version'] ){ $headers['Project-Id-Version'] = ''; } // finally allow headers to be modified via filter $replaced = apply_filters( 'loco_po_headers', $headers ); if( $replaced instanceof LocoPoHeaders && $replaced !== $headers ){ $this->setHeaders($replaced); } return $this->initPo(); } /** * @param string $domain * @return Loco_gettext_Data */ public function templatize( $domain = '' ){ $date = gmdate('Y-m-d H:i').'+0000'; // <- forcing UCT $defaults = [ 'Project-Id-Version' => 'PACKAGE VERSION', 'Report-Msgid-Bugs-To' => '', ]; $required = [ 'POT-Creation-Date' => $date, 'PO-Revision-Date' => 'YEAR-MO-DA HO:MI+ZONE', 'Last-Translator' => 'FULL NAME ', 'Language-Team' => '', 'Language' => '', 'Plural-Forms' => 'nplurals=INTEGER; plural=EXPRESSION;', 'MIME-Version' => '1.0', 'Content-Type' => 'text/plain; charset=UTF-8', 'Content-Transfer-Encoding' => '8bit', 'X-Generator' => 'Loco https://localise.biz/', 'X-Loco-Version' => sprintf('%s; wp-%s', loco_plugin_version(), $GLOBALS['wp_version'] ), 'X-Domain' => $domain, ]; $headers = $this->applyHeaders($required,$defaults); // finally allow headers to be modified via filter $replaced = apply_filters( 'loco_pot_headers', $headers ); if( $replaced instanceof LocoPoHeaders && $replaced !== $headers ){ $this->setHeaders($replaced); } return $this->initPot(); } /** * @return LocoPoHeaders */ private function applyHeaders( array $required = [], array $defaults = [], array $custom = [] ){ $headers = $this->getHeaders(); // only set absent or empty headers from default list foreach( $defaults as $key => $value ){ if( ! $headers[$key] ){ $headers[$key] = $value; } } // add required headers with custom ones overriding if( $custom ){ $required = array_merge( $required, $custom ); } // TODO fix ordering weirdness here. required headers seem to get appended wrongly foreach( $required as $key => $value ){ $headers[$key] = $value; } return $headers; } /** * Remap proprietary base path when PO file is moving to another location. * * @param Loco_fs_File $origin the file that was originally extracted to (POT) * @param Loco_fs_File $target the file that must now target references relative to itself * @param string $vendor name used in header keys * @return bool whether base header was altered */ public function rebaseHeader( Loco_fs_File $origin, Loco_fs_File $target, $vendor ){ $base = $target->getParent(); $head = $this->getHeaders(); $key = $head->normalize('X-'.$vendor.'-Basepath'); if( $key ){ $oldRelBase = $head[$key]; $oldAbsBase = new Loco_fs_Directory($oldRelBase); $oldAbsBase->normalize( $origin->getParent() ); $newRelBase = $oldAbsBase->getRelativePath($base); // new base path is relative to $target location $head[$key] = $newRelBase; return true; } return false; } /** * Inherit meta values from header given, but leave standard headers intact. */ public function inheritHeader( LocoPoHeaders $source ){ $target = $this->getHeaders(); foreach( $source as $key => $value ){ if( 'X-' === substr($key,0,2) ) { $target[$key] = $value; } } } /** * @param string $podate Gettext data formatted "YEAR-MO-DA HO:MI+ZONE" * @return int */ public static function parseDate( $podate ){ if( method_exists('DateTime','createFromFormat') ){ $objdate = DateTime::createFromFormat('Y-m-d H:iO', $podate); if( $objdate instanceof DateTime ){ return $objdate->getTimestamp(); } } return strtotime($podate); } } src/gettext/SyncOptions.php000064400000004442147206622240012031 0ustar00head = $head; } /** * Test if PO file has alternative template path * @return bool */ public function hasTemplate(){ return '' !== $this->head->trimmed('X-Loco-Template'); } /** * Get *relative* path to alternative template path. * @return Loco_fs_LocaleFile */ public function getTemplate(){ return new Loco_fs_LocaleFile( $this->head['X-Loco-Template'] ); } /** * Set *relative* path to alternative template path. * @param string $path */ public function setTemplate( $path ){ $this->head['X-Loco-Template'] = (string) $path; } /** * Test if translations (msgstr fields) are to be merged. * @return bool true if NOT in pot mode */ public function mergeMsgstr(){ return 0 === preg_match( '/\\bpot\\b/', $this->getSyncMode() ); } /** * Test if JSON files are to be merged. * @return bool */ public function mergeJson(){ return 1 === preg_match( '/\\bjson\\b/', $this->getSyncMode() ); } /** * @return string */ public function getSyncMode(){ $mode = strtolower( $this->head->trimmed('X-Loco-Template-Mode') ); // Default sync mode when undefined is to honour the type of source. // i.e. for legacy compatibility, copy msgstr fields if source is a PO file. if( '' === $mode ){ $mode = $this->hasTemplate() ? strtolower( $this->getTemplate()->extension() ) : 'pot'; } return $mode; } /** * @param string $mode */ public function setSyncMode( $mode ){ $this->head['X-Loco-Template-Mode'] = (string) $mode; } /** * Remove redundant headers * @return LocoPoHeaders */ public function getHeaders(){ if( ! $this->hasTemplate() ){ $this->head->offsetUnset('X-Loco-Template'); if( 'pot' === $this->getSyncMode() ){ $this->head->offsetUnset('X-Loco-Template-Mode'); } } return $this->head; } } src/gettext/String.php000064400000004432147206622240011006 0ustar00raw = [ 'source' => (string) $msgid, 'context' => (string) $msgctxt, ]; } /** * Get singular form as raw array data * @internal * @return string[] */ public function exportSingular(){ return $this->raw; } /** * Get plural form as raw array data * @internal * @return string[] */ public function exportPlural(){ return [ 'source' => $this->plural, ]; } /** * @param string $prop * @param string|array $value * @param string $glue * @return void */ private function merge( $prop, $value, $glue ){ if( is_string($value) ){ $value = [$value]; } else if( ! is_array($value) ){ throw new InvalidArgumentException('Expected Array or String'); } if( array_key_exists($prop,$this->raw) ){ $value = array_merge( explode($glue,$this->raw[$prop]), $value ); } $this->raw[$prop] = implode($glue,$value); } /** * @param array|string $refs * @return self */ public function addFileReferences( $refs ){ $this->merge('refs',$refs,' '); return $this; } /** * @param array|string $notes * @return self */ public function addExtractedComment( $notes ){ $this->merge('notes',$notes,' '); return $this; } /** * @param string $msgid_plural * @return self */ public function pluralize( $msgid_plural ){ $this->plural = (string) $msgid_plural; return $this; } /** * @return bool */ public function hasPlural(){ return is_string($this->plural) && '' !== $this->plural; } /*public function __toString(){ return json_encode( $this->raw ); }*/ }src/gettext/Matcher.php000064400000025701147206622240011125 0ustar00project = $project; } /** * Initialize matcher with current valid source strings (ref.pot) * @param Loco_gettext_Data $pot POT reference * @param bool $translate Whether copying translations from reference data * @return int */ public function loadRefs( Loco_gettext_Data $pot, $translate = false ){ $ntotal = 0; $this->translate = (bool) $translate; $this->translated = 0; /* @var LocoPoMessage $new */ foreach( $pot as $new ){ $ntotal++; $this->add($new); } return $ntotal; } /** * Perform a reverse lookup for a file reference from its pre-computed hash */ private function findScript( $hash ){ $map = $this->hashes; // build full index of all script hashes under configured source locations. if( is_null($map) ){ $map = []; $scripts = clone $this->project->getSourceFinder(); $scripts->filterExtensions(['js']); $basepath = $this->project->getBundle()->getDirectoryPath(); /* @var Loco_fs_File $jsfile */ foreach( $scripts->export() as $jsfile ){ $ref = $jsfile->getRelativePath($basepath); if( substr($ref,-7) === '.min.js' ) { $ref = substr($ref,0,-7).'.js'; } $map[ md5($ref) ] = $ref; } $this->hashes = $map; } return array_key_exists($hash,$map) ? $map[$hash] : ''; } /** * Add further source strings from JSON/JED file * @param Loco_fs_File $file json file * @return int */ private function loadJson( Loco_fs_File $file ){ $unique = 0; $jed = json_decode( $file->getContents(), true ); if( ! is_array($jed) || ! array_key_exists('locale_data',$jed) || ! is_array($jed['locale_data']) ){ throw new Loco_error_Debug( $file->basename().' is not JED formatted'); } // without a file reference, strings will never be compiled back to the correct JSON. // if missing from JED, we'll attempt reverse match from scripts found on disk. $ref = array_key_exists('source',$jed) ? $jed['source'] : ''; if( '' === $ref || ! is_string($ref) ){ $name = $file->basename(); $ref = preg_match('/-([0-9a-f]{32})\\.json$/',$name,$r) ? $this->findScript($r[1]) : ''; if( '' === $ref ){ throw new Loco_error_Debug($name.' has no "source" key; script is unknown'); } // The hash is pre-computed and .js file is known to exist, so we'll skip filters here. // The compiler will still filter this reference, so it could potentially yield a different hash. Loco_error_AdminNotices::debug($name.' has no "source" key; reverse matched '.$ref); } // file reference most likely won't have a line number, as it applies to all strings in the JSON // As most deployed JavaScript will be minified, we probably only have one line anyway. // $ref .= ':1'; // not checking domain key. Should be valid if passed here and should only be one. foreach( $jed['locale_data'] as /*$domain =>*/ $keys ){ foreach( $keys as $msgid => $arr ){ if( '' === $msgid || ! is_array($arr) || ! isset($arr[0]) ){ continue; } $msgctxt = ''; // Unglue "msgctxt\4msgid" unique key $parts = explode("\4",$msgid,2); if( array_key_exists(1,$parts) ){ list($msgctxt,$msgid) = $parts; // TODO handle empty msgid case that uses weird "msgctxt\4(msgctxt)" format? } // string may exist in original template, and also in multiple JSONs. $new = ['source'=>$msgid,'context'=>$msgctxt,'refs'=>$ref ]; $old = $this->getArrayRef($new); if( $old ){ $refs = array_key_exists('refs',$old) ? (string) $old['refs'] : ''; if( '' === $refs ){ $old['refs'] = $ref; } else if( 0 === preg_match('/\\b'.preg_quote($ref,'/').'\\b/',$refs) ){ $old['refs'].= ' '.$ref; } $new = $old; } else { $unique++; } // Add translation from JSON only if not present in merged PO already if( $this->translate && ( ! array_key_exists('target',$new) || '' === $new['target'] ) ){ $new['target'] = $arr[0]; } $message = new LocoPoMessage($new); $this->add($message); // handle plurals, noting that msgid_plural is not stored in JED structure if( 1 < count($arr) ){ $index = 0; $plurals = $old && array_key_exists('plurals',$old) ? $old['plurals'] : []; while( array_key_exists(++$index,$arr) ){ if( array_key_exists($index,$plurals) ){ $raw = $plurals[$index]; if( $raw instanceof ArrayObject ){ $raw = $raw->getArrayCopy(); } } else { $raw = ['source'=>'','target'=>'']; } if( $this->translate && ( ! array_key_exists('target',$raw) || '' === $raw['target'] ) ){ $raw['target'] = $arr[$index]; } // use translation as missing msgid_plural only if msgid matches msgstr (English file) if( 1 === $index && '' === $raw['source'] ){ if( $arr[0] === $msgid ){ $raw['source'] = $arr[1]; } /*else { Loco_error_AdminNotices::debug('msgid_plural missing for msgid '.json_encode($msgid) ); }*/ } $plurals[$index] = new LocoPoMessage($raw); } $message['plurals'] = $plurals; } } } return $unique; } /** * Shortcut for loading multiple jsons with error tolerance * @param Loco_fs_File[] $jsons * @return int */ public function loadJsons( array $jsons ){ $n = 0; foreach( $jsons as $jsonfile ){ try { $n += $this->loadJson($jsonfile); } catch( Loco_error_Exception $e ){ Loco_error_AdminNotices::add($e); } } return $n; } /** * Update still-valid sources, deferring unmatched (new strings) for deferred fuzzy match * @param LocoPoIterator $original Existing definitions * @param LocoPoIterator $merged Resultant definitions * @return string[] keys matched exactly */ public function mergeValid( LocoPoIterator $original, LocoPoIterator $merged ){ $valid = []; $translate = $this->translate; /* @var LocoPoMessage $old */ foreach( $original as $old ){ $new = $this->match($old); // if existing source is still valid, merge any changes if( $new instanceof LocoPoMessage ){ $p = clone $old; $p->merge($new,$translate); $merged->push($p); $valid[] = $p->getKey(); // increment counter if translation was merged if( $translate && ! $old->translated() ){ $this->translated += $new->translated(); } } } return $valid; } /** * Perform fuzzy matching after all exact matches have been attempted * @param LocoPoIterator $merged Resultant definitions * @return string[] strings fuzzy-matched */ public function mergeFuzzy( LocoPoIterator $merged ){ $fuzzy = []; foreach( $this->getFuzzyMatches() as $pair ){ list($old,$new) = $pair; $p = clone $old; $p->merge($new); $merged->push($p); $fuzzy[] = $p->getKey(); } return $fuzzy; } /** * Add unmatched strings remaining as NEW source strings * @param LocoPoIterator $merged Resultant definitions to accept new strings * @return string[] strings added */ public function mergeAdded( LocoPoIterator $merged ){ $added = []; $translate = $this->translate; /* @var LocoPoMessage $new */ foreach( $this->unmatched() as $new ){ $p = clone $new; // remove translations unless configured to keep if( $p->translated() && ! $translate ){ $p->strip(); } $merged->push($p); $added[] = $p->getKey(); } return $added; } /** * Perform full merge and return result suitable from front end. * @param LocoPoIterator $original Existing definitions * @param LocoPoIterator $merged Resultant definitions * @return array result */ public function merge( LocoPoIterator $original, LocoPoIterator $merged ){ $this->mergeValid($original,$merged); $fuzzy = $this->mergeFuzzy($merged); $added = $this->mergeAdded($merged); /* @var LocoPoMessage $old */ $dropped = []; foreach( $this->redundant() as $old ){ $dropped[] = $old->getKey(); } // return to JavaScript with stats in the same form as old front end merge return [ 'add' => $added, 'fuz' => $fuzzy, 'del' => $dropped, 'trn' => $this->translated, ]; } /** * @param array $a * @return array */ private function getArrayRef( array $a ){ $r = $this->getRef($a); if( is_null($r) ){ return []; } if( $r instanceof ArrayObject ){ return $r->getArrayCopy(); } throw new Exception( (is_object($r)?get_class($r):gettype($r) ).' returned from '.get_class($this).'::getRef'); } } src/gettext/SearchPaths.php000064400000005750147206622240011751 0ustar00getExcluded() ); /* @var Loco_fs_Directory $base */ foreach( $this->getRootDirectories() as $base ){ $file = new Loco_fs_File($ref); $path = $file->normalize( (string) $base ); if( $file->exists() && ! $excluded->check($path) ){ return $file; } } return null; } /** * Build search paths from a given PO/POT file that references other files * @return Loco_gettext_SearchPaths */ public function init( Loco_fs_File $pofile, LocoHeaders $head = null ){ if( is_null($head) ){ loco_require_lib('compiled/gettext.php'); $head = LocoPoHeaders::fromSource( $pofile->getContents() ); } $ninc = 0; foreach( ['Poedit'] as $vendor ){ $key = 'X-'.$vendor.'-Basepath'; if( ! $head->has($key) ){ continue; } $dir = new Loco_fs_Directory( $head[$key] ); $base = $dir->normalize( $pofile->dirname() ); // base should be absolute, with the following search paths relative to it $i = 0; while( true ){ $key = sprintf('X-%s-SearchPath-%u', $vendor, $i++); if( ! $head->has($key) ){ break; } // map search path to given base $include = new Loco_fs_File( $head[$key] ); $include->normalize( $base ); if( $include->exists() ){ if( $include->isDirectory() ){ $this->addRoot( (string) $include ); $ninc++; } /*else { TODO force specific file in Loco_fs_FileFinder }*/ } } // exclude from search paths $i = 0; while( true ){ $key = sprintf('X-%s-SearchPathExcluded-%u', $vendor, $i++); if( ! $head->has($key) ){ break; } // map excluded path to given base $exclude = new Loco_fs_File( $head[$key] ); $exclude->normalize($base); if( $exclude->exists() ){ $this->exclude( (string) $exclude ); } // TODO implement wildcard exclusion } } // Add po file location if no proprietary headers used if( ! $ninc ){ $this->addRoot( $pofile->dirname() ); } return $this; } }src/cli/FetchCommand.php000064400000023544147206622240011160 0ustar00getInstalledCore() as $tag ){ if( 'en_US' === $tag ){ continue; } $locale = Loco_Locale::parse($tag); if( $locale->isValid() ){ $locales[] = $locale; } } if( ! $locales ){ throw new Loco_error_Exception('No installed languages, try with --locale='); } } foreach( $projects as $project ){ $type = strtolower( $project->getBundle()->getType() ); $domain = $project->getDomain()->getName(); $info = $project->getBundle()->getHeaderInfo(); $version = $info->Version; // Currently only supporting WordPress community translation sources. $args = [ 'version' => $version ]; if( 'core' !== $type ){ $type.= 's'; if( $project->getSlug() !== $domain ){ WP_CLI::warning( sprintf('Skipping %s, only single text domain %s supported',$project->getId(),$type)); continue; } $args['slug'] = $domain; } WP_CLI::log( sprintf('Looking up %s v%s..',$project,$version) ); Loco_cli_Utils::debug('Querying WordPress translations API for %s => %s..',$type,json_encode($args) ); $result = $wp->apiGet($type,$args); // pre-index installable language packs $packages = []; foreach( $result['translations'] as $data ){ $packages[$data['language']] = $data['package']; } // Translations API does not error when GlotPress project doesn't exist, it just returns empty. if( ! $packages ){ Loco_cli_Utils::debug('No installable language packs available for %s. Checking if a GlotPress project exists..',$project); // Ping GlotPress project page. This is the only way we can know if an incomplete project exists $response = wp_remote_head( sprintf('https://translate.wordpress.org/projects/wp-%s/%s/',$type,$args['slug']) ); $status = wp_remote_retrieve_response_code($response); if( 404 === $status ){ WP_CLI::warning( sprintf("Skipping %s: 404 from translate.wordpress.org. Probably no GlotPress project.",$project) ); continue; } else if( 200 !== $status ){ WP_CLI::warning( sprintf("Status %u from translate.wordpress.org. Skipping %s.",$status,$project) ); } Loco_cli_Utils::debug('> Ok, looks like GlotPress project exists; probably no locales above the threshold for a package build'); } // Save path is under "system" location because we are installing from GlotPress $dir = new Loco_fs_Directory( 'core' === $type ? '.' : $type ); $dir->normalize( loco_constant('WP_LANG_DIR') ); foreach( $locales as $locale ){ $tag = (string) $locale; if( 'en_US' == $tag ){ WP_CLI::warning('There are no translations in en_US. It is the source locale.'); continue; } // Map WP locale codes to GlotPress teams. They differ, naturally. $team = $locale->lang; if( $locale->region ){ $team.= '-'.strtolower($locale->region); } $gp = Loco_data_CompiledData::get('gp'); if( array_key_exists($team,$gp['aliases']) ){ $team = $gp['aliases'][$team]; } // variant code (e.g. formal) is a sub-entity and not part of team language id $variant = $locale->variant; if( ! $variant ){ $variant = 'default'; } if( 'core' === $type ){ // core projects are per-version. "dev" being upcoming. Then e.g. 5.6.x for stable if( $opts['trunk'] || preg_match('/^\\d.\\d-(?:rc|dev|beta)/i',$version) ){ $slug = 'dev'; } else { list($major,$minor) = explode('.',$version,3); $slug = sprintf('%u.%u.x',$major,$minor); } // Core projects are sub projects. plugins and themes don't have this $map = [ 'default.' => '', 'default.admin' => '/admin', 'default.admin-network' => '/admin/network', 'continents-cities' => '/cc', ]; $slug .= $map[ $project->getId() ]; $url = 'https://translate.wordpress.org/projects/wp/'.$slug.'/'.$team.'/'.$variant.'/export-translations/?format=po'; } else { $slug = $domain; // plugins are either "stable" or "dev"; themes don't appear to have stability/version slug ?? if( 'plugins' === $type ) { $slug .= $opts['trunk'] ? '/dev' : '/stable'; } $url = 'https://translate.wordpress.org/projects/wp-'.$type.'/'.$slug.'/'.$team.'/' . $variant . '/export-translations/?format=po'; } // Note that this export URL is not a documented API and may change without notice // TODO We could pass If-Modified-Since with current PO file header, BUT that could not know if existing file is purged or not. Make configurable? WP_CLI::log( sprintf('Fetching PO from %s..',$url)); $response = wp_remote_get($url); $status = wp_remote_retrieve_response_code($response); if( 200 !== $status ){ WP_CLI::warning( sprintf('Status %u from translate.wordpress.org; skipping "%s". Probably no translation team',$status,$tag) ); continue; } Loco_cli_Utils::debug('OK, last modified %s', wp_remote_retrieve_header($response,'last-modified') ); /*/ TODO fallback to installable package if( $packages && ! array_key_exists($tag,$packages) ){ WP_ClI::warning( sprintf('%s is not installable in `%s` (probably not complete enough)',$project,$tag) ); }*/ // Parse PO data to check it's valid, and also because we're going to compile it. $pobody = wp_remote_retrieve_body($response); $podata = Loco_gettext_Data::fromSource($pobody); $response = null; // keep translations if file already exists in this location. $pofile = $project->initLocaleFile($dir,$locale); $info = new Loco_mvc_FileParams( [], $pofile ); Loco_cli_Utils::debug('Saving %s..', $info->relpath ); $compiler = new Loco_gettext_Compiler($pofile); if( $pofile->exists() ){ $info = new Loco_mvc_FileParams( [], $pofile ); Loco_cli_Utils::debug('PO already exists at %s (%s), merging..',$info->relpath,$info->size); $original = Loco_gettext_Data::load($pofile); $matcher = new Loco_gettext_Matcher($project); $matcher->loadRefs($podata,true); // downloaded file is in memory can be replaced with merged version $podata = clone $original; $podata->clear(); $stats = $matcher->merge($original,$podata); $original = null; if( ! $stats['add'] && ! $stats['del'] && ! $stats['fuz'] && ! $stats['trn'] ){ WP_CLI::log( sprintf('%s unchanged in "%s". Skipping %s', $project,$locale,$info->relpath) ); continue; } // Overwrite merged PO, which will back up first if configured Loco_cli_Utils::debug('OK: %u added, %u dropped, %u fuzzy', count($stats['add']), count($stats['del']), count($stats['fuz']) ); $podata->localize($locale); $compiler->writePo($podata); } // Copy PO directly to disk as per remote source else { $compiler->writeFile($pofile,$pobody); $podata->inheritHeader( Loco_gettext_Data::dummy()->localize($locale)->getHeaders() ); } // Compile new MO and JSON files.. Loco_cli_Utils::debug('Compiling %s.{mo,json}',$pofile->filename() ); $compiler->writeMo($podata); $compiler->writeJson($project,$podata); $pofile->clearStat(); WP_CLI::success( sprintf('Fetched %s for "%s": %s PO at %s', $project,$locale,$info->size,$info->relpath) ); Loco_error_AdminNotices::get()->flush(); // clean up memory and ready for next file unset($podata,$pobody); $done++; } } if( 0 === $done ){ WP_CLI::success('Completed OK, but no files were installed'); } } } src/cli/ExtractCommand.php000064400000007446147206622240011544 0ustar00getId(), '.' ); WP_CLI::log( sprintf('Extracting "%s" (%s)',$project->getName(),$id) ); // POT file may or may not exist currently $potfile = $project->getPot(); if( ! $potfile ){ WP_CLI::warning('Skipping undefined POT'); continue; } if( $potfile->locked() ){ WP_CLI::warning('Skipping unwritable POT'); Loco_cli_Utils::tabulateFiles( $potfile->getParent(), $potfile ); continue; } // Do extraction and grab only given domain's strings $ext = new Loco_gettext_Extraction( $project->getBundle() ); $domain = $project->getDomain()->getName(); $data = $ext->addProject($project)->includeMeta()->getTemplate( $domain ); Loco_cli_Utils::debug('Extracted %u strings', count($data) ); $list = $ext->getSkipped(); if( $list ){ $current = Loco_data_Settings::get()->max_php_size; $suggest = ceil( $ext->getMaxPhpSize() / 1024 ); WP_CLI::warning(sprintf('%u source files skipped over %s. Consider running with --maxsize=%uK',count($list),$current,$suggest) ); foreach( $list as $file ) { $f = new Loco_mvc_FileParams([],$file); Loco_cli_Utils::debug('%s (%s)', $f->relpath, $f->size ); } } // if POT exists check if update is necessary. $data->sort(); if( $potfile->exists() && ! $force ){ try { Loco_cli_Utils::debug('Checking if sources have changed since '.date('c',$potfile->modified()) ); $prev = Loco_gettext_Data::fromSource( $potfile->getContents() ); if( $prev->equal($data) ){ WP_CLI::log('No update required for '.$potfile->basename() ); continue; } } catch( Loco_error_ParseException $e ){ Loco_cli_Utils::debug( $e->getMessage().' in '.$potfile->basename() ); } } if( $noop ){ WP_CLI::success( sprintf('**DRY RUN** would update %s', $potfile->basename() ) ); continue; } // additional headers to set in new POT file $head = $data->getHeaders(); $head['Project-Id-Version'] = $project->getName(); $head['X-Domain'] = $domain; // write POT file to disk returning byte length Loco_cli_Utils::debug('Writing POT file...'); $bytes = $potfile->putContents( $data->msgcat() ); Loco_cli_Utils::debug('%u bytes written to %s',$bytes, $potfile->getRelativePath($content_dir) ); WP_CLI::success( sprintf('Updated %s', $potfile->basename() ) ); $updated++; } // sync summary if( 0 === $updated ){ WP_CLI::log('Nothing updated'); } else { WP_CLI::success( sprintf('%u POT files written',$updated) ); } } }src/cli/Commands.php000064400000007667147206622240010401 0ustar00] * : Restrict to a type of bundle (plugins|themes|core); a single bundle (e.g. plugins:); or a Text Domain * * [--locale=] * : Restrict to one or more locales. Separate multiple codes with commas. * * [--fuzziness=] * : Override plugin settings for fuzzy matching tolerance (0-100). * * [--noop] * : Specify dry run. Makes no changes on disk. * * [--force] * : Update even when nothing has changed. Useful for recompiling MO/JSON. * * ## EXAMPLES * * wp loco sync plugins * * @param string[] $args * @param string[] $opts */ public function sync( $args, $opts ){ if( array_key_exists('fuzziness',$opts) ){ Loco_data_Settings::get()->fuzziness = (int) $opts['fuzziness']; } try { Loco_cli_SyncCommand::run ( Loco_cli_Utils::collectProjects( isset($args[0]) ? $args[0] : '' ), Loco_cli_Utils::collectLocales( isset($opts['locale']) ? $opts['locale'] : '' ), Loco_cli_Utils::bool($opts,'noop'), Loco_cli_Utils::bool($opts,'force') ); } catch( Loco_error_Exception $e ){ WP_CLI::error( $e->getMessage() ); } } /** * Extract available source strings * * ## OPTIONS * * [] * : Restrict to a type of bundle (plugins|themes|core); a single bundle (e.g. plugins:); or a Text Domain * * [--maxsize=] * : Override plugin settings for maximum PHP file size * * [--noop] * : Specify dry run. Makes no changes on disk. * * [--force] * : Update even when nothing has changed. Useful for updating meta properties. * * ## EXAMPLES * * wp loco extract core --maxsize=400K * * @param string[] $args * @param string[] $opts */ public function extract( $args, $opts ){ try { if( array_key_exists('maxsize',$opts) ){ Loco_data_Settings::get()->max_php_size = $opts['maxsize']; } Loco_cli_ExtractCommand::run ( Loco_cli_Utils::collectProjects( isset($args[0]) ? $args[0] : '' ), Loco_cli_Utils::bool($opts,'noop'), Loco_cli_Utils::bool($opts,'force') ); } catch( Loco_error_Exception $e ){ WP_CLI::error( $e->getMessage() ); } } /** * EXPERIMENTAL. Attempts to install translation source files from an external repository. * Use this to replace *installed* PO files if they are missing or have been purged of script translations. * * ## OPTIONS * * [] * : Restrict to a type of bundle (plugins|themes|core); a single bundle (e.g. plugins:); or a Text Domain * * [--locale=] * : Restrict to one or more locales. Separate multiple codes with commas. * * [--trunk] * : Install strings for upcoming dev version as opposed to latest stable * * ## EXAMPLES * * wp loco fetch loco-translate --locale=en_GB * * @param string[] $args * @param string[] $opts */ public function fetch( $args, $opts ){ try { Loco_cli_FetchCommand::run ( Loco_cli_Utils::collectProjects( isset($args[0]) ? $args[0] : '' ), Loco_cli_Utils::collectLocales( isset($opts['locale']) ? $opts['locale'] : '' ), [ 'trunk' => Loco_cli_Utils::bool($opts,'trunk') ] ); } catch( Loco_error_Exception $e ){ WP_CLI::error( $e->getMessage() ); } } } src/cli/Utils.php000064400000012023147206622240007716 0ustar00getDomain()->getName() !== $domain ){ continue; } if( $slug && $project->getSlug() !== $slug ){ continue; } $projects[] = $project; } } if( ! $projects ){ throw new Loco_error_Exception('No translation sets found'); } return $projects; } /** * Collect locales from one or more language tags * @param string zero or more language tags * @return Loco_Locale[] */ public static function collectLocales( $tags ){ $locales = []; if( '' !== $tags ){ $api = new Loco_api_WordPressTranslations; foreach( preg_split('/[\\s,;]+/i',$tags,-1,PREG_SPLIT_NO_EMPTY) as $tag ){ $locale = Loco_Locale::parse($tag); if( ! $locale->isValid() ){ throw new Loco_error_Exception('Invalid locale: '.json_encode($tag) ); } // TODO could expand language-only tags to known WordPress locales e.g. fr -> fr_FR $locales[ (string) $locale ] = $locale; $locale->ensureName($api); } // empty locales means ALL locales, so refuse to return ALL when filter was non-empty if( 0 === count($locales) ){ throw new Loco_error_Exception('No valid locales in: '.json_encode($tags) ); } } return $locales; } /** * Simple space-padded table * @param string[][] data rows to print */ public static function tabulate( array $t ){ $w = []; foreach( $t as $y => $row ){ foreach( $row as $x => $value ){ $width = mb_strlen($value,'UTF-8'); $w[$x] = isset($w[$x]) ? max($w[$x],$width) : $width; } } foreach( $t as $y => $row ){ $line = []; foreach( $w as $x => $width ){ $value = isset($row[$x]) ? $row[$x] : ''; $value = str_pad($value,$width,' ',STR_PAD_RIGHT); $line[] = $value; } self::debug( implode(' ',$line) ); } } /** * Prints file listing to stdout */ public static function tabulateFiles(){ $t = []; /* @var Loco_fs_File $file */ foreach( func_get_args() as $file ){ if( $file instanceof Loco_fs_File && $file->exists() ){ $f = new Loco_mvc_FileParams([],$file); $t[] = [ $f->owner, $f->group, $f->smode, $f->relpath ]; } } self::tabulate($t); } /** * WP_CLI debug logger */ public static function debug(){ $args = func_get_args(); $message = array_shift($args); if( $args ){ $message = vsprintf($message,$args); } WP_CLI::debug( $message,'loco' ); } /** * Parse boolean command line option. Absence is equal to false * @param string[] * @param string * @return bool */ public static function bool( array $opts, $key ){ $value = isset($opts[$key]) ? $opts[$key] : false; if( ! is_bool($value) ){ $value = $value && 'false' !== $value & 'no' !== $value; } return $value; } } src/cli/SyncCommand.php000064400000020671147206622240011041 0ustar00getId(), '.' ); $base_dir = $project->getBundle()->getDirectoryPath(); WP_CLI::log( sprintf('Syncing "%s" (%s)',$project->getName(),$id) ); // Check if project has POT, which will be used as default template unless PO overrides $pot = null; $potfile = $project->getPot(); if( $potfile && $potfile->exists() ){ Loco_cli_Utils::debug('Parsing template: %s',$potfile->getRelativePath($content_dir)); try { $pot = Loco_gettext_Data::fromSource( $potfile->getContents() ); } catch( Loco_error_ParseException $e ){ WP_CLI::error( $e->getMessage().' in '.$potfile->getRelativePath($content_dir), false ); $potfile = null; } } /* @var Loco_fs_LocaleFile $pofile */ $pofiles = $project->findLocaleFiles('po'); foreach( $pofiles as $pofile ){ $locale = $pofile->getLocale(); $tag = (string) $locale; if( $locales && ! array_key_exists($tag,$locales) ){ continue; } // Preempt write errors and print useful file mode info $mofile = $pofile->cloneExtension('mo'); if( ! $pofile->writable() || $mofile->locked() ){ WP_CLI::warning('Skipping unwritable: '.self::fname($pofile) ); Loco_cli_Utils::tabulateFiles( $pofile->getParent(), $pofile, $mofile ); continue; } // Parsing candidate PO file (definitions) Loco_cli_Utils::debug('Parsing PO: %s',$pofile->getRelativePath($content_dir)); try { $def = Loco_gettext_Data::fromSource( $pofile->getContents() ); } catch( Loco_error_ParseException $e ){ WP_CLI::error( $e->getMessage().' in '.$pofile->getRelativePath($content_dir), false ); continue; } // Check if PO defines alternative template (reference) $ref = $pot; $head = $def->getHeaders(); $opts = new Loco_gettext_SyncOptions($head); $translate = $opts->mergeMsgstr(); if( $opts->hasTemplate() ){ $ref = null; $potfile = $opts->getTemplate(); $potfile->normalize( $base_dir ); if( $potfile->exists() ){ try { Loco_cli_Utils::debug('> Parsing alternative template: %s',$potfile->getRelativePath($content_dir) ); $ref = Loco_gettext_Data::fromSource( $potfile->getContents() ); } catch( Loco_error_ParseException $e ){ WP_CLI::error( $e->getMessage().' in '.$potfile->getRelativePath($content_dir), false ); } } else { Loco_cli_Utils::debug('Template not found (%s)', $potfile->basename() ); } } if( ! $ref ){ WP_CLI::warning( sprintf('Skipping %s; no valid translation template',$pofile->getRelativePath($content_dir) ) ); continue; } // Perform merge if we have a reference file Loco_cli_Utils::debug('Merging %s <- %s', $pofile->basename(), $potfile->basename() ); $matcher = new Loco_gettext_Matcher($project); $matcher->loadRefs($ref,$translate ); // Merge jsons if configured and available if( $opts->mergeJson() ){ $siblings = new Loco_fs_Siblings( $potfile->cloneBasename( $pofile->basename() ) ); $jsons = $siblings->getJsons( $project->getDomain()->getName() ); $njson = $matcher->loadJsons($jsons); Loco_cli_Utils::debug('> merged %u json files', $njson ); } // Get fuzzy matching tolerance from plugin settings, can be set temporarily in command line $fuzziness = Loco_data_Settings::get()->fuzziness; $matcher->setFuzziness( (string) $fuzziness ); // update matches sources, deferring unmatched for deferred fuzzy match $po = clone $def; $po->clear(); $nvalid = count( $matcher->mergeValid($def,$po) ); $nfuzzy = count( $matcher->mergeFuzzy($po) ); $nadded = count( $matcher->mergeAdded($po) ); $ndropped = count( $matcher->redundant() ); // TODO Support --previous to keep old strings, or at least comment them out as #| msgid..... if( $nfuzzy || $nadded || $ndropped ){ Loco_cli_Utils::debug('> unchanged:%u added:%u fuzzy:%u dropped:%u', $nvalid, $nadded, $nfuzzy, $ndropped ); } else { Loco_cli_Utils::debug('> %u identical sources',$nvalid); } // File is synced, but may be identical $po->sort(); if( ! $force && $po->equal($def) ){ WP_CLI::log( sprintf('No update required for %s', self::fname($pofile) ) ); continue; } if( $noop ){ WP_CLI::success( sprintf('**DRY RUN** would update %s', self::fname($pofile) ) ); continue; } try { $locale->ensureName($wp_locales); $po->localize($locale); $compiler = new Loco_gettext_Compiler($pofile); $bytes = $compiler->writePo($po); Loco_cli_Utils::debug('+ %u bytes written to %s',$bytes, $pofile->basename()); $updated++; // compile MO $bytes = $compiler->writeMo($po); if( $bytes ){ Loco_cli_Utils::debug('+ %u bytes written to %s',$bytes, $mofile->basename()); $compiled++; } // Done PO/MO pair, now generate JSON fragments as applicable $jsons = $compiler->writeJson($project,$po); foreach( $jsons as $file ){ $compiled++; $param = new Loco_mvc_FileParams([],$file); Loco_cli_Utils::debug('+ %u bytes written to %s',$param->size,$param->name); } // Done compile of this set Loco_error_AdminNotices::get()->flush(); WP_CLI::success( sprintf('Updated %s', self::fname($pofile) ) ); } catch( Loco_error_WriteException $e ){ WP_CLI::error( $e->getMessage(), false ); } } } // sync summary if( 0 === $updated ){ WP_CLI::log('Nothing updated'); } else { WP_CLI::success( sprintf('%u PO files synced, %u files compiled',$updated,$compiled) ); } } /** * Debug file name showing directory location * @param Loco_fs_File * @return string */ private static function fname( Loco_fs_File $file ){ $dir = new Loco_fs_LocaleDirectory( $file->dirname() ); return $file->filename().' ('.$dir->getTypeLabel( $dir->getTypeId() ).')'; } }src/ajax/PingController.php000064400000001207147206622240011735 0ustar00validate(); // echo back bytes posted if( $post->has('echo') ){ $this->set( 'ping', $post['echo'] ); } // else just send pong else { $this->set( 'ping', 'pong' ); } // always send tick symbol to check json serializing of unicode $this->set( 'utf8', "\xE2\x9C\x93" ); return parent::render(); } }src/ajax/SaveController.php000064400000006631147206622240011744 0ustar00validate(); // path parameter must not be empty $path = $post->path; if( ! $path ){ throw new InvalidArgumentException('Path parameter required'); } // locale must be posted to indicate whether PO or POT $locale = $post->locale; if( is_null($locale) ){ throw new InvalidArgumentException('Locale parameter required'); } $pofile = new Loco_fs_LocaleFile( $path ); $pofile->normalize( loco_constant('WP_CONTENT_DIR') ); // ensure we only deal with PO/POT source files. // posting of MO file paths is permitted when PO is missing, but we're about to fix that $ext = strtolower( $pofile->fullExtension() ); if( 'mo' === $ext ){ $pofile = $pofile->cloneExtension('po'); } else if( 'pot' === $ext ){ $locale = ''; } else if( 'po' !== $ext ){ throw new Loco_error_Exception('Disallowed file extension'); } // Prepare compiler for all save operations. PO/MO/JSON, or just POT $compiler = new Loco_gettext_Compiler($pofile); // data posted may be either 'multipart/form-data' (recommended for large files) if( isset($_FILES['po']) ){ $data = Loco_gettext_Data::fromSource( Loco_data_Upload::src('po') ); } // else 'application/x-www-form-urlencoded' by default else { $data = Loco_gettext_Data::fromSource( $post->data ); } // WordPress-ize some headers that differ from that sent from JavaScript if( $locale ){ $head = $data->getHeaders(); $head['Language'] = strtr( $locale, '-', '_' ); } // commit PO file directly to disk $bytes = $compiler->writePo($data); $mtime = $pofile->modified(); // start success data with bytes written and timestamp $this->set('locale', $locale ); $this->set('pobytes', $bytes ); $this->set('poname', $pofile->basename() ); $this->set('modified', $mtime); $this->set('datetime', Loco_mvc_ViewParams::date_i18n($mtime) ); // add bundle to recent items on file creation // editor permitted to save files not in a bundle, so catching failures try { $bundle = $this->getBundle(); Loco_data_RecentItems::get()->pushBundle($bundle)->persist(); } catch( Exception $e ){ $bundle = null; } // Compile MO and JSON files if PO is localised and not POT (template) if( $locale ){ $mobytes = $compiler->writeMo($data); $numjson = 0; // Project required for JSON writes if( $bundle ){ $project = $this->getProject($bundle); $jsons = $compiler->writeJson($project,$data); $numjson = $jsons->count(); } $this->set( 'mobytes', $mobytes ); $this->set( 'numjson', $numjson ); } // Final summary depending on whether MO and JSON compiled $compiler->getSummary(); return parent::render(); } }src/ajax/DiffController.php000064400000003560147206622240011714 0ustar00validate(); // require x2 valid files for diffing if( ! $post->lhs || ! $post->rhs ){ throw new InvalidArgumentException('Path parameters required'); } $dir = loco_constant('WP_CONTENT_DIR'); $lhs = new Loco_fs_File( $post->lhs ); $lhs->normalize($dir); $rhs = new Loco_fs_File( $post->rhs ); $rhs->normalize($dir); // avoid diffing non Gettext source files $exts = array_flip( [ 'pot', 'pot~', 'po', 'po~' ] ); /* @var $file Loco_fs_File */ foreach( [$lhs,$rhs] as $file ){ if( ! $file->exists() ){ throw new InvalidArgumentException('File paths must exist'); } if( ! $file->underContentDirectory() ){ throw new InvalidArgumentException('Files must be under '.basename($dir) ); } $ext = $file->extension(); if( ! isset($exts[$ext]) ){ throw new InvalidArgumentException('Disallowed file extension'); } } // OK to diff files as HTML table $renderer = new Loco_output_DiffRenderer; $emptysrc = $renderer->_startDiff().$renderer->_endDiff(); $tablesrc = $renderer->renderFiles( $rhs, $lhs ); if( $tablesrc === $emptysrc ){ // translators: Where %s is a file name $message = __('Revisions are identical, you can delete %s','loco-translate'); $this->set( 'error', sprintf( $message, $rhs->basename() ) ); } else { $this->set( 'html', $tablesrc ); } return parent::render(); } } src/ajax/common/BundleController.php000064400000002245147206622240013544 0ustar00get('bundle') ){ // type may be passed as separate argument if( $type = $this->get('type') ){ return Loco_package_Bundle::createType( $type, $id ); } // else embedded in standalone bundle identifier // TODO standardize this across all Ajax end points return Loco_package_Bundle::fromId($id); } // else may have type embedded in bundle throw new Loco_error_Exception('No bundle identifier posted'); } /** * @param Loco_package_Bundle $bundle * @return Loco_package_Project */ protected function getProject( Loco_package_Bundle $bundle ){ $project = $bundle->getProjectById( $this->get('domain') ); if( ! $project ){ throw new Loco_error_Exception('Failed to find translation project'); } return $project; } }src/ajax/XgettextController.php000064400000006075147206622240012664 0ustar00validate(); $bundle = $this->getBundle(); $project = $this->getProject( $bundle ); // target location may not be next to POT file at all $base = loco_constant('WP_CONTENT_DIR'); $target = new Loco_fs_Directory( $this->get('path') ); $target->normalize( $base ); if( $target->exists() && ! $target->isDirectory() ){ throw new Loco_error_Exception('Target is not a directory'); } // basename should be posted from front end $name = $this->get('name'); if( ! $name ){ throw new Loco_error_Exception('Front end did not post $name'); } // POT file should be .pot but we'll allow .po $potfile = new Loco_fs_File( $target.'/'.$name ); $ext = strtolower( $potfile->fullExtension() ); if( 'pot' !== $ext && 'po' !== $ext ){ throw new Loco_error_Exception('Disallowed file extension'); } // File shouldn't exist currently $api = new Loco_api_WordPressFileSystem; $api->authorizeCreate($potfile); // Do extraction and grab only given domain's strings $ext = new Loco_gettext_Extraction( $bundle ); $domain = $project->getDomain()->getName(); $data = $ext->addProject($project)->includeMeta()->getTemplate( $domain ); // additional headers to set in new POT file $head = $data->getHeaders(); $head['Project-Id-Version'] = $project->getName(); // write POT file to disk returning byte length $potsize = $potfile->putContents( $data->msgcat(true) ); // set response data for debugging if( loco_debugging() ){ $this->set( 'debug', [ 'potname' => $potfile->basename(), 'potsize' => $potsize, 'total' => $ext->getTotal(), ] ); } // push recent items on file creation // TODO push project and locale file Loco_data_RecentItems::get()->pushBundle( $bundle )->persist(); // put flash message into session to be displayed on redirected page try { Loco_data_Session::get()->flash('success', __('Template file created','loco-translate') ); Loco_data_Session::close(); } catch( Exception $e ){ Loco_error_AdminNotices::debug( $e->getMessage() ); } // redirect front end to bundle view. Discourages manual editing of template $type = strtolower( $bundle->getType() ); $href = Loco_mvc_AdminRouter::generate( sprintf('%s-view',$type), [ 'bundle' => $bundle->getHandle(), ] ); $hash = '#loco-'.$project->getId(); $this->set( 'redirect', $href.$hash ); return parent::render(); } }src/ajax/MsginitController.php000064400000015237147206622240012462 0ustar00get('use-selector') ){ $tag = $this->get('select-locale'); } else { $tag = $this->get('custom-locale'); } $locale = Loco_Locale::parse($tag); if( ! $locale->isValid() ){ throw new Loco_error_LocaleException('Invalid locale'); } return $locale; } /** * {@inheritdoc} */ public function render(){ $post = $this->validate(); $bundle = $this->getBundle(); $project = $this->getProject( $bundle ); $domain = (string) $project->getDomain(); $locale = $this->getLocale(); $suffix = (string) $locale; // The front end posts a template path, so we must replace the actual locale code $base = loco_constant('WP_CONTENT_DIR'); $path = $post->path[ $post['select-path'] ]; // The request_filesystem_credentials function will try to access the "path" field later $_POST['path'] = $path; $pofile = new Loco_fs_LocaleFile( $path ); if( 'po' !== $pofile->fullExtension() ){ throw new Loco_error_Exception('Disallowed file extension'); } if( $suffix !== $pofile->getSuffix() ){ $pofile = $pofile->cloneLocale( $locale ); if( $suffix !== $pofile->getSuffix() ){ throw new Loco_error_Exception('Failed to suffix file path with locale code'); } } // target PO should not exist yet $pofile->normalize( $base ); $api = new Loco_api_WordPressFileSystem; $api->authorizeCreate( $pofile ); // Target MO probably doesn't exist, but we don't want to overwrite it without asking $mofile = $pofile->cloneExtension('mo'); if( $mofile->exists() ){ throw new Loco_error_Exception( __('MO file exists for this language already. Delete it first','loco-translate') ); } // Permit forcing of any parsable file as strings template $source = (string) $post->source; $compile = false; $mergejson = false; if( '' !== $source ){ $translate = ! $post->strip; $compile = $translate; $potfile = new Loco_fs_LocaleFile( $source ); $potfile->normalize( $base ); $data = Loco_gettext_Data::load($potfile); // When copying a PO file we may need to augment with JSON strings if( $post->json ){ $mergejson = true; $siblings = new Loco_fs_Siblings($potfile); $jsons = $siblings->getJsons($domain); if( $jsons ){ $refs = clone $data; $merge = new Loco_gettext_Matcher($project); $merge->loadRefs($refs,$translate); $merge->loadJsons($jsons); // resolve faux merge into empty instance $data->clear(); $merge->mergeValid($refs,$data); $merge->mergeAdded($data); } } // Remove target strings when copying PO without msgstr fields if( ! $translate && 'pot' !== $potfile->extension() ){ $data->strip(); } } // else parse POT file if project defines one that exists else { $potfile = $project->getPot(); if( $potfile->exists() ){ $data = Loco_gettext_Data::load($potfile); } // else extract directly from source code, assuming domain passed though from front end else { $extr = new Loco_gettext_Extraction( $bundle ); $data = $extr->addProject($project)->includeMeta()->getTemplate($domain); $potfile = null; } } // Let template define Project-Id-Version, else set header to current project name $headers = []; $vers = $data->getHeaders()->{'Project-Id-Version'}; if( ! $vers || 'PACKAGE VERSION' === $vers ){ $headers['Project-Id-Version'] = $project->getName(); } // fallback header not actually used, but keeping for informational purposes if( $potfile instanceof Loco_fs_LocaleFile && $post->link ){ $fallback = $potfile->getLocale(); if( $fallback->isValid() ){ $headers['X-Loco-Fallback'] = (string) $fallback; } } // finalize PO data ready to write to new file $locale->ensureName( new Loco_api_WordPressTranslations ); $data->localize( $locale, $headers ); // save sync options in PO headers if linked to a custom template. if( $potfile && $post->link ){ $opts = new Loco_gettext_SyncOptions( $data->getHeaders() ); $opts->setTemplate( $potfile->getRelativePath( $bundle->getDirectoryPath() ) ); // legacy behaviour was to sync source AND target strings in the absence of the following $mode = $post->strip ? 'POT' : 'PO'; // even if no JSONs were merged we need to keep this option in case JSONs are added in future. if( $mergejson ){ $mode.= ',JSON'; } $opts->setSyncMode($mode); } // compile all files in this set when copying target translation $compiler = new Loco_gettext_Compiler($pofile); if( $compile ){ $compiler->writeAll($data,$project); } // empty translations don't require compiled files, but adding MO for completeness. else { $compiler->writePo($data); $data->clear(); $compiler->writeMo($data); } // return debugging information, used in tests. $this->set('debug',new Loco_mvc_ViewParams( [ 'poname' => $pofile->basename(), 'source' => $potfile ? $potfile->basename() : '', ] ) ); // push recent items on file creation Loco_data_RecentItems::get()->pushBundle($bundle)->persist(); // front end will redirect to the editor $type = strtolower( $this->get('type') ); $this->set( 'redirect', Loco_mvc_AdminRouter::generate( sprintf('%s-file-edit',$type), [ 'path' => $pofile->getRelativePath($base), 'bundle' => $bundle->getHandle(), 'domain' => $project->getId(), ] ) ); return parent::render(); } }src/ajax/DownloadConfController.php000064400000002154147206622240013417 0ustar00validate(); $bundle = $this->getBundle(); $file = new Loco_fs_File( $this->get('path') ); // Download actual loco.xml file if bundle is configured from it if( 'file' === $bundle->isConfigured() && 'xml' === $file->extension() ){ $file->normalize( $bundle->getDirectoryPath() ); if( $file->readable() ){ return $file->getContents(); } } // else render temporary config file $writer = new Loco_config_BundleWriter($bundle); switch( $file->extension() ){ case 'xml': return $writer->toXml(); case 'json': return json_encode( $writer->jsonSerialize() ); } // @codeCoverageIgnoreStart throw new Loco_error_Exception('Specify either XML or JSON file path'); } }src/ajax/FsReferenceController.php000064400000016161147206622240013234 0ustar00get('path') ); $pofile->normalize( loco_constant('WP_CONTENT_DIR') ); if( ! $pofile->exists() ){ throw new InvalidArgumentException('PO/POT file required to resolve reference'); } $search = new Loco_gettext_SearchPaths; $search->init($pofile); if( $srcfile = $search->match($refpath) ){ return $srcfile; } // check against PO file location when no search paths or search paths failed $srcfile = new Loco_fs_File($refpath); $srcfile->normalize( $pofile->dirname() ); if( $srcfile->exists() ){ return $srcfile; } // reference may be resolvable via known project roots try { $bundle = $this->getBundle(); // Loco extractions will always be relative to bundle root $srcfile = new Loco_fs_File( $refpath ); $srcfile->normalize( $bundle->getDirectoryPath() ); if( $srcfile->exists() ){ return $srcfile; } // check relative to parent theme root if( $bundle->isTheme() && ( $parent = $bundle->getParent() ) ){ $srcfile = new Loco_fs_File( $refpath ); $srcfile->normalize( $parent->getDirectoryPath() ); if( $srcfile->exists() ){ return $srcfile; } } // final attempt - search all project source roots // TODO is there too large a risk of false positives? especially with files like index.php /* @var $root Loco_fs_Directory */ /*foreach( $this->getProject($bundle)->getConfiguredSources() as $root ){ if( $root->isDirectory() ){ $srcfile = new Loco_fs_File( $refpath ); $srcfile->normalize( $root->getPath() ); if( $srcfile->exists() ){ return $srcfile; } } }*/ } catch( Loco_error_Exception $e ){ // permitted for there to be no bundle or project when viewing orphaned file } throw new Loco_error_Exception( sprintf('Failed to find source file matching "%s"',$refpath) ); } /** * {@inheritdoc} */ public function render(){ $post = $this->validate(); // at the very least we need a reference to examine if( ! $post->has('ref') ){ throw new InvalidArgumentException('ref parameter required'); } // reference must parse as : $refpath = $post->ref; if( preg_match('/^(.+):(\\d+)$/', $refpath, $r ) ){ $refpath = $r[1]; $refline = (int) $r[2]; } else { $refline = 0; } // find file or fail $srcfile = $this->findSourceFile($refpath); // deny access to sensitive files if( 'wp-config.php' === $srcfile->basename() ){ throw new InvalidArgumentException('File access disallowed'); } // validate allowed source file types, including custom aliases $conf = Loco_data_Settings::get(); $ext = strtolower( $srcfile->extension() ); $type = $conf->ext2type($ext,'none'); if( 'none' === $type ){ throw new InvalidArgumentException('File extension disallowed, '.$ext ); } $this->set('type', $type ); $this->set('line', $refline ); $this->set('path', $srcfile->getRelativePath( loco_constant('WP_CONTENT_DIR') ) ); // source code will be HTML-tokenized into multiple lines $code = []; // observe the same size limits for source highlighting as for string extraction as tokenizing will use the same amount of juice $maxbytes = wp_convert_hr_to_bytes( $conf->max_php_size ); // tokenizers require gettext utilities, easiest just to ping the extraction library if( ! class_exists('Loco_gettext_Extraction',true) ){ throw new RuntimeException('Failed to load tokenizers'); // @codeCoverageIgnore } // PHP is the most likely format. if( 'php' === $type && ( $srcfile->size() <= $maxbytes ) && loco_check_extension('tokenizer') ) { $tokens = new LocoPHPTokens( token_get_all( $srcfile->getContents() ) ); } else if( 'js' === $type ){ $tokens = new LocoJsTokens( $srcfile->getContents() ); } else { $tokens = null; } // highlighting on back end because tokenizer provides more control than highlight.js if( $tokens instanceof LocoTokensInterface ){ $thisline = 1; while( $tok = $tokens->advance() ){ if( is_array($tok) ){ // line numbers added in PHP 5.2.2 - WordPress minimum is 5.2.4 list( $t, $str, $startline ) = $tok; $clss = token_name($t); // tokens can span multiple lines (whitespace/html/comments) $lines = preg_split('/\\R/', $str ); } else { // scalar symbol will always start on the line that the previous token ended on $clss = 'T_NONE'; $lines = [ $tok ]; $startline = $thisline; } // token can span multiple lines, so include only bytes on required line[s] foreach( $lines as $i => $line ){ $thisline = $startline + $i; $html = ''.htmlentities($line,ENT_COMPAT,'UTF-8').''; // append highlighted token to current line $j = $thisline - 1; if( isset($code[$j]) ){ $code[$j] .= $html; } else { $code[$j] = $html; } } } } // permit limited other file types, but without back end highlighting else { foreach( preg_split( '/\\R/u', $srcfile->getContents() ) as $line ){ $code[] = ''.htmlentities($line,ENT_COMPAT,'UTF-8').''; } } // allow 0 line reference when line is unknown (e.g. block.json) else it must exist if( $refline && ! isset($code[$refline-1]) ){ throw new Loco_error_Exception( sprintf('Line %u not in source file', $refline) ); } $this->set( 'code', $code ); return parent::render(); } } src/ajax/ApisController.php000064400000007443147206622240011744 0ustar00validate(); // Fire an event so translation apis can register their hooks as lazily as possible do_action('loco_api_ajax'); // Get request renders API modal contents: if( 0 === $post->count() ){ $apis = Loco_api_Providers::configured(); $this->set('apis',$apis); // modal views for batch-translate and suggest feature $modal = new Loco_mvc_View; $modal->set('apis',$apis); // help buttons $locale = $this->get('locale'); $modal->set( 'help', new Loco_mvc_ViewParams( [ 'text' => __('Help','loco-translate'), 'href' => apply_filters('loco_external','https://localise.biz/wordpress/plugin/manual/providers'), ] ) ); $modal->set('prof', new Loco_mvc_ViewParams( [ 'text' => __('Need a human?','loco-translate'), 'href' => apply_filters('loco_external','https://localise.biz/wordpress/translation?l='.$locale), ] ) ); // render auto-translate modal or prompt for configuration if( $apis ){ $html = $modal->render('ajax/modal-apis-batch'); } else { $html = $modal->render('ajax/modal-apis-empty'); } $this->set('html',$html); return parent::render(); } // else API client id should be posted to perform operation $hook = (string) $post->hook; // API client must be hooked in using loco_api_providers filter $config = null; foreach( Loco_api_Providers::export() as $candidate ){ if( is_array($candidate) && array_key_exists('id',$candidate) && $candidate['id'] === $hook ){ $config = $candidate; break; } } if( is_null($config) ){ throw new Loco_error_Exception('API not registered: '.$hook ); } // Get input texts to translate via registered hook. shouldn't be posted if empty. $sources = $post->sources; if( ! is_array($sources) || ! $sources ){ throw new Loco_error_Exception('Empty sources posted to '.$hook.' hook'); } // The front end sends translations detected as HTML separately. This is to support common external apis. $config['type'] = $post->type; // We need a locale too, which should be valid as it's the same one loaded into the front end. $locale = Loco_Locale::parse( (string) $post->locale ); if( ! $locale->isValid() ){ throw new Loco_error_Exception('Invalid locale'); } // Check if hook is registered, else sources will be returned as-is $action = 'loco_api_translate_'.$hook; if( ! has_filter($action) ){ throw new Loco_error_Exception('API not hooked. Use `add_filter('.var_export($action,1).',...)`'); } // This is effectively a filter whereby the returned array should be a translation of the input array // TODO might be useful for translation hooks to know the PO file this comes from $targets = apply_filters( $action, $sources, $locale, $config ); if( count($targets) !== count($sources) ){ Loco_error_AdminNotices::warn('Number of translations does not match number of source strings'); } // Response data doesn't need anything except the translations $this->set('targets',$targets); return parent::render(); } } src/ajax/UploadController.php000064400000007157147206622240012276 0ustar00validate(); $href = $this->process( $post ); // $this->set('redirect',$href); return parent::render(); } /** * Upload processor shared with standard postback controller * @param Loco_mvc_ViewParams $post script input * @return string redirect to file edit */ public function process( Loco_mvc_ViewParams $post ){ $bundle = $this->getBundle(); $project = $this->getProject( $bundle ); // Chosen folder location should be valid as a posted "dir" parameter if( ! $post->has('dir') ){ throw new Loco_error_Exception('No destination posted'); } $base = loco_constant('WP_CONTENT_DIR'); $parent = new Loco_fs_Directory($post->dir); $parent->normalize($base); // Loco_error_AdminNotices::debug('Destination set to '.$parent->getPath() ); // Ensure file uploaded ok if( ! isset($_FILES['f']) ){ throw new Loco_error_Exception('No file posted'); } $upload = new Loco_data_Upload($_FILES['f']); // Uploaded file will have a temporary name, so real name extension come from _FILES metadata $name = $upload->getOriginalName(); $ext = strtolower( pathinfo($name,PATHINFO_EXTENSION) ); // Loco_error_AdminNotices::debug('Have upload: '.$name.' @ '.$upload->getPath() ); switch( $ext ){ case 'po': case 'mo': $pomo = Loco_gettext_Data::load($upload,$ext); break; default: throw new Loco_error_Exception('Only PO/MO uploads supported'); } // PO/MO data is valid. // get real file name and establish if a locale can be extracted, otherwise get from headers $dummy = new Loco_fs_LocaleFile($name); $locale = $dummy->getLocale(); if( ! $locale->isValid() ){ $value = $pomo->getHeaders()->offsetGet('Language'); $locale = Loco_Locale::parse($value); if( ! $locale->isValid() ){ throw new Loco_error_Exception('Unable to detect language from '.$name ); } } // Fail if user presents a wrongly named file. This is to avoid mixing up text domains. $pofile = $project->initLocaleFile($parent,$locale); if( $pofile->filename() !== $dummy->filename() ){ throw new Loco_error_Exception( sprintf('File must be named %s', $pofile->filename().'.'.$ext ) ); } // Avoid processing if uploaded PO file is identical to existing one if( $pofile->exists() && $pofile->md5() === $upload->md5() ){ throw new Loco_error_Exception( __('Your file is identical to the existing one','loco-translate') ); } // recompile all files including uploaded one $compiler = new Loco_gettext_Compiler($pofile); $compiler->writeAll($pomo,$project); // push recent items on file creation Loco_data_RecentItems::get()->pushBundle($bundle)->persist(); // Redirect to edit this PO. Sync may be required and we're not doing automatically here. $type = strtolower( $this->get('type') ); return Loco_mvc_AdminRouter::generate( sprintf('%s-file-edit',$type), [ 'path' => $pofile->getRelativePath($base), 'bundle' => $bundle->getHandle(), 'domain' => $project->getId(), ] ); } }src/ajax/FsConnectController.php000064400000015713147206622240012731 0ustar00expand() as $file ){ if( ! $this->api->authorizeDelete($file) ){ return false; } } // else no dependants failed deletable test return true; } /** * @param Loco_fs_File file being moved (must exist) * @param Loco_fs_File target path (should not exist) * @return bool */ private function authorizeMove( Loco_fs_File $source, Loco_fs_File $target = null ){ return $this->api->authorizeMove($source,$target); } /** * @param Loco_fs_File $file new file path (should not exist) * @return bool */ private function authorizeCreate( Loco_fs_File $file ){ return $this->api->authorizeCreate($file); } /** * @param Loco_fs_File $file path to update (should exist) * @return bool */ private function authorizeUpdate( Loco_fs_File $file ){ if( ! $this->api->authorizeUpdate($file) ){ return false; } // if backups are enabled, we need to be able to create new files too (i.e. update parent directory) if( Loco_data_Settings::get()->num_backups && ! $this->api->authorizeCopy($file) ){ return false; } // updating file will also recompile binary, which may or may not exist $files = new Loco_fs_Siblings($file); $mofile = $files->getBinary(); if( $mofile && ! $this->api->authorizeSave($mofile) ){ return false; } // else no dependants to update return true; } /** * @param Loco_fs_File $file path which may exist (update it) or may not (create it) * @return bool */ private function authorizeUpload( Loco_fs_File $file ){ if( $file->exists() ){ return $this->api->authorizeUpdate($file); } else { return $this->api->authorizeCreate($file); } } /** * {@inheritdoc} */ public function render(){ // establish operation being authorized (create,delete,etc..) $post = $this->validate(); $type = $post->auth; $func = 'authorize'.ucfirst($type); $auth = [ $this, $func ]; if( ! is_callable($auth) ){ throw new Loco_error_Exception('Unexpected file operation'); } // all auth methods require at least one file argument $file = new Loco_fs_File( $post->path ); $base = loco_constant('WP_CONTENT_DIR'); $file->normalize($base); $args = [$file]; // some auth methods also require a destination/target (move,copy,etc..) if( $dest = $post->dest ){ $file = new Loco_fs_File($dest); $file->normalize($base); $args[] = $file; } // call auth method and respond with status and prompt HTML if connect required try { $this->api = new Loco_api_WordPressFileSystem; if( call_user_func_array($auth,$args) ){ $this->set( 'authed', true ); $this->set( 'valid', $this->api->getOutputCredentials() ); $this->set( 'creds', $this->api->getInputCredentials() ); $this->set( 'method', $this->api->getFileSystem()->method ); $this->set( 'success', __('Connected to remote file system','loco-translate') ); // warning when writing to this location is risky (overwrites during wp update) if( Loco_data_Settings::get()->fs_protect && $file->getUpdateType() ){ if( 'create' === $type ){ $message = __('This file may be overwritten or deleted when you update WordPress','loco-translate'); } else if( 'delete' === $type ){ $message = __('This directory is managed by WordPress, be careful what you delete','loco-translate'); } else if( 'move' === $type ){ $message = __('This directory is managed by WordPress. Removed files may be restored during updates','loco-translate'); } else { $message = __('Changes to this file may be overwritten or deleted when you update WordPress','loco-translate'); } $this->set('warning',$message); } } else { $this->set( 'authed', false ); // HTML form should be set when authorization failed $html = $this->api->getForm(); if( '' === $html || ! is_string($html) ){ // this is the only non-error case where form will not be set. if( 'direct' === loco_constant('FS_METHOD') ){ $html = 'Remote connections are prevented by your WordPress configuration. Direct access only.'; } // else an unknown error occurred when fetching output from request_filesystem_credentials else { $html = 'Failed to get credentials form'; } // displaying error after clicking "connect" to avoid unnecessary warnings when operation may not be required $html = '

      Connection problem

      '.$html.'.

      '; } $this->set( 'prompt', $html ); // supporting text based on file operation type explains why auth is required if( 'create' === $type ){ $message = __('Creating this file requires permission','loco-translate'); } else if( 'delete' === $type ){ $message = __('Deleting this file requires permission','loco-translate'); } else if( 'move' === $type ){ $message = __('This move operation requires permission','loco-translate'); } else { $message = __('Saving this file requires permission','loco-translate'); } // message is printed before default text, so needs delimiting. $this->set('message',$message.'.'); } } catch( Loco_error_WriteException $e ){ $this->set('authed', false ); $this->set('reason', $e->getMessage() ); } return parent::render(); } }src/ajax/SyncController.php000064400000013727147206622240011766 0ustar00validate(); $bundle = Loco_package_Bundle::fromId( $post->bundle ); $project = $bundle->getProjectById( $post->domain ); if( ! $project instanceof Loco_package_Project ){ throw new Loco_error_Exception('No such project '.$post->domain); } // Merging on back end is only required if existing target file exists. // It always should do, and the editor is not permitted to contain unsaved changes when syncing. if( ! $post->has('path') ){ throw new Loco_error_Exception('path argument required'); } $file = new Loco_fs_File( $post->path ); $base = loco_constant('WP_CONTENT_DIR'); $file->normalize($base); $target = Loco_gettext_Data::load($file); // POT file always synced with source code $type = $post->type; if( 'pot' === $type ){ $potfile = null; } // allow front end to configure source file. (will have come from $target headers) else if( $post->sync ){ $potfile = new Loco_fs_File( $post->sync ); $potfile->normalize($base); } // else use project-configured template path (must return a file) else { $potfile = $project->getPot(); } // keep existing behaviour when template is missing, but add warning according to settings. if( $potfile && ! $potfile->exists() ){ $conf = Loco_data_Settings::get()->pot_expected; if( 2 === $conf ){ throw new Loco_error_Exception('Plugin settings disallow missing templates'); } if( 1 === $conf ){ // Translators: %s will be replaced with the name of a missing POT file Loco_error_AdminNotices::warn( sprintf( __('Falling back to source extraction because %s is missing','loco-translate'), $potfile->basename() ) ); } $potfile = null; } // defaults: no msgstr and no json $translate = false; $syncjsons = []; // Parse existing POT for source if( $potfile ){ $this->set('pot', $potfile->basename() ); try { $source = Loco_gettext_Data::load($potfile); } catch( Exception $e ){ // translators: Where %s is the name of the invalid POT file throw new Loco_error_ParseException( sprintf( __('Translation template is invalid (%s)','loco-translate'), $potfile->basename() ) ); } // Sync options are passed through from editor controller via JS $opts = new Loco_gettext_SyncOptions( new LocoPoHeaders ); $opts->setSyncMode( $post->mode ); // Only copy msgstr fields from source if it's a user-defined PO template and "copy translations" was selected. if( 'pot' !== $potfile->extension() ){ $translate = $opts->mergeMsgstr(); } // Only merge JSON translations if specified. This requires we know the localised path where they will be if( $opts->mergeJson() ){ $siblings = new Loco_fs_Siblings($potfile); $syncjsons = $siblings->getJsons( $project->getDomain()->getName() ); } } // else extract POT from source code else { $this->set('pot', '' ); $domain = (string) $project->getDomain(); $extr = new Loco_gettext_Extraction($bundle); $extr->addProject($project); // bail if any files were skipped if( $list = $extr->getSkipped() ){ $n = count($list); $maximum = Loco_mvc_FileParams::renderBytes( wp_convert_hr_to_bytes( Loco_data_Settings::get()->max_php_size ) ); $largest = Loco_mvc_FileParams::renderBytes( $extr->getMaxPhpSize() ); // Translators: (1) Number of files (2) Maximum size of file that will be included (3) Size of the largest encountered $text = _n('%1$s file has been skipped because it\'s %3$s. (Max is %2$s). Check all strings are present before saving.','%1$s files over %2$s have been skipped. (Largest is %3$s). Check all strings are present before saving.',$n,'loco-translate'); $text = sprintf( $text, number_format($n), $maximum, $largest ); // not failing, just warning. Nothing will be saved until user saves editor state Loco_error_AdminNotices::warn( $text ); } // Have source strings. These cannot contain any translations. $source = $extr->includeMeta()->getTemplate($domain); } // establish on back end what strings will be added, removed, and which could be fuzzy-matches $matcher = new Loco_gettext_Matcher($project); $matcher->loadRefs($source,$translate); // merging JSONs must be done before fuzzy matching as it may add source strings if( $syncjsons ) { $matcher->loadJsons($syncjsons); } // Fuzzy matching only applies to syncing PO files. POT files will always do hard sync (add/remove) if( 'po' === $type ){ $fuzziness = Loco_data_Settings::get()->fuzziness; $matcher->setFuzziness( (string) $fuzziness ); } else { $matcher->setFuzziness('0'); } // update matches sources, deferring unmatched for deferred fuzzy match $merged = clone $target; $merged->clear(); $this->set( 'done', $matcher->merge($target,$merged) ); $merged->sort(); $this->set( 'po', $merged->jsonSerialize() ); return parent::render(); } }src/ajax/DownloadController.php000064400000007336147206622240012620 0ustar00filename().'.po'); // Resolving script refs requires configured project $bundle = $this->getBundle(); $project = $this->getProject($bundle); // Create a temporary file for zip, which must work on disk, not in memory $path = wp_tempnam(); if( ! $path || ! file_exists($path) ){ throw new Loco_error_Exception('Failed to create temporary file for zip archive'); } register_shutdown_function('unlink',$path); // initialize zip loco_check_extension('zip'); $z = new ZipArchive; $z->open( $path, ZipArchive::CREATE); $z->setArchiveComment( $bundle->getName() ); $post = Loco_mvc_PostParams::get(); $data = Loco_gettext_Data::fromSource($post->source); $compiler = new Loco_gettext_Compiler($pofile); /* @var Loco_fs_DummyFile $file */ foreach( $compiler->writeAll($data,$project) as $file ){ $z->addFromString( $file->basename(), $file->getContents() ); } $z->close(); return file_get_contents($path); } /** * {@inheritdoc} */ public function render(){ $post = $this->validate(); $path = $this->get('path'); // The UI now replaces .mo with .zip, but requires the ZipArchive extension is installed. if( '.zip' === substr($path,-4) ){ return $this->renderArchive($path); } // Below is for direct .po/pot downloads, plus legacy .mo/l10n.php // mo is only used when zip is not available. php works but not hooked into UI. $file = new Loco_fs_File($path); $file->normalize( loco_constant('WP_CONTENT_DIR') ); $ext = Loco_gettext_Data::ext($file); // posted source must be clean and must parse as whatever the file extension claims to be $raw = $post->source; if( is_string($raw) && '' !== $raw ){ // compile source if target is MO if( 'mo' === $ext ) { $raw = Loco_gettext_Data::fromSource($raw)->msgfmt(); } // supporting .l10n.php for WordPress >= 6.5 else if( 'php' === $ext && class_exists('WP_Translation_File_PHP',false) ){ $raw = Loco_gettext_PhpCache::render( Loco_gettext_Data::fromSource($raw) ); } } // else file can be output directly if it exists. // note that files on disk will not be parsed or manipulated. they will download strictly as-is else if( $file->exists() ){ $raw = $file->getContents(); } // else we can't do anything except bail else { throw new Loco_error_Exception('File not found and no source posted'); } // Observe UTF-8 BOM setting for PO and POT only if( 'po' === $ext || 'pot' === $ext ){ $has_bom = "\xEF\xBB\xBF" === substr($raw,0,3); $use_bom = (bool) Loco_data_Settings::get()->po_utf8_bom; // only alter file if valid UTF-8. Deferring detection overhead until required if( $has_bom !== $use_bom && preg_match('//u',$raw) ){ if( $use_bom ){ $raw = "\xEF\xBB\xBF".$raw; // prepend } else { $raw = substr($raw,3); // strip bom } } } return $raw; } } src/error/ParseException.php000064400000002574147206622240012143 0ustar00context = []; $lines = preg_split( '/\\r?\\n/', $source, $line+1 ); $offset = $line - 1; if( isset($lines[$offset]) ){ $this->context[] = $lines[$offset]; $this->context[] = str_repeat(' ', max(0,$column-1) ).'^'; } $this->message = sprintf("Error at line %u, column %u: %s", $line, $column, $this->message ); return $this; } /** * @param int zero-based offset to failure point * @param string source in which to identify line and column * @return self */ public function setOffsetContext( $offset, $source ){ $lines = preg_split( '/\\r?\\n/', substr($source,0,$offset) ); $line = count($lines); $column = 1 + strlen( end($lines) ); return $this->setContext( $line, $column, $source ); } /** * @return string */ public function getContext(){ return is_array($this->context) ? implode("\n",$this->context) : ''; } } src/error/Success.php000064400000001203147206622240010606 0ustar00getMessage() ); } } src/error/WriteException.php000064400000000436147206622240012156 0ustar00errors as $previous ){ if( $error->isIdentical($previous) ){ return $previous; } } // if exception wasn't thrown we have to do some work to establish where it was invoked if( __FILE__ === $error->getRealFile() ){ $error->setCallee(1); } // write error immediately under WP_CLI if( 'cli' === PHP_SAPI && class_exists('WP_CLI',false) ){ $error->logCli(); return $error; } // else buffer notices for displaying when UI is ready $notices->errors[] = $error; // do late flush if we missed the boat if( did_action('loco_admin_init') ){ $notices->on_loco_admin_init(); } if( did_action('admin_notices') ){ $notices->on_admin_notices(); } // Log message automatically if enabled if( $error->loggable() ){ $error->log(); } return $error; } /** * Raise a success message * @param string $message * @return Loco_error_Exception */ public static function success( $message ){ $notice = new Loco_error_Success($message); return self::add( $notice->setCallee(1) ); } /** * Raise a failure message * @param string $message * @return Loco_error_Exception */ public static function err( $message ){ $notice = new Loco_error_Exception($message); return self::add( $notice->setCallee(1) ); } /** * Raise a warning message * @param string $message * @return Loco_error_Exception */ public static function warn( $message ){ $notice = new Loco_error_Warning($message); return self::add( $notice->setCallee(1) ); } /** * Raise a generic info message * @param string $message * @return Loco_error_Exception */ public static function info( $message ){ $notice = new Loco_error_Notice($message); return self::add( $notice->setCallee(1) ); } /** * Raise a debug notice, if debug is enabled * @param string $message * @return Loco_error_Debug */ public static function debug( $message ){ $notice = new Loco_error_Debug($message); $notice->setCallee(1); loco_debugging() and self::add( $notice ); return $notice; } /** * Destroy and return buffer * @return Loco_error_Exception[] */ public static function destroy(){ $notices = self::$singleton; if( $notices instanceof Loco_error_AdminNotices ){ $buffer = $notices->errors; $notices->errors = []; self::$singleton = null; return $buffer; } return []; } /** * @codeCoverageIgnore * @deprecated Since PHP 5.4 there is no need to cast array via calls to jsonSerialize */ public static function destroyAjax(){ $data = []; foreach( self::destroy() as $notice ){ $data[] = $notice->jsonSerialize(); } return $data; } /** * @return void */ private function flushHtml(){ if( $this->errors ){ $htmls = []; foreach( $this->errors as $error ){ $html = sprintf ( '

      %s: %s

      ', esc_html( $error->getTitle() ), esc_html( $error->getMessage() ) ); $styles = [ 'notice', 'notice-'.$error->getType() ]; if( $this->inline ){ $styles[] = 'inline'; } if( $links = $error->getLinks() ){ $styles[] = 'has-nav'; $html .= ''; } $htmls[] = '
      '.$html.'
      '; } $this->errors = []; echo implode("\n", $htmls),"\n"; } } /** * @return void */ private function flushCli(){ foreach( $this->errors as $e ){ $e->logCli(); } $this->errors = []; } /** * admin_notices action handler. */ public function on_admin_notices(){ if( ! $this->inline ){ $this->flushHtml(); } } /** * loco_admin_notices callback. * Unlike WordPress "admin_notices" this fires from within template layout at the point we want them, hence they are marked as "inline" */ public function on_loco_admin_notices(){ $this->inline = true; $this->flushHtml(); } /** * loco_admin_init callback * When we know a Loco admin controller will render the page we will control the point at which notices are printed */ public function on_loco_admin_init(){ $this->inline = true; } /** * @internal * Make sure we always see notices if hooks didn't fire */ public function __destruct(){ $this->inline = false; $this->flush(); // handle situation where test case will have lost the buffer if( $this->errors && 'cli' === PHP_SAPI ){ throw new RuntimeException('Notices not flushed before destruction'); } } /** * @param int $level * @return Loco_error_Exception[] */ public function filter( $level ){ $e = []; foreach( $this->errors as $error ){ if( $error->getLevel() <= $level ){ $e[] = $error; } } return $e; } /** * @internal */ public function flush(){ if( class_exists('WP_CLI',false) ){ $this->flushCli(); } else if( loco_doing_ajax() ){ $this->errors = []; } else if( 'cli' !== PHP_SAPI ){ $this->flushHtml(); } // else probably in unit test and not properly handled, leave significant errors in buffer else { $this->errors = $this->filter( Loco_error_Exception::LEVEL_WARNING ); } return $this; } } src/error/XmlParseException.php000064400000000271147206622240012614 0ustar00getMessage() ); } }src/error/Exception.php000064400000015455147206622240011152 0ustar00getPrevious() ) ){ $current = $next; } return $current; } /** * @return string */ public function getRealFile(){ if( $this->_file ){ return $this->_file; } return $this->getRootException()->getFile(); } /** * @return int */ public function getRealLine(){ if( $this->_line ){ return $this->_line; } return $this->getRootException()->getLine(); } /** * @return array */ public function getRealTrace(){ return $this->getRootException()->getTrace(); } /** * @param int $depth number of levels up from callee * @return Loco_error_Exception */ public function setCallee( $depth = 0, array $stack = null ){ if( is_null($stack) ) { $stack = debug_backtrace(0); } $callee = $stack[$depth]; $this->_file = $callee['file']; $this->_line = $callee['line']; // TODO could also log the stack trace from $depth upwards, but not required unless being logged or thrown return $this; } /** * Write this error to file regardless of log level * @return void */ public function log(){ $file = new Loco_fs_File( $this->getRealFile() ); $path = $file->getRelativePath( loco_plugin_root() ); $text = sprintf('[Loco.%s] "%s" in %s:%u', $this->getType(), $this->getMessage(), $path, $this->getRealLine() ); // separate error log for cli tests if( 'cli' === PHP_SAPI && defined('LOCO_TEST_DATA_ROOT') ){ error_log( '['.date('c').'] '.$text."\n", 3, 'debug.log' ); } // Else write to default PHP log, but note that WordPress may have set this to wp-content/debug.log. // If no `error_log` is set this will send message to the SAPI, so check your httpd/fast-cgi errors too. else { error_log( $text, 0 ); } } /** * Get view template for rendering error to HTML. * @return string path relative to root tpl directory */ public function getTemplate(){ return 'admin/errors/generic'; } /** * Get notice level short code as a string * @return string */ public function getType(){ return 'error'; } /** * Get verbosity level * @return int */ public function getLevel(){ return self::LEVEL_ERROR; } /** * Call wp cli logging function * @return void */ public function logCli(){ WP_CLI::error( $this->getMessage(), false ); } /** * Get localized notice level name * @return string */ public function getTitle(){ return __('Error','loco-translate'); } /** * @return array */ #[ReturnTypeWillChange] public function jsonSerialize(){ $a = [ 'code' => $this->getCode(), 'type' => $this->getType(), 'title' => $this->getTitle(), 'message' => $this->getMessage(), ]; /*if( loco_debugging() ){ $a['file'] = str_replace( ABSPATH, '', $this->getRealFile() ); $a['line'] = $this->getRealLine(); $a = self::recurseJsonSerialize($a,$this); }*/ return $a; } /** * @param string[] $a * @return array modified from $a * @codeCoverageIgnore */ private static function recurseJsonSerialize( array $a, Exception $child ){ $a['class'] = get_class($child); $a['trace'] = $child->getTraceAsString(); $parent = $child->getPrevious(); if( $parent instanceof Exception ){ $a['previous'] = self::recurseJsonSerialize([],$parent); } return $a; } /** * Push navigation links into error. Use for help pages etc.. * @param string $href * @param string $text * @return Loco_error_Exception */ public function addLink( $href, $text ){ $this->links[] = sprintf('%s', esc_url($href), esc_html($text) ); return $this; } /** * @return array */ public function getLinks(){ return $this->links; } /** * Convert generic exception to one of ours * @param Exception $e original error * @return Loco_error_Exception */ public static function convert( Exception $e ){ if( $e instanceof Loco_error_Exception ){ return $e; } return new Loco_error_Exception( $e->getMessage(), $e->getCode(), $e ); } /** * Test if this error should be automatically logged * @return bool */ public function loggable(){ if( $this->_log ){ // Log messages of minimum priority and up, depending on debug mode // note that non-debug level is in line with error_reporting set by WordPress (notices ignored) $priority = loco_debugging() ? Loco_error_Exception::LEVEL_DEBUG : Loco_error_Exception::LEVEL_WARNING; return $this->getLevel() <= $priority; } return false; } /** * Suppress logging for this error. e.g if you want to warn in UI but don't want to pollute log files. * @return self */ public function noLog(){ $this->_log = false; return $this; } /** * Check if passed exception is effectively the same as this one * @return bool */ public function isIdentical( Exception $other ){ return $this->getCode() === $other->getCode() && $this->getMessage() === $other->getMessage() && $this->getType() === ( $other instanceof Loco_error_Exception ? $other->getType() : 0 ); } } src/error/LocaleException.php000064400000000131147206622240012253 0ustar00getMessage(), 'loco' ); } /** * Log debugging message to file without raising admin notice * @codeCoverageIgnore */ public static function trace( ...$args ){ $message = array_shift($args); if( $args ){ $message = vsprintf($message,$args); } $debug = new Loco_error_Debug($message); $debug->setCallee(1); $debug->log(); } }src/error/Notice.php000064400000001254147206622240010425 0ustar00getMessage() ); } }src/Locale.php000064400000044006147206622240007254 0ustar00setSubtags( loco_parse_wp_locale($tag) ); } catch( Exception $e ){ // isValid should return false } do_action( 'loco_parse_locale', $locale, $tag ); return $locale; } /** * Construct from subtags NOT from composite tag. See self::parse * Note that this skips normalization and validation steps * @param string $lang * @param string $region * @param string $variant */ public function __construct( $lang = '', $region = '', $variant = '' ){ if( 1 == func_num_args() && isset($lang[3]) ){ throw new BadMethodCallException('Did you mean Loco_Locale::parse('.var_export($lang,1).') ?'); } $this->tag = compact('lang','region','variant'); } /** * Allow read access to subtags * @internal * @param string $t subtag * @return string */ public function __get( $t ){ return isset($this->tag[$t]) ? $this->tag[$t] : ''; } /** * Allow write access to subtags * @internal * @param string $t subtag, e.g. "lang" * @param string $s subtag value, e.g. "en" * @return void */ public function __set( $t, $s ){ if( isset($this->tag[$t]) ){ $this->tag[$t] = $s; $this->setSubtags( $this->tag ); } } /** * Set subtags as produced from loco_parse_wp_locale * @param string[] $tag * @return Loco_Locale */ public function setSubtags( array $tag ){ $this->valid = false; $default = [ 'lang' => '', 'region' => '', 'variant' => '' ]; // disallow setting of unsupported tags if( $bad = array_diff_key($tag, $default) ){ throw new Loco_error_LocaleException('Unsupported subtags: '.implode(',',$bad) ); } $tag += $default; // language tag is minimum requirement if( ! $tag['lang'] ){ throw new Loco_error_LocaleException('Locale must have a language'); } // no UN codes in Wordpress if( preg_match('/^\\d+$/',$tag['region']) ){ throw new Loco_error_LocaleException('Numeric regions not supported'); } // non-standard variant code. e.g. formal/informal if( is_array($tag['variant']) ){ $tag['variant'] = implode('_',$tag['variant']); } // normalize case $tag['lang'] = strtolower($tag['lang']); $tag['region'] = strtoupper($tag['region']); $tag['variant'] = strtolower($tag['variant']); // set subtags and invalidate cache of language tag $this->tag = $tag; $this->_tag = null; $this->icon = null; $this->valid = true; return $this; } /** * @return Loco_Locale */ public function normalize(){ try { $this->setSubtags( $this->tag ); } catch( Loco_error_LocaleException $e ){ $this->_tag = ''; $this->icon = null; $this->name = 'Invalid locale'; $this->_name = null; } return $this; } /** * @return string */ public function __toString(){ $str = $this->_tag; if( is_null($str) ){ $str = implode('_',array_filter($this->tag)); $this->_tag = $str; } return $str; } /** * @param bool $translate whether to get name in current display language * @return string | null */ public function getName( $translate = true ){ $name = $this->name; // use canonical native name only when current language matches // deliberately not matching whole tag such that fr_CA would show native name of fr_FR if( $translate ){ $locale = self::parse( function_exists('get_user_locale') ? get_user_locale() : get_locale() ); if( $this->lang === $locale->lang && $this->_name ){ $name = $this->_name; } /*/ Note that no dynamic translation of English name is performed, but can be filtered with loco_parse_locale else { $name = __($name,'loco-translate-languages'); }*/ } if( is_string($name) && '' !== $name ){ return $name; } return null; } /** * Get canonical native name as defined by WordPress * @return string | null */ public function getNativeName(){ $name = $this->_name; if( is_string($name) && '' !== $name ){ return $name; } return null; } /** * @return string */ public function getIcon(){ $icon = $this->icon; if( is_null($icon) ){ $tag = []; if( ! $this->tag['lang'] ){ $tag[] = 'lang lang-zxx'; } foreach( $this->tag as $class => $code ){ if( $code ){ $tag[] = $class.' '.$class.'-'.$code; } } $icon = strtolower( implode(' ',$tag) ); $this->icon = $icon; } return $icon; } /** * @param string $css CSS icon name * @return Loco_Locale */ public function setIcon( $css ){ if( $css ){ $this->icon = (string) $css; } else { $this->icon = null; } return $this; } /** * @param string $english_name * @param string $native_name * @return Loco_Locale */ public function setName( $english_name, $native_name = '' ){ $this->name = apply_filters('loco_locale_name', $english_name, $native_name ); $this->_name = (string) $native_name; return $this; } /** * Test whether locale is valid * @return bool */ public function isValid(){ if( is_null($this->valid) ){ $this->normalize(); } return $this->valid; } /** * Resolve this locale's "official" name from WordPress's translation api * @return string English name currently set */ public function fetchName( Loco_api_WordPressTranslations $api ){ $tag = (string) $this; // pull from WordPress translations API if network allowed $locale = $api->getLocale($tag); if( $locale ){ $this->setName( $locale->getName(false), $locale->getNativeName() ); } return $this->getName(false); } /** * Resolve this locale's name from compiled Loco data * @return string English name currently set */ public function buildName(){ // should at least have a language or not valid if( $this->isValid() ){ $code = $this->tag['lang']; $db = Loco_data_CompiledData::get('languages'); if( $name = $db[$code] ){ // if variant is present add only that in brackets (no lookup required) if( $code = $this->tag['variant'] ){ $name .= ' ('.ucfirst($code).')'; } // else add region in brackets if present else if( $code = $this->tag['region'] ){ $db = Loco_data_CompiledData::get('regions'); if( $extra = $db[$code] ){ $name .= ' ('.$extra.')'; } else { $name .= ' ('.$code.')'; } } $this->setName( $name ); } } else { $this->setName( __('Invalid locale','loco-translate') ); } return $this->getName(); } /** * Ensure locale has a label, even if it has to fall back to language code or error * @return string */ public function ensureName( Loco_api_WordPressTranslations $api ){ $name = $this->getName(); if( ! $name ){ $name = $this->fetchName($api); // failing that, build own own name from components if( ! $name ){ $name = $this->buildName(); // last resort, use tag as name if( ! $name ){ $name = (string) $this; $this->setName( $name ); } } } return $name; } /** * @return array */ #[ReturnTypeWillChange] public function jsonSerialize(){ $a = $this->tag; $a['label'] = $this->getName(); // plural data expected by editor $p = $this->getPluralData(); $a['pluraleq'] = $p[0]; $a['nplurals'] = count($p[1]); $a['plurals'] = $this->getPluralForms(); // tone setting may used by some external translation providers $a['tone'] = $this->getFormality(); return $a; } private function getPluralData(){ if( is_null($this->plurals) ){ $lc = strtolower($this->lang); $db = Loco_data_CompiledData::get('plurals'); $id = $lc && isset($db[$lc]) ? $db[$lc] : 0; list( $this->pluraleq, $this->plurals ) = $db[''][$id]; } return [ $this->pluraleq, $this->plurals ]; } /** * Get translated plural form labels * @return string[] */ public function getPluralForms(){ list( , $plurals ) = $this->getPluralData(); $nplurals = count($plurals); // Languages with no plural forms, where n always yields 0. The UI doesn't show a label for this. if( 1 === $nplurals ){ return [ 'All' ]; } // Germanic plurals can show singular/plural as per source string text boxes // Note that french style plurals include n=0 under the "Single", but we will show "Single (0,1)" if( 2 === $nplurals ){ $l10n = [ 'one' => _x('Single','Editor','loco-translate'), 'other' => _x('Plural',"Editor",'loco-translate'), ]; } // else translate all implemented plural forms and show sample numbers if useful: // for meaning of categories, see http://cldr.unicode.org/index/cldr-spec/plural-rules else { $l10n = [ // Translators: Plural category for zero quantity 'zero' => _x('Zero','Plural category','loco-translate'), // Translators: Plural category for singular quantity 'one' => _x('One','Plural category','loco-translate'), // Translators: Plural category used in some multi-plural languages 'two' => _x('Two','Plural category','loco-translate'), // Translators: Plural category used in some multi-plural languages 'few' => _x('Few','Plural category','loco-translate'), // Translators: Plural category used in some multi-plural languages 'many' => _x('Many','Plural category','loco-translate'), // Translators: General plural category not covered by other forms 'other' => _x('Other','Plural category','loco-translate'), ]; } // process labels to be shown in editor tab, appending sample values of `n` if useful $labels = []; foreach( $plurals as $sample => $tag ){ if( is_int($sample) ){ $sample = sprintf('%u',$sample); } // if CLDR tag is to be used we'll need to translate it if( array_key_exists($tag,$l10n) ){ $name = $l10n[$tag]; } else { $name = $tag; } // show just samples if no name if( '' === $name ){ $labels[] = $sample; } // show just name if label is numeric, or samples are redundant else if( preg_match('/\\d/',$name) || ( 'one' === $tag && '1' === $sample ) || ( 'two' === $tag && '2' === $sample ) || ( 'zero' === $tag && '0' === $sample ) || ( 'other' === $tag && 2 === $nplurals ) ){ $labels[] = $name; } // else both - most common for standard CLDR forms else { $labels[] = sprintf('%s (%s)', $name, $sample ); } } return $labels; } /** * Get PO style Plural-Forms header value comprising number of forms and integer equation for n * @return string */ public function getPluralFormsHeader(){ list( $equation, $forms ) = $this->getPluralData(); return sprintf('nplurals=%u; plural=%s;', count($forms), $equation ); } /** * Apply PO style Plural-Forms header. * @param string $str header value e.g. "nplurals=2; plural=n != 1;" * @return void */ public function setPluralFormsHeader( $str ){ if( ! preg_match('#^nplurals=(\\d);\\s*plural=([-+/*%!=<>|&?:()n\\d ]+);?$#', $str, $match ) ){ throw new InvalidArgumentException('Invalid Plural-Forms header, '.json_encode($str) ); } $nplurals = (int) $match[1]; $pluraleq = trim( $match[2],' '); // single form requires no further inspection if( 2 > $nplurals ){ $this->pluraleq = '0'; $this->plurals = ['other']; return; } // Override new equation in all cases $previous = $this->getPluralData()[0]; $this->pluraleq = $pluraleq; // quit asap if plural forms being set aren't changing anything if( $nplurals === count($this->plurals) && self::hashPlural($previous) === self::hashPlural($pluraleq) ){ return; } // compile sample keys as per built-in CLDR rule for this language $keys = []; $formula = new Plural_Forms($pluraleq); $ns = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,20,21,22,30,31,32,100,101,102,103,104,111,200,201,202,301,302]; for( $i = 0; $i < $nplurals; $i++ ){ $sample = []; $suffix = ''; foreach( $ns as $j => $n ){ if( is_null($n) || $formula->execute($n) !== $i ){ continue; } $ns[$j] = null; if( array_key_exists(2,$sample) ){ $suffix = "\xE2\x80\xA6"; break; } else { $sample[] = $n; } } $keys[] = implode(',',$sample).$suffix; } // cast to string for comparison due to PHP forcing integer keys in this->plurals $expect = implode('|',$keys); $actual = implode('|',array_keys($this->plurals)); // use mnemonic tags only if they match the default (CLDR) tags for the current language if( $expect !== $actual ){ // exception when two forms only and the first accepts n=1 and second n=2 if( 2 === $nplurals && 0 === $formula->execute(1) && 1 === $formula->execute(2) ){ $tags = ['one','other']; } // blanking CLDR tags means only samples will be used as labels else { $tags = array_fill(0,$nplurals,''); // Translators: Shown when a PO file's Plural-Forms header has a different formula from the Unicode CLDR rules Loco_error_AdminNotices::info( __('Plural forms differ from Loco Translate\'s built in rules for this language','loco-translate') ); } // set new plural forms $this->plurals = array_combine($keys,$tags); } } /** * Crude normalizer for a plural equation such that similar formulae can be compared. * @param string $str original plural equation * @return string signature for comparison */ private static function hashPlural( $str ){ return trim( str_replace([' ','<>'],['','!='],$str), '()' ); } /** * Get formality setting, whether implied or explicit. * @return string either "", "formal" or "informal" */ public function getFormality(){ $value = ''; $tag = $this->__toString(); $variant = $this->variant; if( '' === $variant ){ // if a formal variant exists, tone may be implied informal $d = Loco_data_CompiledData::get('locales'); if( $d->offsetExists($tag.'_formal') ){ if( ! $d->offsetExists($tag.'_informal') ) { $value = 'informal'; } } // if an informal variant exists, tone may be implied formal else if( $d->offsetExists($tag.'_informal') ){ if( ! $d->offsetExists($tag.'_formal') ) { $value = 'formal'; } } } else if( 'formal' === $variant || 'informal' === $variant ){ $value = $variant; } return apply_filters('loco_locale_formality',$value,$tag); } } // Depends on compiled library if( ! function_exists('loco_parse_wp_locale') ){ loco_require_lib('compiled/locales.php'); } languages/loco-translate.pot000064400000157514147206622240012207 0ustar00#, fuzzy msgid "" msgstr "" "Project-Id-Version: Loco Translate 2.6.11\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/loco-translate/\n" "POT-Creation-Date: 2024-07-16 12:43+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: \n" "Language: \n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Loco https://localise.biz/\n" "X-Loco-Version: 2.6.11; wp-6.6-RC4-58723\n" "X-Domain: loco-translate" #. Author credit: (1) Product name (2) version number, (3) author name. #: src/package/Header.php:89 #, php-format msgid "\"%1$s\" %2$s by %3$s" msgstr "" #. %s refers to a directory name which was expected to be an ordinary file #: src/fs/FileWriter.php:218 #, php-format msgid "\"%s\" is a directory, not a file" msgstr "" #: tpl/admin/bundle/alias.php:11 msgid "\"Hello Dolly\" is part of the WordPress core" msgstr "" #. (1) Number of files (2) Maximum size of file that will be included (3) Size of the largest encountered #: src/ajax/SyncController.php:99 #, php-format msgid "" "%1$s file has been skipped because it's %3$s. (Max is %2$s). Check all " "strings are present before saving." msgid_plural "" "%1$s files over %2$s have been skipped. (Largest is %3$s). Check all strings " "are present before saving." msgstr[0] "" msgstr[1] "" #. 1: Number of strings; 2: Name of POT file; e.g. "100 strings found in file.pot" #: src/admin/init/InitPoController.php:249 #, php-format msgid "%1$s string found in %2$s" msgid_plural "%1$s strings found in %2$s" msgstr[0] "" msgstr[1] "" #. 1: Number of strings; 2: Service provider; e.g. "50 strings translated via Google Translate" #: src/js/Strings.php:74 #, javascript-format msgid "%1$s string translated via %2$s" msgid_plural "%1$s strings translated via %2$s" msgstr[0] "" msgstr[1] "" #. %s refers to the name of a new file to be created, but which already existed #: src/api/WordPressFileSystem.php:87 #, php-format msgid "%s already exists in this folder" msgstr "" #. characters meaning individual unicode characters of source text #: src/js/Strings.php:62 #, javascript-format msgid "%s characters will be sent for translation." msgstr "" #. Warning that deleting a file will also delete others. %s indicates that quantity. #: src/admin/file/DeleteController.php:104 #, php-format msgid "%s dependent file will also be deleted" msgid_plural "%s dependent files will also be deleted" msgstr[0] "" msgstr[1] "" #. %s is a quantity of files which were found, but whose context is unknown #: src/admin/bundle/SetupController.php:115 #, php-format msgid "%s file can't be matched to a known set of strings" msgid_plural "%s files can't be matched to a known set of strings" msgstr[0] "" msgstr[1] "" #. %s is the quantity of files which failed to be moved #: src/admin/file/MoveController.php:92 #, php-format msgid "%s file could not be moved" msgid_plural "%s files could not be moved" msgstr[0] "" msgstr[1] "" #. Where %s is the type of file, e.g. "po" #: tpl/admin/file/info.php:24 #, php-format msgid "%s file is writeable" msgstr "" #. %s is the quantity of files which were successfully moved #: src/admin/file/MoveController.php:87 #, php-format msgid "%s file moved" msgid_plural "%s files moved" msgstr[0] "" msgstr[1] "" #. Success notice where %s is a file extension, e.g. "PO" #: src/gettext/Compiler.php:291 src/admin/file/HeadController.php:81 #, php-format msgid "%s file saved" msgstr "" #. Shows number of fuzzy strings at top of editor #: src/js/Strings.php:114 src/gettext/Metadata.php:205 #, javascript-format msgid "%s fuzzy" msgstr "" #. Error thrown when attempting to parse a file that is not a supported translation file format #: src/gettext/Data.php:25 #, php-format msgid "%s is not a Gettext file" msgstr "" #. Summary of new strings after running in-editor Sync #: src/js/Strings.php:25 #, javascript-format msgid "%s new string added" msgid_plural "%s new strings added" msgstr[0] "" msgstr[1] "" #. Summary of existing strings that no longer exist after running in-editor Sync #: src/js/Strings.php:29 #, javascript-format msgid "%s obsolete string removed" msgid_plural "%s obsolete strings removed" msgstr[0] "" msgstr[1] "" #. Where %s is the size of a file #: tpl/admin/init/init-pot.php:16 #, php-format msgid "%s on disk" msgstr "" #. %s refers to a non-zero amount of errors detected. #: src/js/Strings.php:51 #, javascript-format msgid "%s possible error detected" msgid_plural "%s possible errors detected" msgstr[0] "" msgstr[1] "" #. Were %s is number of source files that will be scanned #: src/admin/init/InitPoController.php:280 #, php-format msgid "%s source file will be scanned for translatable strings" msgid_plural "%s source files will be scanned for translatable strings" msgstr[0] "" msgstr[1] "" #. Shows total string count at top of editor #. Where %s is any number of strings #: src/js/Strings.php:106 src/gettext/Metadata.php:191 #, javascript-format msgid "%s string" msgid_plural "%s strings" msgstr[0] "" msgstr[1] "" #. Summary of existing translations where the source text has changed slightly #: src/js/Strings.php:33 #, javascript-format msgid "%s string marked Fuzzy" msgid_plural "%s strings marked Fuzzy" msgstr[0] "" msgstr[1] "" #. %s is the quantity of strings modified during a batch process #: src/js/Strings.php:78 #, javascript-format msgid "%s string updated" msgid_plural "%s strings updated" msgstr[0] "" msgstr[1] "" #. Summary of translations copied from a PO file during Sync #: src/js/Strings.php:37 #, javascript-format msgid "%s translation copied" msgid_plural "%s translations copied" msgstr[0] "" msgstr[1] "" #. %s: the number of strings to be sent for translation #: src/js/Strings.php:58 #, javascript-format msgid "%s unique source strings." msgstr "" #. Shows number of untranslated strings at top of editor #: src/js/Strings.php:118 src/gettext/Metadata.php:209 #, javascript-format msgid "%s untranslated" msgstr "" #: tpl/admin/file/info-pot.php:31 #, php-format msgid "%s word" msgid_plural "%s words" msgstr[0] "" msgstr[1] "" #. Shows percentage translated at top of editor #: src/js/Strings.php:110 src/gettext/Metadata.php:202 #, javascript-format msgid "%s%% translated" msgstr "" #. %u is a number of files which were successfully deleted #: src/admin/file/DeleteController.php:59 msgid "%u file deleted" msgid_plural "%u files deleted" msgstr[0] "" msgstr[1] "" #: tpl/admin/config/version.php:14 msgid "A newer version of Loco Translate is available for download" msgstr "" #: tpl/admin/init/init-prompt.php:31 tpl/admin/init/init-pot.php:42 msgid "About templates" msgstr "" #: tpl/admin/errors/no-tokenizer.php:11 msgid "About the Tokenizer" msgstr "" #: tpl/admin/errors/file-sec.php:14 msgid "Access to this file is blocked for security reasons" msgstr "" #: tpl/admin/root.php:39 msgid "Active theme:" msgstr "" #: tpl/ajax/modal-apis-empty.php:11 msgid "Add automatic translation services in the plugin settings." msgstr "" #: tpl/admin/bundle/conf.php:142 msgid "Add set" msgstr "" #: tpl/admin/config/settings.php:75 msgid "Add UTF-8 byte order mark" msgstr "" #: tpl/admin/bundle/view.php:29 msgid "Additional files found" msgstr "" #. %s will be replaced with a URL which may change without affecting the translation. #: src/hooks/AdminHooks.php:92 #, php-format msgid "" "Administrators and auditors may wish to review Loco's plugin " "privacy notice." msgstr "" #: src/admin/bundle/BaseController.php:123 tpl/admin/file/conf.php:58 #: tpl/admin/file/move.php:26 msgid "Advanced" msgstr "" #: src/admin/bundle/ConfController.php:137 #: tpl/admin/bundle/setup/inc-nav.php:19 tpl/admin/bundle/setup/core.php:17 msgid "Advanced configuration" msgstr "" #: src/admin/bundle/ConfController.php:52 msgid "Advanced tab" msgstr "" #: src/mvc/AjaxRouter.php:175 msgid "Ajax controller returned empty JSON" msgstr "" #. Fatal error where %s represents an unexpected value #: src/mvc/AjaxRouter.php:170 #, php-format msgid "Ajax route not found: \"%s\"" msgstr "" #: src/admin/config/SettingsController.php:66 msgid "Allow" msgstr "" #: src/admin/config/SettingsController.php:67 msgid "Allow (with warning)" msgstr "" #: tpl/admin/config/settings.php:216 msgid "Allow full access to these roles" msgstr "" #: src/admin/config/ApisController.php:65 msgid "API key" msgstr "" #: src/admin/config/ApisController.php:12 #: src/admin/config/BaseController.php:20 #: src/admin/config/BaseController.php:40 tpl/admin/help/tab-config-apis.php:2 msgid "API keys" msgstr "" #: src/admin/config/ApisController.php:67 msgid "API region" msgstr "" #: src/admin/config/ApisController.php:66 msgid "API URL" msgstr "" #: tpl/admin/file/delete.php:16 msgid "" "Are you sure you want to permanently delete the following " "file?" msgstr "" #: src/admin/init/InitPoController.php:293 msgid "Assign template" msgstr "" #: tpl/admin/bundle/setup.php:36 msgid "Author details" msgstr "" #: tpl/ajax/modal-apis-batch.php:10 msgid "Auto-translate this file" msgstr "" #: tpl/admin/init/init-po.php:51 msgid "Available languages" msgstr "" #: tpl/admin/errors/no-backups.php:16 msgid "" "Backup files will be written when you save translations from Loco Translate " "editor" msgstr "" #: tpl/admin/file/info-po.php:47 msgid "Binary file missing" msgstr "" #: tpl/admin/bundle/conf.php:128 msgid "Blocked paths" msgstr "" #: tpl/admin/bundle/setup/meta.php:11 tpl/admin/bundle/setup/core.php:10 msgid "Bundle auto-configured" msgstr "" #: tpl/admin/bundle/setup/saved.php:11 msgid "Bundle configuration saved" msgstr "" #: tpl/admin/bundle/locale.php:32 tpl/admin/list/inc-table.php:11 msgid "Bundle name" msgstr "" #: src/admin/bundle/SetupController.php:35 msgid "Bundle setup" msgstr "" #: tpl/admin/file/diff.php:17 msgctxt "Button label for a next revision" msgid "Next" msgstr "" #: tpl/admin/file/diff.php:14 msgctxt "Button label for a previous revision" msgid "Previous" msgstr "" #: tpl/admin/bundle/setup/conf.php:56 msgid "Cancel" msgstr "" #: src/ajax/FsConnectController.php:130 msgid "" "Changes to this file may be overwritten or deleted when you update WordPress" msgstr "" #: src/js/Strings.php:136 msgid "Check console output for debugging information" msgstr "" #: src/js/Strings.php:54 msgid "Check the translations marked with a warning sign" msgstr "" #: src/admin/bundle/ConfController.php:92 msgid "Child theme declares the same Text Domain as the parent theme" msgstr "" #: tpl/admin/init/init-po.php:32 msgid "Choose a language" msgstr "" #: tpl/admin/init/upload.php:15 tpl/admin/init/init-po.php:77 msgid "Choose a location" msgstr "" #: tpl/admin/file/move-po.php:14 msgid "Choose a new location for these translations" msgstr "" #: tpl/admin/common/inc-fsconn.php:56 msgid "Click \"Connect\" to authenticate with the server" msgstr "" #. %s is a URL. Keep the tag intact #: tpl/admin/bundle/view.php:35 tpl/admin/bundle/view.php:57 #, php-format msgid "" "Click the setup tab to complete the bundle configuration" msgstr "" #: tpl/admin/file/info-mo.php:15 msgid "compiled" msgstr "" #: tpl/admin/file/info-mo.php:27 msgid "Compiled translations" msgstr "" #: tpl/admin/config/settings.php:145 tpl/admin/config/settings.php:151 msgid "Compiling JSON files" msgstr "" #: tpl/admin/config/settings.php:120 tpl/admin/config/settings.php:126 msgid "Compiling MO files" msgstr "" #: src/admin/bundle/BaseController.php:66 msgid "Configuration reset" msgstr "" #: src/admin/bundle/BaseController.php:51 msgid "Configuration saved" msgstr "" #. where %s is a plugin or theme #: src/admin/bundle/ConfController.php:16 #, php-format msgid "Configure %s" msgstr "" #: tpl/admin/file/delete.php:12 msgid "Confirm delete" msgstr "" #: tpl/admin/file/move.php:14 msgid "Confirm relocation" msgstr "" #: tpl/admin/common/inc-fsconn.php:60 msgid "Connect" msgstr "" #: src/ajax/FsConnectController.php:117 msgid "Connected to remote file system" msgstr "" #: tpl/admin/bundle/inc-po-table.php:60 tpl/admin/bundle/locale.php:72 msgid "Copy" msgstr "" #. %s will be replaced with a PO file name #: src/admin/init/InitPoController.php:385 #, php-format msgid "Copy %s instead" msgstr "" #: src/admin/init/InitPoController.php:237 msgid "Copy PO file" msgstr "" #: tpl/admin/init/init-po.php:133 tpl/admin/file/conf.php:32 #, php-format msgid "Copy target translations from \"%s\"" msgstr "" #. Page title for core WordPress translations #: src/mvc/AdminRouter.php:56 msgid "Core translations ‹ Loco" msgstr "" #: src/admin/init/InitPoController.php:218 #: src/admin/bundle/ViewController.php:133 tpl/admin/init/init-pot.php:40 msgid "Create template" msgstr "" #: src/admin/init/InitPoController.php:389 msgid "Create template instead" msgstr "" #: src/ajax/FsConnectController.php:154 msgid "Creating this file requires permission" msgstr "" #: tpl/admin/bundle/setup.php:27 msgid "Current configuration as XML" msgstr "" #: tpl/admin/init/init-po.php:63 msgid "Custom language" msgstr "" #: src/admin/bundle/ViewController.php:152 msgid "Custom translations may not work without System translations installed" msgstr "" #: src/mvc/AdminRouter.php:77 src/error/Debug.php:19 #: src/admin/config/DebugController.php:12 #: src/admin/bundle/BaseController.php:128 msgid "Debug" msgstr "" #: src/admin/file/BaseController.php:122 tpl/admin/file/diff.php:32 #: tpl/admin/bundle/inc-po-table.php:66 tpl/admin/bundle/locale.php:75 msgid "Delete" msgstr "" #. Page title where %s is the name of a file to be deleted #: src/admin/file/DeleteController.php:79 #: src/admin/file/DeleteController.php:98 #, php-format msgid "Delete %s" msgstr "" #: tpl/admin/file/delete.php:30 msgid "Delete Permanently" msgstr "" #: tpl/admin/config/settings.php:162 msgid "Delete redundant files" msgstr "" #: src/ajax/FsConnectController.php:157 msgid "Deleting this file requires permission" msgstr "" #. Help tip for "Project name" field in advanced bundle config #: tpl/admin/bundle/conf.php:35 msgid "Descriptive name for this set of translatable strings" msgstr "" #: tpl/admin/file/info.php:55 msgid "Directory doesn't exist" msgstr "" #: tpl/admin/file/info.php:69 msgid "Directory is writeable" msgstr "" #: src/admin/config/SettingsController.php:68 msgid "Disallow" msgstr "" #: src/admin/file/EditController.php:120 src/admin/file/EditController.php:170 #: src/admin/bundle/LocaleController.php:130 tpl/admin/config/settings.php:234 #: tpl/admin/config/apis.php:122 tpl/admin/config/prefs.php:45 #: tpl/admin/common/inc-fsconn.php:16 tpl/admin/common/inc-fsconn.php:41 #: tpl/admin/errors/no-backups.php:22 msgid "Documentation" msgstr "" #: tpl/admin/bundle/conf.php:81 msgid "Domain path" msgstr "" #: src/mvc/AjaxRouter.php:198 msgid "Download action not found" msgstr "" #: src/mvc/AjaxRouter.php:203 msgid "Download controller returned empty output" msgstr "" #: tpl/admin/bundle/inc-po-table.php:51 tpl/admin/bundle/locale.php:63 msgid "Edit" msgstr "" #: tpl/admin/bundle/setup/saved.php:19 msgid "Edit config" msgstr "" #: tpl/admin/file/head.php:10 msgid "Edit file headers" msgstr "" #: src/admin/bundle/ViewController.php:124 msgid "Edit template" msgstr "" #. %1$s is the file name, %2$s is the bundle name #: src/admin/file/EditController.php:18 #, php-format msgid "Editing %1$s in %2$s" msgstr "" #: tpl/admin/config/settings.php:197 msgid "Editing of POT (template) files" msgstr "" #: src/admin/file/BaseController.php:117 msgid "Editor" msgstr "" #. Where %s is the name of the language, e.g. "French translation" #: src/js/Strings.php:150 #, javascript-format msgctxt "Editor" msgid "%s translation" msgstr "" #. button for adding a new string when manually editing a POT file #: src/admin/file/EditController.php:198 msgctxt "Editor" msgid "Add" msgstr "" #. Button that opens window for auto-translating #: src/admin/file/EditController.php:209 msgctxt "Editor" msgid "Auto" msgstr "" #. Button that validates current translation formatting #: src/admin/file/EditController.php:211 msgctxt "Editor" msgid "Check" msgstr "" #. Label for the window pane for entering translator comments #: src/js/Strings.php:156 msgctxt "Editor" msgid "Comments" msgstr "" #. Label for the window pane holding message context #: src/js/Strings.php:153 msgctxt "Editor" msgid "Context" msgstr "" #. Label for the context window when no translation selected #: src/js/Strings.php:174 msgctxt "Editor" msgid "Context not loaded" msgstr "" #. Button for downloading a PO, MO or POT file #: src/admin/file/EditController.php:213 msgctxt "Editor" msgid "Download" msgstr "" #. Label for the plural form of the original English text #: src/Locale.php:402 src/js/Strings.php:90 msgctxt "Editor" msgid "Plural" msgstr "" #. button for removing a string when manually editing a POT file #: src/admin/file/EditController.php:200 msgctxt "Editor" msgid "Remove" msgstr "" #. Button that reloads current screen #: src/admin/file/EditController.php:207 msgctxt "Editor" msgid "Revert" msgstr "" #. Button that saves translations to disk #: src/admin/file/EditController.php:203 msgctxt "Editor" msgid "Save" msgstr "" #. Label for the singular form of the original English text #: src/Locale.php:401 src/js/Strings.php:93 msgctxt "Editor" msgid "Single" msgstr "" #. Label for the window pane holding the original English text #. List heading showing preview of English text for each item #: src/js/Strings.php:146 tpl/admin/file/info-pot.php:30 msgctxt "Editor" msgid "Source text" msgstr "" #. Label for the source text window when no translation selected #: src/js/Strings.php:171 msgctxt "Editor" msgid "Source text not loaded" msgstr "" #: src/js/Strings.php:168 msgctxt "Editor" msgid "Suggest translation" msgstr "" #. Button that runs in-editor sync/operation #: src/admin/file/EditController.php:205 msgctxt "Editor" msgid "Sync" msgstr "" #. Button that toggles between "code" and regular text editing modes #: src/admin/file/EditController.php:219 msgctxt "Editor" msgid "Toggle code view" msgstr "" #: src/js/Strings.php:165 msgctxt "Editor" msgid "Toggle Fuzzy" msgstr "" #. Button that toggles invisible characters #: src/admin/file/EditController.php:217 msgctxt "Editor" msgid "Toggle invisibles" msgstr "" #: src/js/Strings.php:162 msgctxt "Editor" msgid "Translated" msgstr "" #. List heading showing preview of translated text for each item #: src/js/Strings.php:180 msgctxt "Editor" msgid "Translation" msgstr "" #. Label for the translation editing window when no translation selected #: src/js/Strings.php:177 msgctxt "Editor" msgid "Translation not loaded" msgstr "" #: src/js/Strings.php:159 msgctxt "Editor" msgid "Untranslated" msgstr "" #: tpl/admin/errors/file-missing.php:14 msgid "" "Either this file is missing or the server doesn't have permission to access " "it" msgstr "" #: tpl/admin/config/settings.php:81 msgid "Enable Ajax file uploads" msgstr "" #: tpl/admin/file/move-pot.php:14 msgid "Enter a new location for this file" msgstr "" #: src/js/Strings.php:121 src/error/Exception.php:165 msgid "Error" msgstr "" #: tpl/admin/bundle/conf.php:89 tpl/admin/bundle/conf.php:108 msgid "Exclude" msgstr "" #. (1) Number of files excluded (2) the maximum size of file that was included #: tpl/admin/init/init-pot.php:24 #, php-format msgid "Excludes %1$s file over %2$s" msgid_plural "Excludes %1$s files over %2$s" msgstr[0] "" msgstr[1] "" #: tpl/admin/config/settings.php:21 tpl/admin/config/settings.php:27 msgid "Extracting strings" msgstr "" #: src/fs/FileWriter.php:298 msgid "Failed to build directory path" msgstr "" #. %s refers to a file name, for which the chmod operation failed. #: src/fs/FileWriter.php:133 #, php-format msgid "Failed to chmod %s" msgstr "" #: src/fs/FileWriter.php:52 msgid "Failed to connect to remote server" msgstr "" #. (1) Source file name (2) Target file name #: src/fs/FileWriter.php:162 #, php-format msgid "Failed to copy %1$s to %2$s" msgstr "" #. %s refers to a directory where a backup file could not be created due to file permissions #: src/fs/Revisions.php:222 #, php-format msgid "" "Failed to create backup file in \"%s\". Check file permissions or disable " "backups" msgstr "" #: src/fs/FileWriter.php:290 msgid "Failed to create directory" msgstr "" #. %s refers to a file name, for which a delete operation failed. #: src/fs/FileWriter.php:201 #, php-format msgid "Failed to delete %s" msgstr "" #: src/fs/FileWriter.php:253 msgid "Failed to save file" msgstr "" #. %s will be replaced with the name of a missing POT file #: src/ajax/SyncController.php:55 #, php-format msgid "Falling back to source extraction because %s is missing" msgstr "" #: tpl/admin/errors/file-sec.php:11 msgid "File access disallowed" msgstr "" #: tpl/admin/errors/no-backups.php:18 msgid "File backups are disabled in your plugin settings" msgstr "" #: src/admin/file/DiffController.php:45 msgid "File deleted" msgstr "" #: src/admin/file/InfoController.php:169 msgid "File does not have a valid header" msgstr "" #: tpl/admin/file/info.php:14 msgid "File doesn't exist" msgstr "" #: src/admin/file/BaseController.php:119 tpl/admin/bundle/inc-po-table.php:25 #: tpl/admin/bundle/locale.php:43 msgid "File info" msgstr "" #: tpl/admin/errors/file-isdir.php:11 msgid "File is a directory" msgstr "" #: tpl/admin/file/view-mo.php:14 msgid "File is in binary MO format" msgstr "" #: tpl/admin/common/inc-fsconn.php:28 msgid "File is protected by the bundle configuration" msgstr "" #. Refers to bundled plugin or theme translation files - i.e. those supplied by the author #: src/fs/LocaleDirectory.php:63 msgctxt "File location" msgid "Author" msgstr "" #. Refers to translation files in Loco's custom/protected directory #: src/fs/LocaleDirectory.php:69 msgctxt "File location" msgid "Custom" msgstr "" #. Refers to translation files in an alternative location that isn't Author, System or Custom. #: src/fs/LocaleDirectory.php:72 msgctxt "File location" msgid "Other" msgstr "" #. Refers to system-installed translation files - i.e. those under WP_LANG_DIR #: src/fs/LocaleDirectory.php:66 msgctxt "File location" msgid "System" msgstr "" #: src/fs/FileWriter.php:309 msgid "File modification is disallowed by your WordPress config" msgstr "" #: tpl/admin/file/info-mo.php:21 tpl/admin/file/info-pot.php:24 #: tpl/admin/file/info-po.php:27 msgid "File modified" msgstr "" #: tpl/admin/errors/file-missing.php:11 msgid "File not found" msgstr "" #: tpl/admin/bundle/conf.php:53 msgid "File prefix" msgstr "" #: src/admin/file/DiffController.php:35 msgid "File restored" msgstr "" #: tpl/admin/file/info-mo.php:18 tpl/admin/file/info-pot.php:21 #: tpl/admin/file/info-po.php:24 msgid "File size" msgstr "" #: tpl/admin/config/settings.php:170 tpl/admin/config/settings.php:176 msgid "File system access" msgstr "" #. Help tip for "Source file paths" field in advanced bundle config #: tpl/admin/bundle/conf.php:113 msgid "Files and folders within the bundle that contain localized PHP code" msgstr "" #: tpl/admin/list/locales.php:22 msgid "Files found" msgstr "" #: tpl/admin/file/info.php:110 msgid "" "Files in this location can be modified or deleted by WordPress automatic " "updates" msgstr "" #. Placeholder text for text filter above editor #: src/admin/file/EditController.php:215 msgid "Filter translations" msgstr "" #. text field placeholder #: tpl/admin/common/inc-table-filter.php:6 msgid "Filter..." msgstr "" #: tpl/admin/bundle/inc-po-table.php:31 tpl/admin/bundle/locale.php:49 msgid "Folder" msgstr "" #. Help tip for "Domain path" field in advanced bundle config #: tpl/admin/bundle/conf.php:94 msgid "Folders within the bundle that contain author-supplied translations" msgstr "" #. Help tip for "Blocked paths" field in advanced bundle config #: tpl/admin/bundle/conf.php:134 msgid "Folders within the bundle that will never be searched for files" msgstr "" #: tpl/admin/help/tab-config.php:9 tpl/admin/help/tab-config-apis.php:9 msgid "Full documentation" msgstr "" #: tpl/admin/config/settings.php:99 msgid "Fuzzy matching tolerance" msgstr "" #: tpl/admin/config/settings.php:131 msgid "Generate hash tables" msgstr "" #: tpl/admin/bundle/setup/inc-nav.php:25 msgid "Get help with this" msgstr "" #: tpl/admin/help/tab-support.php:6 msgid "Getting help with Loco Translate" msgstr "" #: tpl/admin/bundle/alias.php:17 msgid "Go to WordPress Core" msgstr "" #: tpl/admin/config/settings.php:210 msgid "Grant access to roles" msgstr "" #: src/ajax/ApisController.php:26 src/admin/file/EditController.php:201 msgid "Help" msgstr "" #: src/mvc/AdminController.php:51 msgid "Help & support" msgstr "" #: src/mvc/AdminRouter.php:42 msgid "Home" msgstr "" #. Author URI of the plugin msgid "https://localise.biz/wordpress/plugin" msgstr "" #. URI of the plugin msgid "https://wordpress.org/plugins/loco-translate/" msgstr "" #: tpl/admin/bundle/setup.php:39 msgid "" "If you have trouble translating this bundle, consider asking the author for " "help" msgstr "" #: tpl/admin/bundle/setup/conf.php:49 msgid "" "If you've been given a configuration file by a developer, paste the XML code " "here" msgstr "" #: tpl/admin/bundle/setup/inc-nav.php:22 msgid "Import config from XML" msgstr "" #: tpl/admin/config/apis.php:112 msgid "Important" msgstr "" #: tpl/admin/file/info-po.php:60 msgid "In sync with template" msgstr "" #: tpl/admin/bundle/conf.php:85 tpl/admin/bundle/conf.php:104 msgid "Include" msgstr "" #: tpl/admin/config/settings.php:137 msgid "Include Fuzzy strings" msgstr "" #: tpl/admin/bundle/inc-po-table.php:57 tpl/admin/bundle/locale.php:69 msgid "Info" msgstr "" #: tpl/admin/help/side-bar.php:15 msgid "Info and tutorials" msgstr "" #. %s refers to the slug/handle of a theme or plugin #: src/admin/init/InitPoController.php:115 #, php-format msgid "Initializing new translations in \"%s\"" msgstr "" #: src/admin/init/InitPoController.php:119 msgid "Initializing new translations in unknown set" msgstr "" #: tpl/admin/config/version.php:18 msgid "Install manually" msgstr "" #: src/admin/bundle/LocaleController.php:116 #: src/admin/list/LocalesController.php:35 tpl/admin/init/init-po.php:45 msgid "Installed languages" msgstr "" #: src/Locale.php:329 msgid "Invalid locale" msgstr "" #: tpl/admin/bundle/setup/none.php:15 msgid "It needs configuring before you can do any translations" msgstr "" #. %s refers to a JSON file which could not be compiled due to an error #: src/gettext/Compiler.php:149 src/gettext/Compiler.php:214 #, php-format msgid "JSON compilation failed for %s" msgstr "" #: tpl/admin/init/init-po.php:139 tpl/admin/file/conf.php:38 msgid "Just copy English source strings" msgstr "" #. relative time when something happened in the last 30 seconds #: src/mvc/FileParams.php:149 msgid "Just now" msgstr "" #: src/js/Strings.php:99 msgid "Keep this translation" msgstr "" #: tpl/admin/bundle/inc-po-table.php:13 msgid "Language" msgstr "" #. Warning that the language in the file header (1) does not match the file name (2) #: src/admin/file/InfoController.php:177 #, php-format msgid "Language header is \"%1$s\" but file name contains \"%2$s\"" msgstr "" #: src/mvc/AdminRouter.php:59 msgid "Languages" msgstr "" #. Page title for installed languages page #: src/mvc/AdminRouter.php:61 msgid "Languages ‹ Loco" msgstr "" #. Where %s is the size of a file #: tpl/admin/init/init-pot.php:18 #, php-format msgid "largest is %s" msgstr "" #: tpl/admin/file/info-pot.php:27 msgid "Last extracted" msgstr "" #: tpl/admin/bundle/inc-po-table.php:28 tpl/admin/bundle/locale.php:46 #: tpl/admin/list/locales.php:19 tpl/admin/list/inc-table.php:17 msgid "Last modified" msgstr "" #: tpl/admin/file/info-mo.php:24 tpl/admin/file/info-po.php:30 msgid "Last translation" msgstr "" #: tpl/admin/bundle/setup/conf.php:55 msgid "Load config" msgstr "" #: src/js/Strings.php:96 msgid "Loading suggestions" msgstr "" #: tpl/admin/list/locales.php:16 msgid "Locale code" msgstr "" #: tpl/admin/list/locales.php:13 msgid "Locale name" msgstr "" #: tpl/admin/bundle/conf.php:71 tpl/admin/common/inc-fsconn.php:12 msgid "Locked" msgstr "" #. change "en" in the URL to your language if it's available at https://www.php.net/docs.php #: tpl/admin/errors/no-tokenizer.php:17 msgid "" "Loco requires the Tokenizer extension to scan PHP source code for " "translatable strings" msgstr "" #. Name of the plugin #: src/mvc/AdminRouter.php:37 src/hooks/AdminHooks.php:88 msgid "Loco Translate" msgstr "" #. home screen title where %s is the version number #: src/admin/RootController.php:23 #, php-format msgid "Loco Translate %s" msgstr "" #: src/mvc/AdminController.php:114 msgid "Loco Translate is powered by" msgstr "" #. %s refers to the name of a missing PHP extension, for example "mbstring". #: loco.php:138 #, php-format msgid "" "Loco Translate requires the \"%s\" PHP extension. Ask your hosting provider " "to install it" msgstr "" #. (1) is the name of a PHP script, (2) is a line number in the file #: src/output/Buffer.php:130 #, php-format msgid "Loco was interrupted by output from %1$s:%2$u" msgstr "" #. Page title for plugin home screen #: src/mvc/AdminRouter.php:39 msgid "Loco, Translation Management" msgstr "" #: tpl/ajax/modal-apis-batch.php:29 msgid "Mark new translations as Fuzzy" msgstr "" #: tpl/admin/config/settings.php:68 msgid "Maximum line length (zero disables wrapping)" msgstr "" #: tpl/admin/init/init-po.php:149 tpl/admin/file/conf.php:48 msgid "Merge strings from related JSON files" msgstr "" #. Where %s is the name of the POT template file. Message appears after sync #: src/js/Strings.php:18 #, javascript-format msgid "Merged from %s" msgstr "" #. Message appears after sync operation #: src/js/Strings.php:21 msgid "Merged from source code" msgstr "" #: src/js/Strings.php:194 msgid "Mismatching placeholder type; check against source text formatting" msgstr "" #. %s is the number of formatting arguments accepted by the source text of a translation #: src/js/Strings.php:191 #, javascript-format msgid "Missing placeholders; source text formatting suggests at least %s" msgstr "" #: tpl/admin/file/info-po.php:84 msgid "Missing template" msgstr "" #: src/ajax/MsginitController.php:63 msgid "MO file exists for this language already. Delete it first" msgstr "" #: tpl/admin/config/settings.php:187 msgid "Modification of installed files" msgstr "" #: src/fs/FileWriter.php:314 msgid "Modification of installed files is disallowed by the plugin settings" msgstr "" #: src/fs/FileWriter.php:325 msgid "" "Modification of POT (template) files is disallowed by the plugin settings" msgstr "" #: tpl/admin/file/view-pot.php:11 tpl/admin/file/edit-pot.php:22 #: tpl/admin/bundle/locale.php:14 tpl/admin/common/inc-po-header.php:8 msgctxt "Modified time" msgid "Updated" msgstr "" #: tpl/admin/bundle/inc-po-table.php:63 msgid "Move" msgstr "" #. Page title where %s is the name of a file to be moved #: src/admin/file/MoveController.php:111 src/admin/file/MoveController.php:140 #, php-format msgid "Move %s" msgstr "" #: tpl/admin/file/move.php:24 msgid "Move files" msgstr "" #: src/ajax/ApisController.php:30 msgid "Need a human?" msgstr "" #: src/admin/init/InitPoController.php:16 #: src/admin/init/InitPoController.php:122 #: src/admin/file/BaseController.php:141 src/admin/bundle/ViewController.php:99 msgid "New language" msgstr "" #: src/admin/init/InitPotController.php:16 msgid "New template" msgstr "" #: src/admin/init/InitPotController.php:104 msgid "New template file" msgstr "" #. %s refers to the name of a translation set (theme, plugin or core component) #: src/admin/init/InitPotController.php:106 #, php-format msgid "New translations template for \"%s\"" msgstr "" #: src/admin/bundle/LocaleController.php:129 msgid "No core translation files are installed for this language" msgstr "" #: tpl/admin/errors/no-locale.php:11 msgid "No files found for this language" msgstr "" #. Result of validation when all strings pass validation #: src/js/Strings.php:47 msgid "No formatting errors detected" msgstr "" #: tpl/admin/init/init-po.php:44 msgid "No language selected" msgstr "" #: tpl/admin/errors/no-backups.php:12 msgid "No previous file revisions" msgstr "" #: tpl/ajax/modal-apis-empty.php:9 msgid "No translation APIs configured" msgstr "" #: tpl/admin/bundle/inc-po-table.php:124 #, php-format msgid "No translations found for \"%s\"" msgstr "" #: tpl/admin/bundle/conf.php:94 tpl/admin/bundle/conf.php:113 #: tpl/admin/bundle/conf.php:134 msgid "no wildcards" msgstr "" #: tpl/admin/config/settings.php:75 tpl/admin/config/settings.php:182 msgid "Not recommended" msgstr "" #: tpl/admin/file/info.php:81 msgid "" "Note that the file may not be deletable due to additional ownership " "permissions" msgstr "" #. When text filtering reduces to an empty view #: src/js/Strings.php:14 msgid "Nothing matches the text filter" msgstr "" #: src/js/Strings.php:81 msgid "Nothing needed updating" msgstr "" #: src/js/Strings.php:127 src/error/Notice.php:20 #: tpl/admin/common/inc-fsconn.php:37 msgid "Notice" msgstr "" #: tpl/admin/config/settings.php:62 msgid "Number of backups to keep of each file:" msgstr "" #: tpl/admin/bundle/setup/author.php:11 msgid "Official configuration" msgstr "" #: tpl/admin/help/side-bar.php:9 msgid "Official plugin page" msgstr "" #: src/js/Strings.php:130 src/error/Success.php:19 msgid "OK" msgstr "" #: tpl/admin/file/info-po.php:71 msgid "Out of sync with template" msgstr "" #: src/admin/RootController.php:12 src/admin/file/InfoController.php:26 #: src/admin/config/BaseController.php:39 #: src/admin/bundle/ViewController.php:25 #: src/admin/bundle/LocaleController.php:34 #: src/admin/bundle/BaseController.php:121 #: src/admin/list/LocalesController.php:24 src/admin/list/BaseController.php:45 msgid "Overview" msgstr "" #: tpl/ajax/modal-apis-batch.php:23 msgid "Overwrite existing translations" msgstr "" #: src/mvc/AdminRouter.php:243 msgid "Page not found" msgstr "" #: src/fs/FileWriter.php:249 msgid "Parent directory isn't writable" msgstr "" #: tpl/admin/bundle/conf.php:144 msgid "Parent theme" msgstr "" #: tpl/admin/bundle/setup/partial.php:11 msgid "Partially configured bundle" msgstr "" #: tpl/admin/bundle/inc-po-table.php:22 tpl/admin/bundle/locale.php:39 msgid "Pending" msgstr "" #: src/error/WriteException.php:13 msgid "Permission denied" msgstr "" #: src/fs/FileWriter.php:243 msgid "Permission denied to update file" msgstr "" #. Where %s is the full version number of PHP #: tpl/admin/config/version.php:49 #, php-format msgid "PHP %s" msgstr "" #. %s refers to the handle of a plugin, e.g. "loco-translate/loco.php" #: src/package/Plugin.php:232 #, php-format msgid "Plugin not found: %s" msgstr "" #: src/mvc/AdminRouter.php:66 src/admin/config/ApisController.php:59 #: src/admin/config/PrefsController.php:42 #: src/admin/config/VersionController.php:22 #: src/admin/config/SettingsController.php:79 tpl/admin/help/tab-config.php:2 msgid "Plugin settings" msgstr "" #. Page title for plugin translations #: src/mvc/AdminRouter.php:51 msgid "Plugin translations ‹ Loco" msgstr "" #: src/mvc/AdminRouter.php:49 src/admin/Navigation.php:44 #: src/admin/bundle/LocaleController.php:146 msgid "Plugins" msgstr "" #. Plural category used in some multi-plural languages #: src/Locale.php:416 msgctxt "Plural category" msgid "Few" msgstr "" #. Plural category used in some multi-plural languages #: src/Locale.php:418 msgctxt "Plural category" msgid "Many" msgstr "" #. Plural category for singular quantity #: src/Locale.php:412 msgctxt "Plural category" msgid "One" msgstr "" #. General plural category not covered by other forms #: src/Locale.php:420 msgctxt "Plural category" msgid "Other" msgstr "" #. Plural category used in some multi-plural languages #: src/Locale.php:414 msgctxt "Plural category" msgid "Two" msgstr "" #. Plural category for zero quantity #: src/Locale.php:410 msgctxt "Plural category" msgid "Zero" msgstr "" #. Shown when a PO file's Plural-Forms header has a different formula from the Unicode CLDR rules #: src/Locale.php:528 msgid "" "Plural forms differ from Loco Translate's built in rules for this language" msgstr "" #. Where %s is the name of a template file #: tpl/admin/file/info-po.php:75 #, php-format msgid "" "PO file has different source strings to \"%s\". Try running Sync before " "making any changes." msgstr "" #. Where %s is the name of a template file #: tpl/admin/file/info-po.php:64 #, php-format msgid "PO file has the same source strings as \"%s\"" msgstr "" #: tpl/admin/file/info-mo.php:38 msgid "PO file missing" msgstr "" #: src/gettext/Compiler.php:287 msgid "PO file saved and MO file compiled" msgstr "" #: src/gettext/Compiler.php:284 msgid "PO file saved and MO/JSON files compiled" msgstr "" #: src/gettext/Compiler.php:94 msgid "PO file saved, but MO file compilation failed" msgstr "" #: src/js/Strings.php:183 msgid "Possible syntax error in string formatting" msgstr "" #: tpl/admin/config/settings.php:156 msgid "Pretty formatting" msgstr "" #: tpl/admin/bundle/conf.php:29 msgid "Project name" msgstr "" #: src/js/Strings.php:139 msgid "Provide the following text when reporting a problem" msgstr "" #: tpl/admin/common/inc-fsconn.php:27 msgid "Read only" msgstr "" #: tpl/admin/root.php:27 msgid "Recently updated:" msgstr "" #: tpl/admin/config/settings.php:81 msgid "Recommended" msgstr "" #: src/fs/FileWriter.php:151 msgid "Refusing to copy over an existing file" msgstr "" #. Help tip for "Template file" field in advanced bundle config #: tpl/admin/bundle/conf.php:75 msgid "Relative path from bundle root to the official POT file" msgstr "" #: tpl/admin/file/conf.php:23 msgid "Relative path to template file" msgstr "" #: src/admin/file/BaseController.php:121 msgid "Relocate" msgstr "" #: tpl/admin/bundle/conf.php:159 tpl/admin/bundle/setup/saved.php:18 msgid "Reset config" msgstr "" #: src/admin/file/BaseController.php:120 tpl/admin/file/diff.php:31 msgid "Restore" msgstr "" #. %s is a file name to be rolled back to a previous version. #. Page title where %s refers to a file name #: src/admin/file/DiffController.php:59 src/admin/file/DiffController.php:78 #, php-format msgid "Restore %s" msgstr "" #: tpl/admin/config/prefs.php:29 tpl/admin/config/prefs.php:33 msgid "Restrict locales" msgstr "" #. Where %s is a file name #: src/ajax/DiffController.php:47 #, php-format msgid "Revisions are identical, you can delete %s" msgstr "" #: tpl/admin/root.php:51 msgid "Running plugins:" msgstr "" #: tpl/admin/file/conf.php:57 tpl/admin/file/head.php:34 msgid "Save" msgstr "" #: tpl/admin/bundle/conf.php:141 msgid "Save config" msgstr "" #: tpl/admin/config/settings.php:181 msgid "Save credentials in session" msgstr "" #: tpl/admin/config/settings.php:233 tpl/admin/config/apis.php:121 #: tpl/admin/config/prefs.php:44 msgid "Save settings" msgstr "" #: tpl/admin/config/settings.php:52 tpl/admin/config/settings.php:58 msgid "Saving PO/POT files" msgstr "" #: src/ajax/FsConnectController.php:163 msgid "Saving this file requires permission" msgstr "" #: tpl/admin/config/settings.php:43 msgid "Scan JavaScript files with extensions:" msgstr "" #: tpl/admin/config/settings.php:37 msgid "Scan PHP files with extensions:" msgstr "" #: src/admin/bundle/LocaleController.php:137 msgid "See all core translations" msgstr "" #: tpl/admin/root.php:58 src/admin/bundle/LocaleController.php:147 msgid "See all plugins" msgstr "" #: tpl/admin/root.php:43 src/admin/bundle/LocaleController.php:142 msgid "See all themes" msgstr "" #: tpl/admin/config/apis.php:115 msgid "See full disclaimer" msgstr "" #. Generic error when external process broke an Ajax request #: src/js/Strings.php:133 msgid "Server returned invalid data" msgstr "" #. where %s is a plugin or theme #: src/admin/bundle/SetupController.php:15 #, php-format msgid "Set up %s" msgstr "" #: tpl/admin/list/inc-table.php:20 msgid "Sets" msgstr "" #: src/mvc/AdminRouter.php:67 src/mvc/AdminRouter.php:72 #: tpl/ajax/modal-apis-empty.php:15 src/admin/file/EditController.php:169 #: tpl/admin/common/inc-fsconn.php:18 tpl/admin/common/inc-fsconn.php:43 #: tpl/admin/errors/no-backups.php:24 msgid "Settings" msgstr "" #: src/admin/config/ApisController.php:43 #: src/admin/config/PrefsController.php:26 #: src/admin/config/SettingsController.php:31 msgid "Settings saved" msgstr "" #: src/admin/bundle/BaseController.php:122 msgid "Setup" msgstr "" #: src/admin/bundle/SetupController.php:25 msgid "Setup tab" msgstr "" #: tpl/admin/list/locales.php:25 msgid "Site language" msgstr "" #: src/admin/config/BaseController.php:18 msgid "Site options" msgstr "" #: tpl/admin/help/tab-config.php:5 msgid "" "Site options apply to all users of Loco Translate across the current site" msgstr "" #: tpl/admin/config/settings.php:31 msgid "Skip PHP files larger than:" msgstr "" #: src/admin/init/InitPoController.php:223 msgid "Skip template" msgstr "" #: src/admin/file/BaseController.php:118 msgid "Source" msgstr "" #: tpl/admin/bundle/conf.php:100 msgid "Source file paths" msgstr "" #: tpl/admin/init/init-pot.php:12 msgid "Source files to scan:" msgstr "" #: tpl/admin/init/init-po.php:169 msgid "Start translating" msgstr "" #: src/admin/DebugController.php:903 msgid "String debugger" msgstr "" #: tpl/admin/bundle/inc-po-table.php:19 msgid "Strings" msgstr "" #. Message appears after sync operation, where %s refers to a POT file. #: src/js/Strings.php:41 #, javascript-format msgid "Strings up to date with %s" msgstr "" #. Message appears after sync operation. #: src/js/Strings.php:44 msgid "Strings up to date with source code" msgstr "" #: tpl/admin/init/init-pot.php:30 msgid "Strings will be extracted to:" msgstr "" #: src/js/Strings.php:87 msgid "Suggested translations" msgstr "" #: src/admin/config/SettingsController.php:51 msgid "Super Admin" msgstr "" #: tpl/admin/help/side-bar.php:12 msgid "Support forum" msgstr "" #. Indicates that POT files are optional, which is not recommended #: tpl/admin/config/settings.php:107 msgid "Sync with source when template missing" msgstr "" #: tpl/admin/config/settings.php:89 tpl/admin/config/settings.php:95 msgid "Syncing PO files" msgstr "" #: src/admin/config/BaseController.php:22 msgid "System" msgstr "" #: src/admin/config/DebugController.php:55 msgid "System diagnostics" msgstr "" #: tpl/admin/file/view-pot.php:9 tpl/admin/file/edit-pot.php:20 #: tpl/admin/file/info-pot.php:18 tpl/admin/bundle/inc-po-table.php:45 #: tpl/admin/bundle/conf.php:65 msgid "Template file" msgstr "" #: src/admin/init/InitPotController.php:56 msgid "Template file already exists" msgstr "" #: src/ajax/XgettextController.php:67 msgid "Template file created" msgstr "" #: tpl/admin/init/init-copy.php:15 tpl/admin/init/init-prompt.php:15 msgid "Template missing" msgstr "" #: tpl/admin/init/init-po.php:122 tpl/admin/file/conf.php:12 msgid "Template options" msgstr "" #: tpl/admin/bundle/conf.php:41 tpl/admin/list/inc-table.php:14 msgid "Text domain" msgstr "" #: src/config/FormModel.php:147 msgid "Text Domain cannot be empty" msgstr "" #. "either" meaning that the file itself can't exist without a containing directory #: tpl/admin/file/info.php:59 msgid "The containing directory for this file doesn't exist either" msgstr "" #. Where %s is the name (or number) of an operating system user #: tpl/admin/file/info.php:73 #, php-format msgid "" "The containing directory is writeable by %s, so you can add new files in the " "same location" msgstr "" #: src/admin/bundle/SetupController.php:250 msgid "The default \"languages\" domain path has been detected" msgstr "" #. %s will be replaced with a relative path like "/languages" #: src/admin/bundle/SetupController.php:245 #, php-format msgid "The domain path is declared by the author as \"%s\"" msgstr "" #: tpl/admin/file/move.php:17 msgid "The following file will be moved/renamed to the new location:" msgid_plural "The following files will be moved/renamed to the new location:" msgstr[0] "" msgstr[1] "" #. %s will be replaced with the full name of the site language #: tpl/admin/root.php:14 #, php-format msgid "The language of this site is %s." msgstr "" #. Help tip for "Text domain" field in advanced bundle config #: tpl/admin/bundle/conf.php:47 msgid "The namespace into which WordPress will load translated strings" msgstr "" #. used when username of web server process is unknown #: src/compat/PosixExtension.php:115 msgid "the web server" msgstr "" #. Page title for theme translations #: src/mvc/AdminRouter.php:46 msgid "Theme translations ‹ Loco" msgstr "" #: src/mvc/AdminRouter.php:44 src/admin/Navigation.php:41 #: src/admin/bundle/LocaleController.php:141 msgid "Themes" msgstr "" #: tpl/admin/root.php:54 msgid "" "These plugins have recently loaded translation files into the admin area" msgstr "" #: tpl/admin/file/info-po.php:88 msgid "" "These translations are not linked to a POT file. Sync operations will " "extract strings directly from source code." msgstr "" #: tpl/admin/config/apis.php:114 msgid "" "Third party services are subject to their own terms of use and may incur " "costs from the provider" msgstr "" #: src/admin/bundle/SetupController.php:239 msgid "" "This bundle does't declare a text domain; try configuring it in the Advanced " "tab" msgstr "" #: src/admin/bundle/SetupController.php:253 msgid "" "This bundle doesn't declare a domain path. Add one via the Advanced tab if " "needed" msgstr "" #: tpl/admin/init/init-prompt.php:23 msgid "This bundle doesn't define a translations template file" msgstr "" #: tpl/admin/bundle/view.php:55 msgid "" "This bundle isn't automatically compatible and requires configuring before " "you can use all the functions of Loco Translate" msgstr "" #: tpl/admin/bundle/view.php:32 msgid "" "This bundle isn't fully configured, so we don't know what the following " "files are for" msgstr "" #: tpl/admin/bundle/setup/none.php:14 msgid "This bundle isn't set up for translation in a way we understand" msgstr "" #: tpl/admin/bundle/setup/meta.php:14 msgid "" "This bundle's configuration has been automatically detected and seems to be " "fully compatible" msgstr "" #: tpl/admin/bundle/setup/partial.php:14 msgid "" "This bundle's configuration has been automatically detected, but isn't fully " "complete" msgstr "" #: tpl/admin/bundle/setup/core.php:13 msgid "This bundle's configuration is built into Loco Translate" msgstr "" #: tpl/admin/bundle/setup/author.php:14 msgid "This bundle's configuration is provided by the author" msgstr "" #: tpl/admin/bundle/setup/saved.php:14 msgid "This bundle's configuration is saved in the WordPress database" msgstr "" #: tpl/admin/init/init-prompt.php:19 msgid "" "This bundle's template file doesn't exist yet. We recommend you create it " "before adding languages" msgstr "" #: tpl/admin/file/info.php:92 msgid "This directory can't be written to directly by the web server" msgstr "" #: src/ajax/FsConnectController.php:124 msgid "This directory is managed by WordPress, be careful what you delete" msgstr "" #: src/ajax/FsConnectController.php:127 msgid "" "This directory is managed by WordPress. Removed files may be restored during " "updates" msgstr "" #: tpl/admin/file/info.php:39 msgid "This file can't be updated directly by the web server" msgstr "" #: src/ajax/FsConnectController.php:121 msgid "This file may be overwritten or deleted when you update WordPress" msgstr "" #. Warning when POT file is opened in the file editor. It can be disabled in settings. #: src/admin/file/EditController.php:168 msgid "" "This is NOT a translation file. Manual editing of source strings is not " "recommended." msgstr "" #: src/ajax/FsConnectController.php:160 msgid "This move operation requires permission" msgstr "" #: tpl/admin/errors/file-isdir.php:14 msgid "This page was expecting a file, but the path is actually a directory" msgstr "" #: src/hooks/AdminHooks.php:89 msgid "This plugin doesn't collect any data from public website visitors." msgstr "" #. This warning is shown when a text domain has defaulted to same as the folder name (or slug) #: src/admin/bundle/SetupController.php:229 msgid "" "This plugin doesn't declare a text domain. It's assumed to be the same as " "the slug, but this could be wrong" msgstr "" #: tpl/admin/bundle/alias.php:14 msgid "" "This plugin doesn't have its own translation files, but can be translated in " "the default text domain" msgstr "" #: src/admin/bundle/SetupController.php:235 msgid "This text domain is not in Loco Translate's bundle configuration" msgstr "" #. Author of the plugin msgid "Tim Whitlock" msgstr "" #. Where %s is the name (or number) of an operating system user #: tpl/admin/file/info.php:96 #, php-format msgid "" "To create new files here you'll have to connect to the remote file system, " "or make the directory writeable by %s" msgstr "" #. Where %s is the name (or number) of an operating system user #: tpl/admin/file/info.php:43 #, php-format msgid "" "To make changes you'll have to connect to the remote file system, or make " "the file writeable by %s" msgstr "" #. %s is the number of formatting arguments accepted by the source text of a translation #: src/js/Strings.php:187 #, javascript-format msgid "Too many placeholders; source text formatting suggests a maximum of %s" msgstr "" #: src/hooks/AdminHooks.php:129 tpl/ajax/modal-apis-batch.php:37 msgid "Translate" msgstr "" #: src/admin/list/PluginsController.php:12 msgid "Translate plugins" msgstr "" #: src/admin/list/ThemesController.php:14 msgid "Translate themes" msgstr "" #. Description of the plugin msgid "Translate themes and plugins directly in WordPress" msgstr "" #. %s is the quantity of strings left unprocessed after a job was stopped prematurely #: src/js/Strings.php:70 #, javascript-format msgid "Translation job aborted with %s string remaining" msgid_plural "Translation job aborted with %s strings remaining" msgstr[0] "" msgstr[1] "" #: tpl/admin/file/info-po.php:33 tpl/admin/bundle/inc-po-table.php:16 #: tpl/admin/bundle/locale.php:36 msgid "Translation progress" msgstr "" #. %s%% is a percentage, e.g. 50% #: src/js/Strings.php:66 #, javascript-format msgid "Translation progress %s%%" msgstr "" #. Where %s is the name of the invalid POT file #: src/ajax/SyncController.php:72 src/admin/file/EditController.php:125 #, php-format msgid "Translation template is invalid (%s)" msgstr "" #. %s refers to the name of a POT file #: src/admin/file/EditController.php:119 #, php-format msgid "Translations don't match template. Run sync to update from %s" msgstr "" #: tpl/admin/root.php:30 msgid "Translations have been recently modified in the following bundles" msgstr "" #: tpl/admin/config/prefs.php:16 tpl/admin/config/prefs.php:20 msgid "Translator credit" msgstr "" #: tpl/admin/bundle/view.php:52 msgid "unconfigured" msgstr "" #: tpl/admin/bundle/setup/none.php:11 msgid "Unconfigured bundle" msgstr "" #: tpl/admin/file/info-other.php:11 msgid "Unexpected file type" msgstr "" #: src/package/Header.php:86 src/admin/file/InfoController.php:137 msgid "Unknown author" msgstr "" #: src/js/Strings.php:142 msgid "Unknown error" msgstr "" #: tpl/admin/config/version.php:74 msgid "Update Now" msgstr "" #: tpl/admin/config/version.php:17 #, php-format msgid "Upgrade to %s" msgstr "" #: src/admin/init/UploadController.php:29 tpl/admin/init/upload.php:58 msgid "Upload" msgstr "" #: src/admin/init/UploadController.php:52 msgid "Upload a translation file" msgstr "" #: src/admin/bundle/ViewController.php:107 msgid "Upload PO" msgstr "" #: tpl/admin/init/upload.php:45 msgid "Upload PO file" msgstr "" #: tpl/admin/init/init-po.php:159 msgid "Use this file as template when running Sync" msgstr "" #: src/js/Strings.php:84 msgid "Use this translation" msgstr "" #: tpl/admin/help/side-bar.php:6 msgid "Useful links:" msgstr "" #: src/mvc/AdminRouter.php:71 src/admin/config/PrefsController.php:13 #: src/admin/config/BaseController.php:19 msgid "User options" msgstr "" #: tpl/admin/help/tab-config.php:6 msgid "User options apply to your WordPress login, across all sites" msgstr "" #: src/data/Permissions.php:155 msgctxt "User role" msgid "Translator" msgstr "" #. Help tip for "File prefix" field in advanced bundle config #: tpl/admin/bundle/conf.php:59 msgid "" "Usually the same as the text domain, but don't leave blank unless you mean to" msgstr "" #: src/admin/config/VersionController.php:13 #: src/admin/config/BaseController.php:21 msgid "Version" msgstr "" #: tpl/admin/config/version.php:11 tpl/admin/config/version.php:25 #: tpl/admin/config/version.php:35 #, php-format msgid "Version %s" msgstr "" #: tpl/admin/bundle/inc-po-table.php:54 tpl/admin/bundle/locale.php:66 msgid "View" msgstr "" #: src/admin/bundle/ViewController.php:116 msgid "View template" msgstr "" #: src/package/Header.php:93 msgid "Visit official site" msgstr "" #: src/js/Strings.php:124 src/error/Warning.php:19 msgid "Warning" msgstr "" #: tpl/admin/file/info-po.php:50 msgid "We can't find the binary MO file that belongs with these translations" msgstr "" #: tpl/admin/file/info-mo.php:41 msgid "We can't find the original PO file from which this was compiled" msgstr "" #: src/admin/RootController.php:84 msgid "Welcome to Loco Translate" msgstr "" #: src/admin/init/InitPoController.php:377 tpl/admin/file/info.php:113 msgid "What's this?" msgstr "" #: src/mvc/AdminRouter.php:54 msgid "WordPress" msgstr "" #. Where %s is the full version number of WordPress #: tpl/admin/config/version.php:68 #, php-format msgid "WordPress %s" msgstr "" #: src/package/Core.php:87 src/admin/bundle/LocaleController.php:136 msgid "WordPress Core" msgstr "" #: src/package/Core.php:26 msgid "WordPress core" msgstr "" #: tpl/admin/init/init-po.php:39 msgid "WordPress language" msgstr "" #. %s will be replaced with a text domain, e.g. "loco-translate" #: src/admin/bundle/SetupController.php:222 #, php-format msgid "WordPress says the primary text domain is \"%s\"" msgstr "" #: tpl/admin/file/info.php:107 msgid "WordPress updates" msgstr "" #. When a file or folder cannot be modified due to filesystem permissions #: tpl/admin/file/info.php:36 tpl/admin/file/info.php:89 #: tpl/admin/common/inc-fsconn.php:50 msgid "Write protected" msgstr "" #: tpl/admin/bundle/conf.php:146 msgid "XML" msgstr "" #: src/error/XmlParseException.php:8 msgid "XML parse error" msgstr "" #: tpl/admin/bundle/setup/conf.php:46 msgid "XML setup" msgstr "" #: src/config/XMLModel.php:54 msgid "XML supplied is empty" msgstr "" #: tpl/admin/init/init-copy.php:18 msgid "" "You can copy an existing PO file (recommended), or extract directly from " "source (advanced)" msgstr "" #: tpl/admin/bundle/setup/meta.php:15 tpl/admin/bundle/setup/author.php:15 #: tpl/admin/bundle/setup/core.php:14 msgid "" "You can make changes in the Advanced tab if you need to override the current " "settings" msgstr "" #. %s is a URL. Keep the tag intact #: tpl/admin/errors/no-tokenizer.php:25 #, php-format msgid "" "You can still translate any bundle that has a template" msgstr "" #: tpl/admin/file/info.php:27 msgid "" "You can update these translations directly from the editor to the file system" msgstr "" #: src/admin/init/InitPoController.php:392 msgid "You're creating translations directly from source code" msgstr "" #: src/admin/init/InitPoController.php:396 msgid "You're creating translations from a POT file" msgstr "" #: tpl/admin/config/version.php:28 msgid "You're running a development snapshot of Loco Translate" msgstr "" #: tpl/admin/config/version.php:38 msgid "You're running the latest version of Loco Translate" msgstr "" #. %s will be replaced with the full name of the user profile's admin language #: tpl/admin/root.php:17 #, php-format msgid "Your admin language is %s." msgstr "" #. Warning appears when user tries to refresh or navigate away when editor work is unsaved #: src/js/Strings.php:102 msgid "Your changes will be lost if you continue without saving" msgstr "" #: src/ajax/UploadController.php:70 msgid "Your file is identical to the existing one" msgstr "" #. This is HTML formatted. (1) placeholder for language code, (2) Example language code #: tpl/admin/init/upload.php:50 #, php-format msgid "" "Your file must be named as shown above where %1$s is the language code, e.g. " "%2$s" msgstr "" #. (1) Name of software, e.g. "WordPress" (2) Version number, e.g. "5.6" #: tpl/admin/config/version.php:53 tpl/admin/config/version.php:71 #, php-format msgid "" "Your version of %1$s is out of date. We recommend you upgrade to at least " "v%2$s, but preferably to the latest stable version." msgstr "" loco.xml000064400000001221147206622240006223 0ustar00 src tpl loco.php languages tmp lib pub test readme.txt000064400000046765147206622240006571 0ustar00 === Loco Translate === Contributors: timwhitlock Tags: translation, language, multilingual, l10n, i18n Requires at least: 5.2 Requires PHP: 5.6.20 Tested up to: 6.6.0 Stable tag: 2.6.11 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Translate WordPress plugins and themes directly in your browser == Description == Loco Translate provides in-browser editing of WordPress translation files and integration with automatic translation services. It also provides Gettext/localization tools for developers, such as extracting strings and generating templates. Features include: * Built-in translation editor within WordPress admin * Integration with translation APIs including DeepL, Google, Microsoft and Lecto AI * Create and update language files directly in your theme or plugin * Extraction of translatable strings from your source code * Native MO file compilation without the need for Gettext on your system * Support for PO features including comments, references and plural forms * PO source view with clickable source code references * Protected language directory for saving custom translations * Configurable PO file backups with diff and restore capability * Built-in WordPress locale codes Official [Loco](https://localise.biz/) WordPress plugin by Tim Whitlock. For more information please visit our [plugin page](https://localise.biz/wordpress/plugin). == Installation == = Basic usage: = Translators: To translate a theme into your language, follow these steps: 1. Create the protected languages directory at `wp-content/languages/loco/themes` 2. Ensure this directory writeable by the web server 3. Find your theme in the list at *Loco Translate > Themes* 4. Click `+ New language` and follow the on-screen prompts. Developers: To translate your own theme or plugin for distribution, follow these steps: 1. Create a `languages` subdirectory in your bundle’s root directory 2. Ensure this directory writeable by the web server 3. Find the bundle at either *Loco Translate > Themes* or *Loco Translate > Plugins* 4. Click `+ Create template` and follow the on-screen prompts to extract your strings. 5. Click `+ New language` and follow the on-screen prompts to add your own translations. = Installing manually: = 1. Unzip all files to the `wp-content/plugins/loco-translate` directory 2. Log into WordPress admin and activate the 'Loco Translate' plugin through the 'Plugins' menu 3. Go to *Loco Translate > Home* in the left-hand menu to start translating More information on using the plugin is [available here](https://localise.biz/wordpress/plugin). == Frequently Asked Questions == Please visit the [FAQs page](https://localise.biz/wordpress/plugin/faqs) on our website for the most common issues. = How do I use Loco Translate? = Try our [Guides and Tutorials](https://localise.biz/wordpress/plugin#guides). = How do I get more help? = If you have a problem using Loco Translate, please try our [help pages](https://localise.biz/wordpress/plugin). There's a lot of information there to help you understand how it works and the most common pitfalls to avoid. To report a bug please start a new topic in the [support forum](https://wordpress.org/support/plugin/loco-translate), but please check the [FAQs](https://localise.biz/wordpress/plugin/faqs) for similar issues first. If you decide to submit a bug report please post enough [relevant detail](https://localise.biz/wordpress/plugin/faqs/debug-info) for us to reproduce your issue. = Is my data protected? = We don't collect your data or snoop on you. See the [plugin privacy notice](https://localise.biz/wordpress/plugin/privacy). == Screenshots == 1. Translating strings in the browser with the Loco PO Editor 2. Showing translation progress for theme language files 3. PO source view with text filter and clickable file references 4. Restore tab showing PO diff view with revert function 5. Showing access to translations by installed language 6. Suggestion feature showing results from several providers == Changelog == = 2.6.11 = * Removed accidental console trace * Bumped WordPress compatibility to 6.6.0 * Added lang_dir_for_domain fix to handle system file absence = 2.6.10 = * Added loco_api_provider_{id} filter * JSON compiler observes configured .js aliases * Fixed a missing security check - thanks Nosa Shandy * Added .blade.php tokenizer hack * Bumped WordPress compatibility to 6.5.4 = 2.6.9 = * Rolled back load helper changes * Moved debug messages to action hooks * String debugger improvements = 2.6.8 = * Added string debugger * Added Zip download button instead of MO * Added debug messages about premature domain loading * Added warning when system translations not installed * Compiler avoids writing empty JSON translation files * UI promotes PO copy over msginit/xgettext routes * Populating msginit fields when copying a PO * Bumped WordPress compatibility to 6.5.3 = 2.6.7 = * WordPress 6.5.0 compatible * Support for performant translation files in PHP format * Added block.json and theme.json extraction * Added theme pattern files to php string extractor * Fixed a bug where unused plural forms were counted as untranslated * Replaced CSS .notice with .panel to mitigate nag-blocker problems * Removed bundle debug screen (deprecated since 2.6.5) * Workaround for absent "source" references in JED files * Extension polyfills now restricted to Loco admin screens. = 2.6.6 = * Replaced open_basedir check with error capturing = 2.6.5 = * Added syntax checking function * Removed deepl_api_url config. Free API detected from :fx key suffix. * Fixed bug in relative path calculations * Fixed API suggestions for plural forms * Fixed bug clearing unsaved state icons * Added total strings count to PO file tables * Sharper flags and spinners (@x2 pixel support) * Handling upload_tmp_dir values outside of open_basedir * Suppressing E_WARNING when testing file is_readable * Bundle debug screen is deprecated (moving into Setup) * Showing System Diagnostics when debug is off * Bumped WordPress compatibility to 6.3.1 = 2.6.4 = * Bumped WordPress version to 6.1.1 * Dropped support for Internet Explorer * Updated JavaScript to ECMAScript 6 * Added `loco_bundle_configured` hook * Fixed error icon not clearing after correction = 2.6.3 = * Fixed bug in plural forms comparison * Fixed bug generating author theme jsons * Fixed errors in bundle debugger * Extended cli type argument to filter specific bundle * Bumped WordPress version to 6.0.3 = 2.6.2 = * Bumped WordPress version to 6.0.0 * Better labelling of reverse-engineered plural forms * Removed undocumented loco_locale_plurals filter; use loco_po_headers * Added PO folder location indicator in breadcrumb * Added syntax validation for formatted strings = 2.6.1 = * Bumped WordPress version to 5.9.2 * Fix for CVE-2022-0765 reported by Taurus Omar via wpscan = 2.6.0 = * Dropped support for WordPress < 5.2 * Code upgrades for >= PHP 5.6.20 * Bumped WordPress version to 5.9.1 * Removed Yandex API integration * Added loco_compile_script_reference filter * Plural-Forms retained when copying PO to same language = 2.5.8 = * Compatible with PHP 8.1 * Bumped WordPress version to 5.9 * Added deprecation warning prior to v2.6 = 2.5.7 = * Fixed bug in 2.5.6 where remote APIs could not be used in batch mode * Enforcing 10k character limit per request for Microsoft and Yandex Translators * Style fix for revision/diff table under restore tab = 2.5.6 = * Added loco_api_provider_source filter * Fixed bug loading user preferences saved in older version * Refactored file finder to avoid recursive function calls * Fixed bug displaying two forms for zero plural languages * Added Lecto AI to translation API providers * Bumped WordPress version to 5.8.3 = 2.5.5 = * Fixed double file extension vulnerability reported by WordFence * Better performance when scanning directories for file types = 2.5.4 = * Fixed vulnerability reported by Tomi Ashari via wpscan * Added filters loco_po_headers and loco_pot_headers * Bumped WordPress version to 5.8.1 = 2.5.3 = * Adds option to merge JSON translations when syncing from PO * Adds screen for editing file headers and sync options * Fix for missing responseText in failed Ajax responses * Fix for HTML entities returned from `number_format_i18n` * Localized number formatting in JavaScript * Replaced usage of date_i18n with wp_date * Added configurable API endpoint for DeepL * Bumped WordPress version to 5.7.2 = 2.5.2 = * Added implied formality and loco_locale_formality filter * Added cli fetch command (experimental) * Bumped WordPress version to 5.7 = 2.5.1 = * Support for new Yandex translate API * Support for DeepL formality parameter * Removed literal "1" and "one" instances from singular strings * Buffering compiled JSON to support strings from multiple sources * Added `loco_compile_single_json` filter for specifying custom JSON * Added `loco_extracted_template` hook for adding custom strings * Sync no longer removes the editor's current text filter * Bumped WordPress version to 5.6.2 = 2.5.0 = * PHP 8.0.0 compatibility * Bumped WordPress version to 5.6.0 * Added JSON translation file generation * Added custom JSON loading to LoadHelper * Disabled emoji image replacement on our admin screens = 2.4.6 = * Fixed critical bug syncing PO directly to source code * Added plugin setting for allowing/disallowing missing POT * Fixed WP5.5 issue with multiple ID attributes on script tags = 2.4.5 = * Added WP-CLI sync and extract commands * Fixed {locale} placeholder bug introduced in 2.4.4 * Improved handling of invalid character encodings * Sync (msgmerge) moved to back end * New fuzzy matching with fuzziness setting * Bumped WordPress version to 5.5.3 = 2.4.4 = * Added PO file upload feature * Added download button to file info page * Fix for extracting plurals also used as singulars * Updating API keys no longer require editor page reload * Catching fatal startup errors in loco.php * Supporting max_php_size=0 to mean no size restriction * Auto-update detection now checks new site options * Bumped WordPress version to 5.5.1 = 2.4.3 = * Improved fix for default syncing of msgstr fields * Reverted accidental removal of js debug flag * Minor fixes to API error messages * Removed use of jQuery.browser * Bugfix for new preferences in usermeta = 2.4.2 = * Added loco_file_written hook * Improved script tampering warning * Added keypress for selecting auto-suggestion * Sync no longer copies msgstr fields by default * Style tweaks for WordPress 5.5 = 2.4.1 = * Fixed mapping of some API languages * Added locale filter to user preferences * Added debugging for credential form failures * Fixed deprecated use of array_key_exists * Added DeepL API service provider * Improved script tampering detection * Bumped WordPress version to 5.5 * Added "modern" skin styles = 2.4.0 = * Added support for third party translation APIs * Added file references to editor source pane in code view * Added fuzzy matching during editor Sync operation * Style changes including rearrangement of editor buttons * Elevated warnings when scripts are tampered with * Removed remnants of legacy version 1.x = 2.3.4 = * Updated translatable strings * Added missing template recommendation * Alerting in debug mode when scripts are tampered with * Fix for Hello Dolly being installed into a folder * Removed translation column in POT edit mode * Added setting to prevent 'translating' of POT files * Enabled some linkable translations using wp_kses * Bumped WordPress version to 5.4.1 = 2.3.3 = * Fixed fatal error when class not found = 2.3.2 = * Removed login/email from default Last-Translator credit * Bumped WP compatibility to 5.4 * Fixed PHP 7.4 deprecations = 2.3.1 = * Default POT getter now looks in "lang" directory * Not calling deprecated magic quotes functions under PHP 7.4 * Fixed issue with conflicting page hooks * Ajax file uploads now enabled by default * Removed legacy option migrations from 1.x branch * Bumped WP compatibility to 5.2.4 = 2.3.0 = * Added experimental support for multipart uploads * Added relocation tab for moving translation sets * Creation of missing directories when writing new files * Fixed duplicate file addition when iterating over symlink * Bumped WP compatibility to 5.2.1 = 2.2.2 = * Security fixes as per [exploit-db 46619](https://www.exploit-db.com/exploits/46619) * Fixed old PHP version error in data files * Bumped WP compatibility to 5.1.1 = 2.2.1 = * Fixed bug where plural tabs not displaying RTL * Various improvements to PO parser incl. better charset handling * Excluding node_modules and vendor directories by default * Transients now have maximum lifespan of 10 days, refreshed after 24h * Symlink fix for followed theme paths detected outside theme * Deprecated config repository lookup * Bumped WP compatibility to 5.1 = 2.2.0 = * Fix for empty language code when getting plural rules * Added X-Loco-Version header to generated Gettext files * Added sanity check for mbstring.func_overload madness * Added "Assign template" link on missing template page * Added JavaScript string extraction (experimental) * Editor supports sprintf-js when javascript-format tag present * Fix for duplicate comments when end punctuation differs * Marking msgctxt more clearly in editor views * Added `loco_admin_shutdown` action hook * Bumped WP compatibility to 5.0 (beta) = 2.1.5 = * Updated locale data * Minor fix to file reference resolution * Fixed windows paths with trailing backslash * Fixed ssh-keys toggling issue * Rejigged buffer handling during Ajax * Bumped WP compatibility to 4.9.8 = 2.1.4 = * Bumped WP compatibility to 4.9.6 * Hooked in privacy policy suggestion = 2.1.3 = * Added loco_locale_name filter and updated locale data * Fixed editor column sorting to update as values change * Supporting RTL text in editor preview rows * Minor refactor of debug mode routing check * Minor PO parser improvements * Bumped WP compatibility to 4.9.5 = 2.1.2 = * Fixed undeclared property in admin hook * Fixed incompatibility with older WordPress * Fixed incorrect millisecond reporting in footer * Removed locale progress column for en_US locale * Tweaks to debugging and error logging = 2.1.1 = * Setting `Project-Id-Version` on new POT files * Added source view to quick links in file tables * Supporting only WordPress style locale codes * Editor screen tolerates missing PO headers * Ajax debugging improvements for issue reporting * Added loco_parse_locale action callback = 2.1.0 = * Add `fs_protect` setting to avoid overwriting system files * Fixed bug in connect dialogue where errors not redisplayed * Minor improvements to inline notices * Removed downgrade notice under version tab * Fixed extraction bug where file header confused with comment * Resolved some inconsistencies between PHP and JS utilities * Added Restore tab with diff display * Added `loco_settings` hook * Prevented editor from changing PO document order * Added default string sorting to extracted strings * Added "Languages" section for grouping files by locale * Fixed bug where translations loaded before user profile language set * Added loco_locale_plurals filter for customising plural rules * Allowing PO files to enforce their own Plural-Forms rules * Added `loco_allow_remote` filter for debugging remote problems * Updated plural forms from Unicode CLDR * PHP extractor avoids repeated comments * Bumped WP compatibility to 4.9.4 = 2.0.17 = * Unofficial languages showing in “Installed” dropdown * Fixed extraction bug where comment confused with file header * Fixed issue where src attributes requested from server during HTML strip * Added loco_admin_init hook into ajax router for consistency * Added warning on file info page when file is managed by WordPress * Minor help link and layout tweaks * Bumped WP compatibility to 4.9.1 = 2.0.16 = * File writer observes wp_is_file_mod_allowed * Fixed progress bug in editor for locales with nplurals=1 * Made plural form categories translatable for editor UI * Sync-from-source raises warning when files are skipped * Added hack for extracting from .twig as per .php * Added warning when child themes declare parent text domain * Added option to control PO line wrapping * Bumped WP compatibility to 4.8.2 = 2.0.15 = * Permanently removed legacy version 1.x * Fixed bug where editor code view was not redrawn on resize * Fixed bug where fuzzy flag caused format flag to be ignored * Fixed bug where autoloader responded to very long class names * Purging WP object cache when active plugin list changes * Added experimental source word count into POT info tab * Bumped WP compatibility to 4.8.1 = 2.0.14 = * Editor improvements inc. column sorting * Added warnings that legacy version will be removed * Added PO source view text filtering * Added _fs_nonce for 4.7.5 compatibility * Migrated to canonical text domain * Removed wp class autoloading = 2.0.13 = * CSS conflict fixes * Added option for UTF-8 byte order mark * Printf highlighting observes no-php-format flag * Fixed issue with translator role losing “read” permission = 2.0.12 = * Minor fix for root path configs * Added alternative PHP extensions setting * Bumped WP version to 4.7.3 * LoadHelper fix for core files * Allow revoking of permissions from translator role * Allow network admins to deny access to site admins = 2.0.11 = * Extra debug logging and error diagnostics * Forcefully clear output buffers before Ajax flush * Bumped WordPress version to 4.7 * Experimental wildcard text domain support = 2.0.10 = * Allows missing domain argument in plugin_locale filter * Reverted editor changes that disabled readonly text * Added invisibles and coding editor switches * Added table filtering via text query * Added Last-Translator user preference = 2.0.9 = * Bumped minimum WordPress version to 4.1 * Some optimisation of transient caching * Fixed hash table settings bug = 2.0.8 = * Source refs fix for files in unknown subsets * Downgrades PO formatting exceptions to PHP warnings * Renamed function prefixes to avoid PHP 7 warnings * Better support for php-format and no-php-format flag * PO source and editor UI tweaks * Localised strings and implemented in js = 2.0.7 = * Fixed prototype.js conflict * More Windows file path fixes * Added loco_current_translator filter * Fixed false positive in extra files test = 2.0.6 = * PO wrapping bugfix * Downgraded source code bugfix * Tolerating headerless POT files * Core bundle metadata tweaks = 2.0.5 = * Deferred missing tokenizer warning * Allows editing of files in unconfigured sets * Added maximum PHP file size for string extraction * Display of PHP fatal errors during Ajax = 2.0.4 = * Reduced session failures to debug notices * Added wp_roles support for WP < 4.3 * Fixed domain listener bugs = 2.0.3 = * Added support for Windows servers * Removed incomplete config warning on bundle overview = 2.0.2 = * Fixed bug when absolute path used to get plugins * Added loco_plugins_data filter * Added theme Template Name header extraction * Minor copy amends = 2.0.1 = * Added help link in settings page * Fixed opendir warnings in legacy code * Catching session errors during init * Removing meta row link when plugin not found = 2.0.0 = * First release of completely rebuilt version 2 == Upgrade Notice == = 2.6.11 = * Various improvements and bug fixes == Keyboard shortcuts == The PO file editor supports the following keyboard shortcuts for faster translating: * Done and Next: `Ctrl ↵` * Next string: `Ctrl ↓` * Previous string: `Ctrl ↑` * Next untranslated: `Shift Ctrl ↓` * Previous untranslated: `Shift Ctrl ↑` * Copy from source text: `Ctrl B` * Clear translation: `Ctrl K` * Toggle Fuzzy: `Ctrl U` * Save PO / compile MO: `Ctrl S` * Toggle invisibles: `Shift Ctrl I` * Suggest translation: `Ctrl J` Mac users can use ⌘ Cmd instead of Ctrl. lib/compiled/phpunit.php000064400000012106147206622240011313 0ustar00tag = $r[1]; } if( ! empty($r[2]) ){ $this->attr['id'] = substr($r[2],1); } if( ! empty($r[3]) ){ $this->attr['class'] = substr($r[3],1); } if( ! empty($r[4]) ){ $this->attr[ $r[5] ] = $r[6]; } } public function filter( DOMElement $el ) { if( '' !== $this->tag ){ $list = $el->getElementsByTagName($this->tag); $recursive = false; } else { $list = $el->childNodes; $recursive = true; } if( $this->attr ){ $list = $this->reduce( $list, new ArrayIterator, $recursive )->getArrayCopy(); } return $list; } public function reduce( DOMNodeList $list, ArrayIterator $reduced, $recursive ) { foreach( $list as $node ){ if( $node instanceof DOMElement ){ $matched = false; foreach( $this->attr as $name => $value ){ if( ! $node->hasAttribute($name) ){ $matched = false; break; } $values = array_flip( explode(' ', $node->getAttribute($name) ) ); if( ! isset($values[$value]) ){ $matched = false; break; } $matched = true; } if( $matched ){ $reduced[] = $node; } if( $recursive && $node->hasChildNodes() ){ $this->reduce( $node->childNodes, $reduced, true ); } } } return $reduced; } } class LocoDomQuery extends ArrayIterator { public static function parse( $source ) { $dom = new DOMDocument('1.0', 'UTF-8' ); $source = ''.$source.''; $used_errors = libxml_use_internal_errors(true); $opts = LIBXML_HTML_NODEFDTD; $parsed = $dom->loadHTML( $source, $opts ); $errors = libxml_get_errors(); $used_errors || libxml_use_internal_errors(false); libxml_clear_errors(); if( $errors || ! $parsed ){ $e = new Loco_error_ParseException('Unknown parse error'); foreach( $errors as $error ){ $e = new Loco_error_ParseException( trim($error->message) ); $e->setContext( $error->line, $error->column, $source ); if( LIBXML_ERR_FATAL === $error->level ){ throw $e; } } if( ! $parsed ){ throw $e; } } return $dom; } public function __construct( $value ){ if( $value instanceof DOMDocument ){ $value = [ $value->documentElement ]; } else if( $value instanceof DOMNode ){ $value = [ $value ]; } if( is_iterable($value) ){ $nodes = []; foreach( $value as $node ){ $nodes[] = $node; } } else if( is_string($value) || method_exists($value,'__toString') ){ $value = self::parse( $value ); $nodes = [ $value->documentElement ]; } else { $type = is_object($value) ? get_class($value) : gettype($value); throw new InvalidArgumentException('Cannot construct DOM from '.$type ); } parent::__construct( $nodes ); } public function eq( $index ) { $q = new LocoDomQuery([]); if( $el = $this[$index] ){ $q[] = $el; } return $q; } public function find( $value ) { $q = new LocoDomQuery( [] ); $f = new LocoDomQueryFilter($value); foreach( $this as $el ){ foreach( $f->filter($el) as $match ){ $q[] = $match; } } return $q; } public function children() { $q = new LocoDomQuery([]); foreach( $this as $el ){ if( $el instanceof DOMNode ){ foreach( $el->childNodes as $child ) { $q[] = $child; } } } return $q; } public function text(){ $s = ''; foreach( $this as $el ){ $s .= $el->textContent; } return $s; } public function html() { $s = ''; foreach( $this as $outer ){ foreach( $outer->childNodes as $inner ){ $s .= $inner->ownerDocument->saveXML($inner); } break; } return $s; } public function attr( $name ) { foreach( $this as $el ){ return $el->getAttribute($name); } return null; } public function hasClass( $class ) { foreach( $this as $el ){ $classes = $el->getAttribute('class'); if( is_string($classes) && false !== strpos($classes,$class) ){ return true; } } return false; } public function getFormData() { parse_str( $this->serializeForm(), $data ); return $data; } public function serializeForm() { $pairs = []; foreach( ['input','select','textarea','button'] as $type ){ foreach( $this->find($type) as $field ){ $name = $field->getAttribute('name'); if( ! $name ){ continue; } if( $field->hasAttribute('type') ){ $type = $field->getAttribute('type'); } if( 'select' === $type ){ $value = null; $f = new LocoDomQueryFilter('option'); foreach( $f->filter($field) as $option ){ if( $option->hasAttribute('value') ){ $_value = $option->getAttribute('value'); } else { $_value = $option->nodeValue; } if( $option->hasAttribute('selected') ){ $value = $_value; break; } else if( is_null($value) ){ $value = $_value; } } if( is_null($value) ){ $value = ''; } } else if( 'checkbox' === $type || 'radio' === $type ){ if( $field->hasAttribute('checked') ){ $value = $field->getAttribute('value'); } else { continue; } } else if( 'file' === $type ){ $value = ''; } else if( $field->hasAttribute('value') ){ $value = $field->getAttribute('value'); } else { $value = $field->textContent; } $pairs[] = sprintf('%s=%s', rawurlencode($name), rawurlencode($value) ); } } return implode('&',$pairs); } } lib/compiled/README.md000064400000000222147206622240010366 0ustar00# Compiled libraries These files are built from the Loco core. Do not edit! They've been converted down for PHP 5.2 compatibility in WordPress. lib/compiled/locales.php000064400000001052147206622240011244 0ustar00 strtolower( $tags[1] ), ]; if( array_key_exists(2,$tags) && $tags[2] ){ $data['region'] = strtoupper($tags[2]); } if( array_key_exists(3,$tags) && $tags[3] ){ $data['variant'] = strtolower($tags[3]); } return $data; } lib/compiled/gettext.php000064400000202272147206622240011315 0ustar00map = array_combine( array_map( 'strtolower', $keys ), $keys ); parent::__construct($raw); } } public function normalize( $k ) { $k = strtolower($k); return array_key_exists($k,$this->map) ? $this->map[$k] : null; } public function add($key, $val ) { $this->offsetSet( $key, $val ); return $this; } public function __toString() { $pairs = []; foreach( $this as $key => $val ){ $pairs[] = $key.': '.$val; } return implode("\n", $pairs ); } public function trimmed( $prop ) { return trim( $this->__get($prop) ); } public function has( $key) { return array_key_exists( strtolower($key), $this->map ); } public function __get( $key ){ return $this->offsetGet( $key ); } public function __set( $key, $val ) { $this->offsetSet( $key, $val ); } #[ReturnTypeWillChange] public function offsetExists( $key ) { return $this->has($key); } #[ReturnTypeWillChange] public function offsetGet( $key ) { $k = $this->normalize($key); if( is_null($k) ){ return ''; } return parent::offsetGet($k); } #[ReturnTypeWillChange] public function offsetSet( $key, $value ) { $k = strtolower($key); if( isset($this->map[$k]) && $key !== $this->map[$k] ){ parent::offsetUnset( $this->map[$k] ); } $this->map[$k] = $key; parent::offsetSet( $key, $value ); } #[ReturnTypeWillChange] public function offsetUnset( $key ) { $k = strtolower($key); if( isset($this->map[$k]) ){ parent::offsetUnset( $this->map[$k] ); unset( $this->map[$k] ); } } #[ReturnTypeWillChange] public function jsonSerialize() { return $this->getArrayCopy(); } } function loco_normalize_charset( $cs ) { if( preg_match('/^UTF-?8$/i',$cs) ){ return 'UTF-8'; } try { $aliases = @mb_encoding_aliases($cs); } catch( ValueError $e ){ $aliases = false; } if( false === $aliases ){ throw new InvalidArgumentException('Unsupported character encoding: '.$cs ); } if( preg_grep('/^ISO[-_]\\d+[-_]\\d+$/i',$aliases) ){ $cs = current($aliases); $cs = strtr( strtoupper($cs), '_', '-' ); } else if( in_array('US-ASCII',$aliases,true) ){ $cs = 'US-ASCII'; } return $cs; } class LocoPoHeaders extends LocoHeaders { private $cs = null; public function getCharset() { $cs = $this->cs; if( is_null($cs) ){ $cs = ''; $raw = $this->offsetGet('content-type'); if( $raw && preg_match('!\\bcharset[= ]+([-\\w]+)!',$raw,$r) ){ try { $cs = loco_normalize_charset($r[1]); } catch( InvalidArgumentException $e ){ } catch( Exception $e ){ trigger_error( $e->getMessage(), E_USER_NOTICE ); } } $this->cs = $cs; } return $cs; } public function setCharset( $to ) { $to = loco_normalize_charset($to); $from = $this->getCharset(); $this->cs = $to; $this['Content-Type'] = 'text/plain; charset='.$to; if( '' !== $from && $from !== $to ){ foreach( $this as $key => $val ){ $this[$key] = mb_convert_encoding($val,$to,$from); } } return $to; } public static function fromMsgstr( $str ) { $headers = new LocoPoHeaders; $key = ''; foreach( preg_split('/[\\r\\n]+/',$str) as $line ){ $i = strpos($line,':'); if( is_int($i) ){ $key = trim( substr($line,0,$i), " \t" ); $headers->offsetSet( $key, ltrim( substr($line,++$i)," \t" ) ); } else if( '' !== $key ){ $headers->offsetSet( $key, $headers->offsetGet($key)."\n".$line ); } } $cs = $headers->getCharset(); if( '' !== $cs && 'UTF-8' !== $cs && 'UTF-8' !== mb_detect_encoding($str,['UTF-8',$cs],true) ){ foreach( $headers as $key => $val ){ $headers[$key] = mb_convert_encoding($val,'UTF-8',[$cs]); } } return $headers; } public static function fromSource( $raw ) { $po = new LocoPoParser($raw); $po->parse(0); return $po->getHeader(); } } function loco_convert_utf8( $str, $enc, $strict ) { if( '' === $enc || 'UTF-8' === $enc || 'US-ASCII' === $enc ){ if( false === preg_match('//u',$str) ){ if( $strict ){ $e = new Loco_error_ParseException( $enc ? 'Invalid '.$enc.' encoding' : 'Unknown character encoding' ); if( preg_match('/^(?:[\\x00-\\x7F]|[\\xC0-\\xDF][\\x80-\\xBF]|[\\xE0-\\xEF][\\x80-\\xBF]{2}|[\\xF0-\\xFF][\\x80-\\xBF]{3})*/',$str,$r) && $str !== $r[0] ){ $e->setOffsetContext( strlen($r[0]), $str ); } throw $e; } $str = loco_fix_utf8($str); } } else if( 'ISO-8859-1' === $enc ) { $str = mb_convert_encoding( $str, 'UTF-8', 'cp1252' ); } else { $str = mb_convert_encoding( $str, 'UTF-8', $enc ); } return $str; } function loco_fix_utf8( $str ) { $fix = ''; while( is_string($str) && '' !== $str ){ if( preg_match('/^(?:[\\x00-\\x7F]|[\\xC0-\\xDF][\\x80-\\xBF]|[\\xE0-\\xEF][\\x80-\\xBF]{2}|[\\xF0-\\xFF][\\x80-\\xBF]{3})+/',$str,$r) ){ $fix .= $r[0]; $str = substr($str, strlen($r[0]) ); } else { $fix.= mb_convert_encoding( $str[0], 'UTF-8', 'cp1252' ); $str = substr($str,1); } } return loco_convert_utf8($fix,'',true); } abstract class LocoGettextParser { private $head = null; private $cs = ''; abstract public function parse( $limit = -1 ); protected function setHeader( LocoPoHeaders $head ) { $this->head = $head; $cs = $head->getCharset(); if( '' !== $cs ){ if( '' === $this->cs ){ $this->setCharset($cs); } } return $head; } public function getHeader() { return $this->head; } protected function setCharset( $cs ) { $this->cs = $cs; } protected function getCharset() { return $this->cs; } protected function str( $str ) { if( '' !== $str ){ $str = loco_convert_utf8($str,$this->cs,false); } return $str; } protected function initMsgKey( $key ) { $r = explode("\4",$key); $value = [ 'source' => array_pop($r), 'target' => '', ]; if( isset($r[0]) ){ $value['context'] = $r[0]; } return $value; } } function loco_remove_bom( $s, &$c ) { $bom = substr($s,0,2); if( "\xFF\xFE" === $bom ){ $c = 'UTF-16LE'; return substr($s,2); } if( "\xFE\xFF" === $bom ){ $c = 'UTF-16BE'; return substr($s,2); } if( "\xEF\xBB" === $bom && "\xBF" === $s[2] ){ $c = 'UTF-8'; return substr($s,3); } $c = ''; return $s; } function loco_parse_reference_id( $refs, &$_id ) { if( false === ( $n = strpos($refs,'loco:') ) ){ $_id = ''; return $refs; } $_id = substr($refs, $n+5, 24 ); $refs = substr_replace( $refs, '', $n, 29 ); return trim( $refs ); } class LocoPoParser extends LocoGettextParser implements Iterator { private $lines = []; private $i; private $k; private $m; public function __construct( $src ){ if( '' !== $src ){ $src = loco_remove_bom($src,$cs); if( $cs && 'UTF-8' !== $cs ){ $src = mb_convert_encoding( $src, 'UTF-8', $cs ); $cs = 'UTF-8'; } if( 'UTF-8' === $cs ){ $this->setCharset('UTF-8'); } $this->lines = preg_split('/(\\r\\n?|\\n)/', $src ); } } #[ReturnTypeWillChange] public function rewind() { $this->i = -1; $this->k = -1; $this->next(); } #[ReturnTypeWillChange] public function valid() { return is_int($this->i); } #[ReturnTypeWillChange] public function key() { return $this->k; } #[ReturnTypeWillChange] public function current() { return $this->m; } #[ReturnTypeWillChange] public function next() { $valid = false; $entry = [ '#' => [], 'id' => [null], 'str' => [null] ]; $i = $this->i; while( array_key_exists(++$i,$this->lines) ){ $line = $this->lines[$i]; try { if( '' === $line ){ if( $valid ){ break; } continue; } $c = $line[0]; if( '#' === $c ){ if( $valid ){ $i--; break; } if( '#' === $line ){ continue; } $f = $line[1]; $entry['#'][$f][] = trim( substr( $line, 1+strlen($f) ), "/ \n\r\t" ); } else if( preg_match('/^msg(id(?:_plural)?|ctxt|str(?:\\[(\\d+)])?)[ \\t]*/', $line, $r ) ){ if( isset($r[2]) ){ $key = 'str'; $idx = (int) $r[2]; } else { $key = $r[1]; $idx = 0; } if( $valid && 'str' !== $key && null !== $entry['str'][0] ){ $i--; break; } $snip = strlen($r[0]); if( '"' !== substr($line,$snip,1) ){ throw new Exception('Expected " to follow msg'.$key); } $val = ''; $line = substr($line,$snip); while( true ){ if( '"' === $line || ! substr($line,-1) === '"' ){ throw new Exception('Unterminated msg'.$key ); } $val .= substr( $line, 1, -1 ); $j = $i + 1; if( array_key_exists($j,$this->lines) && ( $line = $this->lines[$j] ) && '"' === $line[0] ){ $i = $j; } else { break; } } if( ! $valid ){ $valid = true; } if( 'id_plural' === $key ){ $key = 'id'; $idx = 1; } $entry[$key][$idx] = stripcslashes($val); } else if( preg_match('/^[ \\t]+$/',$line) ){ if( $valid ) { break; } } else if( '"' === $c ){ throw new Exception('String encountered without keyword'); } else { throw new Exception('Junk'); } } catch( Exception $e ){ } } if( $valid ){ ++$this->k; $this->i = $i; $this->m = $entry; } else { $this->i = null; $this->k = null; $this->m = null; } } public function parse( $limit = -1 ) { $this->rewind(); if( ! $this->valid() ){ throw new Loco_error_ParseException('Invalid PO file'); } $entry = $this->current(); if( '' !== $entry['id'][0] || isset($entry['ctxt']) || is_null($entry['str'][0]) ){ $head = $this->setHeader( new LocoPoHeaders ); } else { $head = $this->setHeader( LocoPoHeaders::fromMsgstr($entry['str'][0]) ); } if( 0 === $limit ){ return []; } $i = -1; $assets = []; $lk = $head['X-Loco-Lookup']; while( $this->valid() ){ $entry = $this->current(); $msgid = $entry['id'][0]; if( is_null($msgid) ){ $this->next(); continue; } if( ++$i === $limit ){ return $assets; } $asset = [ 'source' => $this->str( $msgid ), 'target' => $this->str( (string) $entry['str'][0] ), 'context' => null, ]; $prev_entry = null; if( isset($entry['ctxt']) ){ $asset['context'] = $this->str( $entry['ctxt'][0] ); } $cmt = $entry['#']; if( isset($cmt[' ']) ){ $asset['comment'] = $this->str( implode("\n", $cmt[' '] ) ); } if( isset($cmt['.']) ){ $asset['notes'] = $this->str( implode("\n", $cmt['.'] ) ); } if( isset($cmt[':']) ){ if( $refs = implode( ' ', $cmt[':'] ) ) { $refs = $this->str($refs); if( $refs = loco_parse_reference_id( $refs, $_id ) ){ $asset['refs'] = $refs; } if( $_id ){ $asset['_id'] = $_id; } } } if( isset($cmt[',']) ){ foreach( $cmt[','] as $flags ){ foreach( explode(',',$flags) as $flag ){ if( $flag = trim($flag," \t") ){ if( preg_match('/^((?:no-)?\w+)-format/', $flag, $r ) ){ $asset['format'] = $r[1]; } else if( 'fuzzy' === $flag ){ $asset['flag'] = 4; } } } } } if( isset($cmt['|']) ){ $p = new LocoPoParser(''); $p->lines = $cmt['|']; $p->setCharset( $this->getCharset() ); try { $prev_entry = $p->parse(); } catch( Loco_error_ParseException $e ){ } if( $prev_entry ){ $msgid = $prev_entry[0]['source']; if( $lk && 'text' !== $lk ){ $asset[$lk] = $asset['source']; $asset['source'] = $msgid; } else if( substr($msgid,0,5) === 'loco:' ){ $asset['_id'] = substr($msgid,5); } else { $asset['prev'] = $prev_entry; $prev_entry = null; } } } $assets[] = $asset; if( isset($entry['id'][1]) ){ $idx = 0; $pidx = count($assets) - 1; $num = max( 2, count($entry['str']) ); while( ++$idx < $num ){ $plural = [ 'source' => '', 'target' => isset($entry['str'][$idx]) ? $this->str($entry['str'][$idx]) : '', 'plural' => $idx, 'parent' => $pidx, ]; if( 1 === $idx ){ $plural['source'] = $this->str($entry['id'][1]); if( is_array($prev_entry) && isset($prev_entry[1]) ){ if( $lk && 'text' !== $lk ){ $plural[$lk] = $plural['source']; $plural['source'] = $prev_entry[1]['source']; } } } if( isset($asset['flag']) ){ $plural['flag'] = $asset['flag']; } $assets[] = $plural; } } $this->next(); } if( -1 === $i ){ throw new Loco_error_ParseException('Invalid PO file'); } else if( 0 === $i && '' === $assets[0]['source'] && '' === $assets[0]['target'] ){ throw new Loco_error_ParseException('Invalid PO file' ); } return $assets; } } class LocoMoParser extends LocoGettextParser { private $bin; private $be = null; private $n = null; private $o = null; private $t = null; private $v = null; public function __construct( $bin ){ $this->bin = $bin; } public function getAt( $idx ){ $offset = $this->targetOffset(); $offset += ( $idx * 8 ); $len = $this->integerAt( $offset ); $idx = $this->integerAt( $offset + 4 ); $txt = $this->bytes( $idx, $len ); if( false === strpos( $txt, "\0") ){ return $txt; } return explode( "\0", $txt ); } public function parse( $limit = -1 ) { $i = -1; $r = []; $sourceOffset = $this->sourceOffset(); $targetOffset = $this->targetOffset(); $soffset = $sourceOffset; $toffset = $targetOffset; while( $soffset < $targetOffset ){ $len = $this->integerAt( $soffset ); $idx = $this->integerAt( $soffset + 4 ); $src = $this->bytes( $idx, $len ); $eot = strpos( $src, "\x04" ); if( false === $eot ){ $context = null; } else { $context = $this->str( substr($src, 0, $eot ) ); $src = substr( $src, $eot+1 ); } $sources = explode( "\0", $src, 2 ); $len = $this->integerAt( $toffset ); $idx = $this->integerAt( $toffset + 4 ); $targets = explode( "\0", $this->bytes( $idx, $len ) ); if( -1 === $i && '' === $sources[0] && is_null($context) ){ $this->setHeader( LocoPoHeaders::fromMsgstr($targets[0]) ); } if( ++$i > $limit && -1 !== $limit ){ break; } $r[$i] = [ 'source' => $this->str( $sources[0] ), 'target' => $this->str( $targets[0] ), 'context' => $context, ]; if( isset($sources[1]) ){ $p = count($r) - 1; $nforms = max( 2, count($targets) ); for( $n = 1; $n < $nforms; $n++ ){ $r[++$i] = [ 'source' => isset($sources[$n]) ? $this->str( $sources[$n] ) : sprintf('%s (plural %u)',$r[$p]['source'],$n), 'target' => isset($targets[$n]) ? $this->str( $targets[$n] ) : '', 'parent' => $p, 'plural' => $n, ]; } } $soffset += 8; $toffset += 8; } return $r; } public function isBigendian() { if( is_null($this->be) ){ $str = $this->words( 0, 1 ); if( "\xDE\x12\x04\x95" === $str ){ $this->be = false; } else if( "\x95\x04\x12\xDE" === $str ){ $this->be = true; } else { throw new Loco_error_ParseException('Invalid MO format'); } } return $this->be; } public function version() { if( is_null($this->v) ){ $this->v = $this->integerWord(1); } return $this->v; } #[ReturnTypeWillChange] public function count() { if( is_null($this->n) ){ $this->n = $this->integerWord(2); } return $this->n; } public function sourceOffset() { if( is_null($this->o) ){ $this->o = $this->integerWord(3); } return $this->o; } public function targetOffset() { if( is_null($this->t) ){ $this->t = $this->integerWord(4); } return $this->t; } public function getHashTable() { $s = $this->integerWord(5); $h = $this->integerWord(6); return $this->bytes( $h, $s * 4 ); } private function bytes( $offset, $length ) { $s = substr( $this->bin, $offset, $length ); if( strlen($s) !== $length ){ throw new Loco_error_ParseException('Failed to read '.$length.' bytes at ['.$offset.']' ); } return $s; } private function words( $offset, $length ) { return $this->bytes( $offset * 4, $length * 4 ); } private function integerWord( $offset ) { return $this->integerAt( $offset * 4 ); } private function integerAt( $offset ) { $str = $this->bytes( $offset, 4 ); $fmt = $this->isBigendian() ? 'N' : 'V'; $arr = unpack( $fmt, $str ); if( ! isset($arr[1]) || ! is_int($arr[1]) ){ throw new Loco_error_ParseException('Failed to read integer at byte '.$offset); } return $arr[1]; } } class LocoJedParser extends LocoGettextParser { private $ld; public function __construct( array $struct ){ $this->ld = $struct; } public function parse( $limit = -1 ) { $values = []; foreach( $this->ld as $messages ){ if( ! is_array($messages) ){ throw new Loco_error_ParseException('Array expected'); } $msgid = key($messages); if( '' === $msgid ){ $this->setHeader( new LocoJedHeaders($messages['']) ); unset($messages['']); } else { $this->setHeader( new LocoJedHeaders ); } $values[] = [ 'source' => '', 'target' => $this->getHeader(), ]; $i = -1; foreach( $messages as $key => $list ){ if( ++$i === $limit ){ break; } $value = $this->initMsgKey($key); $index = count($values); foreach( $list as $j => $msgstr ){ if( ! is_string($msgstr) ){ throw new Loco_error_ParseException('msgstr must be scalar'); } $value['target'] = $msgstr; if( 0 < $j ){ $value['plural'] = $j; $value['parent'] = $index; $value['source'] = ''; } $values[] = $value; } } } return $values; } } class LocoJedHeaders extends LocoPoHeaders { public function __construct( array $raw = [] ) { foreach( ['Language'=>'lang','plural_forms'=>'Plural-Forms'] as $canonical => $alias ){ if( array_key_exists($alias,$raw) && ! array_key_exists($canonical,$raw) ){ $raw[$canonical] = $raw[$alias]; } } parent::__construct($raw); } } class LocoMoPhpParser extends LocoGettextParser { private $msgs; public function __construct( array $struct ){ $this->msgs = $struct['messages']; unset($struct['messages']); $this->setHeader( new LocoPoHeaders($struct) ); } public function parse( $limit = -1 ) { $values = [ [ 'source' => '', 'target' => $this->getHeader(), ] ]; $i = -1; foreach( $this->msgs as $key => $bin ){ if( ++$i === $limit ){ break; } $value = $this->initMsgKey($key); $index = count($values); foreach( explode("\0",$bin) as $i => $msgstr ){ $value['target'] = $msgstr; if( 0 < $i ){ $value['plural'] = $i; $value['parent'] = $index; $value['source'] = ''; } $values[] = $value; } } return $values; } } abstract class LocoPo { public static function pair( $key, $text, $width = 79, $eol = "\n", $esc = '\\n' ) { if( '' === $text ){ return $key.' ""'; } $text = addcslashes( $text, "\t\x0B\x0C\x07\x08\\\"" ); if( $esc ) { $text = preg_replace('/\\r\\n?|\\n/', $esc.$eol, $text, -1, $nbr ); } else { $eol = "\n"; $text = preg_replace_callback('/\\r\\n?|\\n/',[__CLASS__,'replace_br'], $text, -1, $nbr ); } if( $nbr ){ } else if( $width && $width < mb_strlen($text,'UTF-8') + strlen($key) + 3 ){ } else { return $key.' "'.$text.'"'; } $lines = [ $key.' "' ]; if( $width ){ $width -= 2; $a = '/^.{0,'.($width-1).'}[-– .,:;?!)\\]}>]/u'; $b = '/^[^-– .,:;?!)\\]}>]+/u'; foreach( explode($eol,$text) as $unwrapped ){ $length = mb_strlen( $unwrapped, 'UTF-8' ); while( $length > $width ){ if( preg_match( $a, $unwrapped, $r ) ){ $line = $r[0]; } else if( preg_match( $b, $unwrapped, $r ) ){ $line = $r[0]; } else { throw new Exception('Wrapping error'); } $lines[] = $line; $trunc = mb_strlen($line,'UTF-8'); $length -= $trunc; $unwrapped = (string) substr( $unwrapped, strlen($line) ); if( ( '' === $unwrapped && 0 !== $length ) || ( 0 === $length && '' !== $unwrapped ) ){ throw new Exception('Truncation error'); } } if( 0 !== $length ){ $lines[] = $unwrapped; } } } else { foreach( explode($eol,$text) as $unwrapped ){ $lines[] = $unwrapped; } } return implode('"'.$eol.'"',$lines).'"'; } private static function replace_br( array $r ) { return addcslashes($r[0],"\r\n")."\n"; } public static function refs( $text, $width = 76, $eol = "\n" ) { $text = preg_replace('/\\s+/u', ' ', $text ); if( $width ){ $text = wordwrap( $text, $width, $eol.'#: ' ); } return '#: '.$text; } public static function prefix( $text, $prefix, $eol = "\n" ) { return $prefix . implode($eol.$prefix, self::split($text) ); } public static function split( $text ) { $lines = preg_split('/\\R/u', $text ); if( false === $lines ){ if( false === preg_match('//u',$text) ){ $text = mb_convert_encoding( $text, 'UTF-8', 'cp1252' ); } $lines = preg_split('/\\r?\\n+/', $text ); } return $lines; } public static function trim( $text ) { $lines = []; $deferred = null; foreach( explode("\n",$text) as $line ){ if( '' === $line ){ continue; } if( preg_match('/^msg[a-z]+(?:\\[\\d+])? ""/',$line) ){ $deferred = $line; continue; } if( $deferred && '"' === $line[0] ){ $lines[] = $deferred; $deferred = null; } $lines[] = $line; } return implode("\n",$lines); } } class LocoPoIndex extends ArrayIterator { public function compare( LocoPoMessage $a, LocoPoMessage $b ){ $h = $a->getHash(); if( ! isset($this[$h]) ){ return 1; } $j = $b->getHash(); if( ! isset($this[$j]) ){ return -1; } return $this[$h] > $this[$j] ? 1 : -1; } } class LocoPoMessage extends ArrayObject { public function __construct( array $r ){ $r['key'] = $r['source']; parent::__construct($r); } public function __get( $prop ) { return $this->offsetExists($prop) ? $this->offsetGet($prop) : null; } public function isFuzzy() { return 4 === $this->__get('flag'); } public function getFormat() { $f = $this->__get('format'); if( is_string($f) && '' !== $f ){ return $f; } return ''; } private function getPoFlags() { $flags = []; foreach( array_merge( [$this], $this->__get('plurals')?:[] ) as $form ){ if( $form->isFuzzy() ){ $flags[0] = 'fuzzy'; } $f = $form->getFormat(); if( '' !== $f ){ $flags[1] = $f.'-format'; } } return array_values($flags); } public function getHash() { $hash = $this->getKey(); if( $this->offsetExists('plurals') ){ foreach( $this->offsetGet('plurals') as $p ){ $hash .= "\0".$p->getHash(); break; } } return $hash; } public function getKey() { $msgid = (string) $this['source']; $msgctxt = (string) $this->__get('context'); if( '' !== $msgctxt ){ if( '' === $msgid ){ $msgid = '('.$msgctxt.')'; } $msgid = $msgctxt."\4".$msgid; } return $msgid; } public function exportSerial( $f = 'target' ) { $a = [ $this[$f] ]; if( $this->offsetExists('plurals') ){ $plurals = $this->offsetGet('plurals'); if( is_array($plurals) ){ foreach( $plurals as $p ){ $a[] = $p[$f]; } } } return $a; } public function __toString(){ return $this->render( 79, 76 ); } public function render( $width, $ref_width, $max_forms = 0 ) { $s = []; try { if( $text = $this->__get('comment') ) { $s[] = LocoPo::prefix( $text, '# '); } if( $text = $this->__get('notes') ) { $s[] = LocoPo::prefix( $text, '#. '); } if( $text = $this->__get('refs') ){ $s[] = LocoPo::refs( $text, $ref_width ); } if( $texts = $this->getPoFlags() ){ $s[] = '#, '.implode(', ',$texts); } $prev = $this->__get('prev'); if( is_array($prev) && $prev ){ foreach( new LocoPoIterator($prev) as $p ){ $text = $p->render( max(0,$width-3), 0 ); $s[] = LocoPo::prefix( LocoPo::trim($text),'#| '); break; } } $text = $this->__get('context'); if( is_string($text) && '' !== $text ){ $s[] = LocoPo::pair('msgctxt', $text, $width ); } $s[] = LocoPo::pair( 'msgid', $this['source'], $width ); $target = $this['target']; $plurals = $this->__get('plurals'); if( is_array($plurals) ){ if( array_key_exists(0,$plurals) ){ $p = $plurals[0]; $s[] = LocoPo::pair('msgid_plural', $p['source'], $width ); $s[] = LocoPo::pair('msgstr[0]', $target, $width ); $i = 0; while( array_key_exists($i,$plurals) ){ $p = $plurals[$i]; if( ++$i === $max_forms ){ break; } $s[] = LocoPo::pair('msgstr['.$i.']', $p['target'], $width ); } } else if( isset($this['plural_key']) ){ $s[] = LocoPo::pair('msgid_plural', $this['plural_key'], $width ); $s[] = LocoPo::pair('msgstr[0]', $target, $width ); } else { trigger_error('Missing plural_key in zero plural export'); $s[] = LocoPo::pair('msgstr', $target, $width ); } } else { $s[] = LocoPo::pair('msgstr', $target, $width ); } } catch( Exception $e ){ trigger_error( $e->getMessage(), E_USER_WARNING ); } return implode("\n",$s)."\n"; } public function merge( LocoPoMessage $def, $translate = false ) { if( $def->getHash() !== $this->getHash() ){ $prev = [ 'source' => '', 'target' => '' ]; $prev = $this->diff('source',$def,$prev); $prev = $this->diff('context',$def,$prev); $this['flag'] = 4; $this['prev'] = [ $prev ]; $defPlural = $def->getPlural(0); $ourPlural = $this->getPlural(0); if( $defPlural && $ourPlural ) { $ourPlural->merge($defPlural); if( $ourPlural->offsetExists('prev') ) { $this['prev'][] = $ourPlural->prev[0]+['parent'=>0,'plural'=>1]; $ourPlural->offsetUnset('prev'); } } else if( $defPlural ){ $this['plurals'] = [ clone $defPlural ]; } else if( $ourPlural ){ $this['prev'][] = $ourPlural->exportBasic() + ['parent'=>0,'plural'=>1]; $this->offsetUnset('plurals'); } } foreach( ['notes','refs','format'] as $f ){ if( $def->offsetExists($f) ){ $this->offsetSet($f,$def->offsetGet($f)); } else if( $this->offsetExists($f) ){ $this->offsetUnset($f); } } if( $translate && '' === $this['target'] && '' !== $def['target'] ){ $this['target'] = $def['target']; if( $def->offsetExists('comment') ) { $this['comment'] = $def['comment']; } if( $this->offsetExists('plurals') ){ foreach( $this['plurals'] as $i => $ourPlural ){ if( '' === $ourPlural['target'] ){ $defPlural = $def->getPlural($i); if( $defPlural ){ $ourPlural['target'] = $defPlural['target']; } } } } } } private function diff( $key, LocoPoMessage $def, array $prev ) { $old = $this->__get($key); $new = $def->__get($key); if( $new !== $old ){ $this->offsetSet($key,$new); if( is_string($old) && '' !== $old ){ $prev[$key] = $old; } } return $prev; } private function getPlural( $i ) { if( $this->offsetExists('plurals') ){ $plurals = $this->offsetGet('plurals'); if( is_array($plurals) && array_key_exists($i,$plurals) ){ return $plurals[$i]; } } return null; } private function exportBasic() { return [ 'source' => $this['source'], 'context' => $this->context, 'target' => '', ]; } public function export() { $a = $this->getArrayCopy(); unset($a['key']); if( array_key_exists('plurals',$a) ){ foreach( $a['plurals'] as $i => $p ){ if( $p instanceof ArrayObject ){ $a['plurals'][$i] = $p->getArrayCopy(); } } } return $a; } public function strip() { $this['target'] = ''; $plurals = $this->plurals; if( is_array($plurals) ){ foreach( $plurals as $p ){ $p->strip(); } } return $this; } public function translated() { $n = 0; if( '' !== (string) $this['target'] ){ $n++; } if( $this->offsetExists('plurals') ){ foreach( $this->offsetGet('plurals') as $plural ) { if( '' !== (string) $plural['target']) { $n++; } } } return $n; } } class LocoPoIterator implements Iterator, Countable { private $po; private $headers = null; private $i; private $t; private $j; private $z = 0; private $w = 79; public function __construct( $po ){ if( is_array($po) ){ $this->po = $po; } else if( $po instanceof Traversable ){ $this->po = iterator_to_array($po,false); } else { throw new InvalidArgumentException('PO data must be array or iterator'); } $this->t = count( $this->po ); if( 0 === $this->t ){ throw new InvalidArgumentException('Empty PO data'); } $h = $po[0]; if( '' !== $h['source'] || ( isset($h['context']) && '' !== $h['context'] ) || ( isset($po[1]['parent']) && 0 === $po[1]['parent'] ) ){ $this->z = -1; } } public function push( LocoPoMessage $p ) { $raw = $p->export(); $plurals = $p->plurals; unset($raw['plurals']); $i = count($this->po); $this->po[$i] = $raw; $this->t++; if( is_array($plurals) ) { $j = 0; foreach( $plurals as $p ) { $raw = $p->export(); $raw['parent'] = $i; $raw['plural'] = ++$j; $this->po[] = $raw; $this->t++; } } } public function concat( LocoPoIterator $more ) { foreach( $more as $message ){ $this->push($message); } return $this; } public function __clone() { if( $this->headers ){ $this->headers = new LocoPoHeaders( $this->headers->getArrayCopy() ); } } #[ReturnTypeWillChange] public function count() { return $this->t - ( $this->z + 1 ); } public function wrap( $width ) { if( $width > 0 ){ $this->w = max( 15, $width ); } else { $this->w = 0; } return $this; } #[ReturnTypeWillChange] public function rewind() { $this->i = $this->z; $this->j = -1; $this->next(); } #[ReturnTypeWillChange] public function key() { return $this->j; } #[ReturnTypeWillChange] public function valid() { return is_int($this->i); } #[ReturnTypeWillChange] public function next() { $i = $this->i; while( ++$i < $this->t ){ if( array_key_exists('parent',$this->po[$i]) ){ continue; } $this->j++; $this->i = $i; return; } $this->i = null; $this->j = null; } #[ReturnTypeWillChange] public function current() { return $this->item( $this->i ); } private function item( $i ) { $po = $this->po; $parent = new LocoPoMessage( $po[$i] ); $plurals = []; $nonseq = $parent->offsetExists('child'); $j = $nonseq ? $parent['child'] : $i+1; while( isset($po[$j]['parent']) && $i === $po[$j]['parent'] ){ $plurals[] = new LocoPoMessage($po[$j++]); } if( $plurals ){ $parent['plurals'] = $plurals; } return $parent; } public function exportEntry( $i ) { return $this->item( $i + ( 1-$this->z) ); } public function getArrayCopy() { $po = $this->po; if( 0 === $this->z ){ $po[0]['target'] = (string) $this->getHeaders(); } return $po; } public function clear() { if( 0 === $this->z ){ $this->po = [ $this->po[0] ]; $this->t = 1; } else { $this->po = []; $this->t = 0; } } public function getHeaders() { if( is_null($this->headers) ){ $header = $this->po[0]; if( 0 === $this->z ){ $value = $header['target']; if( is_string($value) ){ $this->headers = LocoPoHeaders::fromMsgstr($value); } else if( $value instanceof LocoPoHeaders ){ $this->headers = $value; } else if( is_array($value) ){ $this->headers = new LocoPoHeaders($value); } } else { $this->headers = new LocoPoHeaders; } } return $this->headers; } public function setHeaders( LocoPoHeaders $head ) { $this->headers = $head; if( 0 === $this->z ){ $this->po[0]['target'] = null; } return $this; } public function initPo() { if( 0 === $this->z ){ unset( $this->po[0]['flag'] ); } return $this; } public function initPot() { if( 0 === $this->z ){ $this->po[0]['flag'] = 4; } return $this; } public function strip() { $po = $this->po; $i = count($po); $z = $this->z; while( --$i > $z ){ $po[$i]['target'] = ''; } $this->po = $po; return $this; } public function __toString() { try { return $this->render(); } catch( Exception $e ){ trigger_error( $e->getMessage(), E_USER_WARNING ); return ''; } } public function render( callable $sorter = null ) { $width = $this->w; $ref_width = max( 0, $width - 3 ); $h = $this->exportHeader(); $msg = new LocoPoMessage( $h ); $s = $msg->render( $width, $ref_width ); if( $sorter ){ $msgs = []; foreach( $this as $msg ){ $msgs[] = $msg; } usort( $msgs, $sorter ); } else { $msgs = $this; } $h = $this->getHeaders()->offsetGet('Plural-Forms'); if( is_string($h) && preg_match('/nplurals\\s*=\\s*(\\d)/',$h,$r) ){ $max_pl = (int) $r[1]; } else { $max_pl = 0; } foreach( $msgs as $msg ){ $s .= "\n".$msg->render( $width, $ref_width, $max_pl ); } return $s; } public function exportJed() { $head = $this->getHeaders(); $a = [ '' => [ 'domain' => $head['domain'], 'lang' => $head['language'], 'plural-forms' => $head['plural-forms'], ] ]; foreach( $this as $message ){ if( $message->translated() ){ $a[ $message->getKey() ] = $message->exportSerial(); } } return $a; } private function exportHeader() { if( 0 === $this->z ){ $h = $this->po[0]; } else { $h = [ 'source' => '', 'target' => '' ]; } if( $this->headers ){ $h['target'] = (string) $this->headers; } return $h; } public function exportRefs( $grep = '' ) { $a = []; if( '' === $grep ) { $grep = '/(\\S+):\\d+/'; } else { $grep = '/(\\S*'.$grep.'):\\d+/'; } $self = get_class($this); $base = [ $this->exportHeader() ]; foreach( $this as $message ){ if( preg_match_all( $grep, (string) $message->refs, $r ) ){ foreach( $r[1] as $ref ) { if( array_key_exists($ref,$a) ){ $po = $a[$ref]; } else { $po = new $self($base); $a[$ref] = $po; } $po->push($message); } } } return $a; } public function splitRefs( array $map = null ) { $a = []; $self = get_class($this); $base = [ $this->exportHeader() ]; if( is_array($map) ){ $grep = implode('|',array_keys($map)); } else { $grep = '[a-z]+'; } foreach( $this as $message ){ $refs = ltrim( (string) $message->refs ); if( '' !== $refs ){ if( preg_match_all('/\\S+\\.('.$grep.'):\\d+/', $refs, $r, PREG_SET_ORDER ) ){ $tmp = []; foreach( $r as $rr ) { list( $ref, $ext ) = $rr; $tmp[$ext][$ref] = true; } foreach( $tmp as $ext => $refs ){ if( is_array($map) ){ $ext = $map[$ext]; } if( array_key_exists($ext,$a) ){ $po = $a[$ext]; } else { $po = new $self($base); $a[$ext] = $po; } $message = clone $message; $message['refs'] = implode(' ',array_keys($refs) ); $po->push($message); } } } } return $a; } public function getHashes() { $a = []; foreach( $this as $msg ){ $a[] = $msg->getHash(); } sort( $a, SORT_STRING ); return $a; } public function equalSource( LocoPoIterator $that ) { $a = $this->getHashes(); $b = $that->getHashes(); if( count($a) !== count($b) ){ return false; } foreach( $a as $i => $hash ){ if( $hash !== $b[$i] ){ return false; } } return true; } public function equal( LocoPoIterator $that ) { if( $this->t !== $that->t ){ return false; } $i = $this->z; $fields = [ 'source', 'context', 'notes', 'refs', 'target', 'comment', 'flag', 'parent', 'plural' ]; while( ++$i < $this->t ){ $a = $this->po[$i]; $b = $that->po[$i]; foreach( $fields as $f ){ $af = isset($a[$f]) ? $a[$f] : ''; $bf = isset($b[$f]) ? $b[$f] : ''; if( $af !== $bf ){ return false; } } } return true; } public function sort( callable $func = null ) { $order = []; foreach( $this as $msg ){ $order[] = $msg; } usort( $order, $func ?: [__CLASS__,'compare'] ); $this->clear(); foreach( $order as $p ){ $this->push($p); } return $this; } public static function compare( LocoPoMessage $a, LocoPoMessage $b ) { $h = $a->getHash(); $j = $b->getHash(); $n = strcasecmp( $h, $j ); if( 0 === $n ){ $n = strcmp( $h, $j ); if( 0 === $n ){ return 0; } } return $n > 0 ? 1 : -1; } public function createSorter() { $index = []; foreach( $this as $i => $msg ){ $index[ $msg->getHash() ] = $i; } $obj = new LocoPoIndex( $index ); return [ $obj, 'compare' ]; } } class LocoMoTable { private $size = 0; private $bin = ''; private $map = null; public function __construct( $data = '' ){ if( is_array($data) ){ $this->compile( $data ); } else if( '' !== $data ){ $this->parse( $data ); } } #[ReturnTypeWillChange] public function count() { if( is_null($this->size) ){ if( $this->bin ){ $this->size = (int) ( strlen( $this->bin ) / 4 ); } else if( is_array($this->map) ){ $this->size = count($this->map); } else { return 0; } if( ! self::is_prime($this->size) || $this->size < 3 ){ throw new Exception('Size expected to be prime number above 2, got '.$this->size); } } return $this->size; } public function bytes() { return $this->count() * 4; } public function __toString() { return $this->bin; } public function export() { if( is_null($this->map) ){ $this->parse($this->bin); } return $this->map; } private function reset( $length ) { $this->size = max( 3, self::next_prime( $length * 4 / 3 ) ); $this->bin = ''; $this->map = []; return $this->size; } public function compile( array $msgids ) { $hash_tab_size = $this->reset( count($msgids) ); $packed = array_fill( 0, $hash_tab_size, "\0\0\0\0" ); $j = 0; foreach( $msgids as $msgid ){ $hash_val = self::hashpjw( $msgid ); $idx = $hash_val % $hash_tab_size; if( array_key_exists($idx, $this->map) ){ $incr = 1 + ( $hash_val % ( $hash_tab_size - 2 ) ); do { $idx += $incr; if( $hash_val === $idx ){ throw new Exception('Unable to find empty slot in hash table'); } $idx %= $hash_tab_size; } while( array_key_exists($idx, $this->map ) ); } $this->map[$idx] = $j; $packed[$idx] = pack('V', ++$j ); } $this->bin = implode('',$packed); } public function lookup( $msgid, array $msgids ) { $hash_val = self::hashpjw( $msgid ); $idx = $hash_val % $this->size; $incr = 1 + ( $hash_val % ( $this->size - 2 ) ); while( true ){ if( ! array_key_exists($idx, $this->map) ){ break; } $j = $this->map[$idx]; if( isset($msgids[$j]) && $msgid === $msgids[$j] ){ return $j; } $idx += $incr; if( $idx === $hash_val ){ break; } $idx %= $this->size; } return -1; } private function parse( $bin ) { $this->bin = $bin; $this->size = null; $hash_tab_size = $this->count(); $this->map = []; $idx = -1; $byte = 0; while( ++$idx < $hash_tab_size ){ $word = substr( $this->bin, $byte, 4 ); if( "\0\0\0\0" !== $word ){ list(,$j) = unpack('V', $word ); $this->map[$idx] = $j - 1; } $byte += 4; } } public static function hashpjw( $str ) { $i = -1; $hval = 0; $len = strlen($str); while( ++$i < $len ){ $ord = ord( substr($str,$i,1) ); $hval = ( $hval << 4 ) + $ord; $g = $hval & 0xf0000000; if( $g !== 0 ){ $hval ^= $g >> 24; $hval ^= $g; } } return $hval; } private static function next_prime( $seed ) { $seed = (int) floor($seed); $seed |= 1; while ( ! self::is_prime($seed) ){ $seed += 2; } return $seed; } private static function is_prime( $num ) { if( 1 === $num ){ return false; } if( 2 === $num ){ return true; } if( $num % 2 == 0 ) { return false; } for( $i = 3; $i <= ceil(sqrt($num)); $i = $i + 2) { if($num % $i == 0 ){ return false; } } return true; } } class LocoMo { private $bin; private $msgs; private $head; private $hash = null; private $use_fuzzy = false; private $cs = null; public function __construct( Iterator $export, LocoPoHeaders $head = null ){ if( $head ){ $this->head = $head; } else { $this->head = new LocoPoHeaders; $this->setHeader('Project-Id-Version','Loco'); } $this->msgs = $export; $this->bin = ''; } public function setCharset( $cs ) { $cs = $this->head->setCharset($cs); $this->cs = 'UTF-8' === $cs ? null : $cs; } public function enableHash() { $this->hash = new LocoMoTable; } public function useFuzzy() { $this->use_fuzzy = true; } public function setHeader( $key, $val ) { $this->head->add($key,$val); return $this; } private function str( $s ) { if( $cs = $this->cs ){ $s = mb_convert_encoding($s,$cs,['UTF-8']); } return $s; } public function compile() { $table = ['']; $sources = ['']; $targets = [ (string) $this->head ]; $fuzzy_flag = 4; $skip_fuzzy = ! $this->use_fuzzy; if( $this->head->has('Plural-Forms') && preg_match('/^nplurals=(\\d)/',$this->head->trimmed('Plural-Forms'), $r) ){ $nplurals = (int) $r[1]; $maxplural = max( 0, $nplurals-1 ); } else { $maxplural = 1; } foreach( $this->msgs as $r ){ if( $skip_fuzzy && isset($r['flag']) && $fuzzy_flag === $r['flag'] ){ continue; } $msgid = $this->str( $r['key'] ); if( isset($r['context']) ){ $msgctxt = $this->str( $r['context'] ); if( '' !== $msgctxt ){ if( '' === $msgid ){ $msgid = '('.$msgctxt.')'; } $msgid = $msgctxt."\x04".$msgid; } } if( '' === $msgid ){ continue; } $msgstr = $this->str( $r['target'] ); if( '' === $msgstr ){ continue; } $table[] = $msgid; if( isset($r['plurals']) ){ if( $r['plurals'] ){ $i = 0; foreach( $r['plurals'] as $i => $p ){ if( $i === 0 ){ $msgid .= "\0".$this->str($p['key']); } $msgstr .= "\0".$this->str($p['target']); } while( $maxplural > ++$i ){ $msgstr .= "\0"; } } else if( isset($r['plural_key']) ){ $msgid .= "\0".$this->str($r['plural_key']); } } $sources[] = $msgid; $targets[] = $msgstr; } asort( $sources, SORT_STRING ); $this->bin = "\xDE\x12\x04\x95\x00\x00\x00\x00"; $n = count($sources); $this->writeInteger( $n ); $offset = 28; $this->writeInteger( $offset ); $offset += $n * 8; $this->writeInteger( $offset ); if( $this->hash ){ sort( $table, SORT_STRING ); $this->hash->compile( $table ); $s = $this->hash->count(); } else { $s = 0; } $this->writeInteger( $s ); $offset += $n * 8; $this->writeInteger( $offset ); if( $s ){ $offset += $s * 4; } $source = ''; foreach( $sources as $str ){ $source .= $str."\0"; $this->writeInteger( $strlen = strlen($str) ); $this->writeInteger( $offset ); $offset += $strlen + 1; } $target = ''; foreach( array_keys($sources) as $i ){ $str = $targets[$i]; $target .= $str."\0"; $this->writeInteger( $strlen = strlen($str) ); $this->writeInteger( $offset ); $offset += $strlen + 1; } if( $this->hash ){ $this->bin .= $this->hash->__toString(); } $this->bin .= $source; $this->bin .= $target; return $this->bin; } private function writeInteger( $num ) { $this->bin .= pack( 'V', $num ); } } interface LocoTokensInterface extends Iterator { public function advance(); public function ignore( ...$symbols ); } class LocoTokenizer implements LocoTokensInterface { const T_LITERAL = 0; const T_UNKNOWN = -1; private $src; private $pos; private $line; private $col; private $max; private $rules = []; private $skip = []; private $tok; private $len; public function __construct( $src = '' ){ $this->init($src); } public function parse( $src ) { return iterator_to_array( $this->generate($src) ); } public function generate( $src ) { $this->init($src); while( $this->valid() ){ yield $this->current(); $this->next(); } } public function init( $src ) { $this->src = $src; $this->rewind(); return $this; } public function define( $grep, $t = 0 ) { if('^' !== $grep[1] ){ throw new InvalidArgumentException('Expression '.$grep.' isn\'t anchored'); } if( ! is_int($t) && ! is_callable($t) ){ throw new InvalidArgumentException('Non-integer token must be valid callback'); } $sniff = $grep[2]; if( $sniff === preg_quote($sniff,$grep[0]) ){ $this->rules[$sniff][] = [ $grep, $t ]; } else { $this->rules[''][] = [ $grep, $t ]; } return $this; } public function ignore( ...$symbols ) { $this->skip += array_fill_keys( $symbols, true ); return $this; } #[ReturnTypeWillChange] public function current() { return $this->tok; } public function advance() { $tok = $this->current(); $this->next(); return $tok; } #[ReturnTypeWillChange] public function next() { $tok = null; $offset = $this->pos; $column = $this->col; $line = $this->line; while( $offset <= $this->max ){ $t = null; $s = ''; $text = substr($this->src,$offset); foreach( [$text[0],''] as $k ){ if( isset($this->rules[$k]) ) { foreach( $this->rules[$k] as $rule) { if( preg_match($rule[0], $text, $match ) ) { $s = $match[0]; $t = $rule[1]; if( ! is_int($t) ) { $t = call_user_func( $t, $s, $match ); } break 2; } } } } if( is_null($t) ){ $n = preg_match('/^./u',$text,$match); if( false === $n ){ $s = $text[0]; $match = [ mb_convert_encoding($s,'UTF-8','cp1252') ]; } $s = (string) $match[0]; $t = self::T_UNKNOWN; } $length = strlen($s); if( 0 === $length ){ throw new Loco_error_ParseException('Failed to match anything'); } $offset += $length; $lines = preg_split('/\\r?\\n/',$s); $nlines = count($lines); if( $nlines > 1 ){ $next_line = $line + ( $nlines - 1 ); $next_column = strlen( end($lines) ); } else { $next_line = $line; $next_column = $column + $length; } if( array_key_exists($t,$this->skip) ){ $line = $next_line; $column = $next_column; continue; } $tok = self::T_LITERAL === $t ? $s : [ $t, $s, $line, $column ]; $line = $next_line; $column = $next_column; $this->len++; break; } $this->tok = $tok; $this->pos = $offset; $this->col = $column; $this->line = $line; } #[ReturnTypeWillChange] public function key() { return $this->len ? $this->len-1 : null; } #[ReturnTypeWillChange] public function valid() { return null !== $this->tok; } #[ReturnTypeWillChange] public function rewind() { $this->len = 0; $this->pos = 0; $this->col = 0; $this->line = 1; $this->max = strlen($this->src) - 1; $this->next(); } } function loco_utf8_chr( $u ){ if( $u < 0x80 ){ if( $u < 0 ){ throw new RangeException( sprintf('%d is out of Unicode range', $u ) ); } return chr($u); } if( $u < 0x800 ) { return chr( ($u>>6) & 0x1F | 0xC0 ).chr( $u & 0x3F | 0x80 ); } if( $u < 0x10000 ) { return chr( $u>>12 & 15 | 0xE0 ).chr( $u>>6 & 0x3F | 0x80 ).chr( $u & 0x3F | 0x80 ); } if( $u < 0x110000 ) { return chr( $u>>18 & 7 | 0xF0 ).chr( $u>>12 & 0x3F | 0x80 ).chr( $u>>6 & 0x3F | 0x80 ).chr( $u & 0x3F | 0x80 ); } throw new RangeException( sprintf('\\x%X is out of Unicode range', $u ) ); } function loco_resolve_surrogates( $s ){ return preg_replace_callback('/\\xED([\\xA0-\\xAF])([\\x80-\\xBF])\\xED([\\xB0-\\xBF])([\\x80-\\xBF])/', '_loco_resolve_surrogates', $s ); } function _loco_resolve_surrogates( array $r ){ return loco_utf8_chr ( ( ( ( ( 832 | ( ord($r[1]) & 0x3F ) ) << 6 ) | ( ord($r[2]) & 0x3F ) ) - 0xD800 ) * 0x400 + ( ( ( ( 832 | ( ord($r[3]) & 0x3F ) ) << 6 ) | ( ord($r[4]) & 0x3F ) ) - 0xDC00 ) + 0x10000 ); } class LocoEscapeParser { private $map; private $grep; public function __construct( array $map = [] ){ $this->map = $map; $rules = ['\\\\']; if( $map ){ $rules[] = '['.implode(array_keys($map)).']'; } if( ! isset($map['U']) ) { $rules[] = 'U[0-9A-Fa-f]{5,8}'; } if( ! isset($map['u']) ) { $rules[] = 'u(?:\\{[0-9A-Fa-f]+\\}|[0-9A-Fa-f]{1,4})(?:\\\\u(?:\\{[0-9A-Fa-f]+\\}|[0-9A-Fa-f]{1,4}))*'; } $this->grep = '/\\\\('.implode('|',$rules).')/'; } final public function unescape( $s ) { if( '' !== $s ) { return $this->stripSlashes( preg_replace_callback($this->grep, [$this, 'unescapeMatch'], $s) ); } return ''; } final public function unescapeMatch( array $r ) { $s = $r[0]; $c = $s[1]; if( isset($this->map[$c]) ){ return $this->map[$c]; } if( 'u' === $c ){ $str = ''; $surrogates = false; foreach( explode('\\u',$s) as $i => $h ){ if( '' !== $h ){ $h = ltrim( trim($h,'{}'),'0'); $u = intval($h,16); $str.= loco_utf8_chr($u); if( ! $surrogates ){ $surrogates = $u >= 0xD800 && $u <= 0xDBFF; } } } if( $surrogates ){ $str = loco_resolve_surrogates($str); } return $str; } if( 'U' === $c ){ return loco_utf8_chr( intval(substr($s,2),16) ); } if( 'x' === $c ){ return chr( intval(substr($s,2),16) ); } if( ctype_digit($c) ){ return chr( intval(substr($s,1),8) ); } return $s; } protected function stripSlashes( $s ) { return stripcslashes($s); } } class LocoJsTokens extends LocoTokenizer { const T_KWORD = 1; const T_REGEX = 2; private static $lex = null; protected static $words = [ 'true' => 1, 'false' => 1, 'null' => 1, 'break' => T_BREAK, 'else' => T_ELSE, 'new' => T_NEW, 'var' => 1, 'case' => T_CASE, 'finally' => T_FINALLY, 'return' => T_RETURN, 'void' => 1, 'catch' => T_CATCH, 'for' => T_FOR, 'switch' => T_SWITCH, 'while' => T_WHILE, 'continue' => T_CONTINUE, 'function' => T_FUNCTION, 'this' => T_STRING, 'with' => 1, 'default' => T_DEFAULT, 'if' => T_IF, 'throw' => T_THROW, 'delete' => 1, 'in' => 1, 'try' => T_TRY, 'do' => T_DO, 'instanceof' => 1, 'typeof' => 1, ]; public static function decapse( $encapsed ) { $s = substr($encapsed,1,-1); $l = self::$lex; if( is_null($l) ){ $l = new LocoEscapeParser( [ 'U' => 'U', 'a' => 'a', ] ); self::$lex = $l; } return $l->unescape($s); } public function __construct( $src = '' ){ $this->ignore(T_WHITESPACE); $this->define('/^(?:\\\\u[0-9A-F]{4,4}|[$_\\pL\\p{Nl}])(?:\\\\u[0-9A-F]{4}|[$_\\pL\\pN\\p{Mn}\\p{Mc}\\p{Pc}])*/ui', [$this,'matchWord'] ); $this->define('/^\\s+/u', T_WHITESPACE ); $this->define('!^//.*!', T_COMMENT ); $this->define('!^/\\*.*\\*/!Us', [$this,'matchComment'] ); $this->define('/^"(?:\\\\.|[^\\r\\n\\p{Zl}\\p{Zp}"\\\\])*"/u', T_CONSTANT_ENCAPSED_STRING ); $this->define('/^\'(?:\\\\.|[^\\r\\n\\p{Zl}\\p{Zp}\'\\\\])*\'/u', T_CONSTANT_ENCAPSED_STRING ); $this->define('/^[-+;,<>.=:|&^!?*%~(){}[\\]]/'); parent::__construct($src); } public function matchWord( $s ) { if( array_key_exists($s,self::$words) ){ return self::$words[$s]; } return T_STRING; } public function matchComment( $s ) { if( substr($s,0,3) === '/**' ){ return T_DOC_COMMENT; } return T_COMMENT; } } interface LocoExtractorInterface { public function setDomain( $default ); public function tokenize( $src ); public function extract( LocoExtracted $strings, LocoTokensInterface $tokens, $fileref = '' ); public function extractSource( $src, $fileref ); } class LocoExtracted implements Countable { private $exp = []; private $reg = []; private $dom = []; private $dflt = ''; public function extractSource( LocoExtractorInterface $ext, $src, $fileref = '' ) { $ext->extract( $this, $ext->tokenize($src), $fileref ); return $this; } public function export() { return $this->exp; } #[ReturnTypeWillChange] public function count() { return count( $this->exp ); } public function getDomainCounts() { return $this->dom; } public function setDomain( $default ) { $this->dflt = $default; return $this; } public function getDomain() { return $this->dflt; } private function key( array $entry ) { $key = (string) $entry['source']; foreach( ['context','domain'] as $i => $prop ){ if( array_key_exists($prop,$entry) ) { $add = (string) $entry[$prop]; if( '' !== $add ){ $key .= ord($i).$add; } } } return $key; } public function pushEntry( array $entry, $domain ) { if( '' === $domain || '*' === $domain ){ $domain = $this->dflt; } $entry['id'] = ''; $entry['target'] = ''; $entry['domain'] = $domain; $key = $this->key($entry); if( isset($this->reg[$key]) ){ $index = $this->reg[$key]; $clash = $this->exp[$index]; if( $value = $this->mergeField( $clash, $entry, 'refs', ' ') ){ $this->exp[$index]['refs'] = $value; } if( $value = $this->mergeField( $clash, $entry, 'notes', "\n") ){ $this->exp[$index]['notes'] = $value; } } else { $index = count($this->exp); $this->reg[$key] = $index; $this->exp[$index] = $entry; if( isset($this->dom[$domain]) ){ $this->dom[$domain]++; } else { $this->dom[$domain] = 1; } } return $index; } public function pushPlural( array $entry, $sindex ) { $parent = $this->exp[$sindex]; $domain = $parent['domain']; $pkey = $this->key($parent)."\2"; if( ! array_key_exists($pkey,$this->reg) ){ $pindex = count($this->exp); $this->reg[$pkey] = $pindex; $entry += [ 'id' => '', 'target' => '', 'plural' => 1, 'parent' => $sindex, 'domain' => $domain, ]; $this->exp[$pindex] = $entry; if( isset($entry['format']) && ! isset( $parent['format']) ) { $this->exp[$sindex]['format'] = $entry['format']; } if( $pindex !== $sindex + $entry['plural']) { $this->exp[$sindex]['child'] = $pindex; } } } public function mergeField( array $old, array $new, $field, $glue ) { $prev = isset($old[$field]) ? $old[$field] : ''; if( isset($new[$field]) ){ $text = $new[$field]; if( '' !== $prev && $prev !== $text ){ if( 'notes' === $field && preg_match( '/^'.preg_quote( rtrim($text,'. '),'/').'[. ]*$/mu', $prev ) ) { $text = $prev; } else { $text = $prev.$glue.$text; } } return $text; } return $prev; } public function filter( $domain ) { if( '' === $domain ){ $domain = $this->dflt; } $map = []; $newOffset = 1; $matchAll = '*' === $domain; $raw = [ [ 'id' => '', 'source' => '', 'target' => '', 'domain' => $matchAll ? '' : $domain, ] ]; foreach( $this->exp as $oldOffset => $r ){ if( isset($r['parent']) ){ if( isset($map[$r['parent']]) ){ $r['parent'] = $map[ $r['parent'] ]; $raw[ $newOffset++ ] = $r; } } else { if( $matchAll ){ $match = true; } else if( isset($r['domain']) ){ $match = $domain === $r['domain']; } else { $match = $domain === ''; } if( $match ){ $map[ $oldOffset ] = $newOffset; $raw[ $newOffset++ ] = $r; } } } return $raw; } } abstract class LocoExtractor implements LocoExtractorInterface { private $rules; private $wp = []; private $domain = ''; abstract protected function fsniff( $str ); abstract protected function decapse( $raw ); abstract protected function comment( $comment ); public function __construct( array $rules ){ $this->rules = $rules; } public function setDomain( $default ) { $this->domain = $default; return $this; } public function headerize( array $tags, $domain = '' ) { if( isset($this->wp[$domain]) ){ $this->wp[$domain] += $tags; } else { $this->wp[$domain] = $tags; } return $this; } protected function getHeaders() { return $this->wp; } final public function extractSource( $src, $fileref ) { $strings = new LocoExtracted; $this->extract( $strings, $this->tokenize($src), $fileref ); return $strings; } public function rule( $keyword ) { return isset($this->rules[$keyword]) ? $this->rules[$keyword] : ''; } protected function push( LocoExtracted $strings, $rule, array $args, $comment = '', $ref = '' ) { $s = strpos( $rule, 's'); $p = strpos( $rule, 'p'); $c = strpos( $rule, 'c'); $d = strpos( $rule, 'd'); if( false === $s || ! isset($args[$s]) ){ return null; } $msgid = $args[$s]; if( ! is_string($msgid) ){ return null; } $entry = [ 'source' => $msgid, ]; if( is_int($c) && isset($args[$c]) ){ $entry['context'] = $args[$c]; } else if( '' === $msgid ){ return null; } if( $ref ){ $entry['refs'] = $ref; } if( is_int($d) && array_key_exists($d,$args) ){ $domain = $args[$d]; if( is_null($domain) ){ $domain = ''; } } else if( '' === $this->domain ) { $domain = $strings->getDomain(); } else { $domain = $this->domain; } $format = ''; $comment = $this->comment($comment); if( '' !== $comment ){ if( preg_match('/^xgettext:\\s*([-a-z]+)-format\\s*/mi', $comment, $r, PREG_OFFSET_CAPTURE ) ){ $format = $r[1][0]; $entry['format'] = $format; $comment = trim( substr_replace( $comment,'', $r[0][1], strlen($r[0][0]) ) ); } if( preg_match('/^references?:( *.+:\\d+)*\\s*/mi', $comment, $r, PREG_OFFSET_CAPTURE ) ){ $entry['refs'] = trim($r[1][0],' '); $comment = trim( substr_replace( $comment, '', $r[0][1], strlen($r[0][0]) ) ); } $entry['notes'] = $comment; } $msgid_plural = is_int($p) && isset($args[$p]) ? $args[$p] : ''; if( '' === $format ){ $format = $this->fsniff($msgid); if( '' !== $format ){ $entry['format'] = $format; } else if( '' !== $msgid_plural ){ $format = $this->fsniff($msgid_plural); if( '' !== $format ){ $entry['format'] = $format; } } } $index = $strings->pushEntry($entry,$domain); if( '' !== $msgid_plural ){ $entry = [ 'source' => $msgid_plural, ]; if( '' !== $format ) { $entry['format'] = $format; } $strings->pushPlural($entry,$index); } return $index; } protected function utf8( $str ) { if( false === preg_match('//u',$str) ){ $str = mb_convert_encoding( $str, 'UTF-8', 'cp1252' ); } return $str; } } class LocoPHPTokens implements LocoTokensInterface, Countable { private $i = null; private $tokens; private $skip_tokens; private $literal_tokens; public function __construct( array $tokens ){ $this->tokens = $tokens; $this->reset(); } public function reset() { $this->rewind(); $this->literal_tokens = []; $this->skip_tokens = []; } public function literal( ...$symbols ) { $this->literal_tokens += array_fill_keys($symbols,true); return $this; } public function ignore( ...$symbols ) { $this->skip_tokens += array_fill_keys($symbols,true); return $this; } public function export() { return array_values( iterator_to_array($this) ); } public function advance() { if( $this->valid() ){ $tok = $this->current(); $this->next(); return $tok; } return null; } #[ReturnTypeWillChange] public function rewind() { $this->i = ( false === reset($this->tokens) ? null : key($this->tokens) ); } #[ReturnTypeWillChange] public function valid() { while( is_int($this->i) ){ $tok = $this->tokens[$this->i]; if( array_key_exists( is_array($tok)?$tok[0]:$tok, $this->skip_tokens ) ){ $this->next(); } else { return true; } } return false; } #[ReturnTypeWillChange] public function key() { return $this->i; } #[ReturnTypeWillChange] public function next() { $this->i = ( false === next($this->tokens) ? null : key($this->tokens) ); } #[ReturnTypeWillChange] public function current() { $tok = $this->tokens[$this->i]; if( is_array($tok) && isset($this->literal_tokens[$tok[0]]) ){ return $tok[1]; } return $tok; } public function __toString() { $s = []; foreach( $this as $token ){ $s[] = is_array($token) ? $token[1] : $token; } return implode('',$s); } #[ReturnTypeWillChange] public function count() { return count($this->tokens); } } class LocoPHPEscapeParser extends LocoEscapeParser { public function __construct(){ parent::__construct( [ 'n' => "\n", 'r' => "\r", 't' => "\t", 'v' => "\x0B", 'f' => "\x0C", 'e' => "\x1B", '$' => '$', '\\' => '\\', '"' => '"', ] ); } protected function stripSlashes( $s ) { return preg_replace_callback('/\\\\(x[0-9A-Fa-f]{1,2}|[0-3]?[0-7]{1,2})/', [$this,'unescapeMatch'], $s, -1, $n ); } } function loco_unescape_php_string( $s ) { static $l; if( is_null($l) ) { $l = new LocoPHPEscapeParser; } return $l->unescape($s); } function loco_decapse_php_string( $s ) { if( '' === $s ){ return ''; } $q = $s[0]; if( "'" === $q ){ return str_replace( ['\\'.$q, '\\\\'], [$q, '\\'], substr( $s, 1, -1 ) ); } if( '"' !== $q ){ return $s; } return loco_unescape_php_string( substr($s,1,-1) ); } function loco_parse_php_comment( $comment ) { $comment = trim( $comment,"/ \n\r\t" ); if( '' !== $comment && '*' === $comment[0] ){ $lines = []; $junk = "\r\t/ *"; foreach( explode("\n",$comment) as $line ){ $line = trim($line,$junk); if( '' !== $line ){ $lines[] = $line; } } $comment = implode("\n", $lines); } return $comment; } function loco_parse_wp_comment( $block ) { $header = []; if( '/*' === substr($block,0,2) ){ $junk = "\r\t/ *"; foreach( explode("\n", $block) as $line ){ if( false !== ( $i = strpos($line,':') ) ){ $key = substr($line,0,$i); $val = substr($line,++$i); $header[ trim($key,$junk) ] = trim($val,$junk); } } } return $header; } class LocoPHPExtractor extends LocoExtractor { private $defs = []; public function tokenize( $src ) { return new LocoPHPTokens( token_get_all($src) ); } public function decapse( $raw ) { return loco_decapse_php_string( $raw ); } public function fsniff( $str ) { $format = ''; $offset = 0; while( preg_match('/%(?:[1-9]\\d*\\$)?(?:\'.|[-+0 ])*\\d*(?:\\.\\d+)?(.|$)/',$str,$r,PREG_OFFSET_CAPTURE,$offset) ){ $type = $r[1][0]; list($match,$offset) = $r[0]; if( '%' === $type && '%%' !== $match ){ return ''; } if( '' === $type || ! preg_match('/^[bcdeEfFgGosuxX%]/',$type) ){ return ''; } $offset += strlen($match); if( preg_match('/^% +[a-z]/i',$match) || preg_match('/^%[b-ou-x]/i',$match) ){ continue; } $format = 'php'; } return $format; } protected function comment( $comment ) { return preg_replace('/^translators:\\s+/mi', '', loco_parse_php_comment($comment) ); } public function define( $name, $value ) { $this->defs[$name] = $value; return $this; } public function extract( LocoExtracted $strings, LocoTokensInterface $tokens, $fileref = '' ) { $tokens->ignore(T_WHITESPACE); $n = 0; $depth = 0; $comment = ''; $narg = 0; $args = []; $ref = ''; $rule = ''; $wp = $this->getHeaders(); $tokens->rewind(); while( $tok = $tokens->advance() ){ if( is_string($tok) ){ $s = $tok; $t = null; } else { $t = $tok[0]; $s = $tok[1]; } if( $depth ){ if( ')' === $s || ']' === $s ){ if( 0 === --$depth ){ if( $this->push( $strings, $rule, $args, $comment, $ref ) ){ $n++; } $comment = ''; } } else if( '(' === $s || '[' === $s ){ $depth++; $args[$narg] = null; } else if( 1 === $depth ){ if( ',' === $s ){ $narg++; } else if( T_CONSTANT_ENCAPSED_STRING === $t ){ $s = self::utf8($s); $args[$narg] = $this->decapse($s); } else if( T_STRING === $t && array_key_exists($s,$this->defs) ){ $args[$narg] = $this->defs[$s]; } else { $args[$narg] = null; } } } else if( T_COMMENT === $t || T_DOC_COMMENT === $t ){ $was_header = false; $s = self::utf8($s); if( 0 === $n ){ if( false !== strpos($s,'* @package') ){ $was_header = true; } if( $wp && ( $header = loco_parse_wp_comment($s) ) ){ foreach( $wp as $domain => $tags ){ foreach( array_intersect_key($header,$tags) as $tag => $text ){ $ref = $fileref ? $fileref.':'.$tok[2]: ''; $meta = $tags[$tag]; if( is_string($meta) ){ $meta = ['notes'=>$meta]; trigger_error( $tag.' header defaulted to "notes"',E_USER_DEPRECATED); } $strings->pushEntry( ['source'=>$text,'refs'=>$ref] + $meta, (string) $domain ); $was_header = true; } } } } if( ! $was_header ) { $comment = $s; } } else if( T_STRING === $t && '(' === $tokens->advance() && ( $rule = $this->rule($s) ) ){ $ref = $fileref ? $fileref.':'.$tok[2]: ''; $depth = 1; $args = []; $narg = 0; } else if( '' !== $comment && ! preg_match('!^[/* ]+(translators|xgettext):!im',$comment) ){ $comment = ''; } } } } class LocoJsExtractor extends LocoPHPExtractor { public function tokenize( $src ) { return new LocoJsTokens($src); } public function fsniff( $str ) { return parent::fsniff($str) ? 'javascript' : ''; } public function decapse( $raw ) { return LocoJsTokens::decapse($raw); } } class LocoTwigExtractor extends LocoPHPExtractor { public function tokenize( $src ) { return parent::tokenize( 'setBase( rtrim(ABSPATH,'/').'/wp-includes' ); } } public function setBase( $path ) { $this->base = $path; } private function getType( $type ) { if( array_key_exists($type,self::$types) ){ return self::$types[$type]; } $path = $this->base.'/'.$type.'-i18n.json'; if ( ! file_exists($path) ) { throw new Exception( basename($path).' not found in '.$this->base ); } return json_decode( file_get_contents($path) ); } public function tokenize( $src ) { $raw = json_decode($src,true); if( ! is_array($raw) || ! array_key_exists('$schema',$raw) ){ throw new InvalidArgumentException('Invalid JSON'); } if( ! preg_match('!^https?://schemas.wp.org/trunk/(block|theme)\\.json!', $raw['$schema'], $r ) ){ throw new InvalidArgumentException('Unsupported schema'); } if( '' === $this->domain && array_key_exists('textdomain',$raw) ){ $this->domain = $raw['textdomain']; } return new LocoWpJsonStrings( $raw, $this->getType($r[1]) ); } public function setDomain( $default ) { $this->domain = $default; return $this; } public function extract( LocoExtracted $strings, LocoTokensInterface $tokens, $fileref = '' ) { if( ! preg_match('/:\\d+$/',$fileref) ){ $fileref.=':1'; } $tokens->rewind(); while( $tok = $tokens->advance() ){ $tok['refs'] = $fileref; $strings->pushEntry( $tok, $this->domain ); } } final public function extractSource( $src, $fileref ) { $strings = new LocoExtracted; $this->extract( $strings, $this->tokenize($src), $fileref ); return $strings; } } class LocoWpJsonStrings extends ArrayIterator implements LocoTokensInterface { public function __construct( array $raw, stdClass $tpl ){ parent::__construct(); $this->walk( $tpl, $raw ); } public function advance() { $tok = $this->current(); $this->next(); return $tok; } public function ignore( ...$symbols ) { return $this; } private function walk( $tpl, $raw ) { if( is_string($tpl) && is_string($raw) ) { $this->offsetSet( null, [ 'context' => $tpl, 'source' => $raw, ] ); return; } if( is_array($tpl) && is_array($raw) ) { foreach ( $raw as $value ) { self::walk( $tpl[0], $value ); } } else if( is_object($tpl) && is_array($raw) ) { $group_key = '*'; foreach ( $raw as $key => $value ) { if ( isset($tpl->$key) ) { $this->walk( $tpl->$key, $value ); } else if ( isset($tpl->$group_key) ) { $this->walk( $tpl->$group_key, $value ); } } } } } function loco_wp_extractor( $type = 'php', $ext = '' ) { if( 'json' === $type ){ return new LocoWpJsonExtractor; } static $rules = [ '__' => 'sd', '_e' => 'sd', '_c' => 'sd', '_n' => 'sp_d', '_n_noop' => 'spd', '_nc' => 'sp_d', '__ngettext' => 'spd', '__ngettext_noop' => 'spd', '_x' => 'scd', '_ex' => 'scd', '_nx' => 'sp_cd', '_nx_noop' => 'spcd', 'esc_attr__' => 'sd', 'esc_html__' => 'sd', 'esc_attr_e' => 'sd', 'esc_html_e' => 'sd', 'esc_attr_x' => 'scd', 'esc_html_x' => 'scd', ]; if( 'php' === $type ){ return substr($ext,-9) === 'blade.php' ? new LocoBladeExtractor($rules) : new LocoPHPExtractor($rules); } if( 'js' === $type ){ return new LocoJsExtractor($rules); } if( 'twig' === $type ){ return new LocoTwigExtractor($rules); } throw new InvalidArgumentException('No extractor for '.$type); } function loco_string_percent( $n, $t ) { if( ! $t || ! $n ){ return '0'; } if( $t === $n ){ return '100'; } $dp = 0; $n = 100 * $n / $t; if( $n > 99 ){ return number_format( min( $n, 99.9 ), ++$dp ); } if( $n < 0.5 ){ $n = max( $n, 0.0001 ); do { $s = number_format( $n, ++$dp ); } while( preg_match('/^0\\.0+$/',$s) && $dp < 4 ); return substr($s,1); } return number_format( $n, $dp ); } function loco_print_progress( $translated, $untranslated, $flagged ) { $total = $translated + $untranslated; $complete = loco_string_percent( $translated - $flagged, $total ); $class = 'progress'; if( ! $translated && ! $flagged ){ $class .= ' empty'; } else if( '100' === $complete ){ $class .= ' done'; } echo '
      '; if( $flagged ){ $s = loco_string_percent( $flagged, $total ); echo '
       
      '; } if( '0' === $complete ){ echo ' '; } else { $class = 'bar p'; $p = (int) $complete; $class .= sprintf(' p-%u', 10*floor($p/10) ); $style = 'width:'.$complete.'%'; if( $flagged ){ $remain = 100.0 - (float) $s; $style .= '; max-width: '.sprintf('%s',$remain).'%'; } echo '
       
      '; } echo '
      ',$complete,'%
      '; } class LocoFuzzyMatcher implements Countable { private $pot = []; private $po = []; private $diff = []; private $dmax = .20; #[ReturnTypeWillChange] public function count() { return count($this->pot); } public function unmatched() { return array_values($this->pot); } public function redundant() { return array_values($this->po); } public function setFuzziness( $s ) { if( $this->po ){ throw new LogicException('Cannot setFuzziness() after calling match()'); } $this->dmax = (float) max( 0, min( (int) $s, 100 ) ) / 100; } public function add( $a ) { $source = isset($a['source']) ? (string) $a['source'] : ''; $context = isset($a['context']) ? (string) $a['context'] : ''; $key = $source."\4".$context; $this->pot[$key] = $a; } private function key( $a ) { $source = isset($a['source']) ? (string) $a['source'] : ''; $context = isset($a['context']) ? (string) $a['context'] : ''; return $source."\4".$context; } protected function getRef( $a ) { $key = $this->key($a); return array_key_exists($key,$this->pot) ? $this->pot[$key] : null; } public function match( $a ) { $old = $this->key($a); if( isset($this->pot[$old]) ){ $new = $this->pot[$old]; unset($this->pot[$old]); return $new; } $this->po[$old] = $a; $target = isset($a['target']) ? (string) $a['target'] : ''; $comment = isset($a['comment']) ? (string) $a['comment'] : ''; if( '' === $target && '' === $comment ){ return null; } if( 0 < $this->dmax ){ foreach( $this->pot as $new => $_ ){ $dist = $this->distance($old,$new); if( -1 !== $dist ){ $this->diff[] = [ $old, $new, $dist ]; } } } return null; } private function distance( $a, $b ) { $a = strtolower($a); $b = strtolower($b); if( $a === $b ){ return 0; } $lenA = strlen($a); $lenB = strlen($b); $lenDiff = abs($lenA-$lenB); $max = min($lenA,$lenB) + $lenDiff; $max = (int) ceil( $this->dmax * $max ); if( $max < $lenDiff ) { return -1; } $len = max($lenA,$lenB); if( $len < 256 ){ $d = levenshtein($a,$b); return $d > $max ? -1 : $d; } $d = 0; for( $i = 0; $i < $len; $i+=$max ){ $aa = substr($a,$i,$max); $bb = substr($b,$i,$max); $d += levenshtein($aa,$bb); if( $d > $max ){ return -1; } } return $d; } public function getFuzzyMatches() { $pairs = []; usort( $this->diff, [__CLASS__,'compareDistance'] ); foreach( $this->diff as $pair ){ list($old,$new) = $pair; if( ! array_key_exists($new,$this->pot) || ! array_key_exists($old,$this->po) ){ continue; } $pairs[] = [ $this->po[$old], $this->pot[$new], ]; unset($this->po[$old]); unset($this->pot[$new]); if( ! $this->po || ! $this->pot ){ break; } } $this->diff = []; return $pairs; } private static function compareDistance( array $a, array $b ) { return $a[2] - $b[2]; } } defined('T_FINALLY') || define('T_FINALLY',500); if( function_exists('loco_check_extension') ) { loco_check_extension('mbstring'); } lib/data/gp.php000064400000001603147206622240007347 0ustar00'4.0.0','aliases'=>['arg'=>'an','bg-bg'=>'bg','bn-bd'=>'bn','bre'=>'br','bs-ba'=>'bs','ca-valencia'=>'ca-val','cs-cz'=>'cs','da-dk'=>'da','de-de'=>'de','ewe'=>'ee','en-us'=>'en','es-es'=>'es','fa-ir'=>'fa','fr-fr'=>'fr','gl-es'=>'gl','haw-us'=>'haw','he-il'=>'he','hi-in'=>'hi','hu-hu'=>'hu','id-id'=>'id','is-is'=>'is','it-it'=>'it','jv-id'=>'jv','ka-ge'=>'ka','ko-kr'=>'ko','lb-lu'=>'lb','lt-lt'=>'lt','me-me'=>'me','mg-mg'=>'mg','mk-mk'=>'mk','ml-in'=>'ml','ms-my'=>'ms','my-mm'=>'mya','ne-np'=>'ne','nb-no'=>'nb','nl-nl'=>'nl','nn-no'=>'nn','pa-in'=>'pa','art-xpirate'=>'pirate','pl-pl'=>'pl','pt-pt'=>'pt','pt-pt-ao90'=>'pt-ao90','ro-ro'=>'ro','ru-ru'=>'ru','si-lk'=>'si','sk-sk'=>'sk','sl-si'=>'sl','so-so'=>'so','sr-rs'=>'sr','su-id'=>'su','sv-se'=>'sv','ta-in'=>'ta','tr-tr'=>'tr','tt-ru'=>'tt','ug-cn'=>'ug','uz-uz'=>'uz']]; lib/data/languages.php000064400000007612147206622240010715 0ustar00'Afar','ab'=>'Abkhazian','ae'=>'Avestan','af'=>'Afrikaans','ak'=>'Akan','am'=>'Amharic','an'=>'Aragonese','ar'=>'Arabic','arq'=>'Algerian Arabic','ary'=>'Moroccan Arabic','as'=>'Assamese','ast'=>'Asturian','av'=>'Avaric','ay'=>'Aymara','az'=>'Azerbaijani','azb'=>'South Azerbaijani','ba'=>'Bashkir','bal'=>'Baluchi','bcc'=>'Southern Balochi','be'=>'Belarusian','bg'=>'Bulgarian','bgn'=>'Western Balochi','bho'=>'Bhojpuri','bi'=>'Bislama','bm'=>'Bambara','bn'=>'Bengali','bo'=>'Tibetan','br'=>'Breton','brx'=>'Bodo (India)','bs'=>'Bosnian','ca'=>'Catalan','ce'=>'Chechen','ceb'=>'Cebuano','ch'=>'Chamorro','ckb'=>'Central Kurdish','co'=>'Corsican','cr'=>'Cree','cs'=>'Czech','cu'=>'Church Slavic','cv'=>'Chuvash','cy'=>'Welsh','da'=>'Danish','de'=>'German','dsb'=>'Lower Sorbian','dv'=>'Dhivehi','dz'=>'Dzongkha','ee'=>'Ewe','el'=>'Greek','en'=>'English','eo'=>'Esperanto','es'=>'Spanish','et'=>'Estonian','eu'=>'Basque','fa'=>'Persian','ff'=>'Fulah','fi'=>'Finnish','fj'=>'Fijian','fo'=>'Faroese','fon'=>'Fon','fr'=>'French','frp'=>'Arpitan','fuc'=>'Pulaar','fur'=>'Friulian','fy'=>'Western Frisian','ga'=>'Irish','gax'=>'Borana-Arsi-Guji Oromo','gd'=>'Scottish Gaelic','gl'=>'Galician','gn'=>'Guarani','gu'=>'Gujarati','gv'=>'Manx','ha'=>'Hausa','haw'=>'Hawaiian','haz'=>'Hazaragi','he'=>'Hebrew','hi'=>'Hindi','ho'=>'Hiri Motu','hr'=>'Croatian','hsb'=>'Upper Sorbian','ht'=>'Haitian','hu'=>'Hungarian','hy'=>'Armenian','hz'=>'Herero','ia'=>'Interlingua','id'=>'Indonesian','ie'=>'Interlingue','ig'=>'Igbo','ii'=>'Sichuan Yi','ik'=>'Inupiaq','io'=>'Ido','is'=>'Icelandic','it'=>'Italian','iu'=>'Inuktitut','ja'=>'Japanese','jv'=>'Javanese','ka'=>'Georgian','kaa'=>'Kara-Kalpak','kab'=>'Kabyle','kg'=>'Kongo','ki'=>'Kikuyu','kj'=>'Kuanyama','kk'=>'Kazakh','kl'=>'Kalaallisut','km'=>'Central Khmer','kmr'=>'Northern Kurdish','kn'=>'Kannada','ko'=>'Korean','kr'=>'Kanuri','ks'=>'Kashmiri','ku'=>'Kurdish','kv'=>'Komi','kw'=>'Cornish','ky'=>'Kirghiz','la'=>'Latin','lb'=>'Luxembourgish','lg'=>'Ganda','li'=>'Limburgan','lij'=>'Ligurian','lmo'=>'Lombard','ln'=>'Lingala','lo'=>'Lao','lt'=>'Lithuanian','lu'=>'Luba-Katanga','lv'=>'Latvian','mai'=>'Maithili','mfe'=>'Morisyen','mg'=>'Malagasy','mh'=>'Marshallese','mi'=>'Maori','mk'=>'Macedonian','ml'=>'Malayalam','mn'=>'Mongolian','mr'=>'Marathi','ms'=>'Malay','mt'=>'Maltese','my'=>'Burmese','na'=>'Nauru','nb'=>'Norwegian Bokmål','nd'=>'North Ndebele','ne'=>'Nepali','ng'=>'Ndonga','nl'=>'Dutch','nn'=>'Norwegian Nynorsk','no'=>'Norwegian','nqo'=>'N\'Ko','nr'=>'South Ndebele','nv'=>'Navajo','ny'=>'Nyanja','oc'=>'Occitan (post 1500)','oj'=>'Ojibwa','om'=>'Oromo','or'=>'Oriya','ory'=>'Oriya (individual language)','os'=>'Ossetian','pa'=>'Panjabi','pap'=>'Papiamento','pcd'=>'Picard','pcm'=>'Nigerian Pidgin','pi'=>'Pali','pl'=>'Polish','ps'=>'Pushto','pt'=>'Portuguese','qu'=>'Quechua','rhg'=>'Rohingya','rm'=>'Romansh','rn'=>'Rundi','ro'=>'Romanian','ru'=>'Russian','rw'=>'Kinyarwanda','sa'=>'Sanskrit','sah'=>'Yakut','sc'=>'Sardinian','scn'=>'Sicilian','sd'=>'Sindhi','se'=>'Northern Sami','sg'=>'Sango','sh'=>'Serbo-Croatian','si'=>'Sinhala','sk'=>'Slovak','skr'=>'Saraiki','sl'=>'Slovenian','sm'=>'Samoan','sn'=>'Shona','so'=>'Somali','sq'=>'Albanian','sr'=>'Serbian','ss'=>'Swati','st'=>'Southern Sotho','su'=>'Sundanese','sv'=>'Swedish','sw'=>'Swahili','syr'=>'Syriac','szl'=>'Silesian','ta'=>'Tamil','te'=>'Telugu','tg'=>'Tajik','th'=>'Thai','ti'=>'Tigrinya','tk'=>'Turkmen','tl'=>'Tagalog','tn'=>'Tswana','to'=>'Tonga (Tonga Islands)','tr'=>'Turkish','ts'=>'Tsonga','tt'=>'Tatar','tw'=>'Twi','twd'=>'Twents','ty'=>'Tahitian','tzm'=>'Central Atlas Tamazight','ug'=>'Uighur','uk'=>'Ukrainian','ur'=>'Urdu','uz'=>'Uzbek','ve'=>'Venda','vec'=>'Venetian','vi'=>'Vietnamese','vo'=>'Volapük','wa'=>'Walloon','wo'=>'Wolof','xh'=>'Xhosa','yi'=>'Yiddish','yo'=>'Yoruba','za'=>'Zhuang','zgh'=>'Standard Moroccan Tamazight','zh'=>'Chinese','zu'=>'Zulu','tlh'=>'Klingon'];lib/data/regions.php000064400000011721147206622240010411 0ustar00'Andorra','AE'=>'United Arab Emirates','AF'=>'Afghanistan','AG'=>'Antigua and Barbuda','AI'=>'Anguilla','AL'=>'Albania','AM'=>'Armenia','AO'=>'Angola','AQ'=>'Antarctica','AR'=>'Argentina','AS'=>'American Samoa','AT'=>'Austria','AU'=>'Australia','AW'=>'Aruba','AX'=>'Åland Islands','AZ'=>'Azerbaijan','BA'=>'Bosnia and Herzegovina','BB'=>'Barbados','BD'=>'Bangladesh','BE'=>'Belgium','BF'=>'Burkina Faso','BG'=>'Bulgaria','BH'=>'Bahrain','BI'=>'Burundi','BJ'=>'Benin','BL'=>'Saint Barthélemy','BM'=>'Bermuda','BN'=>'Brunei Darussalam','BO'=>'Bolivia','BQ'=>'Bonaire, Sint Eustatius and Saba','BR'=>'Brazil','BS'=>'Bahamas','BT'=>'Bhutan','BV'=>'Bouvet Island','BW'=>'Botswana','BY'=>'Belarus','BZ'=>'Belize','CA'=>'Canada','CC'=>'Cocos (Keeling) Islands','CD'=>'The Democratic Republic of the Congo','CF'=>'Central African Republic','CG'=>'Congo','CH'=>'Switzerland','CI'=>'Côte d\'Ivoire','CK'=>'Cook Islands','CL'=>'Chile','CM'=>'Cameroon','CN'=>'China','CO'=>'Colombia','CR'=>'Costa Rica','CU'=>'Cuba','CV'=>'Cabo Verde; Cape Verde','CW'=>'Curaçao','CX'=>'Christmas Island','CY'=>'Cyprus','CZ'=>'Czech Republic','DE'=>'Germany','DJ'=>'Djibouti','DK'=>'Denmark','DM'=>'Dominica','DO'=>'Dominican Republic','DZ'=>'Algeria','EC'=>'Ecuador','EE'=>'Estonia','EG'=>'Egypt','EH'=>'Western Sahara','ER'=>'Eritrea','ES'=>'Spain','ET'=>'Ethiopia','FI'=>'Finland','FJ'=>'Fiji','FK'=>'Falkland Islands (Malvinas)','FM'=>'Federated States of Micronesia','FO'=>'Faroe Islands','FR'=>'France','GA'=>'Gabon','GB'=>'United Kingdom','GD'=>'Grenada','GE'=>'Georgia','GF'=>'French Guiana','GG'=>'Guernsey','GH'=>'Ghana','GI'=>'Gibraltar','GL'=>'Greenland','GM'=>'Gambia','GN'=>'Guinea','GP'=>'Guadeloupe','GQ'=>'Equatorial Guinea','GR'=>'Greece','GS'=>'South Georgia and the South Sandwich Islands','GT'=>'Guatemala','GU'=>'Guam','GW'=>'Guinea-Bissau','GY'=>'Guyana','HK'=>'Hong Kong','HM'=>'Heard Island and McDonald Islands','HN'=>'Honduras','HR'=>'Croatia','HT'=>'Haiti','HU'=>'Hungary','ID'=>'Indonesia','IE'=>'Ireland','IL'=>'Israel','IM'=>'Isle of Man','IN'=>'India','IO'=>'British Indian Ocean Territory','IQ'=>'Iraq','IR'=>'Islamic Republic of Iran','IS'=>'Iceland','IT'=>'Italy','JE'=>'Jersey','JM'=>'Jamaica','JO'=>'Jordan','JP'=>'Japan','KE'=>'Kenya','KG'=>'Kyrgyzstan','KH'=>'Cambodia','KI'=>'Kiribati','KM'=>'Comoros','KN'=>'Saint Kitts and Nevis','KP'=>'Democratic People\'s Republic of Korea','KR'=>'Republic of Korea','KW'=>'Kuwait','KY'=>'Cayman Islands','KZ'=>'Kazakhstan','LA'=>'Lao People\'s Democratic Republic','LB'=>'Lebanon','LC'=>'Saint Lucia','LI'=>'Liechtenstein','LK'=>'Sri Lanka','LR'=>'Liberia','LS'=>'Lesotho','LT'=>'Lithuania','LU'=>'Luxembourg','LV'=>'Latvia','LY'=>'Libya','MA'=>'Morocco','MC'=>'Monaco','MD'=>'Moldova','ME'=>'Montenegro','MF'=>'Saint Martin (French part)','MG'=>'Madagascar','MH'=>'Marshall Islands','MK'=>'The Former Yugoslav Republic of Macedonia','ML'=>'Mali','MM'=>'Myanmar','MN'=>'Mongolia','MO'=>'Macao','MP'=>'Northern Mariana Islands','MQ'=>'Martinique','MR'=>'Mauritania','MS'=>'Montserrat','MT'=>'Malta','MU'=>'Mauritius','MV'=>'Maldives','MW'=>'Malawi','MX'=>'Mexico','MY'=>'Malaysia','MZ'=>'Mozambique','NA'=>'Namibia','NC'=>'New Caledonia','NE'=>'Niger','NF'=>'Norfolk Island','NG'=>'Nigeria','NI'=>'Nicaragua','NL'=>'Netherlands','NO'=>'Norway','NP'=>'Nepal','NR'=>'Nauru','NU'=>'Niue','NZ'=>'New Zealand','OM'=>'Oman','PA'=>'Panama','PE'=>'Peru','PF'=>'French Polynesia','PG'=>'Papua New Guinea','PH'=>'Philippines','PK'=>'Pakistan','PL'=>'Poland','PM'=>'Saint Pierre and Miquelon','PN'=>'Pitcairn','PR'=>'Puerto Rico','PS'=>'State of Palestine','PT'=>'Portugal','PW'=>'Palau','PY'=>'Paraguay','QA'=>'Qatar','RE'=>'Réunion','RO'=>'Romania','RS'=>'Serbia','RU'=>'Russian Federation','RW'=>'Rwanda','SA'=>'Saudi Arabia','SB'=>'Solomon Islands','SC'=>'Seychelles','SD'=>'Sudan','SE'=>'Sweden','SG'=>'Singapore','SH'=>'Saint Helena, Ascension and Tristan da Cunha','SI'=>'Slovenia','SJ'=>'Svalbard and Jan Mayen','SK'=>'Slovakia','SL'=>'Sierra Leone','SM'=>'San Marino','SN'=>'Senegal','SO'=>'Somalia','SR'=>'Suriname','SS'=>'South Sudan','ST'=>'Sao Tome and Principe','SV'=>'El Salvador','SX'=>'Sint Maarten (Dutch part)','SY'=>'Syrian Arab Republic','SZ'=>'Swaziland','TC'=>'Turks and Caicos Islands','TD'=>'Chad','TF'=>'French Southern Territories','TG'=>'Togo','TH'=>'Thailand','TJ'=>'Tajikistan','TK'=>'Tokelau','TL'=>'Timor-Leste','TM'=>'Turkmenistan','TN'=>'Tunisia','TO'=>'Tonga','TR'=>'Turkey','TT'=>'Trinidad and Tobago','TV'=>'Tuvalu','TW'=>'Taiwan','TZ'=>'United Republic of Tanzania','UA'=>'Ukraine','UG'=>'Uganda','UM'=>'United States Minor Outlying Islands','US'=>'United States','UY'=>'Uruguay','UZ'=>'Uzbekistan','VA'=>'Holy See (Vatican City State)','VC'=>'Saint Vincent and the Grenadines','VE'=>'Venezuela','VG'=>'British Virgin Islands','VI'=>'U.S. Virgin Islands','VN'=>'Viet Nam','VU'=>'Vanuatu','WF'=>'Wallis and Futuna','WS'=>'Samoa','YE'=>'Yemen','YT'=>'Mayotte','ZA'=>'South Africa','ZM'=>'Zambia','ZW'=>'Zimbabwe','ZZ'=>'Private use'];lib/data/plurals.php000064400000005633147206622240010432 0ustar001,'am'=>1,'ar'=>2,'ary'=>2,'be'=>3,'bm'=>4,'bo'=>4,'br'=>1,'bs'=>3,'cs'=>5,'cy'=>6,'dz'=>4,'ff'=>1,'fr'=>1,'ga'=>7,'gd'=>8,'gv'=>9,'hr'=>10,'id'=>4,'ii'=>4,'iu'=>11,'ja'=>4,'ka'=>4,'kk'=>4,'km'=>4,'kn'=>4,'ko'=>4,'kw'=>11,'ky'=>4,'ln'=>1,'lo'=>4,'lt'=>12,'lv'=>13,'mg'=>1,'mi'=>1,'mk'=>14,'ms'=>4,'mt'=>15,'my'=>4,'nr'=>4,'oc'=>1,'pl'=>16,'ro'=>17,'ru'=>3,'sa'=>11,'sg'=>4,'sk'=>5,'sl'=>18,'sm'=>4,'sr'=>3,'su'=>4,'th'=>4,'ti'=>1,'tl'=>1,'to'=>4,'tt'=>4,'ug'=>4,'uk'=>3,'vi'=>4,'wa'=>1,'wo'=>4,'yo'=>4,'zh'=>4,''=>[0=>[0=>'n != 1',1=>[1=>'one','0,2,3…'=>'other']],1=>[0=>'n > 1',1=>['0,1'=>'one','2,3,4…'=>'other']],2=>[0=>'n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100 >= 3 && n%100<=10 ? 3 : n%100 >= 11 && n%100<=99 ? 4 : 5',1=>[0=>'zero',1=>'one',2=>'two','3,4,5…'=>'few','11,12,13…'=>'many','100,101,102…'=>'other']],3=>[0=>'(n%10==1 && n%100!=11 ? 0 : n%10 >= 2 && n%10<=4 &&(n%100<10||n%100 >= 20)? 1 : 2)',1=>['1,21,31…'=>'one','2,3,4…'=>'few','0,5,6…'=>'other']],4=>[0=>'0',1=>['0,1,2…'=>'other']],5=>[0=>'( n == 1 ) ? 0 : ( n >= 2 && n <= 4 ) ? 1 : 2',1=>[1=>'one','2,3,4'=>'few','0,5,6…'=>'other']],6=>[0=>'n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n==3 ? 3 : n==6 ? 4 : 5',1=>[0=>'zero',1=>'one',2=>'two',3=>'few',6=>'many','4,5,7…'=>'other']],7=>[0=>'n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4',1=>[1=>'one',2=>'two','0,3,4…'=>'few','7,8,9…'=>'many','11,12,13…'=>'other']],8=>[0=>'n==1||n==11 ? 0 : n==2||n==12 ? 1 :(n >= 3 && n<=10)||(n >= 13 && n<=19)? 2 : 3',1=>['1,11'=>'one','2,12'=>'two','3,4,5…'=>'few','0,20,21…'=>'other']],9=>[0=>'n%10==1 ? 0 : n%10==2 ? 1 : n%20==0 ? 2 : 3',1=>['1,11,21…'=>'one','2,12,22…'=>'two','0,20,100…'=>'few','3,4,5…'=>'other']],10=>[0=>'n%10==1 && n%100!=11 ? 0 : n%10 >= 2 && n%10<=4 &&(n%100<10||n%100 >= 20)? 1 : 2',1=>['1,21,31…'=>'one','2,3,4…'=>'few','0,5,6…'=>'other']],11=>[0=>'n == 1 ? 0 : n == 2 ? 1 : 2',1=>[1=>'one',2=>'two','0,3,4…'=>'other']],12=>[0=>'(n%10==1 && n%100!=11 ? 0 : n%10 >= 2 &&(n%100<10||n%100 >= 20)? 1 : 2)',1=>['1,21,31…'=>'one','2,3,4…'=>'few','0,10,11…'=>'other']],13=>[0=>'n%10==0||( n%100 >= 11 && n%100<=19)? 0 :(n%10==1 && n%100!=11 ? 1 : 2)',1=>['0,10,11…'=>'zero','1,21,31…'=>'one','2,3,4…'=>'other']],14=>[0=>'( n % 10 == 1 && n % 100 != 11 ) ? 0 : 1',1=>['1,21,31…'=>'one','0,2,3…'=>'other']],15=>[0=>'(n==1 ? 0 : n==0||( n%100>1 && n%100<11)? 1 :(n%100>10 && n%100<20)? 2 : 3)',1=>[1=>'one','0,2,3…'=>'few','11,12,13…'=>'many','20,21,22…'=>'other']],16=>[0=>'(n==1 ? 0 : n%10 >= 2 && n%10<=4 &&(n%100<10||n%100 >= 20)? 1 : 2)',1=>[1=>'one','2,3,4…'=>'few','0,5,6…'=>'other']],17=>[0=>'(n==1 ? 0 :(((n%100>19)||(( n%100==0)&&(n!=0)))? 2 : 1))',1=>[1=>'one','0,2,3…'=>'few','20,21,22…'=>'other']],18=>[0=>'n%100==1 ? 0 : n%100==2 ? 1 : n%100==3||n%100==4 ? 2 : 3',1=>['1,101,201…'=>'one','2,102,202…'=>'two','3,4,103…'=>'few','0,5,6…'=>'other']]]]; lib/data/locales.php000064400000014232147206622240010365 0ustar00[0=>'Afrikaans',1=>'Afrikaans'],'am'=>[0=>'Amharic',1=>'አማርኛ'],'arg'=>[0=>'Aragonese',1=>'Aragonés'],'ar'=>[0=>'Arabic',1=>'العربية'],'ary'=>[0=>'Moroccan Arabic',1=>'العربية المغربية'],'as'=>[0=>'Assamese',1=>'অসমীয়া'],'azb'=>[0=>'South Azerbaijani',1=>'گؤنئی آذربایجان'],'az'=>[0=>'Azerbaijani',1=>'Azərbaycan dili'],'bel'=>[0=>'Belarusian',1=>'Беларуская мова'],'bg_BG'=>[0=>'Bulgarian',1=>'Български'],'bn_BD'=>[0=>'Bengali (Bangladesh)',1=>'বাংলা'],'bo'=>[0=>'Tibetan',1=>'བོད་ཡིག'],'bs_BA'=>[0=>'Bosnian',1=>'Bosanski'],'ca'=>[0=>'Catalan',1=>'Català'],'ceb'=>[0=>'Cebuano',1=>'Cebuano'],'cs_CZ'=>[0=>'Czech',1=>'Čeština'],'cy'=>[0=>'Welsh',1=>'Cymraeg'],'da_DK'=>[0=>'Danish',1=>'Dansk'],'de_AT'=>[0=>'German (Austria)',1=>'Deutsch (Österreich)'],'de_DE'=>[0=>'German',1=>'Deutsch'],'de_DE_formal'=>[0=>'German (Formal)',1=>'Deutsch (Sie)'],'de_CH'=>[0=>'German (Switzerland)',1=>'Deutsch (Schweiz)'],'de_CH_informal'=>[0=>'German (Switzerland, Informal)',1=>'Deutsch (Schweiz, Du)'],'dsb'=>[0=>'Lower Sorbian',1=>'Dolnoserbšćina'],'dzo'=>[0=>'Dzongkha',1=>'རྫོང་ཁ'],'el'=>[0=>'Greek',1=>'Ελληνικά'],'en_ZA'=>[0=>'English (South Africa)',1=>'English (South Africa)'],'en_GB'=>[0=>'English (UK)',1=>'English (UK)'],'en_CA'=>[0=>'English (Canada)',1=>'English (Canada)'],'en_NZ'=>[0=>'English (New Zealand)',1=>'English (New Zealand)'],'en_AU'=>[0=>'English (Australia)',1=>'English (Australia)'],'eo'=>[0=>'Esperanto',1=>'Esperanto'],'es_AR'=>[0=>'Spanish (Argentina)',1=>'Español de Argentina'],'es_CL'=>[0=>'Spanish (Chile)',1=>'Español de Chile'],'es_ES'=>[0=>'Spanish (Spain)',1=>'Español'],'es_CO'=>[0=>'Spanish (Colombia)',1=>'Español de Colombia'],'es_VE'=>[0=>'Spanish (Venezuela)',1=>'Español de Venezuela'],'es_CR'=>[0=>'Spanish (Costa Rica)',1=>'Español de Costa Rica'],'es_EC'=>[0=>'Spanish (Ecuador)',1=>'Español de Ecuador'],'es_PE'=>[0=>'Spanish (Peru)',1=>'Español de Perú'],'es_DO'=>[0=>'Spanish (Dominican Republic)',1=>'Español de República Dominicana'],'es_UY'=>[0=>'Spanish (Uruguay)',1=>'Español de Uruguay'],'es_PR'=>[0=>'Spanish (Puerto Rico)',1=>'Español de Puerto Rico'],'es_MX'=>[0=>'Spanish (Mexico)',1=>'Español de México'],'es_GT'=>[0=>'Spanish (Guatemala)',1=>'Español de Guatemala'],'et'=>[0=>'Estonian',1=>'Eesti'],'eu'=>[0=>'Basque',1=>'Euskara'],'fa_IR'=>[0=>'Persian',1=>'فارسی'],'fa_AF'=>[0=>'Persian (Afghanistan)',1=>'(فارسی (افغانستان'],'fi'=>[0=>'Finnish',1=>'Suomi'],'fr_FR'=>[0=>'French (France)',1=>'Français'],'fr_BE'=>[0=>'French (Belgium)',1=>'Français de Belgique'],'fr_CA'=>[0=>'French (Canada)',1=>'Français du Canada'],'fur'=>[0=>'Friulian',1=>'Friulian'],'fy'=>[0=>'Frisian',1=>'Frysk'],'gd'=>[0=>'Scottish Gaelic',1=>'Gàidhlig'],'gl_ES'=>[0=>'Galician',1=>'Galego'],'gu'=>[0=>'Gujarati',1=>'ગુજરાતી'],'haz'=>[0=>'Hazaragi',1=>'هزاره گی'],'he_IL'=>[0=>'Hebrew',1=>'עִבְרִית'],'hi_IN'=>[0=>'Hindi',1=>'हिन्दी'],'hr'=>[0=>'Croatian',1=>'Hrvatski'],'hsb'=>[0=>'Upper Sorbian',1=>'Hornjoserbšćina'],'hu_HU'=>[0=>'Hungarian',1=>'Magyar'],'hy'=>[0=>'Armenian',1=>'Հայերեն'],'id_ID'=>[0=>'Indonesian',1=>'Bahasa Indonesia'],'is_IS'=>[0=>'Icelandic',1=>'Íslenska'],'it_IT'=>[0=>'Italian',1=>'Italiano'],'ja'=>[0=>'Japanese',1=>'日本語'],'jv_ID'=>[0=>'Javanese',1=>'Basa Jawa'],'ka_GE'=>[0=>'Georgian',1=>'ქართული'],'kab'=>[0=>'Kabyle',1=>'Taqbaylit'],'kk'=>[0=>'Kazakh',1=>'Қазақ тілі'],'km'=>[0=>'Khmer',1=>'ភាសាខ្មែរ'],'kn'=>[0=>'Kannada',1=>'ಕನ್ನಡ'],'ko_KR'=>[0=>'Korean',1=>'한국어'],'ckb'=>[0=>'Kurdish (Sorani)',1=>'كوردی‎'],'kir'=>[0=>'Kyrgyz',1=>'Кыргызча'],'lo'=>[0=>'Lao',1=>'ພາສາລາວ'],'lt_LT'=>[0=>'Lithuanian',1=>'Lietuvių kalba'],'lv'=>[0=>'Latvian',1=>'Latviešu valoda'],'mk_MK'=>[0=>'Macedonian',1=>'Македонски јазик'],'ml_IN'=>[0=>'Malayalam',1=>'മലയാളം'],'mn'=>[0=>'Mongolian',1=>'Монгол'],'mr'=>[0=>'Marathi',1=>'मराठी'],'ms_MY'=>[0=>'Malay',1=>'Bahasa Melayu'],'my_MM'=>[0=>'Myanmar (Burmese)',1=>'ဗမာစာ'],'nb_NO'=>[0=>'Norwegian (Bokmål)',1=>'Norsk bokmål'],'ne_NP'=>[0=>'Nepali',1=>'नेपाली'],'nl_NL_formal'=>[0=>'Dutch (Formal)',1=>'Nederlands (Formeel)'],'nl_NL'=>[0=>'Dutch',1=>'Nederlands'],'nl_BE'=>[0=>'Dutch (Belgium)',1=>'Nederlands (België)'],'nn_NO'=>[0=>'Norwegian (Nynorsk)',1=>'Norsk nynorsk'],'oci'=>[0=>'Occitan',1=>'Occitan'],'pa_IN'=>[0=>'Panjabi (India)',1=>'ਪੰਜਾਬੀ'],'pl_PL'=>[0=>'Polish',1=>'Polski'],'ps'=>[0=>'Pashto',1=>'پښتو'],'pt_PT'=>[0=>'Portuguese (Portugal)',1=>'Português'],'pt_PT_ao90'=>[0=>'Portuguese (Portugal, AO90)',1=>'Português (AO90)'],'pt_AO'=>[0=>'Portuguese (Angola)',1=>'Português de Angola'],'pt_BR'=>[0=>'Portuguese (Brazil)',1=>'Português do Brasil'],'rhg'=>[0=>'Rohingya',1=>'Ruáinga'],'ro_RO'=>[0=>'Romanian',1=>'Română'],'ru_RU'=>[0=>'Russian',1=>'Русский'],'sah'=>[0=>'Sakha',1=>'Сахалыы'],'snd'=>[0=>'Sindhi',1=>'سنڌي'],'si_LK'=>[0=>'Sinhala',1=>'සිංහල'],'sk_SK'=>[0=>'Slovak',1=>'Slovenčina'],'skr'=>[0=>'Saraiki',1=>'سرائیکی'],'sl_SI'=>[0=>'Slovenian',1=>'Slovenščina'],'sq'=>[0=>'Albanian',1=>'Shqip'],'sr_RS'=>[0=>'Serbian',1=>'Српски језик'],'sv_SE'=>[0=>'Swedish',1=>'Svenska'],'sw'=>[0=>'Swahili',1=>'Kiswahili'],'szl'=>[0=>'Silesian',1=>'Ślōnskŏ gŏdka'],'ta_IN'=>[0=>'Tamil',1=>'தமிழ்'],'ta_LK'=>[0=>'Tamil (Sri Lanka)',1=>'தமிழ்'],'te'=>[0=>'Telugu',1=>'తెలుగు'],'th'=>[0=>'Thai',1=>'ไทย'],'tl'=>[0=>'Tagalog',1=>'Tagalog'],'tr_TR'=>[0=>'Turkish',1=>'Türkçe'],'tt_RU'=>[0=>'Tatar',1=>'Татар теле'],'tah'=>[0=>'Tahitian',1=>'Reo Tahiti'],'ug_CN'=>[0=>'Uighur',1=>'ئۇيغۇرچە'],'uk'=>[0=>'Ukrainian',1=>'Українська'],'ur'=>[0=>'Urdu',1=>'اردو'],'uz_UZ'=>[0=>'Uzbek',1=>'O‘zbekcha'],'vi'=>[0=>'Vietnamese',1=>'Tiếng Việt'],'zh_CN'=>[0=>'Chinese (China)',1=>'简体中文'],'zh_HK'=>[0=>'Chinese (Hong Kong)',1=>'香港中文'],'zh_TW'=>[0=>'Chinese (Taiwan)',1=>'繁體中文']];