diff --git a/core/includes/common.inc b/core/includes/common.inc index a4fcf511ce198c0e056813364ccaddc85f547d82..9c6564774974c8771de6aaf184835ecc99dcbead 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1723,7 +1723,8 @@ function format_plural($count, $singular, $plural, array $args = array(), array // Get the plural index through the gettext formula. $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, isset($options['langcode']) ? $options['langcode'] : NULL) : -1; - // Backwards compatibility. + // If the index cannot be computed, use the plural as a fallback (which + // allows for most flexiblity with the replaceable @count value). if ($index < 0) { return t($plural, $args, $options); } diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 1f3c77568b5b80df5b291e5cd9430d54fe516997..573139386a5c107197e0c4ad540fac59125a3f0b 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -743,29 +743,46 @@ function locale_reset() { * @param $langcode * Optional language code to translate to a language other than * what is used to display the page. + * @return + * The numeric index of the plural variant to use for this $langcode and + * $count combination or -1 if the language was not found or does not have a + * plural formula. */ function locale_get_plural($count, $langcode = NULL) { global $language; - $locale_plurals = &drupal_static(__FUNCTION__, array()); - $plurals = &drupal_static(__FUNCTION__ . ':plurals', array()); + + // Used to locally cache the plural formulas for all languages. + $plural_formulas = &drupal_static(__FUNCTION__, array()); + // Used to store precomputed plural indexes corresponding to numbers + // individually for each language. + $plural_indexes = &drupal_static(__FUNCTION__ . ':plurals', array()); $langcode = $langcode ? $langcode : $language->langcode; - if (!isset($plurals[$langcode][$count])) { - if (empty($locale_plural_formulas)) { - $locale_plurals = variable_get('locale_translation_plurals', array()); + if (!isset($plural_indexes[$langcode][$count])) { + // Retrieve and statically cache the plural formulas for all languages. + if (empty($plural_formulas)) { + $plural_formulas = variable_get('locale_translation_plurals', array()); } - if (!empty($locale_plurals[$langcode])) { + // If there is a plural formula for the language, evaluate it for the given + // $count and statically cache the result for the combination of language + // and count, since the result will always be identical. + if (!empty($plural_formulas[$langcode])) { + // $n is used inside the expression in the eval(). $n = $count; - $plurals[$langcode][$count] = @eval('return intval(' . $locale_plurals[$langcode]['formula'] . ');'); - return $plurals[$langcode][$count]; + $plural_indexes[$langcode][$count] = @eval('return intval(' . $plural_formulas[$langcode]['formula'] . ');'); + } + // In case there is no plural formula for English (no imported translation + // for English), use a default formula. + elseif ($langcode == 'en') { + $plural_indexes[$langcode][$count] = (int) ($count != 1); } + // Otherwise, return -1 (unknown). else { - $plurals[$langcode][$count] = -1; - return -1; + $plural_indexes[$langcode][$count] = -1; } } - return $plurals[$langcode][$count]; + return $plural_indexes[$langcode][$count]; } diff --git a/core/modules/locale/locale.test b/core/modules/locale/locale.test index 0ba8469380da0886c7a0d91835fe6ec89352809d..2070d1ce672a4c41df6f708764ad0785d9f9dc38 100644 --- a/core/modules/locale/locale.test +++ b/core/modules/locale/locale.test @@ -585,6 +585,129 @@ class LocaleTranslationFunctionalTest extends DrupalWebTestCase { } } +/** + * Tests plural index computation functionality. + */ +class LocalePluralFormatTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Plural formula evaluation', + 'description' => 'Tests plural formula evaluation for various languages.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale', 'locale_test'); + + $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages')); + $this->drupalLogin($admin_user); + } + + /** + * Tests locale_get_plural() functionality. + */ + function testGetPluralFormat() { + // Import some .po files with formulas to set up the environment. + // These will also add the languages to the system and enable them. + $this->importPoFile($this->getPoFileWithSimplePlural(), array( + 'langcode' => 'fr', + )); + $this->importPoFile($this->getPoFileWithComplexPlural(), array( + 'langcode' => 'hr', + )); + + // Reset static caches from locale_get_plural() to ensure we get fresh data. + drupal_static_reset('locale_get_plural'); + drupal_static_reset('locale_get_plural:plurals'); + + // Test locale_get_plural() for English (no formula presnt). + $this->assertIdentical(locale_get_plural(1, 'en'), 0, t("Computed plural index for 'en' with count 1 is 0.")); + $this->assertIdentical(locale_get_plural(0, 'en'), 1, t("Computed plural index for 'en' with count 0 is 1.")); + $this->assertIdentical(locale_get_plural(5, 'en'), 1, t("Computed plural index for 'en' with count 5 is 1.")); + + // Test locale_get_plural() for French (simpler formula). + $this->assertIdentical(locale_get_plural(1, 'fr'), 0, t("Computed plural index for 'fr' with count 1 is 0.")); + $this->assertIdentical(locale_get_plural(0, 'fr'), 0, t("Computed plural index for 'fr' with count 0 is 0.")); + $this->assertIdentical(locale_get_plural(5, 'fr'), 1, t("Computed plural index for 'fr' with count 5 is 1.")); + + // Test locale_get_plural() for Croatian (more complex formula). + $this->assertIdentical(locale_get_plural( 1, 'hr'), 0, t("Computed plural index for 'hr' with count 1 is 0.")); + $this->assertIdentical(locale_get_plural(21, 'hr'), 0, t("Computed plural index for 'hr' with count 21 is 0.")); + $this->assertIdentical(locale_get_plural( 0, 'hr'), 2, t("Computed plural index for 'hr' with count 0 is 2.")); + $this->assertIdentical(locale_get_plural( 2, 'hr'), 1, t("Computed plural index for 'hr' with count 2 is 1.")); + $this->assertIdentical(locale_get_plural( 8, 'hr'), 2, t("Computed plural index for 'hr' with count 8 is 2.")); + + // Test locale_get_plural() for Hungarian (nonexistent language). + $this->assertIdentical(locale_get_plural( 1, 'hu'), -1, t("Computed plural index for 'hu' with count 1 is -1.")); + $this->assertIdentical(locale_get_plural(21, 'hu'), -1, t("Computed plural index for 'hu' with count 21 is -1.")); + $this->assertIdentical(locale_get_plural( 0, 'hu'), -1, t("Computed plural index for 'hu' with count 0 is -1.")); + } + + /** + * Imports a standalone .po file in a given language. + * + * @param $contents + * Contents of the .po file to import. + * @param $options + * Additional options to pass to the translation import form. + */ + function importPoFile($contents, array $options = array()) { + $name = tempnam('temporary://', "po_") . '.po'; + file_put_contents($name, $contents); + $options['files[file]'] = $name; + $this->drupalPost('admin/config/regional/translate/import', $options, t('Import')); + drupal_unlink($name); + } + + /** + * Returns a .po file with a simple plural formula. + */ + function getPoFileWithSimplePlural() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 7\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "1 hour" +msgid_plural "@count hours" +msgstr[0] "1 heure" +msgstr[1] "@count heures" + +msgid "Monday" +msgstr "lundi" +EOF; + } + + /** + * Returns a .po file with a complex plural formula. + */ + function getPoFileWithComplexPlural() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 7\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n" + +msgid "1 hour" +msgid_plural "@count hours" +msgstr[0] "@count sat" +msgstr[1] "@count sata" +msgstr[2] "@count sati" + +msgid "Monday" +msgstr "Ponedjeljak" +EOF; + } +} + /** * Functional tests for the import of translation files. */