CLDR Plural rules based plural form calculation

* Use the plurals.xml of CLDR for the plural rules of languages
* Use plurals-mediawiki.xml to override or extend the rules inside MW
* Remove the convertPlural method in each LanguageXX.php
* Parse and load the xml files in LocalisationCache
* Use the CLDRPluralRuleEvaluator.php for parsing the cldr plural rules
  (This is taken from Translate extension and might require a replacement
   parser without using eval)
* Add getPluralRules() to make the CLDR plural rules available to JS.

PS3: More method documentation, cleanup

Change-Id: I58a9cdfe60c7b9027bf031c91370472054f04ae2
This commit is contained in:
Santhosh Thottingal 2012-06-18 13:58:44 +05:30 committed by Tim Starling
parent c51a9a288b
commit bbbcf089db
21 changed files with 341 additions and 518 deletions

View file

@ -1006,6 +1006,7 @@ $wgAutoloadLocalClasses = array(
'FakeConverter' => 'languages/Language.php',
'Language' => 'languages/Language.php',
'LanguageConverter' => 'languages/LanguageConverter.php',
'CLDRPluralRuleEvaluator' => 'languages/utils/CLDRPluralRuleEvaluator.php',
# maintenance
'ConvertLinks' => 'maintenance/convertLinks.php',

View file

@ -110,7 +110,7 @@ class LocalisationCache {
'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
'imageFiles', 'preloadedMessages', 'namespaceGenderAliases',
'digitGroupingPattern'
'digitGroupingPattern', 'pluralRules'
);
/**
@ -118,7 +118,7 @@ class LocalisationCache {
* by a fallback sequence.
*/
static public $mergeableMapKeys = array( 'messages', 'namespaceNames',
'dateFormats', 'imageFiles', 'preloadedMessages',
'dateFormats', 'imageFiles', 'preloadedMessages', 'pluralRules'
);
/**
@ -154,6 +154,11 @@ class LocalisationCache {
*/
static public $preloadedKeys = array( 'dateFormats', 'namespaceNames' );
/*
* Associative array containing plural rules.
*/
var $pluralRules = array();
var $mergeableKeys = null;
/**
@ -202,6 +207,7 @@ class LocalisationCache {
$this->$var = $conf[$var];
}
}
$this->readPluralRules();
}
/**
@ -234,9 +240,9 @@ class LocalisationCache {
*/
public function getItem( $code, $key ) {
if ( !isset( $this->loadedItems[$code][$key] ) ) {
wfProfileIn( __METHOD__.'-load' );
wfProfileIn( __METHOD__ . '-load' );
$this->loadItem( $code, $key );
wfProfileOut( __METHOD__.'-load' );
wfProfileOut( __METHOD__ . '-load' );
}
if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
@ -256,9 +262,9 @@ class LocalisationCache {
public function getSubitem( $code, $key, $subkey ) {
if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
!isset( $this->loadedItems[$code][$key] ) ) {
wfProfileIn( __METHOD__.'-load' );
wfProfileIn( __METHOD__ . '-load' );
$this->loadSubitem( $code, $key, $subkey );
wfProfileOut( __METHOD__.'-load' );
wfProfileOut( __METHOD__ . '-load' );
}
if ( isset( $this->data[$code][$key][$subkey] ) ) {
@ -367,7 +373,7 @@ class LocalisationCache {
*/
public function isExpired( $code ) {
if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
wfDebug( __METHOD__."($code): forced reload\n" );
wfDebug( __METHOD__ . "($code): forced reload\n" );
return true;
}
@ -376,7 +382,7 @@ class LocalisationCache {
$preload = $this->store->get( $code, 'preload' );
// Different keys may expire separately, at least in LCStore_Accel
if ( $deps === null || $keys === null || $preload === null ) {
wfDebug( __METHOD__."($code): cache missing, need to make one\n" );
wfDebug( __METHOD__ . "($code): cache missing, need to make one\n" );
return true;
}
@ -386,7 +392,7 @@ class LocalisationCache {
// anymore (e.g. uninstalled extensions)
// When this happens, always expire the cache
if ( !$dep instanceof CacheDependency || $dep->isExpired() ) {
wfDebug( __METHOD__."($code): cache for $code expired due to " .
wfDebug( __METHOD__ . "($code): cache for $code expired due to " .
get_class( $dep ) . "\n" );
return true;
}
@ -481,11 +487,43 @@ class LocalisationCache {
} elseif ( $_fileType == 'aliases' ) {
$data = compact( 'aliases' );
} else {
throw new MWException( __METHOD__.": Invalid file type: $_fileType" );
throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" );
}
return $data;
}
/**
* Read the plural rule xml files.
* First the CLDR xml will be read and it will be extended with
* mediawiki specific tailoring.
* @since 1.20
*/
protected function readPluralRules() {
$CLDRPlural = __DIR__ . "/../languages/data/plurals.xml";
$MWPlural = __DIR__ . "/../languages/data/plurals-mediawiki.xml";
# Load CLDR plural rules
$this->parsePluralXML( $CLDRPlural );
if ( file_exists( $MWPlural ) ) {
// override or extend.
$this->parsePluralXML( $MWPlural );
}
}
private function parsePluralXML( $xmlFile ) {
$pluraldoc = new DOMDocument();
$pluraldoc->load( $xmlFile );
$rulesets = $pluraldoc->getElementsByTagName( "pluralRules" );
foreach ( $rulesets as $ruleset ) {
$codes = $ruleset->getAttribute( 'locales' );
$parsedRules = array();
$rules = $ruleset->getElementsByTagName( "pluralRule" );
foreach ( $rules as $rule ) {
$parsedRules[$rule->getAttribute( 'count' )] = $rule->nodeValue;
}
foreach ( explode( ' ', $codes ) as $code ) {
$this->pluralRules[$code] = $parsedRules;
}
}
}
/**
* Merge two localisation values, a primary and a fallback, overwriting the
@ -587,12 +625,12 @@ class LocalisationCache {
# Load the primary localisation from the source file
$fileName = Language::getMessagesFileName( $code );
if ( !file_exists( $fileName ) ) {
wfDebug( __METHOD__.": no localisation file for $code, using fallback to en\n" );
wfDebug( __METHOD__ . ": no localisation file for $code, using fallback to en\n" );
$coreData['fallback'] = 'en';
} else {
$deps[] = new FileDependency( $fileName );
$data = $this->readPHPFile( $fileName, 'core' );
wfDebug( __METHOD__.": got localisation for $code from source\n" );
wfDebug( __METHOD__ . ": got localisation for $code from source\n" );
# Merge primary localisation
foreach ( $data as $key => $value ) {
@ -605,7 +643,6 @@ class LocalisationCache {
if ( is_null( $coreData['fallback'] ) ) {
$coreData['fallback'] = $code === 'en' ? false : 'en';
}
if ( $coreData['fallback'] === false ) {
$coreData['fallbackSequence'] = array();
} else {
@ -654,7 +691,7 @@ class LocalisationCache {
$used = false;
foreach ( $data as $key => $item ) {
if( $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ) ) {
if ( $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ) ) {
$used = true;
}
}
@ -684,19 +721,22 @@ class LocalisationCache {
$page = str_replace( ' ', '_', $page );
}
# Decouple the reference to prevent accidental damage
unset($page);
unset( $page );
# Set the list keys
$allData['list'] = array();
foreach ( self::$splitKeys as $key ) {
$allData['list'][$key] = array_keys( $allData[$key] );
}
# Load CLDR plural rules
if ( isset( $this->pluralRules[$code] ) ) {
$allData['pluralRules'] = $this->pluralRules[$code];
}
# Run hooks
wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData ) );
if ( is_null( $allData['namespaceNames'] ) ) {
throw new MWException( __METHOD__.': Localisation data failed sanity check! ' .
throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' .
'Check that your languages/messages/MessagesEn.php file is intact.' );
}
@ -924,7 +964,7 @@ class LCStore_DB implements LCStore {
}
if ( !$code ) {
throw new MWException( __METHOD__.": Invalid language \"$code\"" );
throw new MWException( __METHOD__ . ": Invalid language \"$code\"" );
}
$this->dbw = wfGetDB( DB_MASTER );
@ -968,7 +1008,7 @@ class LCStore_DB implements LCStore {
}
if ( is_null( $this->currentLang ) ) {
throw new MWException( __CLASS__.': must call startWrite() before calling set()' );
throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' );
}
$this->batch[] = array(
@ -1040,7 +1080,7 @@ class LCStore_CDB implements LCStore {
}
// Close reader to stop permission errors on write
if( !empty($this->readers[$code]) ) {
if ( !empty( $this->readers[$code] ) ) {
$this->readers[$code]->close();
}
@ -1058,14 +1098,14 @@ class LCStore_CDB implements LCStore {
public function set( $key, $value ) {
if ( is_null( $this->writer ) ) {
throw new MWException( __CLASS__.': must call startWrite() before calling set()' );
throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' );
}
$this->writer->set( $key, serialize( $value ) );
}
protected function getFileName( $code ) {
if ( !$code || strpos( $code, '/' ) !== false ) {
throw new MWException( __METHOD__.": Invalid language \"$code\"" );
throw new MWException( __METHOD__ . ": Invalid language \"$code\"" );
}
return "{$this->directory}/l10n_cache-$code.cdb";
}
@ -1181,8 +1221,9 @@ class LocalisationCache_BulkLoad extends LocalisationCache {
while ( count( $this->data ) > $this->maxLoadedLangs && count( $this->mruLangs ) ) {
reset( $this->mruLangs );
$code = key( $this->mruLangs );
wfDebug( __METHOD__.": unloading $code\n" );
wfDebug( __METHOD__ . ": unloading $code\n" );
$this->unload( $code );
}
}
}

View file

@ -29,7 +29,7 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderModule {
protected $language;
/**
* Get the grammer forms for the site content language.
* Get the grammar forms for the site content language.
*
* @return array
*/
@ -37,6 +37,15 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderModule {
return $this->language->getGrammarForms();
}
/**
* Get the plural forms for the site content language.
*
* @return array
*/
protected function getPluralRules() {
return $this->language->getPluralRules();
}
/**
* Get the digit transform table for the content language
* Seperator transform table also required here to convert
@ -61,17 +70,19 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderModule {
* @return array
*/
protected function getData() {
return array( 'grammarForms' => $this->getSiteLangGrammarForms(),
'digitTransformTable' => $this->getDigitTransformTable()
);
return array(
'digitTransformTable' => $this->getDigitTransformTable(),
'grammarForms' => $this->getSiteLangGrammarForms(),
'pluralRules' => $this->getPluralRules(),
);
}
/**
* @param $context ResourceLoaderContext
* @return string: Javascript code
* @return string: JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
$this->language = Language::factory( $context ->getLanguage() );
$this->language = Language::factory( $context->getLanguage() );
return Xml::encodeJsCall( 'mw.language.setData', array(
$this->language->getCode(),
$this->getData()

View file

@ -266,9 +266,9 @@ class Language {
*/
public static function isValidBuiltInCode( $code ) {
if( !is_string($code) ) {
if ( !is_string( $code ) ) {
$type = gettype( $code );
if( $type === 'object' ) {
if ( $type === 'object' ) {
$addmsg = " of class " . get_class( $code );
} else {
$addmsg = '';
@ -742,7 +742,7 @@ class Language {
$names = array();
if( $inLanguage ) {
if ( $inLanguage ) {
# TODO: also include when $inLanguage is null, when this code is more efficient
wfRunHooks( 'LanguageGetTranslatedLanguageNames', array( &$names, $inLanguage ) );
}
@ -762,11 +762,11 @@ class Language {
$returnMw = array();
$coreCodes = array_keys( $mwNames );
foreach( $coreCodes as $coreCode ) {
foreach ( $coreCodes as $coreCode ) {
$returnMw[$coreCode] = $names[$coreCode];
}
if( $include === 'mwfile' ) {
if ( $include === 'mwfile' ) {
$namesMwFile = array();
# We do this using a foreach over the codes instead of a directory
# loop so that messages files in extensions will work correctly.
@ -3418,9 +3418,9 @@ class Language {
if ( !count( $forms ) ) {
return '';
}
$forms = $this->preConvertPlural( $forms, 2 );
return ( $count == 1 ) ? $forms[0] : $forms[1];
$pluralForm = $this->getPluralForm( $count );
$pluralForm = min( $pluralForm, count( $forms ) - 1 );
return $forms[$pluralForm];
}
/**
@ -4189,4 +4189,25 @@ class Language {
public function getConvRuleTitle() {
return $this->mConverter->getConvRuleTitle();
}
/**
* Get the plural rules for the language
* @since 1.20
* @return array Associative array with plural form, and plural rule as key-value pairs
*/
public function getPluralRules() {
return self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
}
/**
* Find the plural form matching to the given number
* It return the form index.
* @return int The index of the plural form
*/
private function getPluralForm( $number ) {
$pluralRules = $this->getPluralRules();
$form = CLDRPluralRuleEvaluator::evaluate( $number, $pluralRules );
return $form;
}
}

View file

@ -1,44 +0,0 @@
<?php
/**
* Amharic (አማርኛ) specific code.
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Language
*/
/**
* Amharic (አማርኛ)
*
* @ingroup Language
*/
class LanguageAm extends Language {
/**
* Use singular form for zero
*
* @param $count int
* @param $forms array
*
* @return string
*/
function convertPlural( $count, $forms ) {
if ( !count( $forms ) ) { return ''; }
$forms = $this->preConvertPlural( $forms, 2 );
return ( $count <= 1 ) ? $forms[0] : $forms[1];
}
}

View file

@ -29,31 +29,6 @@
*/
class LanguageAr extends Language {
/**
* @param $count int
* @param $forms array
* @return string
*/
function convertPlural( $count, $forms ) {
if ( !count( $forms ) ) { return ''; }
$forms = $this->preConvertPlural( $forms, 6 );
if ( $count == 0 ) {
$index = 0;
} elseif ( $count == 1 ) {
$index = 1;
} elseif ( $count == 2 ) {
$index = 2;
} elseif ( $count % 100 >= 3 && $count % 100 <= 10 ) {
$index = 3;
} elseif ( $count % 100 >= 11 && $count % 100 <= 99 ) {
$index = 4;
} else {
$index = 5;
}
return $forms[$index];
}
/**
* Temporary hack for bug 9413: replace Arabic presentation forms with their
* standard equivalents.

View file

@ -1,62 +0,0 @@
<?php
/**
* Belarusian normative (Беларуская мова) specific code.
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
* @license http://www.gnu.org/copyleft/fdl.html GNU Free Documentation License
* @ingroup Language
*/
/**
* Belarusian normative (Беларуская мова)
*
* This is still the version from Be-x-old, only duplicated for consistency of
* plural and grammar functions. If there are errors please send a patch.
*
* @ingroup Language
* @see http://be.wikipedia.org/wiki/Talk:LanguageBe.php
*/
class LanguageBe extends Language {
/**
* @param $count int
* @param $forms array
*
* @return string
*/
function convertPlural( $count, $forms ) {
if ( !count( $forms ) ) { return ''; }
// @todo FIXME: CLDR defines 4 plural forms instead of 3
// http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
$forms = $this->preConvertPlural( $forms, 3 );
if ( $count > 10 && floor( ( $count % 100 ) / 10 ) == 1 ) {
return $forms[2];
} else {
switch ( $count % 10 ) {
case 1: return $forms[0];
case 2:
case 3:
case 4: return $forms[1];
default: return $forms[2];
}
}
}
}

View file

@ -1,44 +0,0 @@
<?php
/**
* Bihari (भोजपुरी) specific code.
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Language
*/
/**
* Bihari (भोजपुरी)
*
* @ingroup Language
*/
class LanguageBh extends Language {
/**
* Use singular form for zero
*
* @param $count int
* @param $forms array
*
* @return string
*/
function convertPlural( $count, $forms ) {
if ( !count( $forms ) ) { return ''; }
$forms = $this->preConvertPlural( $forms, 2 );
return ( $count <= 1 ) ? $forms[0] : $forms[1];
}
}

View file

@ -28,29 +28,6 @@
*/
class LanguageBs extends Language {
/**
* @param $count int
* @param $forms array
* @return string
*/
function convertPlural( $count, $forms ) {
if ( !count( $forms ) ) { return ''; }
$forms = $this->preConvertPlural( $forms, 3 );
// @todo FIXME: CLDR defines 4 plural forms instead of 3. Plural for decimals is missing.
// http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
if ( $count > 10 && floor( ( $count % 100 ) / 10 ) == 1 ) {
return $forms[2];
} else {
switch ( $count % 10 ) {
case 1: return $forms[0];
case 2:
case 3:
case 4: return $forms[1];
default: return $forms[2];
}
}
}
/**
* Convert from the nominative form of a noun to some other case

View file

@ -1,56 +0,0 @@
<?php
/**
* Czech (čeština [subst.], český [adj.], česky [adv.]) specific code.
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Language
*/
/**
* Czech (čeština [subst.], český [adj.], česky [adv.])
*
* @ingroup Language
*/
class LanguageCs extends Language {
/**
* Plural transformations
* Invoked by putting
* {{plural:count|form1|form2-4|form0,5+}} for two forms plurals
* {{plural:count|form1|form0,2+}} for single form plurals
* in a message
* @param $count int
* @param $forms array
* @return string
*/
function convertPlural( $count, $forms ) {
if ( !count( $forms ) ) { return ''; }
$forms = $this->preConvertPlural( $forms, 3 );
switch ( $count ) {
case 1:
return $forms[0];
case 2:
case 3:
case 4:
return $forms[1];
default:
return $forms[2];
}
}
}

View file

@ -20,7 +20,7 @@
* @file
* @ingroup Language
*/
/**
* Old Church Slavonic (Ѩзыкъ словѣньскъ)
*

View file

@ -1,50 +0,0 @@
<?php
/**
* Welsh (Cymraeg) specific code.
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @author Niklas Laxström
* @ingroup Language
*/
/**
* Welsh (Cymraeg)
*
* @ingroup Language
*/
class LanguageCy extends Language {
/**
* @param $count int
* @param $forms array
* @return string
*/
function convertPlural( $count, $forms ) {
if ( !count( $forms ) ) { return ''; }
$forms = $this->preConvertPlural( $forms, 6 );
$count = abs( $count );
if ( $count >= 0 && $count <= 3 ) {
return $forms[$count];
} elseif ( $count == 6 ) {
return $forms[4];
} else {
return $forms[5];
}
}
}

View file

@ -54,21 +54,4 @@ class LanguageDsb extends Language {
return $word; # this will return the original value for 'nominatiw' (nominativ) and all undefined case values
}
/**
* @param $count int
* @param $forms array
* @return string
*/
function convertPlural( $count, $forms ) {
if ( !count( $forms ) ) { return ''; }
$forms = $this->preConvertPlural( $forms, 4 );
switch ( abs( $count ) % 100 ) {
case 1: return $forms[0]; // singular
case 2: return $forms[1]; // dual
case 3:
case 4: return $forms[2]; // plural
default: return $forms[3]; // pluralgen
}
}
}

View file

@ -1,44 +0,0 @@
<?php
/**
* French (Français) specific code.
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Language
*/
/**
* French (Français)
*
* @ingroup Language
*/
class LanguageFr extends Language {
/**
* Use singular form for zero (see bug 7309)
*
* @param $count int
* @param $forms array
*
* @return string
*/
function convertPlural( $count, $forms ) {
if ( !count( $forms ) ) { return ''; }
$forms = $this->preConvertPlural( $forms, 2 );
return ( $count <= 1 ) ? $forms[0] : $forms[1];
}
}

View file

@ -64,24 +64,4 @@ class LanguageGa extends Language {
return $word;
}
/**
* @param $count int
* @param $forms array
* @return string
*/
function convertPlural( $count, $forms ) {
if ( !count( $forms ) ) { return ''; }
// plural forms per http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ga
$forms = $this->preConvertPlural( $forms, 3 );
if ( $count == 1 ) {
$index = 0;
} elseif ( $count == 2 ) {
$index = 1;
} else {
$index = 2;
}
return $forms[$index];
}
}

View file

@ -1,68 +0,0 @@
<?php
/**
* Scots Gaelic (Gàidhlig) specific code.
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @author Raimond Spekking
* @author Niklas Laxström
* @ingroup Language
*/
/**
* Scots Gaelic (Gàidhlig)
*
* @ingroup Language
*/
class LanguageGd extends Language {
/**
* Plural form transformations
* Based on this discussion: http://translatewiki.net/wiki/Thread:Support/New_plural_rules_for_Scots_Gaelic_(gd)
*
* $forms[0] - 1
* $forms[1] - 2
* $forms[2] - 11
* $forms[3] - 12
* $forms[4] - 3-10, 13-19
* $forms[5] - 0, 20, rest
*
* @param $count int
* @param $forms array
*
* @return string
*/
function convertPlural( $count, $forms ) {
if ( !count( $forms ) ) { return ''; }
$forms = $this->preConvertPlural( $forms, 6 );
$count = abs( $count );
if ( $count == 1 ) {
return $forms[0];
} elseif ( $count == 2 ) {
return $forms[1];
} elseif ( $count == 11 ) {
return $forms[2];
} elseif ( $count == 12 ) {
return $forms[3];
} elseif ( ($count >= 3 && $count <= 10) || ($count >= 13 && $count <= 19) ) {
return $forms[4];
} else {
return $forms[5];
}
}
}

View file

@ -68,23 +68,4 @@ class LanguageHe extends Language {
return $word;
}
/**
* Gets a number and uses the suited form of the word.
*
* @param $count Integer: the number of items
* @param $forms Array with 3 items: the three plural forms
* @return String: the suited form of word
*/
function convertPlural( $count, $forms ) {
if ( !count( $forms ) ) { return ''; }
$forms = $this->preConvertPlural( $forms, 3 );
if ( $count == 1 ) {
return $forms[0]; // Singular
} elseif ( $count == 2 ) {
return $forms[2]; // Dual or plural if dual is not provided (filled in preConvertPlural)
} else {
return $forms[1]; // Plural
}
}
}

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd">
<supplementalData>
<plurals>
<pluralRules locales="he">
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
</pluralRules>
</plurals>
<pluralRules locales="dsb">
<pluralRule count="one">n mod 100 is 1</pluralRule>
<pluralRule count="two">n mod 100 is 2</pluralRule>
<pluralRule count="few">n mod 100 in 3..4</pluralRule>
</pluralRules>
<pluralRules locales="cu">
<pluralRule count="one">n mod 10 is 1</pluralRule>
<pluralRule count="two">n mod 10 is 2</pluralRule>
<pluralRule count="few">n mod 10 in 3..4</pluralRule>
</pluralRules>
<!-- Plural form transformations
Based on this discussion: http://translatewiki.net/wiki/Thread:Support/New_plural_rules_for_Scots_Gaelic_(gd)
$forms[0] - 1
$forms[1] - 2
$forms[2] - 11
$forms[3] - 12
$forms[4] - 3-10, 13-19
$forms[5] - 0, 20, rest -->
<pluralRules locales="gd">
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
<pluralRule count="elevan">n is 11</pluralRule>
<pluralRule count="twelve">n is 12</pluralRule>
<pluralRule count="few">n in 3..10 or n in 13..19</pluralRule>
</pluralRules>
</supplementalData>

114
languages/data/plurals.xml Normal file
View file

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd">
<supplementalData>
<version number="$Revision: 6155 $"/>
<generation date="$Date: 2011-09-21 23:51:12 +0530 (ബു, 21 സെപ് 2011) $"/>
<plurals>
<!-- if locale is known to have no plurals, there are no rules -->
<pluralRules locales="az bm bo dz fa id ig ii hu ja jv ka kde kea km kn ko lo ms my sah ses sg th to tr vi wo yo zh"/>
<pluralRules locales="ar">
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
<pluralRule count="few">n mod 100 in 3..10</pluralRule>
<pluralRule count="many">n mod 100 in 11..99</pluralRule>
</pluralRules>
<pluralRules locales="asa af bem bez bg bn brx ca cgg chr da de dv ee el en eo es et eu fi fo fur fy gl gsw gu ha haw he is it jmc kaj kcg kk kl ksb ku lb lg mas ml mn mr nah nb nd ne nl nn no nr ny nyn om or pa pap ps pt rof rm rwk saq seh sn so sq ss ssy st sv sw syr ta te teo tig tk tn ts ur wae ve vun xh xog zu">
<pluralRule count="one">n is 1</pluralRule>
</pluralRules>
<pluralRules locales="ak am bh fil tl guw hi ln mg nso ti wa">
<pluralRule count="one">n in 0..1</pluralRule>
</pluralRules>
<pluralRules locales="ff fr kab">
<pluralRule count="one">n within 0..2 and n is not 2</pluralRule>
</pluralRules>
<pluralRules locales="lv">
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n mod 10 is 1 and n mod 100 is not 11</pluralRule>
</pluralRules>
<pluralRules locales="iu kw naq se sma smi smj smn sms">
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
</pluralRules>
<pluralRules locales="ga"> <!-- http://unicode.org/cldr/trac/ticket/3915 -->
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
<pluralRule count="few">n in 3..6</pluralRule>
<pluralRule count="many">n in 7..10</pluralRule>
</pluralRules>
<pluralRules locales="ro mo">
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="few">n is 0 OR n is not 1 AND n mod 100 in 1..19</pluralRule>
</pluralRules>
<pluralRules locales="lt">
<pluralRule count="one">n mod 10 is 1 and n mod 100 not in 11..19</pluralRule>
<pluralRule count="few">n mod 10 in 2..9 and n mod 100 not in 11..19</pluralRule>
</pluralRules>
<pluralRules locales="be bs hr ru sh sr uk">
<pluralRule count="one">n mod 10 is 1 and n mod 100 is not 11</pluralRule>
<pluralRule count="few">n mod 10 in 2..4 and n mod 100 not in 12..14</pluralRule>
<pluralRule count="many">n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14</pluralRule>
<!-- others are fractions -->
</pluralRules>
<pluralRules locales="cs sk">
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="few">n in 2..4</pluralRule>
</pluralRules>
<pluralRules locales="pl">
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="few">n mod 10 in 2..4 and n mod 100 not in 12..14</pluralRule>
<pluralRule count="many">n is not 1 and n mod 10 in 0..1 or n mod 10 in 5..9 or n mod 100 in 12..14</pluralRule>
<!-- others are fractions -->
<!-- and n mod 100 not in 22..24 from Tamplin -->
</pluralRules>
<pluralRules locales="sl">
<pluralRule count="one">n mod 100 is 1</pluralRule>
<pluralRule count="two">n mod 100 is 2</pluralRule>
<pluralRule count="few">n mod 100 in 3..4</pluralRule>
</pluralRules>
<pluralRules locales="mt"> <!-- from Tamplin's data -->
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="few">n is 0 or n mod 100 in 2..10</pluralRule>
<pluralRule count="many">n mod 100 in 11..19</pluralRule>
</pluralRules>
<pluralRules locales="mk"> <!-- from Tamplin's data -->
<pluralRule count="one">n mod 10 is 1 and n is not 11</pluralRule>
</pluralRules>
<pluralRules locales="cy"> <!-- from http://www.saltcymru.org/wordpress/?p=99&lang=en -->
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
<pluralRule count="few">n is 3</pluralRule>
<pluralRule count="many">n is 6</pluralRule>
</pluralRules>
<pluralRules locales="lag">
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n within 0..2 and n is not 0 and n is not 2</pluralRule>
</pluralRules>
<pluralRules locales="shi">
<pluralRule count="one">n within 0..1</pluralRule>
<pluralRule count="few">n in 2..10</pluralRule>
</pluralRules>
<pluralRules locales="br"> <!-- from http://unicode.org/cldr/trac/ticket/2886 -->
<pluralRule count="one">n mod 10 is 1 and n mod 100 not in 11,71,91</pluralRule>
<pluralRule count="two">n mod 10 is 2 and n mod 100 not in 12,72,92</pluralRule>
<pluralRule count="few">n mod 10 in 3..4,9 and n mod 100 not in 10..19,70..79,90..99</pluralRule>
<pluralRule count="many">n mod 1000000 is 0 and n is not 0</pluralRule>
</pluralRules>
<pluralRules locales="ksh">
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n is 1</pluralRule>
</pluralRules>
<pluralRules locales="tzm">
<pluralRule count="one">n in 0..1 or n in 11..99</pluralRule>
</pluralRules>
<pluralRules locales="gv">
<pluralRule count="one">n mod 10 in 1..2 or n mod 20 is 0</pluralRule>
</pluralRules>
<pluralRules locales="gd">
<pluralRule count="one">n in 1,11</pluralRule>
<pluralRule count="two">n in 2,12</pluralRule>
<pluralRule count="few">n in 3..10,13..19</pluralRule>
</pluralRules>
</plurals>
</supplementalData>

View file

@ -0,0 +1,72 @@
<?php
/**
* Parse and evaluate a plural rule
*
* @author Niklas Laxstrom
*
* @copyright Copyright © 2010-2012, Niklas Laxström
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
* @file
*/
class CLDRPluralRuleEvaluator {
/**
* Evaluate a number against a set of plural rules. If a rule passes,
* return the index of plural rule.
*
* @param int The number to be evaluated against the rules
* @param array The associative array of plural rules in pluralform => rule format.
* @return int The index of the plural form which passed the evaluation
*/
public static function evaluate( $number, $rules ) {
$formIndex = 0;
if ( !$rules ) {
return 0;
}
foreach ( $rules as $form => $rule ) {
$parsedRule = self::parseCLDRRule( $rule, $number );
// FIXME eval is bad.
if ( eval( "return $parsedRule;" ) ) {
return $formIndex;
}
$formIndex++;
}
return $formIndex;
}
private static function parseCLDRRule( $rule ) {
$rule = preg_replace( '/\bn\b/', '$number', $rule );
$rule = preg_replace( '/([^ ]+) mod (\d+)/', 'self::mod(\1,\2)', $rule );
$rule = preg_replace( '/([^ ]+) is not (\d+)/' , '\1!=\2', $rule );
$rule = preg_replace( '/([^ ]+) is (\d+)/', '\1==\2', $rule );
$rule = preg_replace( '/([^ ]+) not in (\d+)\.\.(\d+)/', '!self::in(\1,\2,\3)', $rule );
$rule = preg_replace( '/([^ ]+) not within (\d+)\.\.(\d+)/', '!self::within(\1,\2,\3)', $rule );
$rule = preg_replace( '/([^ ]+) in (\d+)\.\.(\d+)/', 'self::in(\1,\2,\3)', $rule );
$rule = preg_replace( '/([^ ]+) within (\d+)\.\.(\d+)/', 'self::within(\1,\2,\3)', $rule );
// AND takes precedence over OR
$andrule = '/([^ ]+) and ([^ ]+)/i';
while ( preg_match( $andrule, $rule ) ) {
$rule = preg_replace( $andrule, '(\1&&\2)', $rule );
}
$orrule = '/([^ ]+) or ([^ ]+)/i';
while ( preg_match( $orrule, $rule ) ) {
$rule = preg_replace( $orrule, '(\1||\2)', $rule );
}
return $rule;
}
private static function in( $num, $low, $high ) {
return is_int( $num ) && $num >= $low && $num <= $high;
}
private static function within( $num, $low, $high ) {
return $num >= $low && $num <= $high;
}
private static function mod( $num, $mod ) {
if ( is_int( $num ) ) {
return (int) fmod( $num, $mod );
}
return fmod( $num, $mod );
}
}

View file

@ -18,31 +18,31 @@ class LanguageHeTest extends MediaWikiTestCase {
/** @dataProvider providerPluralDual */
function testPluralDual( $result, $value ) {
$forms = array( 'one', 'many', 'two' );
$forms = array( 'one', 'two', 'other' );
$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
}
function providerPluralDual() {
return array (
array( 'many', 0 ), // Zero -> plural
array( 'other', 0 ), // Zero -> plural
array( 'one', 1 ), // Singular
array( 'two', 2 ), // Dual
array( 'many', 3 ), // Plural
array( 'other', 3 ), // Plural
);
}
/** @dataProvider providerPlural */
function testPlural( $result, $value ) {
$forms = array( 'one', 'many' );
$forms = array( 'one', 'other' );
$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
}
function providerPlural() {
return array (
array( 'many', 0 ), // Zero -> plural
array( 'other', 0 ), // Zero -> plural
array( 'one', 1 ), // Singular
array( 'many', 2 ), // Plural, no dual provided
array( 'many', 3 ), // Plural
array( 'other', 2 ), // Plural, no dual provided
array( 'other', 3 ), // Plural
);
}
}