IceHrm v18.0

This commit is contained in:
Thilina Hasantha
2016-08-04 14:27:59 +05:30
parent 9e243073df
commit a10fbba14a
882 changed files with 90619 additions and 2546 deletions

View File

@@ -273,13 +273,17 @@ AdapterBase.method('getFailCallBack', function(callBackData,serverData) {
});
AdapterBase.method('getElement', function(id,callBackData) {
AdapterBase.method('getElement', function(id,callBackData,clone) {
var that = this;
var sourceMappingJson = JSON.stringify(this.getSourceMapping());
sourceMappingJson = this.fixJSON(sourceMappingJson);
that.showLoader();
$.post(this.moduleRelativeURL, {'t':this.table,'a':'getElement','id':id,'sm':sourceMappingJson}, function(data) {
if(data.status == "SUCCESS"){
if(clone){
delete data.object.id;
}
this.currentElement = data.object;
that.getElementSuccessCallBack.apply(that,[callBackData,data.object]);
}else{
that.getElementFailCallBack.apply(that,[callBackData,data.object]);
@@ -523,6 +527,61 @@ IdNameAdapter.method('getFormFields', function() {
});
/**
* LogViewAdapter
*/
function LogViewAdapter(endPoint,tab,filter,orderBy){
this.initAdapter(endPoint,tab,filter,orderBy);
}
LogViewAdapter.inherits(AdapterBase);
LogViewAdapter.method('getLogs', function(id) {
var that = this;
var object = {"id":id};
var reqJson = JSON.stringify(object);
var callBackData = [];
callBackData['callBackData'] = [];
callBackData['callBackSuccess'] = 'getLogsSuccessCallBack';
callBackData['callBackFail'] = 'getLogsFailCallBack';
this.customAction('getLogs','admin='+this.modulePathName,reqJson,callBackData);
});
LogViewAdapter.method('getLogsSuccessCallBack', function(callBackData) {
var tableLog = '<table class="table table-condensed table-bordered table-striped" style="font-size:14px;"><thead><tr><th>Notes</th></tr></thead><tbody>_days_</tbody></table> ';
var rowLog = '<tr><td><span class="logTime label label-default">_date_</span>&nbsp;&nbsp;<b>_status_</b><br/>_note_</td></tr>';
var logs = callBackData.data;
var html = "";
var rowsLogs = "";
for(var i=0;i<logs.length;i++){
trow = rowLog;
trow = trow.replace(/_date_/g,logs[i].time);
trow = trow.replace(/_status_/g,logs[i].status_from+" -> "+logs[i].status_to);
trow = trow.replace(/_note_/g,logs[i].note);
rowsLogs += trow;
}
if(rowsLogs != ""){
tableLog = tableLog.replace('_days_',rowsLogs);
html+= tableLog;
}
this.showMessage("Logs",html);
timeUtils.convertToRelativeTime($(".logTime"));
});
LogViewAdapter.method('getLogsFailCallBack', function(callBackData) {
this.showMessage("Error","Error occured while getting data");
});
/**
* ApproveAdminAdapter
*/
@@ -531,11 +590,16 @@ function ApproveAdminAdapter(endPoint,tab,filter,orderBy) {
this.initAdapter(endPoint,tab,filter,orderBy);
}
ApproveAdminAdapter.inherits(AdapterBase);
ApproveAdminAdapter.inherits(LogViewAdapter);
ApproveAdminAdapter.method('getStatusFieldPosition', function() {
var dm = this.getDataMapping();
return dm.length - 1;
});
ApproveAdminAdapter.method('openStatus', function(id,status) {
$('#'+this.itemNameLower+'StatusModel').modal('show');
$('#'+this.itemNameLower+'_status').html(this.getStatusOptions(status));
$('#'+this.itemNameLower+'_status').val(status);
this.statusChangeId = id;
});
@@ -582,11 +646,19 @@ ApproveAdminAdapter.method('changeStatusFailCallBack', function(callBackData) {
ApproveAdminAdapter.method('getActionButtonsHtml', function(id,data) {
var editButton = '<img class="tableActionButton" src="_BASE_images/edit.png" style="cursor:pointer;" rel="tooltip" title="Edit" onclick="modJs.edit(_id_);return false;"></img>';
var deleteButton = '<img class="tableActionButton" src="_BASE_images/delete.png" style="margin-left:15px;cursor:pointer;" rel="tooltip" title="Delete" onclick="modJs.deleteRow(_id_);return false;"></img>';
var statusChangeButton = '<img class="tableActionButton" src="_BASE_images/run.png" style="margin-left:15px;cursor:pointer;" rel="tooltip" title="Change Status" onclick="modJs.openStatus(_id_);return false;"></img>';
var statusChangeButton = '<img class="tableActionButton" src="_BASE_images/run.png" style="margin-left:15px;cursor:pointer;" rel="tooltip" title="Change Status" onclick="modJs.openStatus(_id_, \'_cstatus_\');return false;"></img>';
var viewLogsButton = '<img class="tableActionButton" src="_BASE_images/log.png" style="margin-left:15px;cursor:pointer;" rel="tooltip" title="View Logs" onclick="modJs.getLogs(_id_);return false;"></img>';
var html = '<div style="width:80px;">_edit__delete__status_</div>';
var html = '<div style="width:120px;">_edit__delete__status__logs_</div>';
html = html.replace('_status_',statusChangeButton);
var optiondata = this.getStatusOptionsData(data[this.getStatusFieldPosition()]);
if(Object.keys(optiondata).length > 0){
html = html.replace('_status_',statusChangeButton);
}else{
html = html.replace('_status_','');
}
html = html.replace('_logs_',viewLogsButton);
if(this.showDelete){
html = html.replace('_delete_',deleteButton);
@@ -603,6 +675,7 @@ ApproveAdminAdapter.method('getActionButtonsHtml', function(id,data) {
html = html.replace(/_id_/g,id);
html = html.replace(/_BASE_/g,this.baseUrl);
html = html.replace(/_cstatus_/g,data[this.getStatusFieldPosition()]);
return html;
});
@@ -614,6 +687,33 @@ ApproveAdminAdapter.method('isSubProfileTable', function() {
}
});
ApproveAdminAdapter.method('getStatusOptionsData', function(currentStatus) {
var data = {};
if(currentStatus == 'Approved'){
}else if(currentStatus == 'Pending'){
data["Approved"] = "Approved";
data["Rejected"] = "Rejected";
}else if(currentStatus == 'Rejected'){
}else if(currentStatus == 'Cancelled'){
}else if(currentStatus == 'Processing'){
}else{
data["Cancellation Requested"] = "Cancellation Requested";
data["Cancelled"] = "Cancelled";
}
return data;
});
ApproveAdminAdapter.method('getStatusOptions', function(currentStatus) {
return this.generateOptions(this.getStatusOptionsData(currentStatus));
});
/**
* ApproveModuleAdapter
@@ -623,7 +723,7 @@ function ApproveModuleAdapter(endPoint,tab,filter,orderBy) {
this.initAdapter(endPoint,tab,filter,orderBy);
}
ApproveModuleAdapter.inherits(AdapterBase);
ApproveModuleAdapter.inherits(LogViewAdapter);
ApproveModuleAdapter.method('cancelRequest', function(id) {
var that = this;
@@ -653,8 +753,12 @@ ApproveModuleAdapter.method('getActionButtonsHtml', function(id,data) {
var editButton = '<img class="tableActionButton" src="_BASE_images/edit.png" style="cursor:pointer;" rel="tooltip" title="Edit" onclick="modJs.edit(_id_);return false;"></img>';
var deleteButton = '<img class="tableActionButton" src="_BASE_images/delete.png" style="margin-left:15px;cursor:pointer;" rel="tooltip" title="Delete" onclick="modJs.deleteRow(_id_);return false;"></img>';
var requestCancellationButton = '<img class="tableActionButton" src="_BASE_images/delete.png" style="margin-left:15px;cursor:pointer;" rel="tooltip" title="Cancel '+this.itemName+'" onclick="modJs.cancelRequest(_id_);return false;"></img>';
var viewLogsButton = '<img class="tableActionButton" src="_BASE_images/log.png" style="margin-left:15px;cursor:pointer;" rel="tooltip" title="View Logs" onclick="modJs.getLogs(_id_);return false;"></img>';
var html = '<div style="width:80px;">_edit__delete_</div>';
var html = '<div style="width:120px;">_edit__logs__delete_</div>';
html = html.replace('_logs_',viewLogsButton);
if(this.showDelete){
if(data[7] == "Approved"){
@@ -678,6 +782,54 @@ ApproveModuleAdapter.method('getActionButtonsHtml', function(id,data) {
return html;
});
/**
* ApproveApproverAdapter
*/
function ApproveApproverAdapter() {
}
ApproveApproverAdapter.method('getActionButtonsHtml', function(id,data) {
var statusChangeButton = '<img class="tableActionButton" src="_BASE_images/run.png" style="cursor:pointer;" rel="tooltip" title="Change Status" onclick="modJs.openStatus(_id_, \'_cstatus_\');return false;"></img>';
var viewLogsButton = '<img class="tableActionButton" src="_BASE_images/log.png" style="margin-left:15px;cursor:pointer;" rel="tooltip" title="View Logs" onclick="modJs.getLogs(_id_);return false;"></img>';
var html = '<div style="width:80px;">_status__logs_</div>';
html = html.replace('_logs_',viewLogsButton);
if(data[this.getStatusFieldPosition()] == 'Processing'){
html = html.replace('_status_',statusChangeButton);
}else{
html = html.replace('_status_','');
}
html = html.replace(/_id_/g,id);
html = html.replace(/_BASE_/g,this.baseUrl);
html = html.replace(/_cstatus_/g,data[this.getStatusFieldPosition()]);
return html;
});
ApproveApproverAdapter.method('getStatusOptionsData', function(currentStatus) {
var data = {};
if(currentStatus != 'Processing'){
}else{
data["Approved"] = "Approved";
data["Rejected"] = "Rejected";
}
return data;
});
ApproveApproverAdapter.method('getStatusOptions', function(currentStatus) {
return this.generateOptions(this.getStatusOptionsData(currentStatus));
});
/**

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +1,23 @@
/*
This file is part of Ice Framework.
This file is part of Ice Framework.
Ice Framework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Ice Framework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Ice Framework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
Ice Framework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Ice Framework. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with Ice Framework. If not, see <http://www.gnu.org/licenses/>.
------------------------------------------------------------------
------------------------------------------------------------------
Original work Copyright (c) 2012 [Gamonoid Media Pvt. Ltd]
Developer: Thilina Hasantha (thilina.hasantha[at]gmail.com / facebook.com/thilinah)
Original work Copyright (c) 2012 [Gamonoid Media Pvt. Ltd]
Developer: Thilina Hasantha (thilina.hasantha[at]gmail.com / facebook.com/thilinah)
*/
function FormValidation(formId,validateAll,options) {
@@ -29,73 +29,73 @@ function FormValidation(formId,validateAll,options) {
this.popupDialog = null;
this.validateAll = validateAll;
this.errorMap = new Array();
this.settings = {"thirdPartyPopup":null,"LabelErrorClass":false, "ShowPopup":true};
this.settings = jQuery.extend(this.settings,options);
this.inputTypes = new Array( "text", "radio", "checkbox", "file", "password", "select-one","select-multi", "textarea","fileupload" ,"signature");
this.validator = {
float: function (str) {
var floatstr = /^[-+]?[0-9]+(\.[0-9]+)?$/;
if (str != null && str.match(floatstr)) {
return true;
} else {
return false;
float: function (str) {
var floatstr = /^[-+]?[0-9]+(\.[0-9]+)?$/;
if (str != null && str.match(floatstr)) {
return true;
} else {
return false;
}
},
number: function (str) {
var numstr = /^[0-9]+$/;
if (str != null && str.match(numstr)) {
return true;
} else {
return false;
}
},
numberOrEmpty: function (str) {
if(str == ""){
return true;
}
var numstr = /^[0-9]+$/;
if (str != null && str.match(numstr)) {
return true;
} else {
return false;
}
},
email: function (str) {
var emailPattern = /^\s*[\w\-\+_]+(\.[\w\-\+_]+)*\@[\w\-\+_]+\.[\w\-\+_]+(\.[\w\-\+_]+)*\s*$/;
return str != null && emailPattern.test(str);
},
emailOrEmpty: function (str) {
if(str == ""){
return true;
}
var emailPattern = /^\s*[\w\-\+_]+(\.[\w\-\+_]+)*\@[\w\-\+_]+\.[\w\-\+_]+(\.[\w\-\+_]+)*\s*$/;
return str != null && emailPattern.test(str);
},
username: function (str) {
var username = /^[a-zA-Z0-9\.-]+$/;
return str != null && username.test(str);
},
input: function (str) {
if (str != null && str.length > 0) {
return true;
} else {
return false;
}
}
},
number: function (str) {
var numstr = /^[0-9]+$/;
if (str != null && str.match(numstr)) {
return true;
} else {
return false;
}
},
numberOrEmpty: function (str) {
if(str == ""){
return true;
}
var numstr = /^[0-9]+$/;
if (str != null && str.match(numstr)) {
return true;
} else {
return false;
}
},
email: function (str) {
var emailPattern = /^\s*[\w\-\+_]+(\.[\w\-\+_]+)*\@[\w\-\+_]+\.[\w\-\+_]+(\.[\w\-\+_]+)*\s*$/;
return str != null && emailPattern.test(str);
},
emailOrEmpty: function (str) {
if(str == ""){
return true;
}
var emailPattern = /^\s*[\w\-\+_]+(\.[\w\-\+_]+)*\@[\w\-\+_]+\.[\w\-\+_]+(\.[\w\-\+_]+)*\s*$/;
return str != null && emailPattern.test(str);
},
username: function (str) {
var username = /^[a-zA-Z0-9\.-]+$/;
return str != null && username.test(str);
},
input: function (str) {
if (str != null && str.length > 0) {
return true;
} else {
return false;
}
}
};
};
}
@@ -104,7 +104,7 @@ FormValidation.method('clearError' , function(formInput, overrideMessage) {
$('#'+ this.formId +' #field_'+id).removeClass('error');
$('#'+ this.formId +' #help_'+id).html('');
});
FormValidation.method('addError' , function(formInput, overrideMessage) {
this.formError = true;
if(formInput.attr("message") != null) {
@@ -133,10 +133,10 @@ FormValidation.method('addError' , function(formInput, overrideMessage) {
}
}
}
});
FormValidation.method('showErrors' , function() {
if(this.formError) {
@@ -149,13 +149,13 @@ FormValidation.method('showErrors' , function() {
}else{
this.alert("Errors Found",this.errorMessages,-1);
}
}
}
}
}
});
FormValidation.method('checkValues' , function(options) {
this.tempOptions = options;
var that = this;
@@ -166,100 +166,100 @@ FormValidation.method('checkValues' , function(options) {
if(that.settings['LabelErrorClass'] != false){
$("label[for='" + name + "']").removeClass(that.settings['LabelErrorClass']);
}
var id = inputObject.attr("id");
var name = inputObject.attr("name");
var type = inputObject.attr("type");
var id = inputObject.attr("id");
var name = inputObject.attr("name");
var type = inputObject.attr("type");
if(inputObject.hasClass('select2-focusser') || inputObject.hasClass('select2-input')){
return true;
}
if(jQuery.inArray(type, that.inputTypes ) >= 0) {
if(inputObject.hasClass('uploadInput')){
inputValue = inputObject.attr("val");
//}else if(inputObject.hasClass('datetimeInput')){
//inputValue = inputObject.getDate()+":00";
}else{
//inputValue = (type == "radio" || type == "checkbox")?$("input[name='" + name + "']:checked").val():inputObject.val();
inputValue = null;
if(type == "radio" || type == "checkbox"){
inputValue = $("input[name='" + name + "']:checked").val();
}else if(inputObject.hasClass('select2Field')){
if($('#'+id).select2('data') != null && $('#'+id).select2('data') != undefined){
inputValue = $('#'+id).select2('data').id;
}else{
inputValue = "";
}
}else if(inputObject.hasClass('select2Multi')){
if($('#'+id).select2('data') != null && $('#'+id).select2('data') != undefined){
inputValueObjects = $('#'+id).select2('data');
inputValue = [];
for(var i=0;i<inputValueObjects.length;i++){
inputValue.push(inputValueObjects[i].id);
}
inputValue = JSON.stringify(inputValue);
}else{
inputValue = "";
}
}else if(inputObject.hasClass('signatureField')){
if($('#'+id).data('signaturePad').isEmpty()){
inputValue = '';
}else{
inputValue = $('#'+id).data('signaturePad').toDataURL();
}
if(inputObject.hasClass('select2-focusser') || inputObject.hasClass('select2-input')){
return true;
}
}else{
inputValue = inputObject.val();
}
}
var validation = inputObject.attr('validation');
var valid = false;
if(validation != undefined && validation != null && that.validator[validation] != undefined && that.validator[validation] != null){
valid = that.validator[validation](inputValue);
}else{
if(jQuery.inArray(type, that.inputTypes ) >= 0) {
if(inputObject.hasClass('uploadInput')){
inputValue = inputObject.attr("val");
//}else if(inputObject.hasClass('datetimeInput')){
//inputValue = inputObject.getDate()+":00";
}else{
//inputValue = (type == "radio" || type == "checkbox")?$("input[name='" + name + "']:checked").val():inputObject.val();
inputValue = null;
if(type == "radio" || type == "checkbox"){
inputValue = $("input[name='" + name + "']:checked").val();
}else if(inputObject.hasClass('select2Field')){
if($('#'+id).select2('data') != null && $('#'+id).select2('data') != undefined){
inputValue = $('#'+id).select2('data').id;
}else{
inputValue = "";
}
}else if(inputObject.hasClass('select2Multi')){
if($('#'+id).select2('data') != null && $('#'+id).select2('data') != undefined){
inputValueObjects = $('#'+id).select2('data');
inputValue = [];
for(var i=0;i<inputValueObjects.length;i++){
inputValue.push(inputValueObjects[i].id);
}
inputValue = JSON.stringify(inputValue);
}else{
inputValue = "";
}
}else if(inputObject.hasClass('signatureField')){
if($('#'+id).data('signaturePad').isEmpty()){
inputValue = '';
}else{
inputValue = $('#'+id).data('signaturePad').toDataURL();
}
}else{
inputValue = inputObject.val();
}
}
var validation = inputObject.attr('validation');
var valid = false;
if(validation != undefined && validation != null && that.validator[validation] != undefined && that.validator[validation] != null){
valid = that.validator[validation](inputValue);
}else{
if(that.validateAll){
if(validation != undefined && validation != null && validation == "none"){
valid = true;
}else{
valid = that.validator['input'](inputValue);
}
}else{
valid = true;
}
$(that.formObject).attr(id,inputValue);
}
if(!valid) {
that.addError(inputObject, null);
}else{
that.clearError(inputObject, null);
$(that.formObject).attr(id,inputValue);
}
}
};
if(that.validateAll){
if(validation != undefined && validation != null && validation == "none"){
valid = true;
}else{
valid = that.validator['input'](inputValue);
}
}else{
valid = true;
}
$(that.formObject).attr(id,inputValue);
}
if(!valid) {
that.addError(inputObject, null);
}else{
that.clearError(inputObject, null);
$(that.formObject).attr(id,inputValue);
}
}
};
var inputs = $('#'+ this.formId + " :input");
inputs.each(function() {
var that = $(this);
validate(that);
});
inputs.each(function() {
var that = $(this);
validate(that);
});
inputs = $('#'+ this.formId + " .uploadInput");
inputs.each(function() {
var that = $(this);
validate(that);
});
inputs = $('#'+ this.formId + " .uploadInput");
inputs.each(function() {
var that = $(this);
validate(that);
});
this.showErrors();
this.tempOptions = {};
this.showErrors();
this.tempOptions = {};
return !this.formError;
});
@@ -269,9 +269,9 @@ FormValidation.method('getFormParameters' , function() {
FormValidation.method('alert', function (title,text,top) {
alert(text);
});

View File

@@ -1 +0,0 @@
http://icehrm.com

View File

@@ -131,7 +131,7 @@ if(!$isDataFolderExists){
return;
}
if(request["BASE_URL"].indexOf("http://") == 0 || request["BASE_URL"].indexOf("https://") == 0){
if(request["BASE_URL"].indexOf("http://") == 0 || request["BASE_URL"].indexOf("https://")){
}else{
alert("Invalid Base URL");
return;

View File

@@ -0,0 +1,163 @@
<?php
class ApprovalStatus{
const APP_ST_APPROVED = 1;
const APP_ST_REJECTED = 0;
private static $me = null;
private function __construct(){}
public static function getInstance(){
if(empty(self::$me)){
self::$me = new ApprovalStatus();
}
return self::$me;
}
public function isDirectApproval($employeeId){
$emp = new Employee();
$emp->Load("id = ?",array($employeeId));
if(empty($emp->approver1) && empty($emp->approver2) && empty($emp->approver3)){
return true;
}
return false;
}
public function getResolvedStatuses($type, $id){
$employeeApproval = new EmployeeApproval();
$eas = $employeeApproval->Find("type = ? and element = ? and status > -1 order by level", array($type, $id));
return $eas;
}
public function approvalChainExists($type, $id){
$list = $this->getAllStatuses($type, $id);
return count($list) > 0;
}
public function getAllStatuses($type, $id){
$employeeApproval = new EmployeeApproval();
$eas = $employeeApproval->Find("type = ? and element = ? order by level", array($type, $id));
return $eas;
}
public function initializeApprovalChain($type, $id){
$element = new $type();
$element->Load("id = ?",array($id));
$employeeId = $element->employee;
for($i = 1; $i < 4; $i++){
$approver = $this->getApproverByLevel($i, $employeeId);
if(!empty($approver)){
$employeeApproval = new EmployeeApproval();
$employeeApproval->type = $type;
$employeeApproval->element = $id;
$employeeApproval->approver = $approver;
$employeeApproval->level = $i;
$employeeApproval->status = -1;
$employeeApproval->active = 0;
$employeeApproval->created = date("Y-m-d H:i:s");
$employeeApproval->updated = date("Y-m-d H:i:s");
$ok = $employeeApproval->Save();
if(!$ok){
LogManager::getInstance()->error("Error:".$employeeApproval->ErrorMsg());
}
}else{
LogManager::getInstance()->error("Approver is empty level:".$i);
}
}
}
public function updateApprovalStatus($type, $id, $currentEmployee, $status){
LogManager::getInstance()->error('updateApprovalStatus 1');
if(!$this->approvalChainExists($type, $id)){
LogManager::getInstance()->error('updateApprovalStatus 2');
return new IceResponse(IceResponse::SUCCESS, array(NULL, NULL));
}
if($status != 0 && $status != 1){
return new IceResponse(IceResponse::ERROR, "Invalid data");
}
$element = new $type();
$element->Load("id = ?",array($id));
$employeeId = $element->employee;
$eas = $this->getAllStatuses($type, $id);
$level = 0;
//check if the element is already rejected
foreach($eas as $ea){
if($ea->status == 0){
return new IceResponse(IceResponse::ERROR, "This item is already rejected");
}else if($ea->active == 1){
$level = intval($ea->level);
}
}
LogManager::getInstance()->error('level '.$level);
$currentAL = NULL;
if($level > 0){
$currentAL = new EmployeeApproval();
$currentAL->Load("type = ? and element = ? and level = ?",array($type, $id, $level));
}
$nextAL = null;
if($level < 3){
$nextAL = new EmployeeApproval();
$nextAL->Load("type = ? and element = ? and level = ?",array($type, $id, intval($level)+1));
LogManager::getInstance()->error('next AL '.print_r($nextAL,true));
if(empty($nextAL->id)){
$nextAL = NULL;
}
}
//Check if the current employee is allowed to approve
if($level > 0 && $currentEmployee != $currentAL->approver){
return new IceResponse(IceResponse::ERROR, "You are not allowed to approve or reject");
}
if(!empty($currentAL)){
//Now mark the approval status
$currentAL->status = $status;
$currentAL->Save();
}
if(!empty($nextAL)) {
foreach ($eas as $ea) {
if ($ea->id == $nextAL->id) {
$nextAL->active = 1;
$nextAL->Save();
} else {
$ea->active = 0;
$ea->Save();
}
}
}
if(!empty($currentAL)){
$oldCurrAlId = $currentAL->id;
$currentAL = new EmployeeApproval();
$currentAL->Load("id = ?",array($oldCurrAlId));
}
return new IceResponse(IceResponse::SUCCESS, array($currentAL, $nextAL));
}
private function getApproverByLevel($level, $employeeId){
$emp = new Employee();
$emp->Load("id = ?",array($employeeId));
$approver = NULL;
$alevel = "approver".$level;
return $emp->$alevel;
}
}

View File

@@ -1,10 +1,24 @@
<?php
abstract class ApproveAdminActionManager extends SubActionManager{
abstract class ApproveCommonActionManager extends SubActionManager{
public function getLogs($req){
$class = $this->getModelClass();
$logs = StatusChangeLogManager::getInstance()->getLogs($class, $req->id);
return new IceResponse(IceResponse::SUCCESS, $logs);
}
}
abstract class ApproveAdminActionManager extends ApproveCommonActionManager{
public abstract function getModelClass();
public abstract function getItemName();
public abstract function getModuleName();
public abstract function getModuleTabUrl();
public abstract function getModuleSubordinateTabUrl();
public abstract function getModuleApprovalTabUrl();
public function changeStatus($req){
@@ -19,18 +33,81 @@ abstract class ApproveAdminActionManager extends SubActionManager{
return new IceResponse(IceResponse::ERROR,"$itemName not found");
}
/*
if($this->user->user_level != 'Admin' && $this->user->user_level != 'Manager'){
return new IceResponse(IceResponse::ERROR,"Only an admin or manager can do this");
}*/
//Check if this needs to be multi-approved
$apStatus = 0;
if($req->status == "Approved"){
$apStatus = 1;
}
if($req->status == "Approved" || $req->status == "Rejected"){
$approvalResp = ApprovalStatus::getInstance()->updateApprovalStatus($class,
$obj->id,
BaseService::getInstance()->getCurrentProfileId(),
$apStatus);
if($approvalResp->getStatus() == IceResponse::SUCCESS){
$objResp = $approvalResp->getObject();
$currentAp = $objResp[0];
$nextAp = $objResp[1];
$sendApprovalEmailto = null;
if(empty($currentAp) && empty($nextAp)){
//No multi level approvals
LogManager::getInstance()->debug($obj->id."|No multi level approvals|");
if($req->status == "Approved"){
$req->status = "Approved";
}
}else if(empty($currentAp) && !empty($nextAp)){
//Approval process is defined, but this person is a supervisor
LogManager::getInstance()->debug($obj->id."|Approval process is defined, but this person is a supervisor|");
$sendApprovalEmailto = $nextAp->approver;
if($req->status == "Approved"){
$req->status = "Processing";
}
}else if(!empty($currentAp) && empty($nextAp)){
//All multi level approvals completed, now we can approve
LogManager::getInstance()->debug($obj->id."|All multi level approvals completed, now we can approve|");
if($req->status == "Approved"){
$req->status = "Approved";
}
}else{
//Current employee is an approver and we have another approval level left
LogManager::getInstance()->debug($obj->id."|Current employee is an approver and we have another approval level left|");
$sendApprovalEmailto = $nextAp->approver;
if($req->status == "Approved"){
$req->status = "Processing";
}
}
}else{
return $approvalResp;
}
}
$oldStatus = $obj->status;
$obj->status = $req->status;
if($oldStatus == $req->status && $req->status != "Processing"){
return new IceResponse(IceResponse::SUCCESS,"");
}
$ok = $obj->Save();
if(!$ok){
LogManager::getInstance()->info($obj->ErrorMsg());
return new IceResponse(IceResponse::ERROR,"Error occurred while saving $itemName information. Please contact admin");
}
StatusChangeLogManager::getInstance()->addLog($class, $obj->id,
BaseService::getInstance()->getCurrentUser()->id, $oldStatus, $req->status, "");
$this->baseService->audit(IceConstants::AUDIT_ACTION, "$itemName status changed from:".$oldStatus." to:".$obj->status." id:".$obj->id);
$currentEmpId = $this->getCurrentProfileId();
@@ -47,6 +124,16 @@ abstract class ApproveAdminActionManager extends SubActionManager{
}
if(!empty($sendApprovalEmailto)){
$employee = $this->baseService->getElement('Employee',BaseService::getInstance()->getCurrentProfileId());
$notificationMsg = "You have been assigned ".$itemName." for approval by ".$employee->first_name." ".$employee->last_name;
$this->baseService->notificationManager->addNotification($sendApprovalEmailto,$notificationMsg,'{"type":"url","url":"'.$this->getModuleApprovalTabUrl().'"}',$this->getModuleName(), null, false, true);
}
return new IceResponse(IceResponse::SUCCESS,"");
}
@@ -54,7 +141,7 @@ abstract class ApproveAdminActionManager extends SubActionManager{
}
abstract class ApproveModuleActionManager extends SubActionManager{
abstract class ApproveModuleActionManager extends ApproveCommonActionManager{
public abstract function getModelClass();
public abstract function getItemName();
@@ -94,14 +181,18 @@ abstract class ApproveModuleActionManager extends SubActionManager{
$notificationMsg = $employee->first_name." ".$employee->last_name." cancelled a expense. Visit expense management module to approve";
$this->baseService->notificationManager->addNotification($employee->supervisor,$notificationMsg,'{"type":"url","url":"'.$this->getModuleTabUrl().'"}',
$this->getModuleTabUrl(), null, false, true);
$this->getModuleName(), null, false, true);
return new IceResponse(IceResponse::SUCCESS,$obj);
}
}
class ApproveModel extends ICEHRM_Record {
abstract class ApproveModel extends ICEHRM_Record {
public function isMultiLevelApprovalsEnabled(){
return false;
}
public function executePreSaveActions($obj){
$preApprove = SettingsManager::getInstance()->getSetting($this->preApproveSettingName);
@@ -215,4 +306,32 @@ class ApproveModel extends ICEHRM_Record {
return new IceResponse(IceResponse::SUCCESS,$obj);
}
}
public function executePostSaveActions($obj){
$directAppr = ApprovalStatus::getInstance()->isDirectApproval($obj->employee);
if(!$directAppr && $this->isMultiLevelApprovalsEnabled()){
ApprovalStatus::getInstance()->initializeApprovalChain(get_called_class(),$obj->id);
}
}
abstract public function getType();
public function findApprovals($obj, $whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array()){
$currentEmployee = BaseService::getInstance()->getCurrentProfileId();
$approveal = new EmployeeApproval();
$approveals = $approveal->Find("type = ? and approver = ? and status = -1 and active = 1",array($this->getType(), $currentEmployee));
$ids = array();
foreach ($approveals as $appr){
$ids[] = $appr->element;
}
$data = $obj->Find("id in (".implode(",",$ids).")",array());
return $data;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -64,6 +64,9 @@ class FileService{
}
public function saveInCache($key, $data, $expire){
if(!class_exists('Memcached')){
return;
}
try{
if(empty($this->memcache)){
$this->memcache = new Memcached();

View File

@@ -0,0 +1,75 @@
<?php
use Gettext\Translations;
use Gettext\Translator;
class LanguageManager{
private static $me = null;
private $translator = null;
private $translations = null;
private function __construct(){
}
private static function getInstance(){
if(empty(self::$me)){
self::$me = new LanguageManager();
self::$me->loadLanguage();
}
return self::$me;
}
private function loadLanguage(){
$lang = $this->getCurrentLang();
$this->translations = Translations::fromPoFile(APP_BASE_PATH.'lang/'.$lang.'.po');
if(file_exists(APP_BASE_PATH.'lang/'.$lang.'-ext.po')){
$this->translations->addFromPoFile(APP_BASE_PATH.'lang/'.$lang.'-ext.po');
}
$t = new Translator();
$t->loadTranslations($this->translations);
$t->register();
$this->translator = $t;
}
private function getCurrentLang(){
$user = BaseService::getInstance()->getCurrentUser();
LogManager::getInstance()->info("User:".json_encode($user));
if(empty($user) || empty($user->lang) || $user->lang == "NULL"){
$lang = SettingsManager::getInstance()->getSetting('System: Language');
LogManager::getInstance()->info("System Lang:".$lang);
}else{
$lang = $user->lang;
}
if(empty($lang) || !file_exists(APP_BASE_PATH.'lang/'.$lang.'.po')){
$lang = 'en';
}
LogManager::getInstance()->info("Current Language:".$lang);
return $lang;
}
public static function getTranslations(){
$me = self::getInstance();
return Gettext\Generators\Json::toString($me->translations);
}
public static function tran($text){
$me = self::getInstance();
return $me->translator->gettext($text);
}
public static function translateTnrText($string){
$me = self::getInstance();
$pattern = "#<t>(.*?)</t>#";
preg_match_all($pattern, $string, $matches);
for($i = 0;$i<count($matches[0]); $i++){
$tagVal = $matches[1][$i];
$fullVal = $matches[0][$i];
$string = str_replace($fullVal,$me::tran($tagVal),$string);
}
return $string;
}
}

View File

@@ -81,9 +81,9 @@ class ModuleTab{
public function getHTML(){
$active = ($this->isActive)?"active":"";
if(!$this->isInsideGroup) {
return '<li class="' . $active . '"><a id="tab' . $this->name . '" href="#tabPage' . $this->name . '">' . $this->label . '</a></li>';
return '<li class="' . $active . '"><a id="tab' . $this->name . '" href="#tabPage' . $this->name . '">' . LanguageManager::tran($this->label) . '</a></li>';
}else{
return '<li class="' . $active . '"><a id="tab' . $this->name . '" href="#tabPage' . $this->name . '">' . $this->label . '</a></li>';
return '<li class="' . $active . '"><a id="tab' . $this->name . '" href="#tabPage' . $this->name . '">' . LanguageManager::tran($this->label) . '</a></li>';
}
}

View File

@@ -29,7 +29,7 @@ class NotificationManager{
if(!empty($noti->fromProfile)){
$profile = $this->baseService->getElement($profileClass,$noti->fromProfile,null,true);
if(!empty($profile)){
$fs = new FileService();
$fs = FileService::getInstance();
$profile = $fs->updateProfileImage($profile);
$noti->image = $profile->image;
}

View File

@@ -2,21 +2,31 @@
class ReportHandler{
public function handleReport($request){
if(!empty($request['id'])){
$report = new Report();
$reportMgrClass = $request['t'];
$report = new $reportMgrClass();
$report->Load("id = ?",array($request['id']));
if($report->id."" == $request['id']){
include APP_BASE_PATH.'admin/reports/reportClasses/ReportBuilder.php';
if($report->type == 'Query'){
$where = $this->buildQueryOmmit(json_decode($report->paramOrder,true), $request);
$query = str_replace("_where_", $where[0], $report->query);
return $this->executeReport($report,$query,$where[1]);
return $this->executeReport(new CSVReportBuilder(), $report,$query,$where[1]);
}else if($report->type == 'Class'){
$className = $report->query;
include APP_BASE_PATH.'admin/reports/reportClasses/ReportBuilder.php';
include APP_BASE_PATH.'admin/reports/reportClasses/'.$className.".php";
if($request['t'] == "Report"){
include APP_BASE_PATH.'admin/reports/reportClasses/'.$className.".php";
}else{
include APP_BASE_PATH.'modules/reports/reportClasses/'.$className.".php";
}
$cls = new $className();
$data = $cls->getData($report,$request);
return $this->generateReport($report,$data);
if(empty($data)){
return array("ERROR", "No data found");
}
return $this->generateReport($cls, $report,$data);
}
}else{
return array("ERROR","Report id not found");
@@ -25,7 +35,7 @@ class ReportHandler{
}
private function executeReport($report,$query,$parameters){
private function executeReport($reportBuilder, $report,$query,$parameters){
$report->DB()->SetFetchMode(ADODB_FETCH_ASSOC);
$rs = $report->DB()->Execute($query,$parameters);
@@ -55,70 +65,21 @@ class ReportHandler{
array_unshift($reportData,$columnNames);
return $this->generateReport($report, $reportData);
return $this->generateReport($reportBuilder, $report, $reportData);
}
private function generateReport($report, $data){
$fileFirst = "Report_".str_replace(" ", "_", $report->name)."-".date("Y-m-d_H-i-s");
$file = $fileFirst.".csv";
$fileName = CLIENT_BASE_PATH.'data/'.$file;
$fp = fopen($fileName, 'w');
foreach ($data as $fields) {
fputcsv($fp, $fields);
}
fclose($fp);
$uploadedToS3 = false;
$uploadFilesToS3 = SettingsManager::getInstance()->getSetting("Files: Upload Files to S3");
$uploadFilesToS3Key = SettingsManager::getInstance()->getSetting("Files: Amazon S3 Key for File Upload");
$uploadFilesToS3Secret = SettingsManager::getInstance()->getSetting("Files: Amazone S3 Secret for File Upload");
$s3Bucket = SettingsManager::getInstance()->getSetting("Files: S3 Bucket");
$s3WebUrl = SettingsManager::getInstance()->getSetting("Files: S3 Web Url");
if($uploadFilesToS3.'' == '1' && !empty($uploadFilesToS3Key)
&& !empty($uploadFilesToS3Secret) && !empty($s3Bucket) && !empty($s3WebUrl)){
$uploadname = CLIENT_NAME."/".$file;
$s3FileSys = new S3FileSystem($uploadFilesToS3Key, $uploadFilesToS3Secret);
$res = $s3FileSys->putObject($s3Bucket, $uploadname, $fileName, 'authenticated-read');
if(empty($res)){
return array("ERROR",$file);
}
unlink($fileName);
$file_url = $s3WebUrl.$uploadname;
$file_url = $s3FileSys->generateExpiringURL($file_url);
$uploadedToS3 = true;
}
$fileObj = new File();
$fileObj->name = $fileFirst;
$fileObj->filename = $file;
$fileObj->file_group = "Report";
$ok = $fileObj->Save();
if(!$ok){
LogManager::getInstance()->info($fileObj->ErrorMsg());
return array("ERROR","Error generating report");
}
protected function generateReport($reportBuilder, $report, $data){
$reportCreationData = $reportBuilder->createReportFile($report, $data);
$saveResp = $reportBuilder->saveFile($reportCreationData[0], $reportCreationData[1], $reportCreationData[2]);
$headers = array_shift($data);
if($uploadedToS3){
return array("SUCCESS",array($file_url,$headers,$data));
}else{
return array("SUCCESS",array($file,$headers,$data));
}
return array($saveResp[0],array($saveResp[1],$headers,$data));
}
private function buildQueryOmmit($names, $params){

View File

@@ -0,0 +1,72 @@
<?php
class StatusChangeLogManager{
private static $me = null;
private function __construct(){}
public static function getInstance(){
if(empty(self::$me)){
self::$me = new StatusChangeLogManager();
}
return self::$me;
}
public function addLog($type, $element, $userId, $oldStatus, $newStatus, $note){
$statusChangeLog = new StatusChangeLog();
$statusChangeLog->type = $type;
$statusChangeLog->element = $element;
$statusChangeLog->user_id = $userId;
$statusChangeLog->status_from = $oldStatus;
$statusChangeLog->status_to = $newStatus;
$statusChangeLog->created = date("Y-m-d H:i:s");
$statusChangeLog->data = $note;
$ok = $statusChangeLog->Save();
if(!$ok){
LogManager::getInstance()->info($statusChangeLog->ErrorMsg());
return new IceResponse(IceResponse::ERROR, NULL);
}
return new IceResponse(IceResponse::SUCCESS, $statusChangeLog);
}
public function getLogs($type, $element){
$statusChangeLog = new StatusChangeLog();
$logsTemp = $statusChangeLog->Find("type = ? and element = ? order by created",array($type, $element));
$logs = array();
foreach($logsTemp as $statusChangeLog){
$t = array();
$t['time'] = $statusChangeLog->created;
$t['status_from'] = $statusChangeLog->status_from;
$t['status_to'] = $statusChangeLog->status_to;
$t['time'] = $statusChangeLog->created;
$userName = null;
if(!empty($statusChangeLog->user_id)){
$lgUser = new User();
$lgUser->Load("id = ?",array($statusChangeLog->user_id));
if($lgUser->id == $statusChangeLog->user_id){
if(!empty($lgUser->employee)){
$lgEmployee = new Employee();
$lgEmployee->Load("id = ?",array($lgUser->employee));
$userName = $lgEmployee->first_name." ".$lgEmployee->last_name;
}else{
$userName = $lgUser->userName;
}
}
}
if(!empty($userName)){
$t['note'] = $statusChangeLog->data." (by: ".$userName.")";
}else{
$t['note'] = $statusChangeLog->data;
}
$logs[] = $t;
}
return new IceResponse(IceResponse::SUCCESS, $logs);
}
}

View File

@@ -44,7 +44,7 @@ class UIManager{
$template = str_replace("#_".$key."_#", $value, $template);
}
return $template;
return LanguageManager::translateTnrText($template);
}
public function setCurrentUser($user){
@@ -153,17 +153,12 @@ class UIManager{
}
if($this->user->user_level == "Admin"){
$reg = '';
$num = '';
$other = '';
if(class_exists('ProVersion')){
$pro = new ProVersion();
if(!empty($pro->employeeLimit)){
$num = "<br/>Employee Limit: ".$pro->employeeLimit;
}
if(method_exists($pro,'getRegisteredTo')){
$reg = "<br/>Registered To: ".$pro->getRegisteredTo();
if(method_exists($pro, 'getDetails')){
$other = $pro->getDetails();
}
}
@@ -172,8 +167,7 @@ class UIManager{
"APP_NAME"=>APP_NAME,
"VERSION"=>VERSION,
"VERSION_DATE"=>VERSION_DATE,
"REG"=>$reg,
"NUM"=>$num
"OTHER"=>$other
));
}
@@ -192,7 +186,8 @@ class UIManager{
}
public function addQuickAccessMenuItem($name, $icon, $link, $userLevels = array()){
$this->quickAccessMenuItems[] = array($name, $icon, $link, $userLevels);
$newName = LanguageManager::tran($name);
$this->quickAccessMenuItems[] = array($newName, $icon, $link, $userLevels);
}
public function getQuickAccessMenuItemsHTML(){

View File

@@ -1,8 +1,9 @@
{
"require": {
"guzzle/guzzle": "~3.7"
"monolog/monolog": "1.13.1",
"twig/twig": "1.23.*",
"gettext/gettext": "4.0.0"
},
"require-dev": {
"phpunit/phpunit": "4.5.*"
}
}

View File

@@ -1,11 +1,126 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "c6263cc0d124a7e40ee7c52782f80cff",
"hash": "95e43fed311dc21e1acaec81abd3dcda",
"content-hash": "cfb1065268b2b1b5e656e13a78e2b2a4",
"packages": [
{
"name": "gettext/gettext",
"version": "v4.0.0",
"source": {
"type": "git",
"url": "https://github.com/oscarotero/Gettext.git",
"reference": "7efdd4a01afd7fab85a90fb64fb88eeaef06f3b1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/oscarotero/Gettext/zipball/7efdd4a01afd7fab85a90fb64fb88eeaef06f3b1",
"reference": "7efdd4a01afd7fab85a90fb64fb88eeaef06f3b1",
"shasum": ""
},
"require": {
"gettext/languages": "2.*",
"php": ">=5.4.0"
},
"require-dev": {
"illuminate/view": "*",
"symfony/yaml": "~2",
"twig/extensions": "*",
"twig/twig": "*"
},
"suggest": {
"illuminate/view": "Is necessary if you want to use the Blade extractor",
"symfony/yaml": "Is necessary if you want to use the Yaml extractor/generator",
"twig/extensions": "Is necessary if you want to use the Twig extractor",
"twig/twig": "Is necessary if you want to use the Twig extractor"
},
"type": "library",
"autoload": {
"psr-4": {
"Gettext\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Oscar Otero",
"email": "oom@oscarotero.com",
"homepage": "http://oscarotero.com",
"role": "Developer"
}
],
"description": "PHP gettext manager",
"homepage": "https://github.com/oscarotero/Gettext",
"keywords": [
"JS",
"gettext",
"i18n",
"mo",
"po",
"translation"
],
"time": "2016-06-15 18:14:14"
},
{
"name": "gettext/languages",
"version": "2.1.2",
"source": {
"type": "git",
"url": "https://github.com/mlocati/cldr-to-gettext-plural-rules.git",
"reference": "c43ade7e3fb68bcf2379036513dce8d20553d9c8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mlocati/cldr-to-gettext-plural-rules/zipball/c43ade7e3fb68bcf2379036513dce8d20553d9c8",
"reference": "c43ade7e3fb68bcf2379036513dce8d20553d9c8",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Gettext\\Languages\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michele Locati",
"email": "mlocati@gmail.com",
"role": "Developer"
}
],
"description": "gettext languages with plural rules",
"homepage": "https://github.com/mlocati/cldr-to-gettext-plural-rules",
"keywords": [
"cldr",
"i18n",
"internationalization",
"l10n",
"language",
"languages",
"localization",
"php",
"plural",
"plural rules",
"plurals",
"translate",
"translations",
"unicode"
],
"time": "2015-03-27 11:32:41"
},
{
"name": "monolog/monolog",
"version": "1.13.1",
@@ -116,6 +231,67 @@
"psr-3"
],
"time": "2012-12-21 11:40:51"
},
{
"name": "twig/twig",
"version": "v1.23.3",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "ae53fc2c312fdee63773b75cb570304f85388b08"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/ae53fc2c312fdee63773b75cb570304f85388b08",
"reference": "ae53fc2c312fdee63773b75cb570304f85388b08",
"shasum": ""
},
"require": {
"php": ">=5.2.7"
},
"require-dev": {
"symfony/debug": "~2.7",
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.23-dev"
}
},
"autoload": {
"psr-0": {
"Twig_": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"role": "Project Founder"
},
{
"name": "Twig Team",
"homepage": "http://twig.sensiolabs.org/contributors",
"role": "Contributors"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "http://twig.sensiolabs.org",
"keywords": [
"templating"
],
"time": "2016-01-11 14:02:19"
}
],
"packages-dev": [],

View File

@@ -13,9 +13,7 @@
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0 class loader
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
@@ -39,6 +37,8 @@ namespace Composer\Autoload;
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
@@ -147,7 +147,7 @@ class ClassLoader
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-0 base directories
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException

21
src/composer/vendor/composer/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
Copyright (c) 2016 Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -6,5 +6,6 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Twig_' => array($vendorDir . '/twig/twig/lib'),
'Psr\\Log\\' => array($vendorDir . '/psr/log'),
);

View File

@@ -7,4 +7,6 @@ $baseDir = dirname($vendorDir);
return array(
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
'Gettext\\Languages\\' => array($vendorDir . '/gettext/languages/src'),
'Gettext\\' => array($vendorDir . '/gettext/gettext/src'),
);

View File

@@ -23,19 +23,26 @@ class ComposerAutoloaderInit91d733469d809ee1828b45ab2da48a10
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit91d733469d809ee1828b45ab2da48a10', 'loadClassLoader'));
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
call_user_func(\Composer\Autoload\ComposerStaticInit91d733469d809ee1828b45ab2da48a10::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
@@ -43,8 +50,3 @@ class ComposerAutoloaderInit91d733469d809ee1828b45ab2da48a10
return $loader;
}
}
function composerRequire91d733469d809ee1828b45ab2da48a10($file)
{
require $file;
}

View File

@@ -0,0 +1,62 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit91d733469d809ee1828b45ab2da48a10
{
public static $prefixLengthsPsr4 = array (
'M' =>
array (
'Monolog\\' => 8,
),
'G' =>
array (
'Gettext\\Languages\\' => 18,
'Gettext\\' => 8,
),
);
public static $prefixDirsPsr4 = array (
'Monolog\\' =>
array (
0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
),
'Gettext\\Languages\\' =>
array (
0 => __DIR__ . '/..' . '/gettext/languages/src',
),
'Gettext\\' =>
array (
0 => __DIR__ . '/..' . '/gettext/gettext/src',
),
);
public static $prefixesPsr0 = array (
'T' =>
array (
'Twig_' =>
array (
0 => __DIR__ . '/..' . '/twig/twig/lib',
),
),
'P' =>
array (
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log',
),
),
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit91d733469d809ee1828b45ab2da48a10::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit91d733469d809ee1828b45ab2da48a10::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit91d733469d809ee1828b45ab2da48a10::$prefixesPsr0;
}, null, ClassLoader::class);
}
}

View File

@@ -113,5 +113,186 @@
"logging",
"psr-3"
]
},
{
"name": "twig/twig",
"version": "v1.23.3",
"version_normalized": "1.23.3.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "ae53fc2c312fdee63773b75cb570304f85388b08"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/ae53fc2c312fdee63773b75cb570304f85388b08",
"reference": "ae53fc2c312fdee63773b75cb570304f85388b08",
"shasum": ""
},
"require": {
"php": ">=5.2.7"
},
"require-dev": {
"symfony/debug": "~2.7",
"symfony/phpunit-bridge": "~2.7"
},
"time": "2016-01-11 14:02:19",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.23-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-0": {
"Twig_": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"role": "Project Founder"
},
{
"name": "Twig Team",
"homepage": "http://twig.sensiolabs.org/contributors",
"role": "Contributors"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "http://twig.sensiolabs.org",
"keywords": [
"templating"
]
},
{
"name": "gettext/languages",
"version": "2.1.2",
"version_normalized": "2.1.2.0",
"source": {
"type": "git",
"url": "https://github.com/mlocati/cldr-to-gettext-plural-rules.git",
"reference": "c43ade7e3fb68bcf2379036513dce8d20553d9c8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mlocati/cldr-to-gettext-plural-rules/zipball/c43ade7e3fb68bcf2379036513dce8d20553d9c8",
"reference": "c43ade7e3fb68bcf2379036513dce8d20553d9c8",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"time": "2015-03-27 11:32:41",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Gettext\\Languages\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michele Locati",
"email": "mlocati@gmail.com",
"role": "Developer"
}
],
"description": "gettext languages with plural rules",
"homepage": "https://github.com/mlocati/cldr-to-gettext-plural-rules",
"keywords": [
"cldr",
"i18n",
"internationalization",
"l10n",
"language",
"languages",
"localization",
"php",
"plural",
"plural rules",
"plurals",
"translate",
"translations",
"unicode"
]
},
{
"name": "gettext/gettext",
"version": "v4.0.0",
"version_normalized": "4.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/oscarotero/Gettext.git",
"reference": "7efdd4a01afd7fab85a90fb64fb88eeaef06f3b1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/oscarotero/Gettext/zipball/7efdd4a01afd7fab85a90fb64fb88eeaef06f3b1",
"reference": "7efdd4a01afd7fab85a90fb64fb88eeaef06f3b1",
"shasum": ""
},
"require": {
"gettext/languages": "2.*",
"php": ">=5.4.0"
},
"require-dev": {
"illuminate/view": "*",
"symfony/yaml": "~2",
"twig/extensions": "*",
"twig/twig": "*"
},
"suggest": {
"illuminate/view": "Is necessary if you want to use the Blade extractor",
"symfony/yaml": "Is necessary if you want to use the Yaml extractor/generator",
"twig/extensions": "Is necessary if you want to use the Twig extractor",
"twig/twig": "Is necessary if you want to use the Twig extractor"
},
"time": "2016-06-15 18:14:14",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Gettext\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Oscar Otero",
"email": "oom@oscarotero.com",
"homepage": "http://oscarotero.com",
"role": "Developer"
}
],
"description": "PHP gettext manager",
"homepage": "https://github.com/oscarotero/Gettext",
"keywords": [
"JS",
"gettext",
"i18n",
"mo",
"po",
"translation"
]
}
]

View File

@@ -0,0 +1,17 @@
Contributing to Gettext
=======================
Looking to contribute something to this library? Here's how you can help.
## Bugs
A bug is a demonstrable problem that is caused by the code in the repository. Good bug reports are extremely helpful thank you!
Please try to be as detailed as possible in your report. Include specific information about the environment version of PHP, version of gettext, etc, and steps required to reproduce the issue.
## Pull Requests
Good pull requests patches, improvements, new features are a fantastic help. New extractors or generator are welcome. Before create a pull request, please follow these instructions:
* The code must be PSR-2 compliant
* Write some tests

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Oscar Otero Marzoa
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,401 @@
Gettext
=======
[![Build Status](https://travis-ci.org/oscarotero/Gettext.png?branch=master)](https://travis-ci.org/oscarotero/Gettext)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/oscarotero/Gettext/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/oscarotero/Gettext/?branch=master)
[![Reference Status](https://www.versioneye.com/php/gettext:gettext/reference_badge.svg?style=flat)](https://www.versioneye.com/php/gettext:gettext/references)
[![Latest Stable Version](https://poser.pugx.org/gettext/gettext/v/stable.svg)](https://packagist.org/packages/gettext/gettext)
[![Total Downloads](https://poser.pugx.org/gettext/gettext/downloads.svg)](https://packagist.org/packages/gettext/gettext)
[![Monthly Downloads](https://poser.pugx.org/gettext/gettext/d/monthly.png)](https://packagist.org/packages/gettext/gettext)
[![License](https://poser.pugx.org/gettext/gettext/license.svg)](https://packagist.org/packages/gettext/gettext)
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/496dc2a6-43be-4046-a283-f8370239dd47/big.png)](https://insight.sensiolabs.com/projects/496dc2a6-43be-4046-a283-f8370239dd47)
Created by Oscar Otero <http://oscarotero.com> <oom@oscarotero.com> (MIT License)
Gettext is a PHP (>=5.4) library to import/export/edit gettext from PO, MO, PHP, JS files, etc.
## Installation
With composer (recomended):
```
composer require gettext/gettext
```
If you don't use composer in your project, you have to download and place this package in a directory of your project. You need to install also [gettext/languages](https://github.com/mlocati/cldr-to-gettext-plural-rules). Then, include the autoloaders of both projects in any place of your php code:
```php
include_once "libs/gettext/src/autoloader.php";
include_once "libs/cldr-to-gettext-plural-rules/src/autoloader.php";
```
## Classes and functions
This package contains the following classes:
* `Gettext\Translation` - A translation definition
* `Gettext\Translations` - A collection of translations
* `Gettext\Extractors\*` - Import translations from various sources (po, mo, php, js, etc)
* `Gettext\Generators\*` - Export translations to various formats (po, mo, php, json, etc)
* `Gettext\Translator` - To use the translations in your php templates instead the [gettext extension](http://php.net/gettext)
* `Gettext\GettextTranslator` - To use the [gettext extension](http://php.net/gettext)
## Usage example
```php
use Gettext\Translations;
//import from a .po file:
$translations = Translations::fromPoFile('locales/gl.po');
//edit some translations:
$translation = $translations->find(null, 'apple');
if ($translation) {
$translation->setTranslation('Mazá');
}
//export to a php array:
$translations->toPhpArrayFile('locales/gl.php');
//and to a .mo file
$translations->toMoFile('Locale/gl/LC_MESSAGES/messages.mo');
```
If you want use this translations in your php templates without using the gettext extension:
```php
use Gettext\Translator;
//Create the translator instance
$t = new Translator();
//Load your translations (exported as PhpArray):
$t->loadTranslations('locales/gl.php');
//Use it:
echo $t->gettext('apple'); // "Mazá"
//If you want use global functions:
$t->register();
echo __('apple'); // "Mazá"
__e('apple'); // "Mazá"
```
To use this translations with the gettext extension:
```php
use Gettext\GettextTranslator;
//Create the translator instance
$t = new GettextTranslator();
//Set the language and load the domain
$t->setLanguage('gl');
$t->loadDomain('messages', 'Locale');
//Use it:
echo $t->gettext('apple'); // "Mazá"
//Or use the gettext functions
echo gettext('apple'); // "Mazá"
//If you want use the global functions
$t->register();
echo __('apple'); // "Mazá"
```
The benefits of using the functions provided by this library (`__()` instead `_()` or `gettext()`) are:
* You are using the same functions, no matter whether the translations are provided by gettext extension or any other method
* You can use variables easier because sprintf functionality is included. For example: `__('Hello %s', 'world')` instead `sprintf(_('Hello %s'), 'world')`.
## Translation
The `Gettext\Translation` class stores all information about a translation: the original text, the translated text, source references, comments, etc.
```php
// __construct($context, $original, $plural)
$translation = new Gettext\Translation('comments', 'One comment', '%s comments');
$translation->setTranslation('Un comentario');
$translation->setPluralTranslation('%s comentarios');
$translation->addReference('templates/comments/comment.php', 34);
$translation->addComment('To display the amount of comments in a post');
echo $translation->getContext(); // comments
echo $translation->getOriginal(); // One comment
echo $translation->getTranslation(); // Un comentario
// etc...
```
## Translations
The `Gettext\Translations` class stores a collection of translations:
```php
$translations = new Gettext\Translations();
//You can add new translations using the array syntax
$translations[] = new Gettext\Translation('comments', 'One comment', '%s comments');
//Or using the "insert" method
$insertedTranslation = $translations->insert('comments', 'One comments', '%s comments');
//Find a specific translation
$translation = $translations->find('comments', 'One comments');
//Edit headers, domain, etc
$translations->setHeader('Last-Translator', 'Oscar Otero');
$translations->setDomain('my-blog');
```
## Extractors
The extrators allows to fetch gettext values from any source. For example, to scan a .po file:
```php
$translations = new Gettext\Translations();
//From a file
Gettext\Extractors\Po::fromFile('locales/en.po', $translations);
//From a string
$string = file_get_contents('locales2/en.po');
Gettext\Extractors\Po::fromString($string, $translations);
```
The better way to use extractors is using the magic methods of `Gettext\Translations`:
```php
//Create a Translations instance using a po file
$translations = Gettext\Translations::fromPoFile('locales/en.po');
//Add more messages from other files
$translations->addFromPoFile('locales2/en.po');
```
The available extractors are the following:
Name | Description | Example
---- | ----------- | --------
**Blade** | Scans a Blade template (For laravel users). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/4/Input.Blade.php)
**Csv** | Gets the messages from csv. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/Csv.csv)
**CsvDictionary** | Gets the messages from csv (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/CsvDictionary.csv)
**Jed** | Gets the messages from a json compatible with [Jed](http://slexaxton.github.com/Jed/). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/Jed.json)
**JsCode** | Scans javascript code looking for all gettext functions (the same than PhpCode but for javascript). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/8/Input.JsCode.js)
**Json** | Gets the messages from json. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/Json.json)
**JsonDictionary** | Gets the messages from a json (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/JsonDictionary.json)
**Mo** | Gets the messages from MO. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/Mo.mo)
**PhpArray** | Gets the messages from a php file that returns an array. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/PhpArray.php)
**PhpCode** | Scans php code looking for all gettext functions (see `translator_functions.php`). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/2/Input.PhpCode.php)
**Po** | Gets the messages from PO. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/Po.po)
**Twig** | To scan a Twig template. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/6/Input.Twig.php)
**Xliff** | Gets the messages from [xliff (2.0)](http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/Xliff.xlf)
**Yaml** | Gets the messages from yaml. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/Yaml.yml)
**YamlDictionary** | Gets the messages from a yaml (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/YamlDictionary.yml)
## Generators
The generators export a `Gettext\Translations` instance to any format (po, mo, array, etc).
```php
//Save to a file
Gettext\Generators\Po::toFile($translations, 'locales/en.po');
//Return as a string
$content = Gettext\Generators\Po::toString($translations);
file_put_contents('locales/en.po', $content);
```
Like extractors, the better way to use generators is using the magic methods of `Gettext\Translations`:
```php
//Extract messages from a php code file
$translations = Gettext\Translations::fromPhpCodeFile('templates/index.php');
//Export to a po file
$translations->toPoFile('locales/en.po');
//Export to a po string
$content = $translatons->toPoString();
file_put_contents('locales/en.po', $content);
```
The available generators are the following:
Name | Description | Example
---- | ----------- | --------
**Csv** | Exports to csv. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/Csv.csv)
**CsvDictionary** | Exports to csv (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/CsvDictionary.csv)
**Json** | Exports to json. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/Json.json)
**JsonDictionary** | Exports to json (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/JsonDictionary.json)
**Mo** | Exports to Mo. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/Mo.mo)
**PhpArray** | Exports to php code that returns an array. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/PhpArray.php)
**Po** | Exports to Po. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/Po.po)
**Jed** | Exports to json format compatible with [Jed](http://slexaxton.github.com/Jed/). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/Jed.json)
**Xliff** | Exports to [xliff (2.0)](http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/Xliff.xlf)
**Yaml** | Exports to yaml. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/Yaml.yml)
**YamlDictionary** | Exports to yaml (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/1/YamlDictionary.yml)
## Translator
The class `Gettext\Translator` implements the gettext functions in php. Useful if you don't have the native gettext extension for php or want to avoid problems with it. You can load the translations from a php array file or using a `Gettext\Translations` instance:
```php
use Gettext\Translator;
//Create a new instance of the translator
$t = new Translator();
//Load the translations using any of the following ways:
// 1. from php files (generated by Gettext\Extractors\PhpArray)
$t->loadTranslations('locales/gl.php');
// 2. using the array directly
$array = include 'locales/gl.php';
$t->loadTranslations($array);
// 3. using a Gettext\Translations instance (slower)
$translations = Gettext\Translations::fromPoFile('locales/gl.po');
$t->loadTranslations($translations);
//Now you can use it in your templates
echo $t->gettext('apple');
```
## GettextTranslator
The class `Gettext\GettextTranslator` uses the gettext extension. It's useful because combines the performance of using real gettext functions but with the same API than `Translator` class, so you can switch to one or other translator deppending of the environment without change code of your app.
```php
use Gettext\GettextTranslator;
//Create a new instance
$t = new GettextTranslator();
//It detects the environment variables to set the locale, but you can change it:
$t->setLanguage('gl');
//Load the domains:
$t->loadDomain('messages', 'project/Locale');
//this means you have the file "project/Locale/gl/LC_MESSAGES/messages.po"
//Now you can use it in your templates
echo $t->gettext('apple');
```
## Global functions
To ease the use of translations in your php templates, you can use the provided functions:
```php
//Register the translator to use the global functions
$t->register();
echo __('apple'); // it's the same than $t->gettext('apple');
__e('apple'); // it's the same than echo $t->gettext('apple');
```
You can scan the php files containing these functions and extract the values with the PhpCode extractor:
```html
<!-- index.php -->
<html>
<body>
<?php echo __('Hello world'); ?>
</body>
</html>
```
## Merge translations
To work with different translations you may want merge them in an unique file. There are two ways to do this:
The simplest way is adding new translations:
```php
use Gettext\Translations;
$translations = Translations::fromPoFile('my-file1.po');
$translations->addFromPoFile('my-file2.po');
```
A more advanced way is merge two `Translations` instances:
```php
use Gettext\Translations;
//Create a new Translations instances with our translations.
$translations1 = Translations::fromPoFile('my-file1.po');
$translations2 = Translations::fromPoFile('my-file2.po');
//Merge one inside other:
$translations1->mergeWith($translations2);
//Now translations1 has all values
```
The second argument of `mergeWith` defines how the merge will be done. Use the `Gettext\Merge` constants to configure the merging:
Constant | Description
--------- | -----------
`Merge::ADD` | Adds the translations from `$translations2` that are missing
`Merge::REMOVE` | Removes the translations missing in `$translations2`
`Merge::HEADERS_ADD` | Adds the headers from `$translations2` that are missing
`Merge::HEADERS_REMOVE` | Removes the headers missing in `$translations2`
`Merge::HEADERS_OVERRIDE` | Overrides the headers with the values of `$translations2`
`Merge::LANGUAGE_OVERRIDE` | Set the language defined in `$translations2`
`Merge::DOMAIN_OVERRIDE` | Set the domain defined in `$translations2`
`Merge::TRANSLATION_OVERRIDE` | Override the translation and plural translations with the value of `$translation2`
`Merge::COMMENTS_OURS` | Use only the comments of `$translation1`
`Merge::COMMENTS_THEIRS` | Use only the comments of `$translation2`
`Merge::EXTRACTED_COMMENTS_OURS` | Use only the extracted comments of `$translation1`
`Merge::EXTRACTED_COMMENTS_THEIRS` | Use only the extracted comments of `$translation2`
`Merge::FLAGS_OURS` | Use only the flags of `$translation1`
`Merge::FLAGS_THEIRS` | Use only the flags of `$translation2`
`Merge::REFERENCES_OURS` | Use only the references of `$translation1`
`Merge::REFERENCES_THEIRS` | Use only the references of `$translation2`
Example:
```php
use Gettext\Translations;
use Gettext\Merge;
//Scan the php code to find the latest gettext translations
$phpTranslations = Translations::fromPhpCodeFile('my-templates.php');
//Get the translations of the code that are stored in a po file
$poTranslations = Translations::fromPoFile('locale.po');
//Merge the translations from the po file using the references from `$phpTranslations`:
$translations->mergeWith($poTranslations, Merge::REFERENCES_OURS);
//Now save a po file with the result
$translations->toPoFile('locale.po');
```
Note, if the second argument is not defined, the default value is `Merge::DEFAULTS` that's equivalent to `Merge::ADD | Merge::HEADERS_ADD`.
## Use from CLI
There's a Robo task to use this library from the command line interface: https://github.com/oscarotero/GettextRobo
## Use in the browser
If you want to use your translations in the browser, there's a javascript translator: https://github.com/oscarotero/gettext-translator
## Contributors
Thanks to all [contributors](https://github.com/oscarotero/Gettext/graphs/contributors) specially to [@mlocati](https://github.com/mlocati).

View File

@@ -0,0 +1,46 @@
{
"name": "gettext/gettext",
"type": "library",
"description": "PHP gettext manager",
"keywords": ["js", "gettext", "i18n", "translation", "po", "mo"],
"homepage": "https://github.com/oscarotero/Gettext",
"license": "MIT",
"authors": [
{
"name": "Oscar Otero",
"email": "oom@oscarotero.com",
"homepage": "http://oscarotero.com",
"role": "Developer"
}
],
"support": {
"email": "oom@oscarotero.com",
"issues": "https://github.com/oscarotero/Gettext/issues"
},
"require": {
"php": ">=5.4.0",
"gettext/languages": "2.*"
},
"require-dev": {
"illuminate/view": "*",
"twig/twig": "*",
"twig/extensions": "*",
"symfony/yaml": "~2"
},
"suggest": {
"illuminate/view": "Is necessary if you want to use the Blade extractor",
"twig/twig": "Is necessary if you want to use the Twig extractor",
"twig/extensions": "Is necessary if you want to use the Twig extractor",
"symfony/yaml": "Is necessary if you want to use the Yaml extractor/generator"
},
"autoload": {
"psr-4": {
"Gettext\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Gettext\\Tests\\": "tests"
}
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Gettext;
abstract class BaseTranslator implements TranslatorInterface
{
/** @var TranslatorInterface */
public static $current;
/**
* @see TranslatorInterface
*/
public function register()
{
$previous = self::$current;
self::$current = $this;
include_once __DIR__.'/translator_functions.php';
return $previous;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Illuminate\Filesystem\Filesystem;
use Illuminate\View\Compilers\BladeCompiler;
/**
* Class to get gettext strings from blade.php files returning arrays.
*/
class Blade extends Extractor implements ExtractorInterface
{
/**
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
$bladeCompiler = new BladeCompiler(new Filesystem(), null);
$string = $bladeCompiler->compileString($string);
PhpCode::fromString($string, $translations, $options);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Gettext\Utils\HeadersExtractorTrait;
/**
* Class to get gettext strings from csv.
*/
class Csv extends Extractor implements ExtractorInterface
{
use HeadersExtractorTrait;
/**
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
$handle = fopen('php://memory', 'w');
fputs($handle, $string);
rewind($handle);
while ($row = fgetcsv($handle)) {
$context = array_shift($row);
$original = array_shift($row);
if ($context === '' && $original === '') {
self::extractHeaders(array_shift($row), $translations);
continue;
}
$translation = $translations->insert($context, $original);
if (!empty($row)) {
$translation->setTranslation(array_shift($row));
$translation->setPluralTranslations($row);
}
}
fclose($handle);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Gettext\Utils\HeadersExtractorTrait;
/**
* Class to get gettext strings from csv.
*/
class CsvDictionary extends Extractor implements ExtractorInterface
{
use HeadersExtractorTrait;
/**
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
$handle = fopen('php://memory', 'w');
fputs($handle, $string);
rewind($handle);
while ($message = fgetcsv($handle)) {
list($original, $translation) = $message + ['', ''];
if ($original === '') {
self::extractHeaders($translation, $translations);
continue;
}
$translations->insert(null, $original)->setTranslation($translation);
}
fclose($handle);
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Gettext\Extractors;
use Exception;
use InvalidArgumentException;
use Gettext\Translations;
abstract class Extractor implements ExtractorInterface
{
/**
* {@inheritdoc}
*/
public static function fromFile($file, Translations $translations, array $options = [])
{
foreach (self::getFiles($file) as $file) {
$options['file'] = $file;
static::fromString(self::readFile($file), $translations, $options);
}
}
/**
* Checks and returns all files.
*
* @param string|array $file The file/s
*
* @return array The file paths
*/
protected static function getFiles($file)
{
if (empty($file)) {
throw new InvalidArgumentException('There is not any file defined');
}
if (is_string($file)) {
if (!is_file($file)) {
throw new InvalidArgumentException("'$file' is not a valid file");
}
if (!is_readable($file)) {
throw new InvalidArgumentException("'$file' is not a readable file");
}
return [$file];
}
if (is_array($file)) {
$files = [];
foreach ($file as $f) {
$files = array_merge($files, self::getFiles($f));
}
return $files;
}
throw new InvalidArgumentException('The first argumet must be string or array');
}
/**
* Reads and returns the content of a file.
*
* @param string $file
*
* @return string
*/
protected static function readFile($file)
{
$length = filesize($file);
if (!($fd = fopen($file, 'rb'))) {
throw new Exception("Cannot read the file '$file', probably permissions");
}
$content = $length ? fread($fd, $length) : '';
fclose($fd);
return $content;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
interface ExtractorInterface
{
/**
* Extract the translations from a file.
*
* @param array|string $file A path of a file or files
* @param Translations $translations The translations instance to append the new translations.
* @param array $options
*/
public static function fromFile($file, Translations $translations, array $options = []);
/**
* Parses a string and append the translations found in the Translations instance.
*
* @param string $string
* @param Translations $translations
* @param array $options
*/
public static function fromString($string, Translations $translations, array $options = []);
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
/**
* Class to get gettext strings from json files.
*/
class Jed extends Extractor implements ExtractorInterface
{
/**
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
self::extract(json_decode($string, true), $translations);
}
/**
* Handle an array of translations and append to the Translations instance.
*
* @param array $content
* @param Translations $translations
*/
public static function extract(array $content, Translations $translations)
{
$messages = current($content);
$headers = isset($messages['']) ? $messages[''] : null;
unset($messages['']);
if (!empty($headers['domain'])) {
$translations->setDomain($headers['domain']);
}
if (!empty($headers['lang'])) {
$translations->setLanguage($headers['lang']);
}
if (!empty($headers['plural-forms'])) {
$translations->setHeader(Translations::HEADER_PLURAL, $headers['plural-forms']);
}
$context_glue = '\u0004';
foreach ($messages as $key => $translation) {
$key = explode($context_glue, $key);
$context = isset($key[1]) ? array_shift($key) : '';
$translations->insert($context, array_shift($key))
->setTranslation(array_shift($translation))
->setPluralTranslations($translation);
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Gettext\Utils\JsFunctionsScanner;
/**
* Class to get gettext strings from javascript files.
*/
class JsCode extends Extractor implements ExtractorInterface
{
public static $options = [
'functions' => [
'gettext' => 'gettext',
'__' => 'gettext',
'ngettext' => 'ngettext',
'n__' => 'ngettext',
'pgettext' => 'pgettext',
'p__' => 'pgettext',
'dgettext' => 'dgettext',
'd__' => 'dgettext',
'dpgettext' => 'dpgettext',
'dp__' => 'dpgettext',
'npgettext' => 'npgettext',
'np__' => 'npgettext',
'dnpgettext' => 'dnpgettext',
'dnp__' => 'dnpgettext',
],
];
/**
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
$options += static::$options;
$functions = new JsFunctionsScanner($string);
$functions->saveGettextFunctions($translations, $options);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Gettext\Utils\MultidimensionalArrayTrait;
/**
* Class to get gettext strings from json.
*/
class Json extends Extractor implements ExtractorInterface
{
use MultidimensionalArrayTrait;
/**
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
$messages = json_decode($string, true);
if (is_array($messages)) {
self::fromArray($messages, $translations);
}
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Gettext\Utils\DictionaryTrait;
/**
* Class to get gettext strings from plain json.
*/
class JsonDictionary extends Extractor implements ExtractorInterface
{
use DictionaryTrait;
/**
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
$messages = json_decode($string, true);
if (is_array($messages)) {
self::fromArray($messages, $translations);
}
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace Gettext\Extractors;
use Exception;
use Gettext\Translations;
use Gettext\Utils\StringReader;
/**
* Class to get gettext strings from .mo files.
*/
class Mo extends Extractor implements ExtractorInterface
{
const MAGIC1 = -1794895138;
const MAGIC2 = -569244523;
const MAGIC3 = 2500072158;
/**
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
$stream = new StringReader($string);
$magic = self::readInt($stream, 'V');
if (($magic === self::MAGIC1) || ($magic === self::MAGIC3)) { //to make sure it works for 64-bit platforms
$byteOrder = 'V'; //low endian
} elseif ($magic === (self::MAGIC2 & 0xFFFFFFFF)) {
$byteOrder = 'N'; //big endian
} else {
throw new Exception('Not MO file');
}
self::readInt($stream, $byteOrder);
$total = self::readInt($stream, $byteOrder); //total string count
$originals = self::readInt($stream, $byteOrder); //offset of original table
$tran = self::readInt($stream, $byteOrder); //offset of translation table
$stream->seekto($originals);
$table_originals = self::readIntArray($stream, $byteOrder, $total * 2);
$stream->seekto($tran);
$table_translations = self::readIntArray($stream, $byteOrder, $total * 2);
for ($i = 0; $i < $total; ++$i) {
$next = $i * 2;
$stream->seekto($table_originals[$next + 2]);
$original = $stream->read($table_originals[$next + 1]);
$stream->seekto($table_translations[$next + 2]);
$translated = $stream->read($table_translations[$next + 1]);
if ($original === '') {
// Headers
foreach (explode("\n", $translated) as $headerLine) {
if ($headerLine === '') {
continue;
}
$headerChunks = preg_split('/:\s*/', $headerLine, 2);
$translations->setHeader($headerChunks[0], isset($headerChunks[1]) ? $headerChunks[1] : '');
}
continue;
}
$chunks = explode("\x04", $original, 2);
if (isset($chunks[1])) {
$context = $chunks[0];
$original = $chunks[1];
} else {
$context = '';
}
$chunks = explode("\x00", $original, 2);
if (isset($chunks[1])) {
$original = $chunks[0];
$plural = $chunks[1];
} else {
$plural = '';
}
$translation = $translations->insert($context, $original, $plural);
if ($translated === '') {
continue;
}
if ($plural === '') {
$translation->setTranslation($translated);
continue;
}
$v = explode("\x00", $translated);
$translation->setTranslation(array_shift($v));
$translation->setPluralTranslations($v);
}
}
/**
* @param StringReader $stream
* @param string $byteOrder
*/
private static function readInt(StringReader $stream, $byteOrder)
{
if (($read = $stream->read(4)) === false) {
return false;
}
$read = unpack($byteOrder, $read);
return array_shift($read);
}
/**
* @param StringReader $stream
* @param string $byteOrder
* @param int $count
*/
private static function readIntArray(StringReader $stream, $byteOrder, $count)
{
return unpack($byteOrder.$count, $stream->read(4 * $count));
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Gettext\Extractors;
use BadMethodCallException;
use Gettext\Translations;
use Gettext\Utils\MultidimensionalArrayTrait;
/**
* Class to get gettext strings from php files returning arrays.
*/
class PhpArray extends Extractor implements ExtractorInterface
{
use MultidimensionalArrayTrait;
/**
* {@inheritdoc}
*/
public static function fromFile($file, Translations $translations, array $options = [])
{
foreach (static::getFiles($file) as $file) {
self::fromArray(include($file), $translations);
}
}
/**
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
throw new BadMethodCallException('PhpArray::fromString() cannot be called. Use PhpArray::fromFile()');
}
}

View File

@@ -0,0 +1,127 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Gettext\Utils\PhpFunctionsScanner;
/**
* Class to get gettext strings from php files returning arrays.
*/
class PhpCode extends Extractor implements ExtractorInterface
{
public static $options = [
// - false: to not extract comments
// - empty string: to extract all comments
// - non-empty string: to extract comments that start with that string
'extractComments' => false,
'functions' => [
'gettext' => 'gettext',
'__' => 'gettext',
'ngettext' => 'ngettext',
'n__' => 'ngettext',
'pgettext' => 'pgettext',
'p__' => 'pgettext',
'dgettext' => 'dgettext',
'd__' => 'dgettext',
'dpgettext' => 'dpgettext',
'dp__' => 'dpgettext',
'npgettext' => 'npgettext',
'np__' => 'npgettext',
'dnpgettext' => 'dnpgettext',
'dnp__' => 'dnpgettext',
],
];
/**
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
$options += static::$options;
$functions = new PhpFunctionsScanner($string);
if ($options['extractComments'] !== false) {
$functions->enableCommentsExtraction($options['extractComments']);
}
$functions->saveGettextFunctions($translations, $options);
}
/**
* Decodes a T_CONSTANT_ENCAPSED_STRING string.
*
* @param string $value
*
* @return string
*/
public static function convertString($value)
{
if (strpos($value, '\\') === false) {
return substr($value, 1, -1);
}
if ($value[0] === "'") {
return strtr(substr($value, 1, -1), ['\\\\' => '\\', '\\\'' => '\'']);
}
$value = substr($value, 1, -1);
return preg_replace_callback('/\\\(n|r|t|v|e|f|\$|"|\\\|x[0-9A-Fa-f]{1,2}|u{[0-9a-f]{1,6}}|[0-7]{1,3})/', function ($match) {
switch ($match[1][0]) {
case 'n':
return "\n";
case 'r':
return "\r";
case 't':
return "\t";
case 'v':
return "\v";
case 'e':
return "\e";
case 'f':
return "\f";
case '$':
return '$';
case '"':
return '"';
case '\\':
return '\\';
case 'x':
return chr(hexdec(substr($match[0], 1)));
case 'u':
return self::unicodeChar(hexdec(substr($match[0], 1)));
default:
return chr(octdec($match[0]));
}
}, $value);
}
//http://php.net/manual/en/function.chr.php#118804
private static function unicodeChar($dec)
{
if ($dec < 0x80) {
return chr($dec);
}
if ($dec < 0x0800) {
return chr(0xC0 + ($dec >> 6))
.chr(0x80 + ($dec & 0x3f));
}
if ($dec < 0x010000) {
return chr(0xE0 + ($dec >> 12))
.chr(0x80 + (($dec >> 6) & 0x3f))
.chr(0x80 + ($dec & 0x3f));
}
if ($dec < 0x200000) {
return chr(0xF0 + ($dec >> 18))
.chr(0x80 + (($dec >> 12) & 0x3f))
.chr(0x80 + (($dec >> 6) & 0x3f))
.chr(0x80 + ($dec & 0x3f));
}
}
}

View File

@@ -0,0 +1,199 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Gettext\Translation;
use Gettext\Utils\HeadersExtractorTrait;
/**
* Class to get gettext strings from php files returning arrays.
*/
class Po extends Extractor implements ExtractorInterface
{
use HeadersExtractorTrait;
/**
* Parses a .po file and append the translations found in the Translations instance.
*
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
$lines = explode("\n", $string);
$i = 0;
$translation = new Translation('', '');
for ($n = count($lines); $i < $n; ++$i) {
$line = trim($lines[$i]);
$line = self::fixMultiLines($line, $lines, $i);
if ($line === '') {
if ($translation->is('', '')) {
self::extractHeaders($translation->getTranslation(), $translations);
} elseif ($translation->hasOriginal()) {
$translations[] = $translation;
}
$translation = new Translation('', '');
continue;
}
$splitLine = preg_split('/\s+/', $line, 2);
$key = $splitLine[0];
$data = isset($splitLine[1]) ? $splitLine[1] : '';
switch ($key) {
case '#':
$translation->addComment($data);
$append = null;
break;
case '#.':
$translation->addExtractedComment($data);
$append = null;
break;
case '#,':
foreach (array_map('trim', explode(',', trim($data))) as $value) {
$translation->addFlag($value);
}
$append = null;
break;
case '#:':
foreach (preg_split('/\s+/', trim($data)) as $value) {
if (preg_match('/^(.+)(:(\d*))?$/U', $value, $matches)) {
$translation->addReference($matches[1], isset($matches[3]) ? $matches[3] : null);
}
}
$append = null;
break;
case 'msgctxt':
$translation = $translation->getClone(self::convertString($data));
$append = 'Context';
break;
case 'msgid':
$translation = $translation->getClone(null, self::convertString($data));
$append = 'Original';
break;
case 'msgid_plural':
$translation->setPlural(self::convertString($data));
$append = 'Plural';
break;
case 'msgstr':
case 'msgstr[0]':
$translation->setTranslation(self::convertString($data));
$append = 'Translation';
break;
case 'msgstr[1]':
$translation->setPluralTranslations([self::convertString($data)]);
$append = 'PluralTranslation';
break;
default:
if (strpos($key, 'msgstr[') === 0) {
$p = $translation->getPluralTranslations();
$p[] = self::convertString($data);
$translation->setPluralTranslations($p);
$append = 'PluralTranslation';
break;
}
if (isset($append)) {
if ($append === 'Context') {
$translation = $translation->getClone($translation->getContext()."\n".self::convertString($data));
break;
}
if ($append === 'Original') {
$translation = $translation->getClone(null, $translation->getOriginal()."\n".self::convertString($data));
break;
}
if ($append === 'PluralTranslation') {
$p = $translation->getPluralTranslations();
$p[] = array_pop($p)."\n".self::convertString($data);
$translation->setPluralTranslations($p);
break;
}
$getMethod = 'get'.$append;
$setMethod = 'set'.$append;
$translation->$setMethod($translation->$getMethod()."\n".self::convertString($data));
}
break;
}
}
if ($translation->hasOriginal() && !in_array($translation, iterator_to_array($translations))) {
$translations[] = $translation;
}
}
/**
* Gets one string from multiline strings.
*
* @param string $line
* @param array $lines
* @param int &$i
*
* @return string
*/
private static function fixMultiLines($line, array $lines, &$i)
{
for ($j = $i, $t = count($lines); $j < $t; ++$j) {
if (substr($line, -1, 1) == '"'
&& isset($lines[$j + 1])
&& substr(trim($lines[$j + 1]), 0, 1) == '"'
) {
$line = substr($line, 0, -1).substr(trim($lines[$j + 1]), 1);
} else {
$i = $j;
break;
}
}
return $line;
}
/**
* Convert a string from its PO representation.
*
* @param string $value
*
* @return string
*/
public static function convertString($value)
{
if (!$value) {
return '';
}
if ($value[0] === '"') {
$value = substr($value, 1, -1);
}
return strtr(
$value,
[
'\\\\' => '\\',
'\\a' => "\x07",
'\\b' => "\x08",
'\\t' => "\t",
'\\n' => "\n",
'\\v' => "\x0b",
'\\f' => "\x0c",
'\\r' => "\r",
'\\"' => '"',
]
);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Twig_Loader_String;
use Twig_Environment;
use Twig_Extensions_Extension_I18n;
/**
* Class to get gettext strings from twig files returning arrays.
*/
class Twig extends Extractor implements ExtractorInterface
{
public static $options = [
'twig' => null
];
/**
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
$options += static::$options;
$twig = $options['twig'] ?: self::createTwig();
PhpCode::fromString($twig->compileSource($string), $translations, $options);
}
/**
* Returns a Twig instance.
*
* @return Twig_Environment
*/
private static function createTwig()
{
$twig = new Twig_Environment(new Twig_Loader_String());
$twig->addExtension(new Twig_Extensions_Extension_I18n());
return static::$options['twig'] = $twig;
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Gettext\Translation;
use SimpleXMLElement;
/**
* Class to get gettext strings from xliff format.
*/
class Xliff extends Extractor implements ExtractorInterface
{
/**
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
$xml = new SimpleXMLElement($string, null, false);
foreach ($xml->file as $file) {
if (isset($file->notes)) {
foreach ($file->notes->note as $note) {
$translations->setHeader($note['id'], (string) $note);
}
}
foreach ($file->unit as $unit) {
foreach ($unit->segment as $segment) {
$targets = [];
foreach ($segment->target as $target) {
$targets[] = (string) $target;
}
$translation = new Translation(null, (string) $segment->source);
$translation->setTranslation(array_shift($targets));
$translation->setPluralTranslations($targets);
if (isset($unit->notes)) {
foreach ($unit->notes->note as $note) {
switch ($note['category']) {
case 'context':
$translation = $translation->getClone((string) $note);
break;
case 'extracted-comment':
$translation->addExtractedComment((string) $note);
break;
case 'flag':
$translation->addFlag((string) $note);
break;
case 'reference':
$ref = explode(':', (string) $note, 2);
$translation->addReference($ref[0], isset($ref[1]) ? $ref[1] : null);
break;
default:
$translation->addComment((string) $note);
break;
}
}
}
$translations[] = $translation;
}
}
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Gettext\Utils\MultidimensionalArrayTrait;
use Symfony\Component\Yaml\Yaml as YamlParser;
/**
* Class to get gettext strings from yaml.
*/
class Yaml extends Extractor implements ExtractorInterface
{
use MultidimensionalArrayTrait;
/**
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
$messages = YamlParser::parse($string);
if (is_array($messages)) {
self::fromArray($messages, $translations);
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Gettext\Utils\DictionaryTrait;
use Symfony\Component\Yaml\Yaml as YamlParser;
/**
* Class to get gettext strings from yaml.
*/
class YamlDictionary extends Extractor implements ExtractorInterface
{
use DictionaryTrait;
/**
* {@inheritdoc}
*/
public static function fromString($string, Translations $translations, array $options = [])
{
$messages = YamlParser::parse($string);
if (is_array($messages)) {
self::fromArray($messages, $translations);
}
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
use Gettext\Utils\HeadersGeneratorTrait;
/**
* Class to export translations to csv.
*/
class Csv extends Generator implements GeneratorInterface
{
use HeadersGeneratorTrait;
public static $options = [
'includeHeaders' => false,
];
/**
* {@parentDoc}.
*/
public static function toString(Translations $translations, array $options = [])
{
$options += static::$options;
$handle = fopen('php://memory', 'w');
if ($options['includeHeaders']) {
fputcsv($handle, ['', '', self::generateHeaders($translations)]);
}
foreach ($translations as $translation) {
$line = [$translation->getContext(), $translation->getOriginal(), $translation->getTranslation()];
if ($translation->hasPluralTranslations(true)) {
$line = array_merge($line, $translation->getPluralTranslations());
}
fputcsv($handle, $line);
}
rewind($handle);
$csv = stream_get_contents($handle);
fclose($handle);
return $csv;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
use Gettext\Utils\DictionaryTrait;
class CsvDictionary extends Generator implements GeneratorInterface
{
use DictionaryTrait;
public static $options = [
'includeHeaders' => false,
];
/**
* {@parentDoc}.
*/
public static function toString(Translations $translations, array $options = [])
{
$options += static::$options;
$handle = fopen('php://memory', 'w');
foreach (self::toArray($translations, $options['includeHeaders']) as $original => $translation) {
fputcsv($handle, [$original, $translation]);
}
rewind($handle);
$csv = stream_get_contents($handle);
fclose($handle);
return $csv;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
abstract class Generator implements GeneratorInterface
{
/**
* {@inheritdoc}
*/
public static function toFile(Translations $translations, $file, array $options = [])
{
$content = static::toString($translations);
if (file_put_contents($file, $content) === false) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
interface GeneratorInterface
{
/**
* Saves the translations in a file.
*
* @param Translations $translations
* @param string $file
* @param array $options
*
* @return bool
*/
public static function toFile(Translations $translations, $file, array $options = []);
/**
* Generates a string with the translations ready to save in a file.
*
* @param Translations $translations
* @param array $options
*
* @return string
*/
public static function toString(Translations $translations, array $options = []);
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
class Jed extends Generator implements GeneratorInterface
{
public static $options = [
'json' => 0,
];
/**
* {@parentDoc}.
*/
public static function toString(Translations $translations, array $options = [])
{
$domain = $translations->getDomain() ?: 'messages';
$options += static::$options;
return json_encode([
$domain => [
'' => [
'domain' => $domain,
'lang' => $translations->getLanguage() ?: 'en',
'plural-forms' => $translations->getHeader('Plural-Forms') ?: 'nplurals=2; plural=(n != 1);',
],
] + self::buildMessages($translations),
], $options['json']);
}
/**
* Generates an array with all translations.
*
* @param Translations $translations
*
* @return array
*/
private static function buildMessages(Translations $translations)
{
$pluralForm = $translations->getPluralForms();
$pluralLimit = is_array($pluralForm) ? ($pluralForm[0] - 1) : null;
$messages = [];
$context_glue = '\u0004';
foreach ($translations as $translation) {
$key = ($translation->hasContext() ? $translation->getContext().$context_glue : '').$translation->getOriginal();
if ($translation->hasPluralTranslations(true)) {
$message = $translation->getPluralTranslations($pluralLimit);
array_unshift($message, $translation->getTranslation());
} else {
$message = [$translation->getTranslation()];
}
$messages[$key] = $message;
}
return $messages;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
use Gettext\Utils\MultidimensionalArrayTrait;
class Json extends Generator implements GeneratorInterface
{
use MultidimensionalArrayTrait;
public static $options = [
'json' => 0,
'includeHeaders' => false,
];
/**
* {@inheritdoc}
*/
public static function toString(Translations $translations, array $options = [])
{
$options += static::$options;
return json_encode(self::toArray($translations, $options['includeHeaders'], true), $options['json']);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
use Gettext\Utils\DictionaryTrait;
class JsonDictionary extends Generator implements GeneratorInterface
{
use DictionaryTrait;
public static $options = [
'json' => 0,
'includeHeaders' => false,
];
/**
* {@parentDoc}.
*/
public static function toString(Translations $translations, array $options = [])
{
$options += static::$options;
return json_encode(self::toArray($translations, $options['includeHeaders']), $options['json']);
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
use Gettext\Utils\HeadersGeneratorTrait;
class Mo extends Generator implements GeneratorInterface
{
use HeadersGeneratorTrait;
public static $options = [
'includeHeaders' => true,
];
/**
* {@parentDoc}.
*/
public static function toString(Translations $translations, array $options = [])
{
$options += static::$options;
$messages = [];
if ($options['includeHeaders']) {
$messages[''] = self::generateHeaders($translations);
}
foreach ($translations as $translation) {
if (!$translation->hasTranslation()) {
continue;
}
if ($translation->hasContext()) {
$originalString = $translation->getContext()."\x04".$translation->getOriginal();
} else {
$originalString = $translation->getOriginal();
}
$messages[$originalString] = $translation;
}
ksort($messages);
$numEntries = count($messages);
$originalsTable = '';
$translationsTable = '';
$originalsIndex = [];
$translationsIndex = [];
$pluralForm = $translations->getPluralForms();
$pluralLimit = is_array($pluralForm) ? ($pluralForm[0] - 1) : null;
foreach ($messages as $originalString => $translation) {
if (is_string($translation)) {
// Headers
$translationString = $translation;
} else {
/* @var $translation \Gettext\Translation */
if ($translation->hasPlural() && $translation->hasPluralTranslations(true)) {
$originalString .= "\x00".$translation->getPlural();
$translationString = $translation->getTranslation();
$translationString .= "\x00".implode("\x00", $translation->getPluralTranslations($pluralLimit));
} else {
$translationString = $translation->getTranslation();
}
}
$originalsIndex[] = ['relativeOffset' => strlen($originalsTable), 'length' => strlen($originalString)];
$originalsTable .= $originalString."\x00";
$translationsIndex[] = ['relativeOffset' => strlen($translationsTable), 'length' => strlen($translationString)];
$translationsTable .= $translationString."\x00";
}
// Offset of table with the original strings index: right after the header (which is 7 words)
$originalsIndexOffset = 7 * 4;
// Size of table with the original strings index
$originalsIndexSize = $numEntries * (4 + 4);
// Offset of table with the translation strings index: right after the original strings index table
$translationsIndexOffset = $originalsIndexOffset + $originalsIndexSize;
// Size of table with the translation strings index
$translationsIndexSize = $numEntries * (4 + 4);
// Hashing table starts after the header and after the index table
$originalsStringsOffset = $translationsIndexOffset + $translationsIndexSize;
// Translations start after the keys
$translationsStringsOffset = $originalsStringsOffset + strlen($originalsTable);
// Let's generate the .mo file binary data
$mo = '';
// Magic number
$mo .= pack('L', 0x950412de);
// File format revision
$mo .= pack('L', 0);
// Number of strings
$mo .= pack('L', $numEntries);
// Offset of table with original strings
$mo .= pack('L', $originalsIndexOffset);
// Offset of table with translation strings
$mo .= pack('L', $translationsIndexOffset);
// Size of hashing table: we don't use it.
$mo .= pack('L', 0);
// Offset of hashing table: it would start right after the translations index table
$mo .= pack('L', $translationsIndexOffset + $translationsIndexSize);
// Write the lengths & offsets of the original strings
foreach ($originalsIndex as $info) {
$mo .= pack('L', $info['length']);
$mo .= pack('L', $originalsStringsOffset + $info['relativeOffset']);
}
// Write the lengths & offsets of the translated strings
foreach ($translationsIndex as $info) {
$mo .= pack('L', $info['length']);
$mo .= pack('L', $translationsStringsOffset + $info['relativeOffset']);
}
// Write original strings
$mo .= $originalsTable;
// Write translation strings
$mo .= $translationsTable;
return $mo;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
use Gettext\Utils\MultidimensionalArrayTrait;
class PhpArray extends Generator implements GeneratorInterface
{
use MultidimensionalArrayTrait;
public static $options = [
'includeHeaders' => true,
];
/**
* {@inheritdoc}
*/
public static function toString(Translations $translations, array $options = [])
{
$array = self::generate($translations, $options);
return '<?php return '.var_export($array, true).';';
}
/**
* Generates an array with the translations.
*
* @param Translations $translations
* @param array $options
*
* @return array
*/
public static function generate(Translations $translations, array $options = [])
{
$options += static::$options;
return self::toArray($translations, $options['includeHeaders'], true);
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
class Po extends Generator implements GeneratorInterface
{
/**
* {@parentDoc}.
*/
public static function toString(Translations $translations, array $options = [])
{
$lines = ['msgid ""', 'msgstr ""'];
foreach ($translations->getHeaders() as $name => $value) {
$lines[] = sprintf('"%s: %s\\n"', $name, $value);
}
$lines[] = '';
//Translations
foreach ($translations as $translation) {
if ($translation->hasComments()) {
foreach ($translation->getComments() as $comment) {
$lines[] = '# '.$comment;
}
}
if ($translation->hasExtractedComments()) {
foreach ($translation->getExtractedComments() as $comment) {
$lines[] = '#. '.$comment;
}
}
if ($translation->hasReferences()) {
foreach ($translation->getReferences() as $reference) {
$lines[] = '#: '.$reference[0].(!is_null($reference[1]) ? ':'.$reference[1] : null);
}
}
if ($translation->hasFlags()) {
$lines[] = '#, '.implode(',', $translation->getFlags());
}
if ($translation->hasContext()) {
$lines[] = 'msgctxt '.self::convertString($translation->getContext());
}
self::addLines($lines, 'msgid', $translation->getOriginal());
if ($translation->hasPlural()) {
self::addLines($lines, 'msgid_plural', $translation->getPlural());
self::addLines($lines, 'msgstr[0]', $translation->getTranslation());
foreach ($translation->getPluralTranslations() as $k => $v) {
self::addLines($lines, 'msgstr['.($k + 1).']', $v);
}
} else {
self::addLines($lines, 'msgstr', $translation->getTranslation());
}
$lines[] = '';
}
return implode("\n", $lines);
}
/**
* Escapes and adds double quotes to a string.
*
* @param string $string
*
* @return string
*/
private static function multilineQuote($string)
{
$lines = explode("\n", $string);
$last = count($lines) - 1;
foreach ($lines as $k => $line) {
if ($k === $last) {
$lines[$k] = self::convertString($line);
} else {
$lines[$k] = self::convertString($line."\n");
}
}
return $lines;
}
/**
* Add one or more lines depending whether the string is multiline or not.
*
* @param array &$lines
* @param string $name
* @param string $value
*/
private static function addLines(array &$lines, $name, $value)
{
$newLines = self::multilineQuote($value);
if (count($newLines) === 1) {
$lines[] = $name.' '.$newLines[0];
} else {
$lines[] = $name.' ""';
foreach ($newLines as $line) {
$lines[] = $line;
}
}
}
/**
* Convert a string to its PO representation.
*
* @param string $value
*
* @return string
*/
public static function convertString($value)
{
return '"'.strtr(
$value,
[
"\x00" => '',
'\\' => '\\\\',
"\t" => '\t',
"\n" => '\n',
'"' => '\\"',
]
).'"';
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
use DOMDocument;
class Xliff extends Generator implements GeneratorInterface
{
/**
* {@inheritdoc}
*/
public static function toString(Translations $translations, array $options = [])
{
$dom = new DOMDocument('1.0', 'utf-8');
$dom->formatOutput = true;
$xliff = $dom->appendChild($dom->createElement('xliff'));
$xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:2.0');
$xliff->setAttribute('version', '2.0');
$xliff->setAttribute('srcLang', $translations->getLanguage());
$xliff->setAttribute('trgLang', $translations->getLanguage());
$file = $xliff->appendChild($dom->createElement('file'));
$file->setAttribute('id', $translations->getDomain().'.'.$translations->getLanguage());
//Save headers as notes
$notes = $dom->createElement('notes');
foreach ($translations->getHeaders() as $name => $value) {
$notes->appendChild(self::createTextNode($dom, 'note', $value))->setAttribute('id', $name);
}
if ($notes->hasChildNodes()) {
$file->appendChild($notes);
}
foreach ($translations as $translation) {
$unit = $dom->createElement('unit');
$unit->setAttribute('id', md5($translation->getContext().$translation->getOriginal()));
//Save comments as notes
$notes = $dom->createElement('notes');
$notes->appendChild(self::createTextNode($dom, 'note', $translation->getContext()))->setAttribute('category', 'context');
foreach ($translation->getComments() as $comment) {
$notes->appendChild(self::createTextNode($dom, 'note', $comment))->setAttribute('category', 'comment');
}
foreach ($translation->getExtractedComments() as $comment) {
$notes->appendChild(self::createTextNode($dom, 'note', $comment))->setAttribute('category', 'extracted-comment');
}
foreach ($translation->getFlags() as $flag) {
$notes->appendChild(self::createTextNode($dom, 'note', $flag))->setAttribute('category', 'flag');
}
foreach ($translation->getReferences() as $reference) {
$notes->appendChild(self::createTextNode($dom, 'note', $reference[0].':'.$reference[1]))->setAttribute('category', 'reference');
}
$unit->appendChild($notes);
$segment = $unit->appendChild($dom->createElement('segment'));
$segment->appendChild(self::createTextNode($dom, 'source', $translation->getOriginal()));
$segment->appendChild(self::createTextNode($dom, 'target', $translation->getTranslation()));
foreach ($translation->getPluralTranslations() as $plural) {
if ($plural !== '') {
$segment->appendChild(self::createTextNode($dom, 'target', $plural));
}
}
$file->appendChild($unit);
}
return $dom->saveXML();
}
private static function createTextNode(DOMDocument $dom, $name, $string)
{
$node = $dom->createElement($name);
$text = (preg_match('/[&<>]/', $string) === 1) ? $dom->createCDATASection($string) : $dom->createTextNode($string);
$node->appendChild($text);
return $node;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
use Gettext\Utils\MultidimensionalArrayTrait;
use Symfony\Component\Yaml\Yaml as YamlDumper;
class Yaml extends Generator implements GeneratorInterface
{
use MultidimensionalArrayTrait;
public static $options = [
'includeHeaders' => false,
'indent' => 2,
'inline' => 4,
];
/**
* {@inheritdoc}
*/
public static function toString(Translations $translations, array $options = [])
{
$options += static::$options;
return YamlDumper::dump(self::toArray($translations, $options['includeHeaders']), $options['inline'], $options['indent']);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
use Gettext\Utils\DictionaryTrait;
use Symfony\Component\Yaml\Yaml as YamlDumper;
class YamlDictionary extends Generator implements GeneratorInterface
{
use DictionaryTrait;
public static $options = [
'includeHeaders' => false,
'indent' => 2,
'inline' => 3,
];
/**
* {@inheritdoc}
*/
public static function toString(Translations $translations, array $options = [])
{
$options += static::$options;
return YamlDumper::dump(self::toArray($translations, $options['includeHeaders']), $options['inline'], $options['indent']);
}
}

View File

@@ -0,0 +1,161 @@
<?php
namespace Gettext;
class GettextTranslator extends BaseTranslator implements TranslatorInterface
{
/**
* Constructor. Detects the current language using the environment variables.
*
* @param string $language
*/
public function __construct($language = null)
{
if (!function_exists('gettext')) {
throw new \RuntimeException('This class require the gettext extension for PHP');
}
//detects the language environment respecting the priority order
//http://php.net/manual/en/function.gettext.php#114062
if (empty($language)) {
$language = getenv('LANGUAGE') ?: getenv('LC_ALL') ?: getenv('LC_MESSAGES') ?: getenv('LANG');
}
if (!empty($language)) {
$this->setLanguage($language);
}
}
/**
* Define the current locale.
*
* @param string $language
* @param int|null $category
*
* @return self
*/
public function setLanguage($language, $category = null)
{
if ($category === null) {
$category = defined('LC_MESSAGES') ? LC_MESSAGES : LC_ALL;
}
setlocale($category, $language);
putenv('LANGUAGE='.$language);
return $this;
}
/**
* Loads a gettext domain.
*
* @param string $domain
* @param string $path
* @param bool $default
*
* @return self
*/
public function loadDomain($domain, $path = null, $default = true)
{
bindtextdomain($domain, $path);
bind_textdomain_codeset($domain, 'UTF-8');
if ($default) {
textdomain($domain);
}
return $this;
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function gettext($original)
{
return gettext($original);
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function ngettext($original, $plural, $value)
{
return ngettext($original, $plural, $value);
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function dngettext($domain, $original, $plural, $value)
{
return dngettext($domain, $original, $plural, $value);
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function npgettext($context, $original, $plural, $value)
{
$message = $context."\x04".$original;
$translation = ngettext($message, $plural, $value);
return ($translation === $message) ? $original : $translation;
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function pgettext($context, $original)
{
$message = $context."\x04".$original;
$translation = gettext($message);
return ($translation === $message) ? $original : $translation;
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function dgettext($domain, $original)
{
return dgettext($domain, $original);
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function dpgettext($domain, $context, $original)
{
$message = $context."\x04".$original;
$translation = dgettext($domain, $message);
return ($translation === $message) ? $original : $translation;
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function dnpgettext($domain, $context, $original, $plural, $value)
{
$message = $context."\x04".$original;
$translation = dngettext($domain, $message, $plural, $value);
return ($translation === $message) ? $original : $translation;
}
}

View File

@@ -0,0 +1,221 @@
<?php
namespace Gettext;
/**
* Static class with merge contants.
*/
class Merge
{
const ADD = 1;
const REMOVE = 2;
const HEADERS_ADD = 4;
const HEADERS_REMOVE = 8;
const HEADERS_OVERRIDE = 16;
const LANGUAGE_OVERRIDE = 32;
const DOMAIN_OVERRIDE = 64;
const TRANSLATION_OVERRIDE = 128;
const COMMENTS_OURS = 256;
const COMMENTS_THEIRS = 512;
const EXTRACTED_COMMENTS_OURS = 1024;
const EXTRACTED_COMMENTS_THEIRS = 2048;
const FLAGS_OURS = 4096;
const FLAGS_THEIRS = 8192;
const REFERENCES_OURS = 16384;
const REFERENCES_THEIRS = 32768;
const DEFAULTS = 5; //1 + 4
/**
* Merge the flags of two translations.
*
* @param Translation $from
* @param Translation $to
* @param int $options
*/
public static function mergeFlags(Translation $from, Translation $to, $options = self::DEFAULTS)
{
if ($options & self::FLAGS_THEIRS) {
$to->deleteFlags();
}
if (!($options & self::FLAGS_OURS)) {
foreach ($from->getFlags() as $flag) {
$to->addFlag($flag);
}
}
}
/**
* Merge the extracted comments of two translations.
*
* @param Translation $from
* @param Translation $to
* @param int $options
*/
public static function mergeExtractedComments(Translation $from, Translation $to, $options = self::DEFAULTS)
{
if ($options & self::EXTRACTED_COMMENTS_THEIRS) {
$to->deleteExtractedComments();
}
if (!($options & self::EXTRACTED_COMMENTS_OURS)) {
foreach ($from->getExtractedComments() as $comment) {
$to->addExtractedComment($comment);
}
}
}
/**
* Merge the comments of two translations.
*
* @param Translation $from
* @param Translation $to
* @param int $options
*/
public static function mergeComments(Translation $from, Translation $to, $options = self::DEFAULTS)
{
if ($options & self::COMMENTS_THEIRS) {
$to->deleteComments();
}
if (!($options & self::COMMENTS_OURS)) {
foreach ($from->getComments() as $comment) {
$to->addComment($comment);
}
}
}
/**
* Merge the references of two translations.
*
* @param Translation $from
* @param Translation $to
* @param int $options
*/
public static function mergeReferences(Translation $from, Translation $to, $options = self::DEFAULTS)
{
if ($options & self::REFERENCES_THEIRS) {
$to->deleteReferences();
}
if (!($options & self::REFERENCES_OURS)) {
foreach ($from->getReferences() as $reference) {
$to->addReference($reference[0], $reference[1]);
}
}
}
/**
* Merge the translations of two translations.
*
* @param Translation $from
* @param Translation $to
* @param int $options
*/
public static function mergeTranslation(Translation $from, Translation $to, $options = self::DEFAULTS)
{
$override = (boolean) ($options & self::TRANSLATION_OVERRIDE);
if (!$to->hasTranslation() || ($from->hasTranslation() && $override)) {
$to->setTranslation($from->getTranslation());
}
if (!$to->hasPlural() || ($from->hasPlural() && $override)) {
$to->setPlural($from->getPlural());
}
if (!$to->hasPluralTranslations() || ($from->hasPluralTranslations() && $override)) {
$to->setPluralTranslations($from->getPluralTranslations());
}
}
/**
* Merge the translations of two translations.
*
* @param Translations $from
* @param Translations $to
* @param int $options
*/
public static function mergeTranslations(Translations $from, Translations $to, $options = self::DEFAULTS)
{
if ($options & self::REMOVE) {
$filtered = [];
foreach ($to as $entry) {
if ($from->find($entry)) {
$filtered[$entry->getId()] = $entry;
}
}
$to->exchangeArray($filtered);
}
foreach ($from as $entry) {
if (($existing = $to->find($entry))) {
$existing->mergeWith($entry);
} elseif ($options & self::ADD) {
$to[] = $entry;
}
}
}
/**
* Merge the headers of two translations.
*
* @param Translations $from
* @param Translations $to
* @param int $options
*/
public static function mergeHeaders(Translations $from, Translations $to, $options = self::DEFAULTS)
{
if ($options & self::HEADERS_REMOVE) {
foreach (array_keys($to->getHeaders()) as $name) {
if ($from->getHeader($name) === null) {
$to->deleteHeader($name);
}
}
}
foreach ($from->getHeaders() as $name => $value) {
$current = $to->getHeader($name);
if (empty($current)) {
if ($options & self::HEADERS_ADD) {
$to->setHeader($name, $value);
}
continue;
}
if (empty($value)) {
continue;
}
switch ($name) {
case Translations::HEADER_LANGUAGE:
case Translations::HEADER_PLURAL:
if ($options & self::LANGUAGE_OVERRIDE) {
$to->setHeader($name, $value);
}
break;
case Translations::HEADER_DOMAIN:
if ($options & self::DOMAIN_OVERRIDE) {
$to->setHeader($name, $value);
}
break;
default:
if ($options & self::HEADERS_OVERRIDE) {
$to->setHeader($name, $value);
}
}
}
}
}

View File

@@ -0,0 +1,480 @@
<?php
namespace Gettext;
/**
* Class to manage a translation string.
*/
class Translation
{
protected $context;
protected $original;
protected $translation = '';
protected $plural;
protected $pluralTranslation = [];
protected $references = [];
protected $comments = [];
protected $extractedComments = [];
protected $flags = [];
/**
* Generates the id of a translation (context + glue + original).
*
* @param string $context
* @param string $original
*
* @return string
*/
public static function generateId($context, $original)
{
return "{$context}\004{$original}";
}
/**
* Construct.
*
* @param string $context The context of the translation
* @param string $original The original string
* @param string $plural The original plural string
*/
public function __construct($context, $original, $plural = '')
{
$this->context = (string) $context;
$this->original = (string) $original;
$this->setPlural($plural);
}
/**
* Clones this translation.
*
* @param null|string $context Optional new context
* @param null|string $original Optional new original
*
* @return Translation
*/
public function getClone($context = null, $original = null)
{
$new = clone $this;
if ($context !== null) {
$new->context = (string) $context;
}
if ($original !== null) {
$new->original = (string) $original;
}
return $new;
}
/**
* Returns the id of this translation.
*
* @return string
*/
public function getId()
{
return static::generateId($this->context, $this->original);
}
/**
* Checks whether the translation matches with the arguments.
*
* @param string $context
* @param string $original
*
* @return bool
*/
public function is($context, $original = '')
{
return (($this->context === $context) && ($this->original === $original)) ? true : false;
}
/**
* Gets the original string.
*
* @return string
*/
public function getOriginal()
{
return $this->original;
}
/**
* Checks if the original string is empty or not.
*
* @return bool
*/
public function hasOriginal()
{
return ($this->original !== '') ? true : false;
}
/**
* Sets the translation string.
*
* @param string $translation
*
* @return self
*/
public function setTranslation($translation)
{
$this->translation = (string) $translation;
return $this;
}
/**
* Gets the translation string.
*
* @return string
*/
public function getTranslation()
{
return $this->translation;
}
/**
* Checks if the translation string is empty or not.
*
* @return bool
*/
public function hasTranslation()
{
return ($this->translation !== '') ? true : false;
}
/**
* Sets the plural translation string.
*
* @param string $plural
*
* @return self
*/
public function setPlural($plural)
{
$this->plural = (string) $plural;
return $this;
}
/**
* Gets the plural translation string.
*
* @return string
*/
public function getPlural()
{
return $this->plural;
}
/**
* Checks if the plural translation string is empty or not.
*
* @return bool
*/
public function hasPlural()
{
return ($this->plural !== '') ? true : false;
}
/**
* Set a new plural translation.
*
* @param array $plural
*
* @return self
*/
public function setPluralTranslations(array $plural)
{
$this->pluralTranslation = $plural;
return $this;
}
/**
* Gets all plural translations.
*
* @param int $limit
*
* @return array
*/
public function getPluralTranslations($limit = null)
{
if ($limit === null) {
return $this->pluralTranslation;
}
$current = count($this->pluralTranslation);
if ($limit > $current) {
return $this->pluralTranslation + array_fill(0, $limit, '');
}
if ($limit < $current) {
return array_slice($this->pluralTranslation, 0, $limit);
}
return $this->pluralTranslation;
}
/**
* Checks if there are any plural translation.
*
* @param bool $checkContent
*
* @return bool
*/
public function hasPluralTranslations($checkContent = false)
{
if ($checkContent) {
return implode('', $this->pluralTranslation) !== '';
}
return !empty($this->pluralTranslation);
}
/**
* Removes all plural translations.
*
* @return self
*/
public function deletePluralTranslation()
{
$this->pluralTranslation = [];
return $this;
}
/**
* Gets the context of this translation.
*
* @return string
*/
public function getContext()
{
return $this->context;
}
/**
* Checks if the context is empty or not.
*
* @return bool
*/
public function hasContext()
{
return (isset($this->context) && ($this->context !== '')) ? true : false;
}
/**
* Adds a new reference for this translation.
*
* @param string $filename The file path where the translation has been found
* @param null|int $line The line number where the translation has been found
*
* @return self
*/
public function addReference($filename, $line = null)
{
$key = "{$filename}:{$line}";
$this->references[$key] = [$filename, $line];
return $this;
}
/**
* Checks if the translation has any reference.
*
* @return bool
*/
public function hasReferences()
{
return !empty($this->references);
}
/**
* Return all references for this translation.
*
* @return array
*/
public function getReferences()
{
return array_values($this->references);
}
/**
* Removes all references.
*
* @return self
*/
public function deleteReferences()
{
$this->references = [];
return $this;
}
/**
* Adds a new comment for this translation.
*
* @param string $comment
*
* @return self
*/
public function addComment($comment)
{
if (!in_array($comment, $this->comments, true)) {
$this->comments[] = $comment;
}
return $this;
}
/**
* Checks if the translation has any comment.
*
* @return bool
*/
public function hasComments()
{
return isset($this->comments[0]);
}
/**
* Returns all comments for this translation.
*
* @return array
*/
public function getComments()
{
return $this->comments;
}
/**
* Removes all comments.
*
* @return self
*/
public function deleteComments()
{
$this->comments = [];
return $this;
}
/**
* Adds a new extracted comment for this translation.
*
* @param string $comment
*
* @return self
*/
public function addExtractedComment($comment)
{
if (!in_array($comment, $this->extractedComments, true)) {
$this->extractedComments[] = $comment;
}
return $this;
}
/**
* Checks if the translation has any extracted comment.
*
* @return bool
*/
public function hasExtractedComments()
{
return isset($this->extractedComments[0]);
}
/**
* Returns all extracted comments for this translation.
*
* @return array
*/
public function getExtractedComments()
{
return $this->extractedComments;
}
/**
* Removes all extracted comments.
*
* @return self
*/
public function deleteExtractedComments()
{
$this->extractedComments = [];
return $this;
}
/**
* Adds a new flag for this translation.
*
* @param string $flag
*
* @return self
*/
public function addFlag($flag)
{
if (!in_array($flag, $this->flags, true)) {
$this->flags[] = $flag;
}
return $this;
}
/**
* Checks if the translation has any flag.
*
* @return bool
*/
public function hasFlags()
{
return isset($this->flags[0]);
}
/**
* Returns all extracted flags for this translation.
*
* @return array
*/
public function getFlags()
{
return $this->flags;
}
/**
* Removes all flags.
*
* @return self
*/
public function deleteFlags()
{
$this->flags = [];
return $this;
}
/**
* Merges this translation with other translation.
*
* @param Translation $translation The translation to merge with
* @param int $options
*
* @return self
*/
public function mergeWith(Translation $translation, $options = Merge::DEFAULTS)
{
Merge::mergeTranslation($translation, $this, $options);
Merge::mergeReferences($translation, $this, $options);
Merge::mergeComments($translation, $this, $options);
Merge::mergeExtractedComments($translation, $this, $options);
Merge::mergeFlags($translation, $this, $options);
return $this;
}
}

View File

@@ -0,0 +1,409 @@
<?php
namespace Gettext;
use Gettext\Languages\Language;
use BadMethodCallException;
use InvalidArgumentException;
/**
* Class to manage a collection of translations.
*
* @method addFromCsvFile(string $filename, array $options = [])
* @method addFromCsvString(array $options = [])
* @method toCsvFile(string $filename, array $options = [])
* @method toCsvString(array $options = [])
* @method addFromCsvDictionaryFile(string $filename, array $options = [])
* @method addFromCsvDictionaryString(array $options = [])
* @method toCsvDictionaryFile(string $filename, array $options = [])
* @method toCsvDictionaryString(array $options = [])
* @method addFromJedFile(string $filename, array $options = [])
* @method addFromJedString(array $options = [])
* @method toJedFile(string $filename, array $options = [])
* @method toJedString(array $options = [])
* @method addFromJsonFile(string $filename, array $options = [])
* @method addFromJsonString(array $options = [])
* @method toJsonFile(string $filename, array $options = [])
* @method toJsonString(array $options = [])
* @method addFromJsonDictionaryFile(string $filename, array $options = [])
* @method addFromJsonDictionaryString(array $options = [])
* @method toJsonDictionaryFile(string $filename, array $options = [])
* @method toJsonDictionaryString(array $options = [])
* @method addFromMoFile(string $filename, array $options = [])
* @method addFromMoString(array $options = [])
* @method toMoFile(string $filename, array $options = [])
* @method toMoString(array $options = [])
* @method addFromPhpArrayFile(string $filename, array $options = [])
* @method addFromPhpArrayString(array $options = [])
* @method toPhpArrayFile(string $filename, array $options = [])
* @method toPhpArrayString(array $options = [])
* @method addFromPoFile(string $filename, array $options = [])
* @method addFromPoString(array $options = [])
* @method toPoFile(string $filename, array $options = [])
* @method toPoString(array $options = [])
* @method addFromXliffFile(string $filename, array $options = [])
* @method addFromXliffString(array $options = [])
* @method toXliffFile(string $filename, array $options = [])
* @method toXliffString(array $options = [])
* @method addFromYamlFile(string $filename, array $options = [])
* @method addFromYamlString(array $options = [])
* @method toYamlFile(string $filename, array $options = [])
* @method toYamlString(array $options = [])
* @method addFromYamlDictionaryFile(string $filename, array $options = [])
* @method addFromYamlDictionaryString(array $options = [])
* @method toYamlDictionaryFile(string $filename, array $options = [])
* @method toYamlDictionaryString(array $options = [])
*/
class Translations extends \ArrayObject
{
const HEADER_LANGUAGE = 'Language';
const HEADER_PLURAL = 'Plural-Forms';
const HEADER_DOMAIN = 'X-Domain';
public static $options = [
'defaultHeaders' => [
'Project-Id-Version' => '',
'Report-Msgid-Bugs-To' => '',
'Last-Translator' => '',
'Language-Team' => '',
'MIME-Version' => '1.0',
'Content-Type' => 'text/plain; charset=UTF-8',
'Content-Transfer-Encoding' => '8bit',
],
'headersSorting' => false,
'defaultDateHeaders' => [
'POT-Creation-Date',
'PO-Revision-Date',
],
];
private $headers;
/**
* @see \ArrayObject::__construct()
*/
public function __construct($input = [], $flags = 0, $iterator_class = 'ArrayIterator')
{
$this->headers = static::$options['defaultHeaders'];
foreach (static::$options['defaultDateHeaders'] as $header) {
$this->headers[$header] = date('c');
}
$this->headers[self::HEADER_LANGUAGE] = '';
parent::__construct($input, $flags, $iterator_class);
}
/**
* Magic method to create new instances using extractors
* For example: Translations::fromMoFile($filename, $options);.
*
* @return Translations
*/
public static function __callStatic($name, $arguments)
{
if (!preg_match('/^from(\w+)(File|String)$/i', $name, $matches)) {
throw new BadMethodCallException("The method $name does not exists");
}
return call_user_func_array([new static(), 'add'.ucfirst($name)], $arguments);
}
/**
* Magic method to import/export the translations to a specific format
* For example: $translations->toMoFile($filename, $options);
* For example: $translations->addFromMoFile($filename, $options);.
*
* @return self|bool
*/
public function __call($name, $arguments)
{
if (!preg_match('/^(addFrom|to)(\w+)(File|String)$/i', $name, $matches)) {
throw new BadMethodCallException("The method $name does not exists");
}
if ($matches[1] === 'addFrom') {
$extractor = 'Gettext\\Extractors\\'.$matches[2].'::from'.$matches[3];
$source = array_shift($arguments);
$options = array_shift($arguments) ?: [];
call_user_func($extractor, $source, $this, $options);
return $this;
}
$generator = 'Gettext\\Generators\\'.$matches[2].'::to'.$matches[3];
array_unshift($arguments, $this);
return call_user_func_array($generator, $arguments);
}
/**
* Magic method to clone each translation on clone the translations object.
*/
public function __clone()
{
$array = [];
foreach ($this as $key => $translation) {
$array[$key] = clone $translation;
}
$this->exchangeArray($array);
}
/**
* Control the new translations added.
*
* @param mixed $index
* @param Translation $value
*
* @throws InvalidArgumentException If the value is not an instance of Gettext\Translation
*
* @return Translation
*/
public function offsetSet($index, $value)
{
if (!($value instanceof Translation)) {
throw new InvalidArgumentException('Only instances of Gettext\\Translation must be added to a Gettext\\Translations');
}
$id = $value->getId();
if ($this->offsetExists($id)) {
$this[$id]->mergeWith($value);
return $this[$id];
}
parent::offsetSet($id, $value);
return $value;
}
/**
* Set the plural definition.
*
* @param int $count
* @param string $rule
*
* @return self
*/
public function setPluralForms($count, $rule)
{
$this->setHeader(self::HEADER_PLURAL, "nplurals={$count}; plural={$rule};");
return $this;
}
/**
* Returns the parsed plural definition.
*
* @param null|array [count, rule]
*/
public function getPluralForms()
{
$header = $this->getHeader(self::HEADER_PLURAL);
if (!empty($header) && preg_match('/^nplurals\s*=\s*(\d+)\s*;\s*plural\s*=\s*([^;]+)\s*;$/', $header, $matches)) {
return [intval($matches[1]), $matches[2]];
}
}
/**
* Set a new header.
*
* @param string $name
* @param string $value
*
* @return self
*/
public function setHeader($name, $value)
{
$name = trim($name);
$this->headers[$name] = trim($value);
return $this;
}
/**
* Returns a header value.
*
* @param string $name
*
* @return null|string
*/
public function getHeader($name)
{
return isset($this->headers[$name]) ? $this->headers[$name] : null;
}
/**
* Returns all header for this translations (in alphabetic order).
*
* @return array
*/
public function getHeaders()
{
if (static::$options['headersSorting']) {
ksort($this->headers);
}
return $this->headers;
}
/**
* Removes all headers.
*
* @return self
*/
public function deleteHeaders()
{
$this->headers = [];
return $this;
}
/**
* Removes one header.
*
* @param string $name
*
* @return self
*/
public function deleteHeader($name)
{
unset($this->headers[$name]);
return $this;
}
/**
* Returns the language value.
*
* @return string $language
*/
public function getLanguage()
{
return $this->getHeader(self::HEADER_LANGUAGE);
}
/**
* Sets the language and the plural forms.
*
* @param string $language
*
* @throws InvalidArgumentException if the language hasn't been recognized
*
* @return self
*/
public function setLanguage($language)
{
$this->setHeader(self::HEADER_LANGUAGE, trim($language));
if (($info = Language::getById($language))) {
return $this->setPluralForms(count($info->categories), $info->formula);
}
throw new InvalidArgumentException(sprintf('The language "%s" is not valid', $language));
}
/**
* Checks whether the language is empty or not.
*
* @return bool
*/
public function hasLanguage()
{
$language = $this->getLanguage();
return (is_string($language) && ($language !== '')) ? true : false;
}
/**
* Set a new domain for this translations.
*
* @param string $domain
*
* @return self
*/
public function setDomain($domain)
{
$this->setHeader(self::HEADER_DOMAIN, trim($domain));
return $this;
}
/**
* Returns the domain.
*
* @return string
*/
public function getDomain()
{
return $this->getHeader(self::HEADER_DOMAIN);
}
/**
* Checks whether the domain is empty or not.
*
* @return bool
*/
public function hasDomain()
{
$domain = $this->getDomain();
return (is_string($domain) && ($domain !== '')) ? true : false;
}
/**
* Search for a specific translation.
*
* @param string|Translation $context The context of the translation or a translation instance
* @param string $original The original string
*
* @return Translation|false
*/
public function find($context, $original = '')
{
if ($context instanceof Translation) {
$id = $context->getId();
} else {
$id = Translation::generateId($context, $original);
}
return $this->offsetExists($id) ? $this[$id] : false;
}
/**
* Creates and insert/merges a new translation.
*
* @param string $context The translation context
* @param string $original The translation original string
* @param string $plural The translation original plural string
*
* @return Translation The translation created
*/
public function insert($context, $original, $plural = '')
{
return $this->offsetSet(null, new Translation($context, $original, $plural));
}
/**
* Merges this translations with other translations.
*
* @param Translations $translations The translations instance to merge with
* @param int $options
*
* @return self
*/
public function mergeWith(Translations $translations, $options = Merge::DEFAULTS)
{
Merge::mergeHeaders($translations, $this, $options);
Merge::mergeTranslations($translations, $this, $options);
return $this;
}
}

View File

@@ -0,0 +1,264 @@
<?php
namespace Gettext;
use Gettext\Generators\PhpArray;
class Translator extends BaseTranslator implements TranslatorInterface
{
private $domain;
private $dictionary = [];
private $plurals = [];
/**
* Loads translation from a Translations instance, a file on an array.
*
* @param Translations|string|array $translations
*
* @return self
*/
public function loadTranslations($translations)
{
if (is_object($translations) && $translations instanceof Translations) {
$translations = PhpArray::generate($translations);
} elseif (is_string($translations) && is_file($translations)) {
$translations = include $translations;
} elseif (!is_array($translations)) {
throw new \InvalidArgumentException('Invalid Translator: only arrays, files or instance of Translations are allowed');
}
$this->addTranslations($translations);
return $this;
}
/**
* Set the default domain.
*
* @param string $domain
*
* @return self
*/
public function defaultDomain($domain)
{
$this->domain = $domain;
return $this;
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function gettext($original)
{
return $this->dpgettext($this->domain, null, $original);
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function ngettext($original, $plural, $value)
{
return $this->dnpgettext($this->domain, null, $original, $plural, $value);
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function dngettext($domain, $original, $plural, $value)
{
return $this->dnpgettext($domain, null, $original, $plural, $value);
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function npgettext($context, $original, $plural, $value)
{
return $this->dnpgettext($this->domain, $context, $original, $plural, $value);
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function pgettext($context, $original)
{
return $this->dpgettext($this->domain, $context, $original);
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function dgettext($domain, $original)
{
return $this->dpgettext($domain, null, $original);
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function dpgettext($domain, $context, $original)
{
$translation = $this->getTranslation($domain, $context, $original);
if (isset($translation[0]) && $translation[0] !== '') {
return $translation[0];
}
return $original;
}
/**
* @see TranslatorInterface
*
* {@inheritdoc}
*/
public function dnpgettext($domain, $context, $original, $plural, $value)
{
$key = $this->getPluralIndex($domain, $value);
$translation = $this->getTranslation($domain, $context, $original);
if (isset($translation[$key]) && $translation[$key] !== '') {
return $translation[$key];
}
return ($key === 0) ? $original : $plural;
}
/**
* Set new translations to the dictionary.
*
* @param array $translations
*/
protected function addTranslations(array $translations)
{
$domain = isset($translations['domain']) ? $translations['domain'] : '';
//Set the first domain loaded as default domain
if ($this->domain === null) {
$this->domain = $domain;
}
if (isset($this->dictionary[$domain])) {
$this->dictionary[$domain] = array_replace_recursive($this->dictionary[$domain], $translations['messages']);
return;
}
if (!empty($translations['plural-forms'])) {
list($count, $code) = array_map('trim', explode(';', $translations['plural-forms'], 2));
// extract just the expression turn 'n' into a php variable '$n'.
// Slap on a return keyword and semicolon at the end.
$this->plurals[$domain] = [
'count' => (int) str_replace('nplurals=', '', $count),
'code' => str_replace('plural=', 'return ', str_replace('n', '$n', $code)).';',
];
}
$this->dictionary[$domain] = $translations['messages'];
}
/**
* Search and returns a translation.
*
* @param string $domain
* @param string $context
* @param string $original
*
* @return string|false
*/
protected function getTranslation($domain, $context, $original)
{
return isset($this->dictionary[$domain][$context][$original]) ? $this->dictionary[$domain][$context][$original] : false;
}
/**
* Executes the plural decision code given the number to decide which
* plural version to take.
*
* @param string $domain
* @param string $n
*
* @return int
*/
protected function getPluralIndex($domain, $n)
{
//Not loaded domain, use a fallback
if (!isset($this->plurals[$domain])) {
return $n == 1 ? 0 : 1;
}
if (!isset($this->plurals[$domain]['function'])) {
$this->plurals[$domain]['function'] = create_function('$n', self::fixTerseIfs($this->plurals[$domain]['code']));
}
if ($this->plurals[$domain]['count'] <= 2) {
return call_user_func($this->plurals[$domain]['function'], $n) ? 1 : 0;
}
return call_user_func($this->plurals[$domain]['function'], $n);
}
/**
* This function will recursively wrap failure states in brackets if they contain a nested terse if.
*
* This because PHP can not handle nested terse if's unless they are wrapped in brackets.
*
* This code probably only works for the gettext plural decision codes.
*
* return ($n==1 ? 0 : $n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2);
* becomes
* return ($n==1 ? 0 : ($n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2));
*
* @param string $code the terse if string
* @param bool $inner If inner is true we wrap it in brackets
*
* @return string A formatted terse If that PHP can work with.
*/
private static function fixTerseIfs($code, $inner = false)
{
/*
* (?P<expression>[^?]+) Capture everything up to ? as 'expression'
* \? ?
* (?P<success>[^:]+) Capture everything up to : as 'success'
* : :
* (?P<failure>[^;]+) Capture everything up to ; as 'failure'
*/
preg_match('/(?P<expression>[^?]+)\?(?P<success>[^:]+):(?P<failure>[^;]+)/', $code, $matches);
// If no match was found then no terse if was present
if (!isset($matches[0])) {
return $code;
}
$expression = $matches['expression'];
$success = $matches['success'];
$failure = $matches['failure'];
// Go look for another terse if in the failure state.
$failure = self::fixTerseIfs($failure, true);
$code = $expression.' ? '.$success.' : '.$failure;
if ($inner) {
return "($code)";
}
// note the semicolon. We need that for executing the code.
return "$code;";
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Gettext;
/**
* Interface used by all translators.
*/
interface TranslatorInterface
{
/**
* Register this translator as global, to use with the gettext functions __(), p__(), etc.
* Returns the previous translator if exists.
*
* @return TranslatorInterface|null
*/
public function register();
/**
* Gets a translation using the original string.
*
* @param string $original
*
* @return string
*/
public function gettext($original);
/**
* Gets a translation checking the plural form.
*
* @param string $original
* @param string $plural
* @param string $value
*
* @return string
*/
public function ngettext($original, $plural, $value);
/**
* Gets a translation checking the domain and the plural form.
*
* @param string $domain
* @param string $original
* @param string $plural
* @param string $value
*
* @return string
*/
public function dngettext($domain, $original, $plural, $value);
/**
* Gets a translation checking the context and the plural form.
*
* @param string $context
* @param string $original
* @param string $plural
* @param string $value
*
* @return string
*/
public function npgettext($context, $original, $plural, $value);
/**
* Gets a translation checking the context.
*
* @param string $context
* @param string $original
*
* @return string
*/
public function pgettext($context, $original);
/**
* Gets a translation checking the domain.
*
* @param string $domain
* @param string $original
*
* @return string
*/
public function dgettext($domain, $original);
/**
* Gets a translation checking the domain and context.
*
* @param string $domain
* @param string $context
* @param string $original
*
* @return string
*/
public function dpgettext($domain, $context, $original);
/**
* Gets a translation checking the domain, the context and the plural form.
*
* @param string $domain
* @param string $context
* @param string $original
* @param string $plural
* @param string $value
*/
public function dnpgettext($domain, $context, $original, $plural, $value);
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Gettext\Utils;
use Gettext\Translations;
/**
* Trait used by all generators that exports the translations to plain dictionary (original => singular-translation).
*/
trait DictionaryTrait
{
use HeadersGeneratorTrait;
use HeadersExtractorTrait;
/**
* Returns a plain dictionary with the format [original => translation].
*
* @param Translations $translations
* @param bool $includeHeaders
*
* @return array
*/
private static function toArray(Translations $translations, $includeHeaders)
{
$messages = [];
if ($includeHeaders) {
$messages[''] = self::generateHeaders($translations);
}
foreach ($translations as $translation) {
$messages[$translation->getOriginal()] = $translation->getTranslation();
}
return $messages;
}
/**
* Extract the entries from a dictionary.
*
* @param array $messages
* @param Translations $translations
*/
private static function fromArray(array $messages, Translations $translations)
{
foreach ($messages as $original => $translation) {
if ($original === '') {
self::extractHeaders($translation, $translations);
continue;
}
$translations->insert(null, $original)->setTranslation($translation);
}
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace Gettext\Utils;
use Exception;
use Gettext\Translations;
abstract class FunctionsScanner
{
/**
* Scan and returns the functions and the arguments.
*
* @return array
*/
abstract public function getFunctions();
/**
* Search for specific functions and create translations.
*
* @param Translations $translations The translations instance where save the values
* @param array $options The extractor options
*/
public function saveGettextFunctions(Translations $translations, array $options)
{
$functions = $options['functions'];
$file = $options['file'];
foreach ($this->getFunctions() as $function) {
list($name, $line, $args) = $function;
if (!isset($functions[$name])) {
continue;
}
$domain = $context = $original = $plural = null;
switch ($functions[$name]) {
case 'gettext':
if (!isset($args[0])) {
continue 2;
}
$original = $args[0];
break;
case 'ngettext':
if (!isset($args[1])) {
continue 2;
}
list($original, $plural) = $args;
break;
case 'pgettext':
if (!isset($args[1])) {
continue 2;
}
list($context, $original) = $args;
break;
case 'dgettext':
if (!isset($args[1])) {
continue 2;
}
list($domain, $original) = $args;
break;
case 'dpgettext':
if (!isset($args[2])) {
continue 2;
}
list($domain, $context, $original) = $args;
break;
case 'npgettext':
if (!isset($args[2])) {
continue 2;
}
list($context, $original, $plural) = $args;
break;
case 'dnpgettext':
if (!isset($args[4])) {
continue 2;
}
list($domain, $context, $original, $plural) = $args;
break;
default:
throw new Exception(sprintf('Not valid function %s', $functions[$name]));
}
if ((string) $original !== '' && ($domain === null || $domain === $translations->getDomain())) {
$translation = $translations->insert($context, $original, $plural);
$translation->addReference($file, $line);
if (isset($function[3])) {
foreach ($function[3] as $extractedComment) {
$translation->addExtractedComment($extractedComment);
}
}
}
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Gettext\Utils;
use Gettext\Translations;
/**
* Trait to provide the functionality of extracting headers.
*/
trait HeadersExtractorTrait
{
/**
* Add the headers found to the translations instance.
*
* @param string $headers
* @param Translations $translations
*
* @return array
*/
private static function extractHeaders($headers, Translations $translations)
{
$headers = explode("\n", $headers);
$currentHeader = null;
foreach ($headers as $line) {
$line = self::convertString($line);
if ($line === '') {
continue;
}
if (self::isHeaderDefinition($line)) {
$header = array_map('trim', explode(':', $line, 2));
$currentHeader = $header[0];
$translations->setHeader($currentHeader, $header[1]);
} else {
$entry = $translations->getHeader($currentHeader);
$translations->setHeader($currentHeader, $entry.$line);
}
}
}
/**
* Checks if it is a header definition line. Useful for distguishing between header definitions
* and possible continuations of a header entry.
*
* @param string $line Line to parse
*
* @return bool
*/
private static function isHeaderDefinition($line)
{
return (bool) preg_match('/^[\w-]+:/', $line);
}
/**
* Normalize a string.
*
* @param string $value
*
* @return string
*/
public static function convertString($value)
{
return $value;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Gettext\Utils;
use Gettext\Translations;
/**
* Trait to provide the functionality of extracting headers.
*/
trait HeadersGeneratorTrait
{
/**
* Returns the headers as a string.
*
* @param Translations $translations
*
* @return string
*/
private static function generateHeaders(Translations $translations)
{
$headers = '';
foreach ($translations->getHeaders() as $name => $value) {
$headers .= sprintf("%s: %s\n", $name, $value);
}
return $headers;
}
}

View File

@@ -0,0 +1,220 @@
<?php
namespace Gettext\Utils;
class JsFunctionsScanner extends FunctionsScanner
{
protected $code;
protected $status = [];
/**
* Constructor.
*
* @param string $code The php code to scan
*/
public function __construct($code)
{
$this->code = $code;
}
/**
* {@inheritdoc}
*/
public function getFunctions()
{
$length = strlen($this->code);
$line = 1;
$buffer = '';
$functions = [];
$bufferFunctions = [];
$char = null;
for ($pos = 0; $pos < $length; ++$pos) {
$prev = $char;
$char = $this->code[$pos];
$next = isset($this->code[$pos]) ? $this->code[$pos] : null;
switch ($char) {
case "\n":
++$line;
if ($this->status('line-comment')) {
$this->upStatus();
}
break;
case '/':
switch ($this->status()) {
case 'simple-quote':
case 'double-quote':
case 'line-comment':
break;
case 'block-comment':
if ($prev === '*') {
$this->upStatus();
}
break;
default:
if ($next === '/') {
$this->downStatus('line-comment');
} elseif ($next === '*') {
$this->downStatus('block-comment');
}
break;
}
break;
case "'":
switch ($this->status()) {
case 'simple-quote':
$this->upStatus();
break;
case 'line-comment':
case 'block-comment':
case 'double-quote':
break;
default:
$this->downStatus('simple-quote');
break;
}
break;
case '"':
switch ($this->status()) {
case 'double-quote':
$this->upStatus();
break;
case 'line-comment':
case 'block-comment':
case 'simple-quote':
break;
default:
$this->downStatus('double-quote');
break;
}
break;
case '(':
switch ($this->status()) {
case 'double-quote':
case 'line-comment':
case 'block-comment':
case 'line-comment':
break;
default:
if ($buffer && preg_match('/(\w+)$/', $buffer, $matches)) {
$this->downStatus('function');
array_unshift($bufferFunctions, [$matches[1], $line, []]);
$buffer = '';
continue 3;
}
break;
}
break;
case ')':
switch ($this->status()) {
case 'function':
if (($argument = self::prepareArgument($buffer))) {
$bufferFunctions[0][2][] = $argument;
}
if (!empty($bufferFunctions)) {
$functions[] = array_shift($bufferFunctions);
}
$buffer = '';
continue 3;
}
case ',':
switch ($this->status()) {
case 'function':
if (($argument = self::prepareArgument($buffer))) {
$bufferFunctions[0][2][] = $argument;
}
$buffer = '';
continue 3;
}
}
switch ($this->status()) {
case 'line-comment':
case 'block-comment':
break;
default:
$buffer .= $char;
break;
}
}
return $functions;
}
/**
* Get the current context of the scan.
*
* @param null|string $match To check whether the current status is this value
*
* @return string|bool
*/
protected function status($match = null)
{
$status = isset($this->status[0]) ? $this->status[0] : null;
if ($match !== null) {
return $status === $match;
}
return $status;
}
/**
* Add a new status to the stack.
*
* @param string $status
*/
protected function downStatus($status)
{
array_unshift($this->status, $status);
}
/**
* Removes and return the current status.
*
* @return string|null
*/
protected function upStatus()
{
return array_shift($this->status);
}
/**
* Prepares the arguments found in functions.
*
* @param string $argument
*
* @return string
*/
protected static function prepareArgument($argument)
{
if ($argument && ($argument[0] === '"' || $argument[0] === "'")) {
if ($argument[0] === '"') {
$argument = str_replace('\\"', '"', $argument);
} else {
$argument = str_replace("\\'", "'", $argument);
}
return substr($argument, 1, -1);
}
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace Gettext\Utils;
use Gettext\Translations;
/**
* Trait used by all generators that exports the translations to multidimensional arrays (context => [original => [translation, plural1, pluraln...]]).
*/
trait MultidimensionalArrayTrait
{
use HeadersGeneratorTrait;
use HeadersExtractorTrait;
/**
* Returns a multidimensional array.
*
* @param Translations $translations
* @param bool $includeHeaders
* @param bool $forceArray
*
* @return array
*/
private static function toArray(Translations $translations, $includeHeaders, $forceArray = false)
{
$pluralForm = $translations->getPluralForms();
$pluralLimit = is_array($pluralForm) ? ($pluralForm[0] - 1) : null;
$messages = [];
if ($includeHeaders) {
$messages[''] = [
'' => [self::generateHeaders($translations)],
];
}
foreach ($translations as $translation) {
$context = $translation->getContext();
$original = $translation->getOriginal();
if (!isset($messages[$context])) {
$messages[$context] = [];
}
if ($translation->hasPluralTranslations(true)) {
$messages[$context][$original] = $translation->getPluralTranslations($pluralLimit);
array_unshift($messages[$context][$original], $translation->getTranslation());
} elseif ($forceArray) {
$messages[$context][$original] = [$translation->getTranslation()];
} else {
$messages[$context][$original] = $translation->getTranslation();
}
}
return [
'domain' => $translations->getDomain(),
'plural-forms' => $translations->getHeader('Plural-Forms'),
'messages' => $messages,
];
}
/**
* Extract the entries from a multidimensional array.
*
* @param array $messages
* @param Translations $translations
*/
private static function fromArray(array $messages, Translations $translations)
{
if (!empty($messages['domain'])) {
$translations->setDomain($messages['domain']);
}
if (!empty($messages['plural-forms'])) {
$translations->setHeader(Translations::HEADER_PLURAL, $messages['plural-forms']);
}
foreach ($messages['messages'] as $context => $contextTranslations) {
foreach ($contextTranslations as $original => $value) {
if ($context === '' && $original === '') {
self::extractHeaders(is_array($value) ? array_shift($value) : $value, $translations);
continue;
}
$translation = $translations->insert($context, $original);
if (is_array($value)) {
$translation->setTranslation(array_shift($value));
$translation->setPluralTranslations($value);
} else {
$translation->setTranslation($value);
}
}
}
}
}

View File

@@ -0,0 +1,148 @@
<?php
namespace Gettext\Utils;
/**
* Function parsed by PhpFunctionsScanner.
*/
class ParsedFunction
{
/**
* The function name.
*
* @var string
*/
protected $name;
/**
* The line where the function starts.
*
* @var int
*/
protected $line;
/**
* The strings extracted from the function arguments.
*
* @var string[]
*/
protected $arguments;
/**
* The current index of the function (-1 if no arguments).
*
* @var int|null
*/
protected $argumentIndex;
/**
* Shall we stop adding string chunks to the current argument?
*
* @var bool
*/
protected $argumentStopped;
/**
* Extracted comments.
*
* @var string[]|null
*/
protected $comments;
/**
* Initializes the instance.
*
* @param string $name The function name.
* @param int $line The line where the function starts.
*/
public function __construct($name, $line)
{
$this->name = $name;
$this->line = $line;
$this->arguments = [];
$this->argumentIndex = -1;
$this->argumentStopped = false;
$this->comments = null;
}
/**
* Stop extracting strings from the current argument (because we found something that's not a string).
*/
public function stopArgument()
{
if ($this->argumentIndex === -1) {
$this->argumentIndex = 0;
}
$this->argumentStopped = true;
}
/**
* Go to the next argument because we a comma was found.
*/
public function nextArgument()
{
if ($this->argumentIndex === -1) {
// This should neve occur, but let's stay safe - During test/development an Exception should be thrown.
$this->argumentIndex = 1;
} else {
++$this->argumentIndex;
}
$this->argumentStopped = false;
}
/**
* Add a string to the current argument.
*
* @param string $chunk
*/
public function addArgumentChunk($chunk)
{
if ($this->argumentStopped === false) {
if ($this->argumentIndex === -1) {
$this->argumentIndex = 0;
}
if (isset($this->arguments[$this->argumentIndex])) {
$this->arguments[$this->argumentIndex] .= $chunk;
} else {
$this->arguments[$this->argumentIndex] = $chunk;
}
}
}
/**
* Add a comment associated to this function.
*
* @param string $comment
*/
public function addComment($comment)
{
if ($this->comments === null) {
$this->comments = [];
}
$this->comments[] = $comment;
}
/**
* A closing parenthesis was found: return the final data.
* The array returned has the following values:
* 0 => string The function name.
* 1 => int The line where the function starts.
* 2 => string[] the strings extracted from the function arguments.
* 3 => string[] the comments associated to the function.
*
* @return array
*/
public function close()
{
$arguments = [];
for ($i = 0; $i <= $this->argumentIndex; ++$i) {
$arguments[$i] = isset($this->arguments[$i]) ? $this->arguments[$i] : '';
}
return [
$this->name,
$this->line,
$arguments,
$this->comments,
];
}
}

View File

@@ -0,0 +1,160 @@
<?php
namespace Gettext\Utils;
use Gettext\Extractors\PhpCode;
class PhpFunctionsScanner extends FunctionsScanner
{
/**
* PHP tokens of the code to be parsed.
*
* @var array
*/
protected $tokens;
/**
* If not false, comments will be extracted.
*
* @var string|false
*/
protected $extractComments = false;
/**
* Enable extracting comments that start with a tag (if $tag is empty all the comments will be extracted).
*
* @param string $tag
*/
public function enableCommentsExtraction($tag = '')
{
$this->extractComments = (string) $tag;
}
/**
* Disable comments extraction.
*/
public function disableCommentsExtraction()
{
$this->extractComments = false;
}
/**
* Constructor.
*
* @param string $code The php code to scan
*/
public function __construct($code)
{
$this->tokens = array_values(
array_filter(
token_get_all($code),
function ($token) {
return !is_array($token) || $token[0] !== T_WHITESPACE;
}
)
);
}
/**
* {@inheritdoc}
*/
public function getFunctions()
{
$count = count($this->tokens);
$bufferFunctions = [];
/* @var ParsedFunction[] $bufferFunctions */
$functions = [];
/* @var ParsedFunction[] $functions */
for ($k = 0; $k < $count; ++$k) {
$value = $this->tokens[$k];
if (is_string($value)) {
if (isset($bufferFunctions[0])) {
switch ($value) {
case ',':
$bufferFunctions[0]->nextArgument();
break;
case ')':
$functions[] = array_shift($bufferFunctions)->close();
break;
case '.':
break;
default:
$bufferFunctions[0]->stopArgument();
break;
}
}
continue;
}
switch ($value[0]) {
case T_CONSTANT_ENCAPSED_STRING:
//add an argument to the current function
if (isset($bufferFunctions[0])) {
$bufferFunctions[0]->addArgumentChunk(PhpCode::convertString($value[1]));
}
break;
case T_STRING:
if (isset($bufferFunctions[0])) {
$bufferFunctions[0]->stopArgument();
}
//new function found
for ($j = $k + 1; $j < $count; ++$j) {
$nextToken = $this->tokens[$j];
if (is_array($nextToken) && $nextToken[0] === T_COMMENT) {
continue;
}
if ($nextToken === '(') {
$newFunction = new ParsedFunction($value[1], $value[2]);
if ($k > 0 && is_array($this->tokens[$k - 1]) && $this->tokens[$k - 1][0] === T_COMMENT) {
$comment = $this->parsePhpComment($this->tokens[$k - 1][1]);
if ($comment !== null) {
$newFunction->addComment($comment);
}
}
array_unshift($bufferFunctions, $newFunction);
$k = $j;
}
break;
}
break;
case T_COMMENT:
if (isset($bufferFunctions[0])) {
$comment = $this->parsePhpComment($value[1]);
if ($comment !== null) {
$bufferFunctions[0]->addComment($comment);
}
}
break;
default:
if (isset($bufferFunctions[0])) {
$bufferFunctions[0]->stopArgument();
}
break;
}
}
return $functions;
}
protected function parsePhpComment($value)
{
$result = null;
if ($this->extractComments !== false) {
if ($value[0] === '#') {
$value = substr($value, 1);
} elseif ($value[1] === '/') {
$value = substr($value, 2);
} else {
$value = substr($value, 2, -2);
}
$value = trim($value);
if ($value !== '' && ($this->extractComments === '' || strpos($value, $this->extractComments) === 0)) {
$result = $value;
}
}
return $result;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Gettext\Utils;
class StringReader
{
public $pos;
public $str;
public $strlen;
/**
* Constructor.
*
* @param string $str The string to read
*/
public function __construct($str)
{
$this->str = $str;
$this->strlen = strlen($this->str);
}
/**
* Read and returns a part of the string.
*
* @param int $bytes The number of bytes to read
*
* @return string
*/
public function read($bytes)
{
$data = substr($this->str, $this->pos, $bytes);
$this->seekto($this->pos + $bytes);
return $data;
}
/**
* Move the cursor to a specific position.
*
* @param int $pos The amount of bytes to move
*
* @return int The new position
*/
public function seekto($pos)
{
$this->pos = ($this->strlen < $pos) ? $this->strlen : $pos;
return $this->pos;
}
}

View File

@@ -0,0 +1,13 @@
<?php
spl_autoload_register(function ($class) {
if (strpos($class, 'Gettext\\') !== 0) {
return;
}
$file = __DIR__.str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen('Gettext'))).'.php';
if (is_file($file)) {
require_once $file;
}
});

View File

@@ -0,0 +1,156 @@
<?php
use Gettext\BaseTranslator;
/**
* Returns the translation of a string.
*
* @param string $original
*
* @return string
*/
function __($original)
{
$text = BaseTranslator::$current->gettext($original);
if (func_num_args() === 1) {
return $text;
}
$args = array_slice(func_get_args(), 1);
return vsprintf($text, is_array($args[0]) ? $args[0] : $args);
}
/**
* Returns the singular/plural translation of a string.
*
* @param string $original
* @param string $plural
* @param string $value
*
* @return string
*/
function n__($original, $plural, $value)
{
$text = BaseTranslator::$current->ngettext($original, $plural, $value);
if (func_num_args() === 3) {
return $text;
}
$args = array_slice(func_get_args(), 3);
return vsprintf($text, is_array($args[0]) ? $args[0] : $args);
}
/**
* Returns the translation of a string in a specific context.
*
* @param string $context
* @param string $original
*
* @return string
*/
function p__($context, $original)
{
$text = BaseTranslator::$current->pgettext($context, $original);
if (func_num_args() === 2) {
return $text;
}
$args = array_slice(func_get_args(), 2);
return vsprintf($text, is_array($args[0]) ? $args[0] : $args);
}
/**
* Returns the translation of a string in a specific domain.
*
* @param string $domain
* @param string $original
*
* @return string
*/
function d__($domain, $original)
{
$text = BaseTranslator::$current->dgettext($domain, $original);
if (func_num_args() === 2) {
return $text;
}
$args = array_slice(func_get_args(), 2);
return vsprintf($text, is_array($args[0]) ? $args[0] : $args);
}
/**
* Returns the translation of a string in a specific domain and context.
*
* @param string $domain
* @param string $context
* @param string $original
*
* @return string
*/
function dp__($domain, $context, $original)
{
$text = BaseTranslator::$current->dpgettext($domain, $context, $original);
if (func_num_args() === 3) {
return $text;
}
$args = array_slice(func_get_args(), 3);
return vsprintf($text, is_array($args[0]) ? $args[0] : $args);
}
/**
* Returns the singular/plural translation of a string in a specific context.
*
* @param string $context
* @param string $original
* @param string $plural
* @param string $value
*
* @return string
*/
function np__($context, $original, $plural, $value)
{
$text = BaseTranslator::$current->npgettext($context, $original, $plural, $value);
if (func_num_args() === 4) {
return $text;
}
$args = array_slice(func_get_args(), 4);
return vsprintf($text, is_array($args[0]) ? $args[0] : $args);
}
/**
* Returns the singular/plural translation of a string in a specific domain and context.
*
* @param string $domain
* @param string $context
* @param string $original
* @param string $plural
* @param string $value
*
* @return string
*/
function dnp__($domain, $context, $original, $plural, $value)
{
$text = BaseTranslator::$current->dnpgettext($domain, $context, $original, $plural, $value);
if (func_num_args() === 5) {
return $text;
}
$args = array_slice(func_get_args(), 5);
return vsprintf($text, is_array($args[0]) ? $args[0] : $args);
}

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Michele Locati
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,50 @@
UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE
Unicode Data Files include all data files under the directories
http://www.unicode.org/Public/, http://www.unicode.org/reports/, and
http://www.unicode.org/cldr/data/. Unicode Data Files do not include PDF
online code charts under the directory http://www.unicode.org/Public/.
Software includes any source code published in the Unicode Standard or under
the directories http://www.unicode.org/Public/,
http://www.unicode.org/reports/, and http://www.unicode.org/cldr/data/.
NOTICE TO USER: Carefully read the following legal agreement. BY
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S DATA FILES
("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), YOU UNEQUIVOCALLY ACCEPT, AND
AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF
YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA
FILES OR SOFTWARE.
COPYRIGHT AND PERMISSION NOTICE
Copyright © 1991-2014 Unicode, Inc. All rights reserved. Distributed under
the Terms of Use in http://www.unicode.org/copyright.html.
Permission is hereby granted, free of charge, to any person obtaining a
copy of the Unicode data files and any associated documentation (the "Data
Files") or Unicode software and any associated documentation (the "Software")
to deal in the Data Files or Software without restriction, including without
limitation the rights to use, copy, modify, merge, publish, distribute, and/or
sell copies of the Data Files or Software, and to permit persons to whom the
Data Files or Software are furnished to do so, provided that (a) the above
copyright notice(s) and this permission notice appear with all copies of the
Data Files or Software, (b) both the above copyright notice(s) and this
permission notice appear in associated documentation, and (c) there is clear
notice in each modified Data File or in the Software as well as in the
documentation associated with the Data File(s) or Software that the data or
software has been modified.
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD
PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN
THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE
DATA FILES OR SOFTWARE.
Except as contained in this notice, the name of a copyright holder shall
not be used in advertising or otherwise to promote the sale, use or other
dealings in these Data Files or Software without prior written authorization
of the copyright holder.

View File

@@ -0,0 +1,3 @@
@echo off
php "%~dpn0.php" %*

View File

@@ -0,0 +1,234 @@
<?php
use Gettext\Languages\Exporter\Exporter;
use Gettext\Languages\Language;
// Let's start by imposing that we don't accept any error or warning.
// This is a really life-saving approach.
error_reporting(E_ALL);
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
Enviro::echoErr("$errstr\nFile: $errfile\nLine: $errline\nCode: $errno\n");
die(5);
});
require_once dirname(__DIR__).'/src/autoloader.php';
// Parse the command line options
Enviro::initialize();
try {
if (isset(Enviro::$languages)) {
$languages = array();
foreach (Enviro::$languages as $languageId) {
$language = Language::getById($languageId);
if (!isset($language)) {
throw new Exception("Unable to find the language with id '$languageId'");
}
$languages[] = $language;
}
} else {
$languages = Language::getAll();
}
if (Enviro::$reduce) {
$languages = Enviro::reduce($languages);
}
if (isset(Enviro::$outputFilename)) {
echo call_user_func(array(Exporter::getExporterClassName(Enviro::$outputFormat), 'toFile'), $languages, Enviro::$outputFilename, array('us-ascii' => Enviro::$outputUSAscii));
} else {
echo call_user_func(array(Exporter::getExporterClassName(Enviro::$outputFormat), 'toString'), $languages, array('us-ascii' => Enviro::$outputUSAscii));
}
} catch (Exception $x) {
Enviro::echoErr($x->getMessage()."\n");
Enviro::echoErr("Trace:\n");
Enviro::echoErr($x->getTraceAsString()."\n");
die(4);
}
die(0);
/**
* Helper class to handle command line options.
*/
class Enviro
{
/**
* Shall the output contain only US-ASCII characters?
* @var bool
*/
public static $outputUSAscii;
/**
* The output format.
* @var string
*/
public static $outputFormat;
/**
* Output file name.
* @var string
*/
public static $outputFilename;
/**
* List of wanted language IDs; it not set: all languages will be returned.
* @var array|null
*/
public static $languages;
/**
* Reduce the language list to the minimum common denominator.
* @var bool
*/
public static $reduce;
/**
* Parse the command line options.
*/
public static function initialize()
{
global $argv;
self::$outputUSAscii = false;
self::$outputFormat = null;
self::$outputFilename = null;
self::$languages = null;
self::$reduce = null;
$exporters = Exporter::getExporters();
if (isset($argv) && is_array($argv)) {
foreach ($argv as $argi => $arg) {
if ($argi === 0) {
continue;
}
if (is_string($arg)) {
$argLC = trim(strtolower($arg));
switch ($argLC) {
case '--us-ascii':
self::$outputUSAscii = true;
break;
case '--reduce=yes':
self::$reduce = true;
break;
case '--reduce=no':
self::$reduce = false;
break;
default:
if (preg_match('/^--output=.+$/', $argLC)) {
if (isset(self::$outputFilename)) {
self::echoErr("The output file name has been specified more than once!\n");
self::showSyntax();
die(3);
}
list(, self::$outputFilename) = explode('=', $arg, 2);
self::$outputFilename = trim(self::$outputFilename);
} elseif (preg_match('/^--languages?=.+$/', $argLC)) {
list(, $s) = explode('=', $arg, 2);
$list = explode(',', $s);
if (is_array(self::$languages)) {
self::$languages = array_merge(self::$languages, $list);
} else {
self::$languages = $list;
}
} elseif (isset($exporters[$argLC])) {
if (isset(self::$outputFormat)) {
self::echoErr("The output format has been specified more than once!\n");
self::showSyntax();
die(3);
}
self::$outputFormat = $argLC;
} else {
self::echoErr("Unknown option: $arg\n");
self::showSyntax();
die(2);
}
break;
}
}
}
}
if (!isset(self::$outputFormat)) {
self::showSyntax();
die(1);
}
if (isset(self::$languages)) {
self::$languages = array_values(array_unique(self::$languages));
}
if (!isset(self::$reduce)) {
self::$reduce = isset(self::$languages) ? false : true;
}
}
/**
* Write out the syntax.
*/
public static function showSyntax()
{
$exporters = array_keys(Exporter::getExporters(true));
self::echoErr("Syntax: php ".basename(__FILE__)." [--us-ascii] [--languages=<LanguageId>[,<LanguageId>,...]] [--reduce=yes|no] [--output=<file name>] <".implode('|', $exporters).">\n");
self::echoErr("Where:\n");
self::echoErr("--us-ascii : if specified, the output will contain only US-ASCII characters.\n");
self::echoErr("--languages: (or --language) export only the specified language codes.\n");
self::echoErr(" Separate languages with commas; you can also use this argument\n");
self::echoErr(" more than once; it's case insensitive and accepts both '_' and\n");
self::echoErr(" '-' as locale chunks separator (eg we accept 'it_IT' as well as\n");
self::echoErr(" 'it-it').\n");
self::echoErr("--reduce : if set to yes the output won't contain languages with the same\n");
self::echoErr(" base language and rules.\n For instance nl_BE ('Flemish') will be\n");
self::echoErr(" omitted because it's the same as nl ('Dutch').\n");
self::echoErr(" Defaults to 'no' --languages is specified, to 'yes' otherwise.\n");
self::echoErr("--output : if specified, the output will be saved to <file name>. If not\n");
self::echoErr(" specified we'll output to standard output.\n");
self::echoErr("Output formats\n");
$len = max(array_map('strlen', $exporters));
foreach ($exporters as $exporter) {
self::echoErr(str_pad($exporter, $len).": ".Exporter::getExporterDescription($exporter)."\n");
}
}
/**
* Print a string to stderr.
* @param string $str The string to be printed out.
*/
public static function echoErr($str)
{
$hStdErr = @fopen('php://stderr', 'a');
if ($hStdErr === false) {
echo $str;
} else {
fwrite($hStdErr, $str);
fclose($hStdErr);
}
}
/**
* Reduce a language list to the minimum common denominator.
* @param Language[] $languages
* @return Language[]
*/
public static function reduce($languages)
{
for ($numChunks = 3; $numChunks >= 2; $numChunks--) {
$filtered = array();
foreach ($languages as $language) {
$chunks = explode('_', $language->id);
$compatibleFound = false;
if (count($chunks) === $numChunks) {
$categoriesHash = serialize($language->categories);
$otherIds = array();
$otherIds[] = $chunks[0];
for ($k = 2; $k < $numChunks; $k++) {
$otherIds[] = $chunks[0].'_'.$chunks[$numChunks - 1];
}
foreach ($languages as $check) {
foreach ($otherIds as $otherId) {
if (($check->id === $otherId) && ($check->formula === $language->formula) && (serialize($check->categories) === $categoriesHash)) {
$compatibleFound = true;
break;
}
}
if ($compatibleFound === true) {
break;
}
}
}
if (!$compatibleFound) {
$filtered[] = $language;
}
}
$languages = $filtered;
}
return $languages;
}
}

View File

@@ -0,0 +1,3 @@
#!/bin/sh
php "$(dirname -- "$0")/export.php" $@

View File

@@ -0,0 +1,38 @@
{
"name": "gettext/languages",
"description": "gettext languages with plural rules",
"keywords": [
"localization",
"l10n",
"internationalization",
"i18n",
"translations",
"translate",
"php",
"unicode",
"cldr",
"language",
"languages",
"plural",
"plurals",
"plural rules"
],
"homepage": "https://github.com/mlocati/cldr-to-gettext-plural-rules",
"license": "MIT",
"authors": [
{
"name": "Michele Locati",
"email": "mlocati@gmail.com",
"role": "Developer"
}
],
"autoload": {
"psr-4": {
"Gettext\\Languages\\": "src/"
}
},
"require": {
"php": ">=5.3"
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace Gettext\Languages;
use Exception;
/**
* A helper class that handles a single category rules (eg 'zero', 'one', ...) and its formula and examples.
*/
class Category
{
/**
* The category identifier (eg 'zero', 'one', ..., 'other').
* @var string
*/
public $id;
/**
* The gettext formula that identifies this category (null if and only if the category is 'other').
* @var string|null
*/
public $formula;
/**
* The CLDR representation of some exemplar numeric ranges that satisfy this category.
* @var string|null
*/
public $examples;
/**
* Initialize the instance and parse the formula.
* @param string $cldrCategoryId The CLDR category identifier (eg 'pluralRule-count-one').
* @param string $cldrFormulaAndExamples The CLDR formula and examples (eg 'i = 1 and v = 0 @integer 1').
* @throws Exception
*/
public function __construct($cldrCategoryId, $cldrFormulaAndExamples)
{
$matches = array();
if (!preg_match('/^pluralRule-count-(.+)$/', $cldrCategoryId, $matches)) {
throw new Exception("Invalid CLDR category: '$cldrCategoryId'");
}
if (!in_array($matches[1], CldrData::$categories)) {
throw new Exception("Invalid CLDR category: '$cldrCategoryId'");
}
$this->id = $matches[1];
$cldrFormulaAndExamplesNormalized = trim(preg_replace('/\s+/', ' ', $cldrFormulaAndExamples));
if (!preg_match('/^([^@]*)(?:@integer([^@]+))?(?:@decimal(?:[^@]+))?$/', $cldrFormulaAndExamplesNormalized, $matches)) {
throw new Exception("Invalid CLDR category rule: $cldrFormulaAndExamples");
}
$cldrFormula = trim($matches[1]);
$s = isset($matches[2]) ? trim($matches[2]) : '';
$this->examples = ($s === '') ? null : $s;
switch ($this->id) {
case CldrData::OTHER_CATEGORY:
if ($cldrFormula !== '') {
throw new Exception("The '".CldrData::OTHER_CATEGORY."' category should not have any formula, but it has '$cldrFormula'");
}
$this->formula = null;
break;
default:
if ($cldrFormula === '') {
throw new Exception("The '{$this->id}' category does not have a formula");
}
$this->formula = FormulaConverter::convertFormula($cldrFormula);
break;
}
}
/**
* Return a list of numbers corresponding to the $examples value.
* @throws Exception Throws an Exception if we weren't able to expand the examples.
* @return int[]
*/
public function getExampleIntegers()
{
return self::expandExamples($this->examples);
}
/**
* Expand a list of examples as defined by CLDR.
* @param string $examples A string like '1, 2, 5...7, …'.
* @throws Exception Throws an Exception if we weren't able to expand $examples.
* @return int[]
*/
public static function expandExamples($examples)
{
$result = array();
$m = null;
if (substr($examples, -strlen(', …')) === ', …') {
$examples = substr($examples, 0, strlen($examples) -strlen(', …'));
}
foreach (explode(',', str_replace(' ', '', $examples)) as $range) {
if (preg_match('/^\d+$/', $range)) {
$result[] = intval($range);
} elseif (preg_match('/^(\d+)~(\d+)$/', $range, $m)) {
$from = intval($m[1]);
$to = intval($m[2]);
$delta = $to - $from;
$step = (int) max(1, $delta / 100);
for ($i = $from; $i < $to; $i += $step) {
$result[] = $i;
}
$result[] = $to;
} else {
throw new Exception("Unhandled test range '$range' in '$examples'");
}
}
if (empty($result)) {
throw new Exception("No test numbers from '$examples'");
}
return $result;
}
}

View File

@@ -0,0 +1,320 @@
<?php
namespace Gettext\Languages;
use Exception;
/**
* Holds the CLDR data.
*/
class CldrData
{
/**
* Super-special plural category: this should always be present for any language.
* @var string
*/
const OTHER_CATEGORY = 'other';
/**
* The list of the plural categories, sorted from 'zero' to 'other'.
* @var string[]
*/
public static $categories = array('zero', 'one', 'two', 'few', 'many', self::OTHER_CATEGORY);
/**
* The loaded CLDR data
* @var array
*/
private static $data;
/**
* Returns the loaded CLDR data.
* @param string $key Can be 'languages', 'territories', 'plurals', 'supersededLanguages', 'scripts', 'standAloneScripts'
*/
private static function getData($key)
{
if (!isset(self::$data)) {
$fixKeys = function ($list, &$standAlone = null) {
$result = array();
$standAlone = array();
$match = null;
foreach ($list as $key => $value) {
$variant = '';
if (preg_match('/^(.+)-alt-(short|variant|stand-alone)$/', $key, $match)) {
$key = $match[1];
$variant = $match[2];
}
$key = str_replace('-', '_', $key);
switch ($key) {
case 'root': // Language: Root
case 'und': // Language: Unknown Language
case 'zxx': // Language: No linguistic content
case 'ZZ': // Territory: Unknown Region
case 'Zinh': // Script: Inherited
case 'Zmth': // Script: Mathematical Notation
case 'Zsym': // Script: Symbols
case 'Zxxx': // Script: Unwritten
case 'Zyyy': // Script: Common
case 'Zzzz': // Script: Unknown Script
break;
default:
if (
((strlen($key) !== 4) || ($key < 'Qaaa') || ($key > 'Qabx')) // Script: Reserved for private use
) {
switch ($variant) {
case 'stand-alone':
$standAlone[$key] = $value;
break;
case '':
$result[$key] = $value;
break;
}
}
break;
}
}
return $result;
};
$data = array();
$json = json_decode(file_get_contents(__DIR__.'/cldr-data/main/en-US/languages.json'), true);
$data['languages'] = $fixKeys($json['main']['en-US']['localeDisplayNames']['languages']);
$json = json_decode(file_get_contents(__DIR__.'/cldr-data/main/en-US/territories.json'), true);
$data['territories'] = $fixKeys($json['main']['en-US']['localeDisplayNames']['territories']);
$json = json_decode(file_get_contents(__DIR__.'/cldr-data/supplemental/plurals.json'), true);
$data['plurals'] = $fixKeys($json['supplemental']['plurals-type-cardinal']);
$json = json_decode(file_get_contents(__DIR__.'/cldr-data/main/en-US/scripts.json'), true);
$data['scripts'] = $fixKeys($json['main']['en-US']['localeDisplayNames']['scripts'], $data['standAloneScripts']);
$data['standAloneScripts'] = array_merge($data['scripts'], $data['standAloneScripts']);
$data['scripts'] = array_merge($data['standAloneScripts'], $data['scripts']);
$data['supersededLanguages'] = array();
// Remove the languages for which we don't have plurals
$m = null;
foreach (array_keys(array_diff_key($data['languages'], $data['plurals'])) as $missingPlural) {
if (preg_match('/^([a-z]{2,3})_/', $missingPlural, $m)) {
if (!isset($data['plurals'][$m[1]])) {
unset($data['languages'][$missingPlural]);
}
} else {
unset($data['languages'][$missingPlural]);
}
}
// Fix the languages for which we have plurals
$formerCodes = array(
'in' => 'id', // former Indonesian
'iw' => 'he', // former Hebrew
'ji' => 'yi', // former Yiddish
'jw' => 'jv', // former Javanese
'mo' => 'ro_MD', // former Moldavian
);
$knownMissingLanguages = array(
'bh' => 'Bihari',
'guw' => 'Gun',
'nah' => 'Nahuatl',
'smi' => 'Sami',
);
foreach (array_keys(array_diff_key($data['plurals'], $data['languages'])) as $missingLanguage) {
if (isset($formerCodes[$missingLanguage]) && isset($data['languages'][$formerCodes[$missingLanguage]])) {
$data['languages'][$missingLanguage] = $data['languages'][$formerCodes[$missingLanguage]];
$data['supersededLanguages'][$missingLanguage] = $formerCodes[$missingLanguage];
} else {
if (isset($knownMissingLanguages[$missingLanguage])) {
$data['languages'][$missingLanguage] = $knownMissingLanguages[$missingLanguage];
} else {
throw new Exception("We have the plural rule for the language '$missingLanguage' but we don't have its language name");
}
}
}
ksort($data['languages'], SORT_STRING);
ksort($data['territories'], SORT_STRING);
ksort($data['plurals'], SORT_STRING);
ksort($data['scripts'], SORT_STRING);
ksort($data['standAloneScripts'], SORT_STRING);
ksort($data['supersededLanguages'], SORT_STRING);
self::$data = $data;
}
if (!@isset(self::$data[$key])) {
throw new Exception("Invalid CLDR data key: '$key'");
}
return self::$data[$key];
}
/**
* Returns a dictionary containing the language names.
* The keys are the language identifiers.
* The values are the language names in US English.
* @return string[]
*/
public static function getLanguageNames()
{
return self::getData('languages');
}
/**
* Return a dictionary containing the territory names (in US English).
* The keys are the territory identifiers.
* The values are the territory names in US English.
* @return string[]
*/
public static function getTerritoryNames()
{
return self::getData('territories');
}
/**
* Return a dictionary containing the script names (in US English).
* The keys are the script identifiers.
* The values are the script names in US English.
* @param bool $standAlone Set to true to retrieve the stand-alone script names, false otherwise.
* @return string[]
*/
public static function getScriptNames($standAlone)
{
return self::getData($standAlone ? 'standAloneScripts' : 'scripts');
}
/**
* @var array
*/
private static $plurals;
/**
* A dictionary containing the plural rules.
* The keys are the language identifiers.
* The values are arrays whose keys are the CLDR category names and the values are the CLDR category definition.
* @example The English key-value pair is somethink like this:
* <code><pre>
* "en": {
* "pluralRule-count-one": "i = 1 and v = 0 @integer 1",
* "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
* }
* </pre></code>
* @var array
*/
public static function getPlurals()
{
return self::getData('plurals');
}
/**
* Return a list of superseded language codes.
* @return array Keys are the former language codes, values are the new language/locale codes.
*/
public static function getSupersededLanguages()
{
return self::getData('supersededLanguages');
}
/**
* Retrieve the name of a language, as well as if a language code is deprecated in favor of another language code.
* @param string $id The language identifier.
* @return array|null Returns an array with the keys 'id' (normalized), 'name', 'supersededBy' (optional), 'territory' (optional), 'script' (optional), 'baseLanguage' (optional), 'categories'. If $id is not valid returns null.
*/
public static function getLanguageInfo($id)
{
$result = null;
$matches = array();
if (preg_match('/^([a-z]{2,3})(?:[_\-]([a-z]{4}))?(?:[_\-]([a-z]{2}|[0-9]{3}))?(?:$|-)/i', $id, $matches)) {
$languageId = strtolower($matches[1]);
$scriptId = (isset($matches[2]) && ($matches[2] !== '')) ? ucfirst(strtolower($matches[2])) : null;
$territoryId = (isset($matches[3]) && ($matches[3] !== '')) ? strtoupper($matches[3]) : null;
$normalizedId = $languageId;
if (isset($scriptId)) {
$normalizedId .= '_'.$scriptId;
}
if (isset($territoryId)) {
$normalizedId .= '_'.$territoryId;
}
// Structure precedence: see Likely Subtags - http://www.unicode.org/reports/tr35/tr35-31/tr35.html#Likely_Subtags
$variants = array();
$variantsWithScript = array();
$variantsWithTerritory = array();
if (isset($scriptId) && isset($territoryId)) {
$variantsWithTerritory[] = $variantsWithScript[] = $variants[] = "{$languageId}_{$scriptId}_{$territoryId}";
}
if (isset($scriptId)) {
$variantsWithScript[] = $variants[] = "{$languageId}_{$scriptId}";
}
if (isset($territoryId)) {
$variantsWithTerritory[] = $variants[] = "{$languageId}_{$territoryId}";
}
$variants[] = $languageId;
$allGood = true;
$scriptName = null;
$scriptStandAloneName = null;
if (isset($scriptId)) {
$scriptNames = self::getScriptNames(false);
if (isset($scriptNames[$scriptId])) {
$scriptName = $scriptNames[$scriptId];
$scriptStandAloneNames = self::getScriptNames(true);
$scriptStandAloneName = $scriptStandAloneNames[$scriptId];
} else {
$allGood = false;
}
}
$territoryName = null;
if (isset($territoryId)) {
$territoryNames = self::getTerritoryNames();
if (isset($territoryNames[$territoryId])) {
if ($territoryId !== '001') {
$territoryName = $territoryNames[$territoryId];
}
} else {
$allGood = false;
}
}
$languageName = null;
$languageNames = self::getLanguageNames();
foreach ($variants as $variant) {
if (isset($languageNames[$variant])) {
$languageName = $languageNames[$variant];
if (isset($scriptName) && (!in_array($variant, $variantsWithScript))) {
$languageName = $scriptName.' '.$languageName;
}
if (isset($territoryName) && (!in_array($variant, $variantsWithTerritory))) {
$languageName .= ' ('.$territoryNames[$territoryId].')';
}
break;
}
}
if (!isset($languageName)) {
$allGood = false;
}
$baseLanguage = null;
if (isset($scriptId) || isset($territoryId)) {
if (isset($languageNames[$languageId]) && ($languageNames[$languageId] !== $languageName)) {
$baseLanguage = $languageNames[$languageId];
}
}
$plural = null;
$plurals = self::getPlurals();
foreach ($variants as $variant) {
if (isset($plurals[$variant])) {
$plural = $plurals[$variant];
break;
}
}
if (!isset($plural)) {
$allGood = false;
}
$supersededBy = null;
$supersededBys = self::getSupersededLanguages();
foreach ($variants as $variant) {
if (isset($supersededBys[$variant])) {
$supersededBy = $supersededBys[$variant];
break;
}
}
if ($allGood) {
$result = array();
$result['id'] = $normalizedId;
$result['name'] = $languageName;
if (isset($supersededBy)) {
$result['supersededBy'] = $supersededBy;
}
if (isset($scriptStandAloneName)) {
$result['script'] = $scriptStandAloneName;
}
if (isset($territoryName)) {
$result['territory'] = $territoryName;
}
if (isset($baseLanguage)) {
$result['baseLanguage'] = $baseLanguage;
}
$result['categories'] = $plural;
}
}
return $result;
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Gettext\Languages\Exporter;
class Docs extends Html
{
/**
* @see Exporter::toStringDo
*/
protected static function toStringDo($languages)
{
$result = <<<EOT
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="Michele Locati">
<title>gettext plural rules - built from CLDR</title>
<meta name="description" content="List of all language rules for gettext .po files automatically generated from the Unicode CLDR data" />
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<a href="https://github.com/mlocati/cldr-to-gettext-plural-rules" class="hidden-xs"><img style="position: fixed; top: 0; right: 0; border: 0; z-index: 2000" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub"></a>
<div class="container-fluid">
EOT;
$result .= static::buildTable($languages, true);
$result .= <<<EOT
</div>
<script src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
<script src="script.js"></script>
</body>
</html>
EOT;
return $result;
}
/**
* @see Exporter::isForPublicUse
*/
public static function isForPublicUse()
{
return false;
}
/**
* @see Exporter::getDescription
*/
public static function getDescription()
{
return 'Build the page http://mlocati.github.io/cldr-to-gettext-plural-rules';
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace Gettext\Languages\Exporter;
use Exception;
use Gettext\Languages\Language;
/**
* Base class for all the exporters.
*/
abstract class Exporter
{
/**
* @var array
*/
private static $exporters;
/**
* Return the list of all the available exporters. Keys are the exporter handles, values are the exporter class names.
* @param bool $onlyForPublicUse If true, internal exporters will be omitted.
* @return string[]
*/
final public static function getExporters($onlyForPublicUse = false)
{
if (!isset(self::$exporters)) {
$exporters = array();
$m = null;
foreach (scandir(__DIR__) as $f) {
if (preg_match('/^(\w+)\.php$/', $f, $m)) {
if ($f !== basename(__FILE__)) {
$exporters[strtolower($m[1])] = $m[1];
}
}
}
self::$exporters = $exporters;
}
if ($onlyForPublicUse) {
$result = array();
foreach (self::$exporters as $handle => $class) {
if (call_user_func(self::getExporterClassName($handle).'::isForPublicUse') === true) {
$result[$handle] = $class;
}
}
} else {
$result = self::$exporters;
}
return $result;
}
/**
* Return the description of a specific exporter.
* @param string $exporterHandle The handle of the exporter.
* @throws Exception Throws an Exception if $exporterHandle is not valid.
* @return string
*/
final public static function getExporterDescription($exporterHandle)
{
$exporters = self::getExporters();
if (!isset($exporters[$exporterHandle])) {
throw new Exception("Invalid exporter handle: '$exporterHandle'");
}
return call_user_func(self::getExporterClassName($exporterHandle).'::getDescription');
}
/**
* Returns the fully qualified class name of a exporter given its handle.
* @param string $exporterHandle The exporter class handle.
* @return string
*/
final public static function getExporterClassName($exporterHandle)
{
return __NAMESPACE__.'\\'.ucfirst(strtolower($exporterHandle));
}
/**
* Convert a list of Language instances to string.
* @param Language[] $languages The Language instances to convert.
* @return string
*/
protected static function toStringDo($languages)
{
throw new Exception(get_called_class().' does not implement the method '.__FUNCTION__);
}
/**
* Convert a list of Language instances to string.
* @param Language[] $languages The Language instances to convert.
* @return string
*/
final public static function toString($languages, $options = null)
{
if (isset($options) && is_array($options)) {
if (isset($options['us-ascii']) && $options['us-ascii']) {
$asciiList = array();
foreach ($languages as $language) {
$asciiList[] = $language->getUSAsciiClone();
}
$languages = $asciiList;
}
}
return static::toStringDo($languages);
}
/**
* Save the Language instances to a file.
* @param Language[] $languages The Language instances to convert.
* @throws Exception
*/
final public static function toFile($languages, $filename, $options = null)
{
$data = self::toString($languages, $options);
if (@file_put_contents($filename, $data) === false) {
throw new Exception("Error writing data to '$filename'");
}
}
/**
* Is this exporter for public use?
* @return bool
*/
public static function isForPublicUse()
{
return true;
}
/**
* Return a short description of the exporter.
* @return string
*/
public static function getDescription()
{
throw new Exception(get_called_class().' does not implement the method '.__FUNCTION__);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Gettext\Languages\Exporter;
class Html extends Exporter
{
/**
* @see Exporter::toStringDo
*/
protected static function toStringDo($languages)
{
return self::buildTable($languages, false);
}
protected static function h($str)
{
return htmlspecialchars($str, ENT_COMPAT, 'UTF-8');
}
protected static function buildTable($languages, $forDocs)
{
$prefix = $forDocs ? ' ' : '';
$lines = array();
$lines[] = $prefix.'<table'.($forDocs ? ' class="table table-bordered table-condensed table-striped"' : '').'>';
$lines[] = $prefix.' <thead>';
$lines[] = $prefix.' <tr>';
$lines[] = $prefix.' <th>Language code</th>';
$lines[] = $prefix.' <th>Language name</th>';
$lines[] = $prefix.' <th># plurals</th>';
$lines[] = $prefix.' <th>Formula</th>';
$lines[] = $prefix.' <th>Plurals</th>';
$lines[] = $prefix.' </tr>';
$lines[] = $prefix.' </thead>';
$lines[] = $prefix.' <tbody>';
foreach ($languages as $lc) {
$lines[] = $prefix.' <tr>';
$lines[] = $prefix.' <td>'.$lc->id.'</td>';
$name = self::h($lc->name);
if (isset($lc->supersededBy)) {
$name .= '<br /><small><span>Superseded by</span> '.$lc->supersededBy.'</small>';
}
$lines[] = $prefix.' <td>'.$name.'</td>';
$lines[] = $prefix.' <td>'.count($lc->categories).'</td>';
$lines[] = $prefix.' <td>'.self::h($lc->formula).'</td>';
$cases = array();
foreach ($lc->categories as $c) {
$cases[] = '<li><span>'.$c->id.'</span><code>'.self::h($c->examples).'</code></li>';
}
$lines[] = $prefix.' <td><ol'.($forDocs ? ' class="cases"' : '').' start="0">'.implode('', $cases).'</ol></td>';
$lines[] = $prefix.' </tr>';
}
$lines[] = $prefix.' </tbody>';
$lines[] = $prefix.'</table>';
return implode("\n", $lines);
}
/**
* @see Exporter::getDescription
*/
public static function getDescription()
{
return 'Build a HTML table';
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Gettext\Languages\Exporter;
class Json extends Exporter
{
/**
* Return the options for json_encode.
* @return int
*/
protected static function getEncodeOptions()
{
$result = 0;
if (defined('\JSON_UNESCAPED_SLASHES')) {
$result |= \JSON_UNESCAPED_SLASHES;
}
if (defined('\JSON_UNESCAPED_UNICODE')) {
$result |= \JSON_UNESCAPED_UNICODE;
}
return $result;
}
/**
* @see Exporter::toStringDo
*/
protected static function toStringDo($languages)
{
$list = array();
foreach ($languages as $language) {
$item = array();
$item['name'] = $language->name;
if (isset($language->supersededBy)) {
$item['supersededBy'] = $language->supersededBy;
}
if (isset($language->script)) {
$item['script'] = $language->script;
}
if (isset($language->territory)) {
$item['territory'] = $language->territory;
}
if (isset($language->baseLanguage)) {
$item['baseLanguage'] = $language->baseLanguage;
}
$item['formula'] = $language->formula;
$item['plurals'] = count($language->categories);
$item['cases'] = array();
$item['examples'] = array();
foreach ($language->categories as $category) {
$item['cases'][] = $category->id;
$item['examples'][$category->id] = $category->examples;
}
$list[$language->id] = $item;
}
return json_encode($list, static::getEncodeOptions());
}
/**
* @see Exporter::getDescription
*/
public static function getDescription()
{
return 'Build a compressed JSON-encoded file';
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Gettext\Languages\Exporter;
class Php extends Exporter
{
/**
* @see Exporter::toStringDo
*/
protected static function toStringDo($languages)
{
$lines = array();
$lines[] = '<?php';
$lines[] = 'return array(';
foreach ($languages as $lc) {
$lines[] = ' \''.$lc->id.'\' => array(';
$lines[] = ' \'name\' => \''.addslashes($lc->name).'\',';
if (isset($lc->supersededBy)) {
$lines[] = ' \'supersededBy\' => \''.$lc->supersededBy.'\',';
}
if (isset($lc->script)) {
$lines[] = ' \'script\' => \''.addslashes($lc->script).'\',';
}
if (isset($lc->territory)) {
$lines[] = ' \'territory\' => \''.addslashes($lc->territory).'\',';
}
if (isset($lc->baseLanguage)) {
$lines[] = ' \'baseLanguage\' => \''.addslashes($lc->baseLanguage).'\',';
}
$lines[] = ' \'formula\' => \''.$lc->formula.'\',';
$lines[] = ' \'plurals\' => '.count($lc->categories).',';
$catNames = array();
foreach ($lc->categories as $c) {
$catNames[] = "'{$c->id}'";
}
$lines[] = ' \'cases\' => array('.implode(', ', $catNames).'),';
$lines[] = ' \'examples\' => array(';
foreach ($lc->categories as $c) {
$lines[] = ' \''.$c->id.'\' => \''.$c->examples.'\',';
}
$lines[] = ' ),';
$lines[] = ' ),';
}
$lines[] = ');';
$lines[] = '';
return implode("\n", $lines);
}
/**
* @see Exporter::getDescription
*/
public static function getDescription()
{
return 'Build a PHP array';
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Gettext\Languages\Exporter;
use Exception;
class Po extends Exporter
{
/**
* @see Exporter::toStringDo
*/
protected static function toStringDo($languages)
{
if (count($languages) !== 1) {
throw new Exception('The '.get_called_class().' exporter can only export one language');
}
$language = $languages[0];
$lines = array();
$lines[] = '"Language: '.$language->id.'\n"';
$lines[] = '"Plural-Forms: nplurals='.count($language->categories).'; plural='.$language->formula.'\n"';
$lines[] = '';
return implode("\n", $lines);
}
/**
* @see Exporter::getDescription
*/
public static function getDescription()
{
return 'Build a string to be used for gettext .po files';
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Gettext\Languages\Exporter;
use Exception;
class Prettyjson extends Json
{
/**
* @see Json::getEncodeOptions
*/
protected static function getEncodeOptions()
{
if (!(defined('\JSON_PRETTY_PRINT') && defined('\JSON_UNESCAPED_SLASHES') && defined('\JSON_UNESCAPED_UNICODE'))) {
throw new Exception('PHP 5.4 or later is required to export uncompressed JSON');
}
return \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE;
}
/**
* @see Exporter::getDescription
*/
public static function getDescription()
{
return 'Build an uncompressed JSON-encoded file (PHP 5.4 or later is needed)';
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Gettext\Languages\Exporter;
class Xml extends Exporter
{
/**
* @see Exporter::toStringDo
*/
protected static function toStringDo($languages)
{
$xml = new \DOMDocument('1.0', 'UTF-8');
$xml->loadXML('<languages
xmlns="https://github.com/mlocati/cldr-to-gettext-plural-rules"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://github.com/mlocati/cldr-to-gettext-plural-rules http://mlocati.github.io/cldr-to-gettext-plural-rules/GettextLanguages.xsd"
/>');
$xLanguages = $xml->firstChild;
foreach ($languages as $language) {
$xLanguage = $xml->createElement('language');
$xLanguage->setAttribute('id', $language->id);
$xLanguage->setAttribute('name', $language->name);
if (isset($language->supersededBy)) {
$xLanguage->setAttribute('supersededBy', $language->supersededBy);
}
if (isset($language->script)) {
$xLanguage->setAttribute('script', $language->script);
}
if (isset($language->territory)) {
$xLanguage->setAttribute('territory', $language->territory);
}
if (isset($language->baseLanguage)) {
$xLanguage->setAttribute('baseLanguage', $language->baseLanguage);
}
$xLanguage->setAttribute('formula', $language->formula);
foreach ($language->categories as $category) {
$xCategory = $xml->createElement('category');
$xCategory->setAttribute('id', $category->id);
$xCategory->setAttribute('examples', $category->examples);
$xLanguage->appendChild($xCategory);
}
$xLanguages->appendChild($xLanguage);
}
$xml->formatOutput = true;
return $xml->saveXML();
}
/**
* @see Exporter::getDescription
*/
public static function getDescription()
{
return 'Build an XML file - schema available at http://mlocati.github.io/cldr-to-gettext-plural-rules/GettextLanguages.xsd';
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace Gettext\Languages;
use Exception;
/**
* A helper class to convert a CLDR formula to a gettext formula.
*/
class FormulaConverter
{
/**
* Converts a formula from the CLDR representation to the gettext representation.
* @param string $cldrFormula The CLDR formula to convert.
* @throws Exception
* @return bool|string Returns true if the gettext will always evaluate to true, false if gettext will always evaluate to false, return the gettext formula otherwise.
*/
public static function convertFormula($cldrFormula)
{
if (strpbrk($cldrFormula, '()') !== false) {
throw new Exception("Unable to convert the formula '$cldrFormula': parenthesis handling not implemented");
}
$orSeparatedChunks = array();
foreach (explode(' or ', $cldrFormula) as $cldrFormulaChunk) {
$gettextFormulaChunk = null;
$andSeparatedChunks = array();
foreach (explode(' and ', $cldrFormulaChunk) as $cldrAtom) {
$gettextAtom = self::convertAtom($cldrAtom);
if ($gettextAtom === false) {
// One atom joined by 'and' always evaluates to false => the whole 'and' group is always false
$gettextFormulaChunk = false;
break;
} elseif ($gettextAtom !== true) {
$andSeparatedChunks[] = $gettextAtom;
}
}
if (!isset($gettextFormulaChunk)) {
if (empty($andSeparatedChunks)) {
// All the atoms joined by 'and' always evaluate to true => the whole 'and' group is always true
$gettextFormulaChunk = true;
} else {
$gettextFormulaChunk = implode(' && ', $andSeparatedChunks);
// Special cases simplification
switch ($gettextFormulaChunk) {
case 'n >= 0 && n <= 2 && n != 2':
$gettextFormulaChunk = 'n == 0 || n == 1';
break;
}
}
}
if ($gettextFormulaChunk === true) {
// One part of the formula joined with the others by 'or' always evaluates to true => the whole formula always evaluates to true
return true;
} elseif ($gettextFormulaChunk !== false) {
$orSeparatedChunks[] = $gettextFormulaChunk;
}
}
if (empty($orSeparatedChunks)) {
// All the parts joined by 'or' always evaluate to false => the whole formula always evaluates to false
return false;
} else {
return implode(' || ', $orSeparatedChunks);
}
}
/**
* Converts an atomic part of the CLDR formula to its gettext representation.
* @param string $cldrAtom The CLDR formula atom to convert.
* @throws Exception
* @return bool|string Returns true if the gettext will always evaluate to true, false if gettext will always evaluate to false, return the gettext formula otherwise.
*/
private static function convertAtom($cldrAtom)
{
$m = null;
$gettextAtom = $cldrAtom;
$gettextAtom = str_replace(' = ', ' == ', $gettextAtom);
$gettextAtom = str_replace('i', 'n', $gettextAtom);
if (preg_match('/^n( % \d+)? (!=|==) \d+$/', $gettextAtom)) {
return $gettextAtom;
}
if (preg_match('/^n( % \d+)? (!=|==) \d+(,\d+|\.\.\d+)+$/', $gettextAtom)) {
return self::expandAtom($gettextAtom);
}
if (preg_match('/^(?:v|w)(?: % 10+)? == (\d+)(?:\.\.\d+)?$/', $gettextAtom, $m)) { // For gettext: v == 0, w == 0
return (intval($m[1]) === 0) ? true : false;
}
if (preg_match('/^(?:v|w)(?: % 10+)? != (\d+)(?:\.\.\d+)?$/', $gettextAtom, $m)) { // For gettext: v == 0, w == 0
return (intval($m[1]) === 0) ? false : true;
}
if (preg_match('/^(?:f|t)(?: % 10+)? == (\d+)(?:\.\.\d+)?$/', $gettextAtom, $m)) { // f == empty, t == empty
return (intval($m[1]) === 0) ? true : false;
}
if (preg_match('/^(?:f|t)(?: % 10+)? != (\d+)(?:\.\.\d+)?$/', $gettextAtom, $m)) { // f == empty, t == empty
return (intval($m[1]) === 0) ? false : true;
}
throw new Exception("Unable to convert the formula chunk '$cldrAtom' from CLDR to gettext");
}
/**
* Expands an atom containing a range (for instance: 'n == 1,3..5').
* @param string $atom
* @throws Exception
* @return string
*/
private static function expandAtom($atom)
{
$m = null;
if (preg_match('/^(n(?: % \d+)?) (==|!=) (\d+(?:\.\.\d+|,\d+)+)$/', $atom, $m)) {
$what = $m[1];
$op = $m[2];
$chunks = array();
foreach (explode(',', $m[3]) as $range) {
$chunk = null;
if ((!isset($chunk)) && preg_match('/^\d+$/', $range)) {
$chunk = "$what $op $range";
}
if ((!isset($chunk)) && preg_match('/^(\d+)\.\.(\d+)$/', $range, $m)) {
$from = intval($m[1]);
$to = intval($m[2]);
if (($to - $from) === 1) {
switch ($op) {
case '==':
$chunk = "($what == $from || $what == $to)";
break;
case '!=':
$chunk = "$what != $from && $what == $to";
break;
}
} else {
switch ($op) {
case '==':
$chunk = "$what >= $from && $what <= $to";
break;
case '!=':
$chunk = "($what < $from || $what > $to)";
break;
}
}
}
if (!isset($chunk)) {
throw new Exception("Unhandled range '$range' in '$atom'");
}
$chunks[] = $chunk;
}
if (count($chunks) === 1) {
return $chunks[0];
}
switch ($op) {
case '==':
return '('.implode(' || ', $chunks).')';break;
case '!=':
return implode(' && ', $chunks);
}
}
throw new Exception("Unable to expand '$atom'");
}
}

View File

@@ -0,0 +1,366 @@
<?php
namespace Gettext\Languages;
use Exception;
/**
* Main class to convert the plural rules of a language from CLDR to gettext.
*/
class Language
{
/**
* The language ID.
* @var string
*/
public $id;
/**
* The language name.
* @var string
*/
public $name;
/**
* If this language is deprecated: the gettext code of the new language.
* @var null|string
*/
public $supersededBy;
/**
* The script name.
* @var string|null
*/
public $script;
/**
* The territory name.
* @var string|null
*/
public $territory;
/**
* The name of the base language
* @var string|null
*/
public $baseLanguage;
/**
* The list of categories.
* @var Category[]
*/
public $categories;
/**
* The gettext formula to decide which category should be applied.
* @var string
*/
public $formula;
/**
* Initialize the instance and parse the language code.
* @param array $info The result of CldrData::getLanguageInfo()
* @throws Exception Throws an Exception if $fullId is not valid.
*/
private function __construct($info)
{
$this->id = $info['id'];
$this->name = $info['name'];
$this->supersededBy = isset($info['supersededBy']) ? $info['supersededBy'] : null;
$this->script = isset($info['script']) ? $info['script'] : null;
$this->territory = isset($info['territory']) ? $info['territory'] : null;
$this->baseLanguage = isset($info['baseLanguage']) ? $info['baseLanguage'] : null;
// Let's build the category list
$this->categories = array();
foreach ($info['categories'] as $cldrCategoryId => $cldrFormulaAndExamples) {
$category = new Category($cldrCategoryId, $cldrFormulaAndExamples);
foreach ($this->categories as $c) {
if ($category->id === $c->id) {
throw new Exception("The category '{$category->id}' is specified more than once");
}
}
$this->categories[] = $category;
}
if (empty($this->categories)) {
throw new Exception("The language '$id' does not have any plural category");
}
// Let's sort the categories from 'zero' to 'other'
usort($this->categories, function (Category $category1, Category $category2) {
return array_search($category1->id, CldrData::$categories) - array_search($category2->id, CldrData::$categories);
});
// The 'other' category should always be there
if ($this->categories[count($this->categories) - 1]->id !== CldrData::OTHER_CATEGORY) {
throw new Exception("The language '$id' does not have the '".CldrData::OTHER_CATEGORY."' plural category");
}
$this->checkAlwaysTrueCategories();
$this->checkAlwaysFalseCategories();
$this->checkAllCategoriesWithExamples();
$this->formula = $this->buildFormula();
}
/**
* Return a list of all languages available.
* @throws Exception
* @return Language[]
*/
public static function getAll()
{
$result = array();
foreach (array_keys(CldrData::getLanguageNames()) as $cldrLanguageId) {
$result[] = new Language(CldrData::getLanguageInfo($cldrLanguageId));
}
return $result;
}
/**
* Return a Language instance given the language id
* @param string $id
* @return Language|null
*/
public static function getById($id)
{
$result = null;
$info = CldrData::getLanguageInfo($id);
if (isset($info)) {
$result = new Language($info);
}
return $result;
}
/**
* Let's look for categories that will always occur.
* This because with decimals (CLDR) we may have more cases, with integers (gettext) we have just one case.
* If we found that (single) category we reduce the categories to that one only.
*/
private function checkAlwaysTrueCategories()
{
$alwaysTrueCategory = null;
foreach ($this->categories as $category) {
if ($category->formula === true) {
if (!isset($category->examples)) {
throw new Exception("The category '{$category->id}' should always occur, but it does not have examples (so for CLDR it will never occur for integers!)");
}
$alwaysTrueCategory = $category;
break;
}
}
if (isset($alwaysTrueCategory)) {
foreach ($this->categories as $category) {
if (($category !== $alwaysTrueCategory) && isset($category->examples)) {
throw new Exception("The category '{$category->id}' should never occur, but it has some examples (so for CLDR it will occur!)");
}
}
$alwaysTrueCategory->id = CldrData::OTHER_CATEGORY;
$alwaysTrueCategory->formula = null;
$this->categories = array($alwaysTrueCategory);
}
}
/**
* Let's look for categories that will never occur.
* This because with decimals (CLDR) we may have more cases, with integers (gettext) we have some less cases.
* If we found those categories we strip them out.
*/
private function checkAlwaysFalseCategories()
{
$filtered = array();
foreach ($this->categories as $category) {
if ($category->formula === false) {
if (isset($category->examples)) {
throw new Exception("The category '{$category->id}' should never occur, but it has examples (so for CLDR it may occur!)");
}
} else {
$filtered[] = $category;
}
}
$this->categories = $filtered;
}
/**
* Let's look for categories that don't have examples.
* This because with decimals (CLDR) we may have more cases, with integers (gettext) we have some less cases.
* If we found those categories, we check that they never occur and we strip them out.
* @throws Exception
*/
private function checkAllCategoriesWithExamples()
{
$allCategoriesIds = array();
$goodCategories = array();
$badCategories = array();
$badCategoriesIds = array();
foreach ($this->categories as $category) {
$allCategoriesIds[] = $category->id;
if (isset($category->examples)) {
$goodCategories[] = $category;
} else {
$badCategories[] = $category;
$badCategoriesIds[] = $category->id;
}
}
if (empty($badCategories)) {
return;
}
$removeCategoriesWithoutExamples = false;
switch (implode(',', $badCategoriesIds).'@'.implode(',', $allCategoriesIds)) {
case CldrData::OTHER_CATEGORY.'@one,few,many,'.CldrData::OTHER_CATEGORY:
switch ($this->buildFormula()) {
case '(n % 10 == 1 && n % 100 != 11) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : ((n % 10 == 0 || n % 10 >= 5 && n % 10 <= 9 || n % 100 >= 11 && n % 100 <= 14) ? 2 : 3))':
// Numbers ending with 0 => case 2 ('many')
// Numbers ending with 1 but not with 11 => case 0 ('one')
// Numbers ending with 11 => case 2 ('many')
// Numbers ending with 2 but not with 12 => case 1 ('few')
// Numbers ending with 12 => case 2 ('many')
// Numbers ending with 3 but not with 13 => case 1 ('few')
// Numbers ending with 13 => case 2 ('many')
// Numbers ending with 4 but not with 14 => case 1 ('few')
// Numbers ending with 14 => case 2 ('many')
// Numbers ending with 5 => case 2 ('many')
// Numbers ending with 6 => case 2 ('many')
// Numbers ending with 7 => case 2 ('many')
// Numbers ending with 8 => case 2 ('many')
// Numbers ending with 9 => case 2 ('many')
// => the 'other' case never occurs: use 'other' for 'many'
$removeCategoriesWithoutExamples = true;
break;
case '(n == 1) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : ((n != 1 && (n % 10 == 0 || n % 10 == 1) || n % 10 >= 5 && n % 10 <= 9 || n % 100 >= 12 && n % 100 <= 14) ? 2 : 3))':
// Numbers ending with 0 => case 2 ('many')
// Numbers ending with 1 but not number 1 => case 2 ('many')
// Number 1 => case 0 ('one')
// Numbers ending with 2 but not with 12 => case 1 ('few')
// Numbers ending with 12 => case 2 ('many')
// Numbers ending with 3 but not with 13 => case 1 ('few')
// Numbers ending with 13 => case 2 ('many')
// Numbers ending with 4 but not with 14 => case 1 ('few')
// Numbers ending with 14 => case 2 ('many')
// Numbers ending with 5 => case 2 ('many')
// Numbers ending with 6 => case 2 ('many')
// Numbers ending with 7 => case 2 ('many')
// Numbers ending with 8 => case 2 ('many')
// Numbers ending with 9 => case 2 ('many')
// => the 'other' case never occurs: use 'other' for 'many'
$removeCategoriesWithoutExamples = true;
break;
}
}
if (!$removeCategoriesWithoutExamples) {
throw new Exception("Unhandled case of plural categories without examples '".implode(', ', $badCategoriesIds)."' out of '".implode(', ', $allCategoriesIds)."'");
}
if ($badCategories[count($badCategories) - 1]->id === CldrData::OTHER_CATEGORY) {
// We're removing the 'other' cagory: let's change the last good category to 'other'
$lastGood = $goodCategories[count($goodCategories) - 1];
$lastGood->id = CldrData::OTHER_CATEGORY;
$lastGood->formula = null;
}
$this->categories = $goodCategories;
}
/**
* Build the formula starting from the currently defined categories.
* @return string
*/
private function buildFormula()
{
$numCategories = count($this->categories);
switch ($numCategories) {
case 1:
// Just one category
return '0';
case 2:
return self::reduceFormula(self::reverseFormula($this->categories[0]->formula));
default:
$formula = strval($numCategories - 1);
for ($i = $numCategories - 2; $i >= 0; $i--) {
$f = self::reduceFormula($this->categories[$i]->formula);
if (!preg_match('/^\([^()]+\)$/', $f)) {
$f = "($f)";
}
$formula = "$f ? $i : $formula";
if ($i > 0) {
$formula = "($formula)";
}
}
return $formula;
}
}
/**
* Reverse a formula.
* @param string $formula
* @throws Exception
* @return string
*/
private static function reverseFormula($formula)
{
if (preg_match('/^n( % \d+)? == \d+(\.\.\d+|,\d+)*?$/', $formula)) {
return str_replace(' == ', ' != ', $formula);
}
if (preg_match('/^n( % \d+)? != \d+(\.\.\d+|,\d+)*?$/', $formula)) {
return str_replace(' != ', ' == ', $formula);
}
if (preg_match('/^\(?n == \d+ \|\| n == \d+\)?$/', $formula)) {
return trim(str_replace(array(' == ', ' || '), array(' != ', ' && '), $formula), '()');
}
$m = null;
if (preg_match('/^(n(?: % \d+)?) == (\d+) && (n(?: % \d+)?) != (\d+)$/', $formula, $m)) {
return "{$m[1]} != {$m[2]} || {$m[3]} == {$m[4]}";
}
switch ($formula) {
case '(n == 1 || n == 2 || n == 3) || n % 10 != 4 && n % 10 != 6 && n % 10 != 9':
return 'n != 1 && n != 2 && n != 3 && (n % 10 == 4 || n % 10 == 6 || n % 10 == 9)';
case '(n == 0 || n == 1) || n >= 11 && n <= 99':
return 'n >= 2 && (n < 11 || n > 99)';
}
throw new Exception("Unable to reverse the formula '$formula'");
}
/**
* Reduce some excessively complex formulas.
* @param string $formula
* @return string
*/
private static function reduceFormula($formula)
{
$map = array(
'n != 0 && n != 1' => 'n > 1' ,
'(n == 0 || n == 1) && n != 0' => 'n == 1',
);
return isset($map[$formula]) ? $map[$formula] : $formula;
}
/**
* Take one variable and, if it's a string, we transliterate it to US-ASCII.
* @param mixed $value The variable to work on.
* @throws Exception
*/
private static function asciifier(&$value)
{
if (is_string($value) && ($value !== '')) {
// Avoid converting from 'Ÿ' to '"Y', let's prefer 'Y'
$transliterated = strtr($value, array(
'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A',
'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E',
'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I',
'Ñ' => 'N',
'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O',
'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U',
'Ÿ' => 'Y', 'Ý' => 'Y',
'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a',
'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e',
'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i',
'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o',
'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u',
'ý' => 'y', 'ÿ' => 'y',
));
$transliterated = @iconv('UTF-8', 'US-ASCII//IGNORE//TRANSLIT', $transliterated);
if (($transliterated === false) || ($transliterated === '')) {
throw new Exception("Unable to transliterate '$value'");
}
$value = $transliterated;
}
}
/**
* Returns a clone of this instance with all the strings to US-ASCII.
* @return Language
*/
public function getUSAsciiClone()
{
$clone = clone $this;
self::asciifier($clone->name);
self::asciifier($clone->formula);
$clone->categories = array();
foreach ($this->categories as $category) {
$categoryClone = clone $category;
self::asciifier($categoryClone->examples);
$clone->categories[] = $categoryClone;
}
return $clone;
}
}

View File

@@ -0,0 +1,12 @@
<?php
spl_autoload_register(
function ($class) {
if (strpos($class, 'Gettext\\Languages\\') !== 0) {
return;
}
$file = __DIR__.str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen('Gettext\\Languages'))).'.php';
if (is_file($file)) {
require_once $file;
}
}
);

View File

@@ -0,0 +1,636 @@
{
"main": {
"en-US": {
"identity": {
"version": {
"_cldrVersion": "27",
"_number": "$Revision: 10669 $"
},
"generation": {
"_date": "$Date: 2014-07-23 16:10:33 -0500 (Wed, 23 Jul 2014) $"
},
"language": "en",
"territory": "US"
},
"localeDisplayNames": {
"languages": {
"aa": "Afar",
"ab": "Abkhazian",
"ace": "Achinese",
"ach": "Acoli",
"ada": "Adangme",
"ady": "Adyghe",
"ae": "Avestan",
"aeb": "Tunisian Arabic",
"af": "Afrikaans",
"afh": "Afrihili",
"agq": "Aghem",
"ain": "Ainu",
"ak": "Akan",
"akk": "Akkadian",
"akz": "Alabama",
"ale": "Aleut",
"aln": "Gheg Albanian",
"alt": "Southern Altai",
"am": "Amharic",
"an": "Aragonese",
"ang": "Old English",
"anp": "Angika",
"ar": "Arabic",
"ar-001": "Modern Standard Arabic",
"arc": "Aramaic",
"arn": "Mapuche",
"aro": "Araona",
"arp": "Arapaho",
"arq": "Algerian Arabic",
"arw": "Arawak",
"ary": "Moroccan Arabic",
"arz": "Egyptian Arabic",
"as": "Assamese",
"asa": "Asu",
"ase": "American Sign Language",
"ast": "Asturian",
"av": "Avaric",
"avk": "Kotava",
"awa": "Awadhi",
"ay": "Aymara",
"az": "Azerbaijani",
"az-alt-short": "Azeri",
"azb": "South Azerbaijani",
"ba": "Bashkir",
"bal": "Baluchi",
"ban": "Balinese",
"bar": "Bavarian",
"bas": "Basaa",
"bax": "Bamun",
"bbc": "Batak Toba",
"bbj": "Ghomala",
"be": "Belarusian",
"bej": "Beja",
"bem": "Bemba",
"bew": "Betawi",
"bez": "Bena",
"bfd": "Bafut",
"bfq": "Badaga",
"bg": "Bulgarian",
"bho": "Bhojpuri",
"bi": "Bislama",
"bik": "Bikol",
"bin": "Bini",
"bjn": "Banjar",
"bkm": "Kom",
"bla": "Siksika",
"bm": "Bambara",
"bn": "Bengali",
"bo": "Tibetan",
"bpy": "Bishnupriya",
"bqi": "Bakhtiari",
"br": "Breton",
"bra": "Braj",
"brh": "Brahui",
"brx": "Bodo",
"bs": "Bosnian",
"bss": "Akoose",
"bua": "Buriat",
"bug": "Buginese",
"bum": "Bulu",
"byn": "Blin",
"byv": "Medumba",
"ca": "Catalan",
"cad": "Caddo",
"car": "Carib",
"cay": "Cayuga",
"cch": "Atsam",
"ce": "Chechen",
"ceb": "Cebuano",
"cgg": "Chiga",
"ch": "Chamorro",
"chb": "Chibcha",
"chg": "Chagatai",
"chk": "Chuukese",
"chm": "Mari",
"chn": "Chinook Jargon",
"cho": "Choctaw",
"chp": "Chipewyan",
"chr": "Cherokee",
"chy": "Cheyenne",
"ckb": "Central Kurdish",
"co": "Corsican",
"cop": "Coptic",
"cps": "Capiznon",
"cr": "Cree",
"crh": "Crimean Turkish",
"cs": "Czech",
"csb": "Kashubian",
"cu": "Church Slavic",
"cv": "Chuvash",
"cy": "Welsh",
"da": "Danish",
"dak": "Dakota",
"dar": "Dargwa",
"dav": "Taita",
"de": "German",
"de-AT": "Austrian German",
"de-CH": "Swiss High German",
"del": "Delaware",
"den": "Slave",
"dgr": "Dogrib",
"din": "Dinka",
"dje": "Zarma",
"doi": "Dogri",
"dsb": "Lower Sorbian",
"dtp": "Central Dusun",
"dua": "Duala",
"dum": "Middle Dutch",
"dv": "Divehi",
"dyo": "Jola-Fonyi",
"dyu": "Dyula",
"dz": "Dzongkha",
"dzg": "Dazaga",
"ebu": "Embu",
"ee": "Ewe",
"efi": "Efik",
"egl": "Emilian",
"egy": "Ancient Egyptian",
"eka": "Ekajuk",
"el": "Greek",
"elx": "Elamite",
"en": "English",
"en-AU": "Australian English",
"en-CA": "Canadian English",
"en-GB": "British English",
"en-GB-alt-short": "UK English",
"en-US": "American English",
"en-US-alt-short": "US English",
"enm": "Middle English",
"eo": "Esperanto",
"es": "Spanish",
"es-419": "Latin American Spanish",
"es-ES": "European Spanish",
"es-MX": "Mexican Spanish",
"esu": "Central Yupik",
"et": "Estonian",
"eu": "Basque",
"ewo": "Ewondo",
"ext": "Extremaduran",
"fa": "Persian",
"fan": "Fang",
"fat": "Fanti",
"ff": "Fulah",
"fi": "Finnish",
"fil": "Filipino",
"fit": "Tornedalen Finnish",
"fj": "Fijian",
"fo": "Faroese",
"fon": "Fon",
"fr": "French",
"fr-CA": "Canadian French",
"fr-CH": "Swiss French",
"frc": "Cajun French",
"frm": "Middle French",
"fro": "Old French",
"frp": "Arpitan",
"frr": "Northern Frisian",
"frs": "Eastern Frisian",
"fur": "Friulian",
"fy": "Western Frisian",
"ga": "Irish",
"gaa": "Ga",
"gag": "Gagauz",
"gan": "Gan Chinese",
"gay": "Gayo",
"gba": "Gbaya",
"gbz": "Zoroastrian Dari",
"gd": "Scottish Gaelic",
"gez": "Geez",
"gil": "Gilbertese",
"gl": "Galician",
"glk": "Gilaki",
"gmh": "Middle High German",
"gn": "Guarani",
"goh": "Old High German",
"gom": "Goan Konkani",
"gon": "Gondi",
"gor": "Gorontalo",
"got": "Gothic",
"grb": "Grebo",
"grc": "Ancient Greek",
"gsw": "Swiss German",
"gu": "Gujarati",
"guc": "Wayuu",
"gur": "Frafra",
"guz": "Gusii",
"gv": "Manx",
"gwi": "Gwichʼin",
"ha": "Hausa",
"hai": "Haida",
"hak": "Hakka Chinese",
"haw": "Hawaiian",
"he": "Hebrew",
"hi": "Hindi",
"hif": "Fiji Hindi",
"hil": "Hiligaynon",
"hit": "Hittite",
"hmn": "Hmong",
"ho": "Hiri Motu",
"hr": "Croatian",
"hsb": "Upper Sorbian",
"hsn": "Xiang Chinese",
"ht": "Haitian",
"hu": "Hungarian",
"hup": "Hupa",
"hy": "Armenian",
"hz": "Herero",
"ia": "Interlingua",
"iba": "Iban",
"ibb": "Ibibio",
"id": "Indonesian",
"ie": "Interlingue",
"ig": "Igbo",
"ii": "Sichuan Yi",
"ik": "Inupiaq",
"ilo": "Iloko",
"inh": "Ingush",
"io": "Ido",
"is": "Icelandic",
"it": "Italian",
"iu": "Inuktitut",
"izh": "Ingrian",
"ja": "Japanese",
"jam": "Jamaican Creole English",
"jbo": "Lojban",
"jgo": "Ngomba",
"jmc": "Machame",
"jpr": "Judeo-Persian",
"jrb": "Judeo-Arabic",
"jut": "Jutish",
"jv": "Javanese",
"ka": "Georgian",
"kaa": "Kara-Kalpak",
"kab": "Kabyle",
"kac": "Kachin",
"kaj": "Jju",
"kam": "Kamba",
"kaw": "Kawi",
"kbd": "Kabardian",
"kbl": "Kanembu",
"kcg": "Tyap",
"kde": "Makonde",
"kea": "Kabuverdianu",
"ken": "Kenyang",
"kfo": "Koro",
"kg": "Kongo",
"kgp": "Kaingang",
"kha": "Khasi",
"kho": "Khotanese",
"khq": "Koyra Chiini",
"khw": "Khowar",
"ki": "Kikuyu",
"kiu": "Kirmanjki",
"kj": "Kuanyama",
"kk": "Kazakh",
"kkj": "Kako",
"kl": "Kalaallisut",
"kln": "Kalenjin",
"km": "Khmer",
"kmb": "Kimbundu",
"kn": "Kannada",
"ko": "Korean",
"koi": "Komi-Permyak",
"kok": "Konkani",
"kos": "Kosraean",
"kpe": "Kpelle",
"kr": "Kanuri",
"krc": "Karachay-Balkar",
"kri": "Krio",
"krj": "Kinaray-a",
"krl": "Karelian",
"kru": "Kurukh",
"ks": "Kashmiri",
"ksb": "Shambala",
"ksf": "Bafia",
"ksh": "Colognian",
"ku": "Kurdish",
"kum": "Kumyk",
"kut": "Kutenai",
"kv": "Komi",
"kw": "Cornish",
"ky": "Kyrgyz",
"ky-alt-variant": "Kirghiz",
"la": "Latin",
"lad": "Ladino",
"lag": "Langi",
"lah": "Lahnda",
"lam": "Lamba",
"lb": "Luxembourgish",
"lez": "Lezghian",
"lfn": "Lingua Franca Nova",
"lg": "Ganda",
"li": "Limburgish",
"lij": "Ligurian",
"liv": "Livonian",
"lkt": "Lakota",
"lmo": "Lombard",
"ln": "Lingala",
"lo": "Lao",
"lol": "Mongo",
"loz": "Lozi",
"lt": "Lithuanian",
"ltg": "Latgalian",
"lu": "Luba-Katanga",
"lua": "Luba-Lulua",
"lui": "Luiseno",
"lun": "Lunda",
"luo": "Luo",
"lus": "Mizo",
"luy": "Luyia",
"lv": "Latvian",
"lzh": "Literary Chinese",
"lzz": "Laz",
"mad": "Madurese",
"maf": "Mafa",
"mag": "Magahi",
"mai": "Maithili",
"mak": "Makasar",
"man": "Mandingo",
"mas": "Masai",
"mde": "Maba",
"mdf": "Moksha",
"mdr": "Mandar",
"men": "Mende",
"mer": "Meru",
"mfe": "Morisyen",
"mg": "Malagasy",
"mga": "Middle Irish",
"mgh": "Makhuwa-Meetto",
"mgo": "Metaʼ",
"mh": "Marshallese",
"mi": "Maori",
"mic": "Micmac",
"min": "Minangkabau",
"mk": "Macedonian",
"ml": "Malayalam",
"mn": "Mongolian",
"mnc": "Manchu",
"mni": "Manipuri",
"moh": "Mohawk",
"mos": "Mossi",
"mr": "Marathi",
"mrj": "Western Mari",
"ms": "Malay",
"mt": "Maltese",
"mua": "Mundang",
"mul": "Multiple Languages",
"mus": "Creek",
"mwl": "Mirandese",
"mwr": "Marwari",
"mwv": "Mentawai",
"my": "Burmese",
"mye": "Myene",
"myv": "Erzya",
"mzn": "Mazanderani",
"na": "Nauru",
"nan": "Min Nan Chinese",
"nap": "Neapolitan",
"naq": "Nama",
"nb": "Norwegian Bokmål",
"nd": "North Ndebele",
"nds": "Low German",
"ne": "Nepali",
"new": "Newari",
"ng": "Ndonga",
"nia": "Nias",
"niu": "Niuean",
"njo": "Ao Naga",
"nl": "Dutch",
"nl-BE": "Flemish",
"nmg": "Kwasio",
"nn": "Norwegian Nynorsk",
"nnh": "Ngiemboon",
"no": "Norwegian",
"nog": "Nogai",
"non": "Old Norse",
"nov": "Novial",
"nqo": "NʼKo",
"nr": "South Ndebele",
"nso": "Northern Sotho",
"nus": "Nuer",
"nv": "Navajo",
"nwc": "Classical Newari",
"ny": "Nyanja",
"nym": "Nyamwezi",
"nyn": "Nyankole",
"nyo": "Nyoro",
"nzi": "Nzima",
"oc": "Occitan",
"oj": "Ojibwa",
"om": "Oromo",
"or": "Oriya",
"os": "Ossetic",
"osa": "Osage",
"ota": "Ottoman Turkish",
"pa": "Punjabi",
"pag": "Pangasinan",
"pal": "Pahlavi",
"pam": "Pampanga",
"pap": "Papiamento",
"pau": "Palauan",
"pcd": "Picard",
"pdc": "Pennsylvania German",
"pdt": "Plautdietsch",
"peo": "Old Persian",
"pfl": "Palatine German",
"phn": "Phoenician",
"pi": "Pali",
"pl": "Polish",
"pms": "Piedmontese",
"pnt": "Pontic",
"pon": "Pohnpeian",
"prg": "Prussian",
"pro": "Old Provençal",
"ps": "Pashto",
"ps-alt-variant": "Pushto",
"pt": "Portuguese",
"pt-BR": "Brazilian Portuguese",
"pt-PT": "European Portuguese",
"qu": "Quechua",
"quc": "Kʼicheʼ",
"qug": "Chimborazo Highland Quichua",
"raj": "Rajasthani",
"rap": "Rapanui",
"rar": "Rarotongan",
"rgn": "Romagnol",
"rif": "Riffian",
"rm": "Romansh",
"rn": "Rundi",
"ro": "Romanian",
"ro-MD": "Moldavian",
"rof": "Rombo",
"rom": "Romany",
"root": "Root",
"rtm": "Rotuman",
"ru": "Russian",
"rue": "Rusyn",
"rug": "Roviana",
"rup": "Aromanian",
"rw": "Kinyarwanda",
"rwk": "Rwa",
"sa": "Sanskrit",
"sad": "Sandawe",
"sah": "Sakha",
"sam": "Samaritan Aramaic",
"saq": "Samburu",
"sas": "Sasak",
"sat": "Santali",
"saz": "Saurashtra",
"sba": "Ngambay",
"sbp": "Sangu",
"sc": "Sardinian",
"scn": "Sicilian",
"sco": "Scots",
"sd": "Sindhi",
"sdc": "Sassarese Sardinian",
"se": "Northern Sami",
"see": "Seneca",
"seh": "Sena",
"sei": "Seri",
"sel": "Selkup",
"ses": "Koyraboro Senni",
"sg": "Sango",
"sga": "Old Irish",
"sgs": "Samogitian",
"sh": "Serbo-Croatian",
"shi": "Tachelhit",
"shn": "Shan",
"shu": "Chadian Arabic",
"si": "Sinhala",
"sid": "Sidamo",
"sk": "Slovak",
"sl": "Slovenian",
"sli": "Lower Silesian",
"sly": "Selayar",
"sm": "Samoan",
"sma": "Southern Sami",
"smj": "Lule Sami",
"smn": "Inari Sami",
"sms": "Skolt Sami",
"sn": "Shona",
"snk": "Soninke",
"so": "Somali",
"sog": "Sogdien",
"sq": "Albanian",
"sr": "Serbian",
"srn": "Sranan Tongo",
"srr": "Serer",
"ss": "Swati",
"ssy": "Saho",
"st": "Southern Sotho",
"stq": "Saterland Frisian",
"su": "Sundanese",
"suk": "Sukuma",
"sus": "Susu",
"sux": "Sumerian",
"sv": "Swedish",
"sw": "Swahili",
"swb": "Comorian",
"swc": "Congo Swahili",
"syc": "Classical Syriac",
"syr": "Syriac",
"szl": "Silesian",
"ta": "Tamil",
"tcy": "Tulu",
"te": "Telugu",
"tem": "Timne",
"teo": "Teso",
"ter": "Tereno",
"tet": "Tetum",
"tg": "Tajik",
"th": "Thai",
"ti": "Tigrinya",
"tig": "Tigre",
"tiv": "Tiv",
"tk": "Turkmen",
"tkl": "Tokelau",
"tkr": "Tsakhur",
"tl": "Tagalog",
"tlh": "Klingon",
"tli": "Tlingit",
"tly": "Talysh",
"tmh": "Tamashek",
"tn": "Tswana",
"to": "Tongan",
"tog": "Nyasa Tonga",
"tpi": "Tok Pisin",
"tr": "Turkish",
"tru": "Turoyo",
"trv": "Taroko",
"ts": "Tsonga",
"tsd": "Tsakonian",
"tsi": "Tsimshian",
"tt": "Tatar",
"ttt": "Muslim Tat",
"tum": "Tumbuka",
"tvl": "Tuvalu",
"tw": "Twi",
"twq": "Tasawaq",
"ty": "Tahitian",
"tyv": "Tuvinian",
"tzm": "Central Atlas Tamazight",
"udm": "Udmurt",
"ug": "Uyghur",
"ug-alt-variant": "Uighur",
"uga": "Ugaritic",
"uk": "Ukrainian",
"umb": "Umbundu",
"und": "Unknown Language",
"ur": "Urdu",
"uz": "Uzbek",
"vai": "Vai",
"ve": "Venda",
"vec": "Venetian",
"vep": "Veps",
"vi": "Vietnamese",
"vls": "West Flemish",
"vmf": "Main-Franconian",
"vo": "Volapük",
"vot": "Votic",
"vro": "Võro",
"vun": "Vunjo",
"wa": "Walloon",
"wae": "Walser",
"wal": "Wolaytta",
"war": "Waray",
"was": "Washo",
"wbp": "Warlpiri",
"wo": "Wolof",
"wuu": "Wu Chinese",
"xal": "Kalmyk",
"xh": "Xhosa",
"xmf": "Mingrelian",
"xog": "Soga",
"yao": "Yao",
"yap": "Yapese",
"yav": "Yangben",
"ybb": "Yemba",
"yi": "Yiddish",
"yo": "Yoruba",
"yrl": "Nheengatu",
"yue": "Cantonese",
"za": "Zhuang",
"zap": "Zapotec",
"zbl": "Blissymbols",
"zea": "Zeelandic",
"zen": "Zenaga",
"zgh": "Standard Moroccan Tamazight",
"zh": "Chinese",
"zh-Hans": "Simplified Chinese",
"zh-Hant": "Traditional Chinese",
"zu": "Zulu",
"zun": "Zuni",
"zxx": "No linguistic content",
"zza": "Zaza"
}
}
}
}
}

View File

@@ -0,0 +1,239 @@
{
"main": {
"en-US": {
"identity": {
"version": {
"_cldrVersion": "27",
"_number": "$Revision: 10669 $"
},
"generation": {
"_date": "$Date: 2014-07-23 16:10:33 -0500 (Wed, 23 Jul 2014) $"
},
"language": "en",
"territory": "US"
},
"localeDisplayNames": {
"scripts": {
"Afak": "Afaka",
"Aghb": "Caucasian Albanian",
"Arab": "Arabic",
"Arab-alt-variant": "Perso-Arabic",
"Armi": "Imperial Aramaic",
"Armn": "Armenian",
"Avst": "Avestan",
"Bali": "Balinese",
"Bamu": "Bamum",
"Bass": "Bassa Vah",
"Batk": "Batak",
"Beng": "Bengali",
"Blis": "Blissymbols",
"Bopo": "Bopomofo",
"Brah": "Brahmi",
"Brai": "Braille",
"Bugi": "Buginese",
"Buhd": "Buhid",
"Cakm": "Chakma",
"Cans": "Unified Canadian Aboriginal Syllabics",
"Cans-alt-short": "UCAS",
"Cari": "Carian",
"Cham": "Cham",
"Cher": "Cherokee",
"Cirt": "Cirth",
"Copt": "Coptic",
"Cprt": "Cypriot",
"Cyrl": "Cyrillic",
"Cyrs": "Old Church Slavonic Cyrillic",
"Deva": "Devanagari",
"Dsrt": "Deseret",
"Dupl": "Duployan shorthand",
"Egyd": "Egyptian demotic",
"Egyh": "Egyptian hieratic",
"Egyp": "Egyptian hieroglyphs",
"Elba": "Elbasan",
"Ethi": "Ethiopic",
"Geok": "Georgian Khutsuri",
"Geor": "Georgian",
"Glag": "Glagolitic",
"Goth": "Gothic",
"Gran": "Grantha",
"Grek": "Greek",
"Gujr": "Gujarati",
"Guru": "Gurmukhi",
"Hang": "Hangul",
"Hani": "Han",
"Hano": "Hanunoo",
"Hans": "Simplified",
"Hans-alt-stand-alone": "Simplified Han",
"Hant": "Traditional",
"Hant-alt-stand-alone": "Traditional Han",
"Hebr": "Hebrew",
"Hira": "Hiragana",
"Hluw": "Anatolian Hieroglyphs",
"Hmng": "Pahawh Hmong",
"Hrkt": "Japanese syllabaries",
"Hung": "Old Hungarian",
"Inds": "Indus",
"Ital": "Old Italic",
"Java": "Javanese",
"Jpan": "Japanese",
"Jurc": "Jurchen",
"Kali": "Kayah Li",
"Kana": "Katakana",
"Khar": "Kharoshthi",
"Khmr": "Khmer",
"Khoj": "Khojki",
"Knda": "Kannada",
"Kore": "Korean",
"Kpel": "Kpelle",
"Kthi": "Kaithi",
"Lana": "Lanna",
"Laoo": "Lao",
"Latf": "Fraktur Latin",
"Latg": "Gaelic Latin",
"Latn": "Latin",
"Lepc": "Lepcha",
"Limb": "Limbu",
"Lina": "Linear A",
"Linb": "Linear B",
"Lisu": "Fraser",
"Loma": "Loma",
"Lyci": "Lycian",
"Lydi": "Lydian",
"Mahj": "Mahajani",
"Mand": "Mandaean",
"Mani": "Manichaean",
"Maya": "Mayan hieroglyphs",
"Mend": "Mende",
"Merc": "Meroitic Cursive",
"Mero": "Meroitic",
"Mlym": "Malayalam",
"Modi": "Modi",
"Mong": "Mongolian",
"Moon": "Moon",
"Mroo": "Mro",
"Mtei": "Meitei Mayek",
"Mymr": "Myanmar",
"Narb": "Old North Arabian",
"Nbat": "Nabataean",
"Nkgb": "Naxi Geba",
"Nkoo": "NKo",
"Nshu": "Nüshu",
"Ogam": "Ogham",
"Olck": "Ol Chiki",
"Orkh": "Orkhon",
"Orya": "Oriya",
"Osma": "Osmanya",
"Palm": "Palmyrene",
"Pauc": "Pau Cin Hau",
"Perm": "Old Permic",
"Phag": "Phags-pa",
"Phli": "Inscriptional Pahlavi",
"Phlp": "Psalter Pahlavi",
"Phlv": "Book Pahlavi",
"Phnx": "Phoenician",
"Plrd": "Pollard Phonetic",
"Prti": "Inscriptional Parthian",
"Qaaa": "Qaaa",
"Qaab": "Qaab",
"Qaac": "Qaac",
"Qaad": "Qaad",
"Qaae": "Qaae",
"Qaaf": "Qaaf",
"Qaag": "Qaag",
"Qaah": "Qaah",
"Qaaj": "Qaaj",
"Qaak": "Qaak",
"Qaal": "Qaal",
"Qaam": "Qaam",
"Qaan": "Qaan",
"Qaao": "Qaao",
"Qaap": "Qaap",
"Qaaq": "Qaaq",
"Qaar": "Qaar",
"Qaas": "Qaas",
"Qaat": "Qaat",
"Qaau": "Qaau",
"Qaav": "Qaav",
"Qaaw": "Qaaw",
"Qaax": "Qaax",
"Qaay": "Qaay",
"Qaaz": "Qaaz",
"Qaba": "Qaba",
"Qabb": "Qabb",
"Qabc": "Qabc",
"Qabd": "Qabd",
"Qabe": "Qabe",
"Qabf": "Qabf",
"Qabg": "Qabg",
"Qabh": "Qabh",
"Qabi": "Qabi",
"Qabj": "Qabj",
"Qabk": "Qabk",
"Qabl": "Qabl",
"Qabm": "Qabm",
"Qabn": "Qabn",
"Qabo": "Qabo",
"Qabp": "Qabp",
"Qabq": "Qabq",
"Qabr": "Qabr",
"Qabs": "Qabs",
"Qabt": "Qabt",
"Qabu": "Qabu",
"Qabv": "Qabv",
"Qabw": "Qabw",
"Qabx": "Qabx",
"Rjng": "Rejang",
"Roro": "Rongorongo",
"Runr": "Runic",
"Samr": "Samaritan",
"Sara": "Sarati",
"Sarb": "Old South Arabian",
"Saur": "Saurashtra",
"Sgnw": "SignWriting",
"Shaw": "Shavian",
"Shrd": "Sharada",
"Sidd": "Siddham",
"Sind": "Khudawadi",
"Sinh": "Sinhala",
"Sora": "Sora Sompeng",
"Sund": "Sundanese",
"Sylo": "Syloti Nagri",
"Syrc": "Syriac",
"Syre": "Estrangelo Syriac",
"Syrj": "Western Syriac",
"Syrn": "Eastern Syriac",
"Tagb": "Tagbanwa",
"Takr": "Takri",
"Tale": "Tai Le",
"Talu": "New Tai Lue",
"Taml": "Tamil",
"Tang": "Tangut",
"Tavt": "Tai Viet",
"Telu": "Telugu",
"Teng": "Tengwar",
"Tfng": "Tifinagh",
"Tglg": "Tagalog",
"Thaa": "Thaana",
"Thai": "Thai",
"Tibt": "Tibetan",
"Tirh": "Tirhuta",
"Ugar": "Ugaritic",
"Vaii": "Vai",
"Visp": "Visible Speech",
"Wara": "Varang Kshiti",
"Wole": "Woleai",
"Xpeo": "Old Persian",
"Xsux": "Sumero-Akkadian Cuneiform",
"Xsux-alt-short": "S-A Cuneiform",
"Yiii": "Yi",
"Zinh": "Inherited",
"Zmth": "Mathematical Notation",
"Zsym": "Symbols",
"Zxxx": "Unwritten",
"Zyyy": "Common",
"Zzzz": "Unknown Script"
}
}
}
}
}

View File

@@ -0,0 +1,324 @@
{
"main": {
"en-US": {
"identity": {
"version": {
"_cldrVersion": "27",
"_number": "$Revision: 10669 $"
},
"generation": {
"_date": "$Date: 2014-07-23 16:10:33 -0500 (Wed, 23 Jul 2014) $"
},
"language": "en",
"territory": "US"
},
"localeDisplayNames": {
"territories": {
"001": "World",
"002": "Africa",
"003": "North America",
"005": "South America",
"009": "Oceania",
"011": "Western Africa",
"013": "Central America",
"014": "Eastern Africa",
"015": "Northern Africa",
"017": "Middle Africa",
"018": "Southern Africa",
"019": "Americas",
"021": "Northern America",
"029": "Caribbean",
"030": "Eastern Asia",
"034": "Southern Asia",
"035": "Southeast Asia",
"039": "Southern Europe",
"053": "Australasia",
"054": "Melanesia",
"057": "Micronesian Region",
"061": "Polynesia",
"142": "Asia",
"143": "Central Asia",
"145": "Western Asia",
"150": "Europe",
"151": "Eastern Europe",
"154": "Northern Europe",
"155": "Western Europe",
"419": "Latin America",
"AC": "Ascension Island",
"AD": "Andorra",
"AE": "United Arab Emirates",
"AF": "Afghanistan",
"AG": "Antigua & Barbuda",
"AI": "Anguilla",
"AL": "Albania",
"AM": "Armenia",
"AN": "Netherlands Antilles",
"AO": "Angola",
"AQ": "Antarctica",
"AR": "Argentina",
"AS": "American Samoa",
"AT": "Austria",
"AU": "Australia",
"AW": "Aruba",
"AX": "Åland Islands",
"AZ": "Azerbaijan",
"BA": "Bosnia & Herzegovina",
"BA-alt-short": "Bosnia",
"BB": "Barbados",
"BD": "Bangladesh",
"BE": "Belgium",
"BF": "Burkina Faso",
"BG": "Bulgaria",
"BH": "Bahrain",
"BI": "Burundi",
"BJ": "Benin",
"BL": "St. Barthélemy",
"BM": "Bermuda",
"BN": "Brunei",
"BO": "Bolivia",
"BQ": "Caribbean Netherlands",
"BR": "Brazil",
"BS": "Bahamas",
"BT": "Bhutan",
"BV": "Bouvet Island",
"BW": "Botswana",
"BY": "Belarus",
"BZ": "Belize",
"CA": "Canada",
"CC": "Cocos (Keeling) Islands",
"CD": "Congo - Kinshasa",
"CD-alt-variant": "Congo (DRC)",
"CF": "Central African Republic",
"CG": "Congo - Brazzaville",
"CG-alt-variant": "Congo (Republic)",
"CH": "Switzerland",
"CI": "Côte dIvoire",
"CI-alt-variant": "Ivory Coast",
"CK": "Cook Islands",
"CL": "Chile",
"CM": "Cameroon",
"CN": "China",
"CO": "Colombia",
"CP": "Clipperton Island",
"CR": "Costa Rica",
"CU": "Cuba",
"CV": "Cape Verde",
"CW": "Curaçao",
"CX": "Christmas Island",
"CY": "Cyprus",
"CZ": "Czech Republic",
"DE": "Germany",
"DG": "Diego Garcia",
"DJ": "Djibouti",
"DK": "Denmark",
"DM": "Dominica",
"DO": "Dominican Republic",
"DZ": "Algeria",
"EA": "Ceuta & Melilla",
"EC": "Ecuador",
"EE": "Estonia",
"EG": "Egypt",
"EH": "Western Sahara",
"ER": "Eritrea",
"ES": "Spain",
"ET": "Ethiopia",
"EU": "European Union",
"FI": "Finland",
"FJ": "Fiji",
"FK": "Falkland Islands",
"FK-alt-variant": "Falkland Islands (Islas Malvinas)",
"FM": "Micronesia",
"FO": "Faroe Islands",
"FR": "France",
"GA": "Gabon",
"GB": "United Kingdom",
"GB-alt-short": "UK",
"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 & South Sandwich Islands",
"GT": "Guatemala",
"GU": "Guam",
"GW": "Guinea-Bissau",
"GY": "Guyana",
"HK": "Hong Kong SAR China",
"HK-alt-short": "Hong Kong",
"HM": "Heard & McDonald Islands",
"HN": "Honduras",
"HR": "Croatia",
"HT": "Haiti",
"HU": "Hungary",
"IC": "Canary Islands",
"ID": "Indonesia",
"IE": "Ireland",
"IL": "Israel",
"IM": "Isle of Man",
"IN": "India",
"IO": "British Indian Ocean Territory",
"IQ": "Iraq",
"IR": "Iran",
"IS": "Iceland",
"IT": "Italy",
"JE": "Jersey",
"JM": "Jamaica",
"JO": "Jordan",
"JP": "Japan",
"KE": "Kenya",
"KG": "Kyrgyzstan",
"KH": "Cambodia",
"KI": "Kiribati",
"KM": "Comoros",
"KN": "St. Kitts & Nevis",
"KP": "North Korea",
"KR": "South Korea",
"KW": "Kuwait",
"KY": "Cayman Islands",
"KZ": "Kazakhstan",
"LA": "Laos",
"LB": "Lebanon",
"LC": "St. 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": "St. Martin",
"MG": "Madagascar",
"MH": "Marshall Islands",
"MK": "Macedonia",
"MK-alt-variant": "Macedonia (FYROM)",
"ML": "Mali",
"MM": "Myanmar (Burma)",
"MM-alt-short": "Myanmar",
"MN": "Mongolia",
"MO": "Macau SAR China",
"MO-alt-short": "Macau",
"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": "St. Pierre & Miquelon",
"PN": "Pitcairn Islands",
"PR": "Puerto Rico",
"PS": "Palestinian Territories",
"PS-alt-short": "Palestine",
"PT": "Portugal",
"PW": "Palau",
"PY": "Paraguay",
"QA": "Qatar",
"QO": "Outlying Oceania",
"RE": "Réunion",
"RO": "Romania",
"RS": "Serbia",
"RU": "Russia",
"RW": "Rwanda",
"SA": "Saudi Arabia",
"SB": "Solomon Islands",
"SC": "Seychelles",
"SD": "Sudan",
"SE": "Sweden",
"SG": "Singapore",
"SH": "St. Helena",
"SI": "Slovenia",
"SJ": "Svalbard & Jan Mayen",
"SK": "Slovakia",
"SL": "Sierra Leone",
"SM": "San Marino",
"SN": "Senegal",
"SO": "Somalia",
"SR": "Suriname",
"SS": "South Sudan",
"ST": "São Tomé & Príncipe",
"SV": "El Salvador",
"SX": "Sint Maarten",
"SY": "Syria",
"SZ": "Swaziland",
"TA": "Tristan da Cunha",
"TC": "Turks & Caicos Islands",
"TD": "Chad",
"TF": "French Southern Territories",
"TG": "Togo",
"TH": "Thailand",
"TJ": "Tajikistan",
"TK": "Tokelau",
"TL": "Timor-Leste",
"TL-alt-variant": "East Timor",
"TM": "Turkmenistan",
"TN": "Tunisia",
"TO": "Tonga",
"TR": "Turkey",
"TT": "Trinidad & Tobago",
"TV": "Tuvalu",
"TW": "Taiwan",
"TZ": "Tanzania",
"UA": "Ukraine",
"UG": "Uganda",
"UM": "U.S. Outlying Islands",
"US": "United States",
"US-alt-short": "US",
"UY": "Uruguay",
"UZ": "Uzbekistan",
"VA": "Vatican City",
"VC": "St. Vincent & Grenadines",
"VE": "Venezuela",
"VG": "British Virgin Islands",
"VI": "U.S. Virgin Islands",
"VN": "Vietnam",
"VU": "Vanuatu",
"WF": "Wallis & Futuna",
"WS": "Samoa",
"XK": "Kosovo",
"YE": "Yemen",
"YT": "Mayotte",
"ZA": "South Africa",
"ZM": "Zambia",
"ZW": "Zimbabwe",
"ZZ": "Unknown Region"
}
}
}
}
}

View File

@@ -0,0 +1,844 @@
{
"supplemental": {
"version": {
"_cldrVersion": "27",
"_number": "$Revision: 11229 $"
},
"generation": {
"_date": "$Date: 2015-02-18 16:11:57 +0100 (Wed, 18 Feb 2015) $"
},
"plurals-type-cardinal": {
"af": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ak": {
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"am": {
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ar": {
"pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000",
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
"pluralRule-count-few": "n % 100 = 3..10 @integer 3~10, 103~110, 1003, … @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 103.0, 1003.0, …",
"pluralRule-count-many": "n % 100 = 11..99 @integer 11~26, 111, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …",
"pluralRule-count-other": " @integer 100~102, 200~202, 300~302, 400~402, 500~502, 600, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"as": {
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"asa": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ast": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"az": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"be": {
"pluralRule-count-one": "n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, …",
"pluralRule-count-few": "n % 10 = 2..4 and n % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 2.0, 3.0, 4.0, 22.0, 23.0, 24.0, 32.0, 33.0, 102.0, 1002.0, …",
"pluralRule-count-many": "n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
"pluralRule-count-other": " @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …"
},
"bem": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"bez": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"bg": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"bh": {
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"bm": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"bn": {
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"bo": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"br": {
"pluralRule-count-one": "n % 10 = 1 and n % 100 != 11,71,91 @integer 1, 21, 31, 41, 51, 61, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 81.0, 101.0, 1001.0, …",
"pluralRule-count-two": "n % 10 = 2 and n % 100 != 12,72,92 @integer 2, 22, 32, 42, 52, 62, 82, 102, 1002, … @decimal 2.0, 22.0, 32.0, 42.0, 52.0, 62.0, 82.0, 102.0, 1002.0, …",
"pluralRule-count-few": "n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99 @integer 3, 4, 9, 23, 24, 29, 33, 34, 39, 43, 44, 49, 103, 1003, … @decimal 3.0, 4.0, 9.0, 23.0, 24.0, 29.0, 33.0, 34.0, 103.0, 1003.0, …",
"pluralRule-count-many": "n != 0 and n % 1000000 = 0 @integer 1000000, … @decimal 1000000.0, 1000000.00, 1000000.000, …",
"pluralRule-count-other": " @integer 0, 5~8, 10~20, 100, 1000, 10000, 100000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, …"
},
"brx": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"bs": {
"pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
"pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …",
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ca": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ce": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"cgg": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"chr": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ckb": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"cs": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-few": "i = 2..4 and v = 0 @integer 2~4",
"pluralRule-count-many": "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …"
},
"cy": {
"pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000",
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
"pluralRule-count-few": "n = 3 @integer 3 @decimal 3.0, 3.00, 3.000, 3.0000",
"pluralRule-count-many": "n = 6 @integer 6 @decimal 6.0, 6.00, 6.000, 6.0000",
"pluralRule-count-other": " @integer 4, 5, 7~20, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"da": {
"pluralRule-count-one": "n = 1 or t != 0 and i = 0,1 @integer 1 @decimal 0.1~1.6",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 2.0~3.4, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"de": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"dsb": {
"pluralRule-count-one": "v = 0 and i % 100 = 1 or f % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
"pluralRule-count-two": "v = 0 and i % 100 = 2 or f % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, … @decimal 0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2, 10.2, 100.2, 1000.2, …",
"pluralRule-count-few": "v = 0 and i % 100 = 3..4 or f % 100 = 3..4 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.3, 0.4, 1.3, 1.4, 2.3, 2.4, 3.3, 3.4, 4.3, 4.4, 5.3, 5.4, 6.3, 6.4, 7.3, 7.4, 10.3, 100.3, 1000.3, …",
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"dv": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"dz": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ee": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"el": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"en": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"eo": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"es": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"et": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"eu": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"fa": {
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ff": {
"pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"fi": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"fil": {
"pluralRule-count-one": "v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9 @integer 0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.3, 0.5, 0.7, 0.8, 1.0~1.3, 1.5, 1.7, 1.8, 2.0, 2.1, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
"pluralRule-count-other": " @integer 4, 6, 9, 14, 16, 19, 24, 26, 104, 1004, … @decimal 0.4, 0.6, 0.9, 1.4, 1.6, 1.9, 2.4, 2.6, 10.4, 100.4, 1000.4, …"
},
"fo": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"fr": {
"pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"fur": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"fy": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ga": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
"pluralRule-count-few": "n = 3..6 @integer 3~6 @decimal 3.0, 4.0, 5.0, 6.0, 3.00, 4.00, 5.00, 6.00, 3.000, 4.000, 5.000, 6.000, 3.0000, 4.0000, 5.0000, 6.0000",
"pluralRule-count-many": "n = 7..10 @integer 7~10 @decimal 7.0, 8.0, 9.0, 10.0, 7.00, 8.00, 9.00, 10.00, 7.000, 8.000, 9.000, 10.000, 7.0000, 8.0000, 9.0000, 10.0000",
"pluralRule-count-other": " @integer 0, 11~25, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"gd": {
"pluralRule-count-one": "n = 1,11 @integer 1, 11 @decimal 1.0, 11.0, 1.00, 11.00, 1.000, 11.000, 1.0000",
"pluralRule-count-two": "n = 2,12 @integer 2, 12 @decimal 2.0, 12.0, 2.00, 12.00, 2.000, 12.000, 2.0000",
"pluralRule-count-few": "n = 3..10,13..19 @integer 3~10, 13~19 @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 3.00",
"pluralRule-count-other": " @integer 0, 20~34, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"gl": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"gsw": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"gu": {
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"guw": {
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"gv": {
"pluralRule-count-one": "v = 0 and i % 10 = 1 @integer 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, …",
"pluralRule-count-two": "v = 0 and i % 10 = 2 @integer 2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, …",
"pluralRule-count-few": "v = 0 and i % 100 = 0,20,40,60,80 @integer 0, 20, 40, 60, 80, 100, 120, 140, 1000, 10000, 100000, 1000000, …",
"pluralRule-count-many": "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
"pluralRule-count-other": " @integer 3~10, 13~19, 23, 103, 1003, …"
},
"ha": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"haw": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"he": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-two": "i = 2 and v = 0 @integer 2",
"pluralRule-count-many": "v = 0 and n != 0..10 and n % 10 = 0 @integer 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 100000, 1000000, …",
"pluralRule-count-other": " @integer 0, 3~17, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"hi": {
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"hr": {
"pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
"pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …",
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"hsb": {
"pluralRule-count-one": "v = 0 and i % 100 = 1 or f % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
"pluralRule-count-two": "v = 0 and i % 100 = 2 or f % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, … @decimal 0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2, 10.2, 100.2, 1000.2, …",
"pluralRule-count-few": "v = 0 and i % 100 = 3..4 or f % 100 = 3..4 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.3, 0.4, 1.3, 1.4, 2.3, 2.4, 3.3, 3.4, 4.3, 4.4, 5.3, 5.4, 6.3, 6.4, 7.3, 7.4, 10.3, 100.3, 1000.3, …",
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"hu": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"hy": {
"pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"id": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ig": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ii": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"in": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"is": {
"pluralRule-count-one": "t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1~1.6, 10.1, 100.1, 1000.1, …",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"it": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"iu": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"iw": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-two": "i = 2 and v = 0 @integer 2",
"pluralRule-count-many": "v = 0 and n != 0..10 and n % 10 = 0 @integer 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 100000, 1000000, …",
"pluralRule-count-other": " @integer 0, 3~17, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ja": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"jbo": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"jgo": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ji": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"jmc": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"jv": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"jw": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ka": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"kab": {
"pluralRule-count-one": "i = 0,1 @integer 0, 1 @decimal 0.0~1.5",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"kaj": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"kcg": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"kde": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"kea": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"kk": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"kkj": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"kl": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"km": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"kn": {
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ko": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ks": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ksb": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ksh": {
"pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000",
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ku": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"kw": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ky": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"lag": {
"pluralRule-count-zero": "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000",
"pluralRule-count-one": "i = 0,1 and n != 0 @integer 1 @decimal 0.1~1.6",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"lb": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"lg": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"lkt": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ln": {
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"lo": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"lt": {
"pluralRule-count-one": "n % 10 = 1 and n % 100 != 11..19 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, …",
"pluralRule-count-few": "n % 10 = 2..9 and n % 100 != 11..19 @integer 2~9, 22~29, 102, 1002, … @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 22.0, 102.0, 1002.0, …",
"pluralRule-count-many": "f != 0 @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …",
"pluralRule-count-other": " @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"lv": {
"pluralRule-count-zero": "n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19 @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
"pluralRule-count-one": "n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.0, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
"pluralRule-count-other": " @integer 2~9, 22~29, 102, 1002, … @decimal 0.2~0.9, 1.2~1.9, 10.2, 100.2, 1000.2, …"
},
"mas": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"mg": {
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"mgo": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"mk": {
"pluralRule-count-one": "v = 0 and i % 10 = 1 or f % 10 = 1 @integer 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
"pluralRule-count-other": " @integer 0, 2~10, 12~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.2~1.0, 1.2~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ml": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"mn": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"mo": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-few": "v != 0 or n = 0 or n != 1 and n % 100 = 1..19 @integer 0, 2~16, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
"pluralRule-count-other": " @integer 20~35, 100, 1000, 10000, 100000, 1000000, …"
},
"mr": {
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ms": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"mt": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-few": "n = 0 or n % 100 = 2..10 @integer 0, 2~10, 102~107, 1002, … @decimal 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 102.0, 1002.0, …",
"pluralRule-count-many": "n % 100 = 11..19 @integer 11~19, 111~117, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …",
"pluralRule-count-other": " @integer 20~35, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"my": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"nah": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"naq": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"nb": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"nd": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ne": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"nl": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"nn": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"nnh": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"no": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"nqo": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"nr": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"nso": {
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ny": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"nyn": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"om": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"or": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"os": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"pa": {
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"pap": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"pl": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …",
"pluralRule-count-many": "v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …",
"pluralRule-count-other": " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"prg": {
"pluralRule-count-zero": "n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19 @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
"pluralRule-count-one": "n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.0, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
"pluralRule-count-other": " @integer 2~9, 22~29, 102, 1002, … @decimal 0.2~0.9, 1.2~1.9, 10.2, 100.2, 1000.2, …"
},
"ps": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"pt": {
"pluralRule-count-one": "n = 0..2 and n != 2 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"pt-PT": {
"pluralRule-count-one": "n = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"rm": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ro": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-few": "v != 0 or n = 0 or n != 1 and n % 100 = 1..19 @integer 0, 2~16, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
"pluralRule-count-other": " @integer 20~35, 100, 1000, 10000, 100000, 1000000, …"
},
"rof": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"root": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ru": {
"pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …",
"pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …",
"pluralRule-count-many": "v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …",
"pluralRule-count-other": " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"rwk": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"sah": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"saq": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"se": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"seh": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ses": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"sg": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"sh": {
"pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
"pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …",
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"shi": {
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
"pluralRule-count-few": "n = 2..10 @integer 2~10 @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00",
"pluralRule-count-other": " @integer 11~26, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~1.9, 2.1~2.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"si": {
"pluralRule-count-one": "n = 0,1 or i = 0 and f = 1 @integer 0, 1 @decimal 0.0, 0.1, 1.0, 0.00, 0.01, 1.00, 0.000, 0.001, 1.000, 0.0000, 0.0001, 1.0000",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.2~0.9, 1.1~1.8, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"sk": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-few": "i = 2..4 and v = 0 @integer 2~4",
"pluralRule-count-many": "v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …"
},
"sl": {
"pluralRule-count-one": "v = 0 and i % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, …",
"pluralRule-count-two": "v = 0 and i % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, …",
"pluralRule-count-few": "v = 0 and i % 100 = 3..4 or v != 0 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …"
},
"sma": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"smi": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"smj": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"smn": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"sms": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-two": "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000",
"pluralRule-count-other": " @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"sn": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"so": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"sq": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"sr": {
"pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …",
"pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …",
"pluralRule-count-other": " @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ss": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ssy": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"st": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"sv": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"sw": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"syr": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ta": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"te": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"teo": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"th": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ti": {
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"tig": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"tk": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"tl": {
"pluralRule-count-one": "v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9 @integer 0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.3, 0.5, 0.7, 0.8, 1.0~1.3, 1.5, 1.7, 1.8, 2.0, 2.1, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …",
"pluralRule-count-other": " @integer 4, 6, 9, 14, 16, 19, 24, 26, 104, 1004, … @decimal 0.4, 0.6, 0.9, 1.4, 1.6, 1.9, 2.4, 2.6, 10.4, 100.4, 1000.4, …"
},
"tn": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"to": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"tr": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ts": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"tzm": {
"pluralRule-count-one": "n = 0..1 or n = 11..99 @integer 0, 1, 11~24 @decimal 0.0, 1.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0",
"pluralRule-count-other": " @integer 2~10, 100~106, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ug": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"uk": {
"pluralRule-count-one": "v = 0 and i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …",
"pluralRule-count-few": "v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …",
"pluralRule-count-many": "v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …",
"pluralRule-count-other": " @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ur": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"uz": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"ve": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"vi": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"vo": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"vun": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"wa": {
"pluralRule-count-one": "n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"wae": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"wo": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"xh": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"xog": {
"pluralRule-count-one": "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"yi": {
"pluralRule-count-one": "i = 1 and v = 0 @integer 1",
"pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"yo": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"zh": {
"pluralRule-count-other": " @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
},
"zu": {
"pluralRule-count-one": "i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04",
"pluralRule-count-other": " @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
}
}
}
}

View File

@@ -0,0 +1,18 @@
; top-most EditorConfig file
root = true
; Unix-style newlines
[*]
end_of_line = LF
[*.php]
indent_style = space
indent_size = 4
[*.test]
indent_style = space
indent_size = 4
[*.rst]
indent_style = space
indent_size = 4

Some files were not shown because too many files have changed in this diff Show More