LocalePluralFormatTest.php 12.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
<?php

/**
 * @file
 * Definition of Drupal\locale\Tests\LocalePluralFormatTest.
 */

namespace Drupal\locale\Tests;

use Drupal\simpletest\WebTestBase;

/**
13 14 15
 * Tests plural handling for various languages.
 *
 * @group locale
16 17
 */
class LocalePluralFormatTest extends WebTestBase {
18 19 20 21 22 23 24 25

  /**
   * Modules to enable.
   *
   * @var array
   */
  public static $modules = array('locale');

26 27 28
  /**
   * {@inheritdoc}
   */
29
  protected function setUp() {
30
    parent::setUp();
31 32 33 34 35 36

    $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
    $this->drupalLogin($admin_user);
  }

  /**
37 38
   * Tests locale_get_plural() and \Drupal::translation()->formatPlural()
   * functionality.
39
   */
40
  public function testGetPluralFormat() {
41
    // Import some .po files with formulas to set up the environment.
42
    // These will also add the languages to the system.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
    $this->importPoFile($this->getPoFileWithSimplePlural(), array(
      'langcode' => 'fr',
    ));
    $this->importPoFile($this->getPoFileWithComplexPlural(), array(
      'langcode' => 'hr',
    ));

    // Attempt to import some broken .po files as well to prove that these
    // will not overwrite the proper plural formula imported above.
    $this->importPoFile($this->getPoFileWithMissingPlural(), array(
      'langcode' => 'fr',
      'overwrite_options[not_customized]' => TRUE,
    ));
    $this->importPoFile($this->getPoFileWithBrokenPlural(), array(
      'langcode' => 'hr',
      'overwrite_options[not_customized]' => TRUE,
    ));

    // 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');
    drupal_static_reset('locale');

    // Expected plural translation strings for each plural index.
    $plural_strings = array(
      // English is not imported in this case, so we assume built-in text
      // and formulas.
      'en' => array(
        0 => '1 hour',
        1 => '@count hours',
      ),
      'fr' => array(
75
        0 => '@count heure',
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
        1 => '@count heures',
      ),
      'hr' => array(
        0 => '@count sat',
        1 => '@count sata',
        2 => '@count sati',
      ),
      // Hungarian is not imported, so it should assume the same text as
      // English, but it will always pick the plural form as per the built-in
      // logic, so only index -1 is relevant with the plural value.
      'hu' => array(
        0 => '1 hour',
        -1 => '@count hours',
      ),
    );

    // Expected plural indexes precomputed base on the plural formulas with
    // given $count value.
    $plural_tests = array(
      'en' => array(
        1 => 0,
        0 => 1,
        5 => 1,
99 100
        123 => 1,
        235 => 1,
101 102 103 104 105
      ),
      'fr' => array(
        1 => 0,
        0 => 0,
        5 => 1,
106 107
        123 => 1,
        235 => 1,
108 109 110 111 112 113 114
      ),
      'hr' => array(
        1 => 0,
        21 => 0,
        0 => 2,
        2 => 1,
        8 => 2,
115 116
        123 => 1,
        235 => 2,
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
      ),
      'hu' => array(
        1 => -1,
        21 => -1,
        0 => -1,
      ),
    );

    foreach ($plural_tests as $langcode => $tests) {
      foreach ($tests as $count => $expected_plural_index) {
        // Assert that the we get the right plural index.
        $this->assertIdentical(locale_get_plural($count, $langcode), $expected_plural_index, 'Computed plural index for ' . $langcode . ' for count ' . $count . ' is ' . $expected_plural_index);
        // Assert that the we get the right translation for that. Change the
        // expected index as per the logic for translation lookups.
        $expected_plural_index = ($count == 1) ? 0 : $expected_plural_index;
        $expected_plural_string = str_replace('@count', $count, $plural_strings[$langcode][$expected_plural_index]);
133
        $this->assertIdentical(\Drupal::translation()->formatPlural($count, '1 hour', '@count hours', array(), array('langcode' => $langcode)), $expected_plural_string, 'Plural translation of 1 hours / @count hours for count ' . $count . ' in ' . $langcode . ' is ' . $expected_plural_string);
134 135 136 137 138 139 140
      }
    }
  }

  /**
   * Tests plural editing and export functionality.
   */
141
  public function testPluralEditExport() {
142
    // Import some .po files with formulas to set up the environment.
143
    // These will also add the languages to the system.
144 145 146 147 148 149 150 151
    $this->importPoFile($this->getPoFileWithSimplePlural(), array(
      'langcode' => 'fr',
    ));
    $this->importPoFile($this->getPoFileWithComplexPlural(), array(
      'langcode' => 'hr',
    ));

    // Get the French translations.
152
    $this->drupalPostForm('admin/config/regional/translate/export', array(
153 154 155
      'langcode' => 'fr',
    ), t('Export'));
    // Ensure we have a translation file.
156
    $this->assertRaw('# French translation of Drupal', 'Exported French translation file.');
157
    // Ensure our imported translations exist in the file.
158
    $this->assertRaw("msgid \"Monday\"\nmsgstr \"lundi\"", 'French translations present in exported file.');
159
    // Check for plural export specifically.
160
    $this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count heure\"\nmsgstr[1] \"@count heures\"", 'Plural translations exported properly.');
161 162

    // Get the Croatian translations.
163
    $this->drupalPostForm('admin/config/regional/translate/export', array(
164 165 166
      'langcode' => 'hr',
    ), t('Export'));
    // Ensure we have a translation file.
167
    $this->assertRaw('# Croatian translation of Drupal', 'Exported Croatian translation file.');
168
    // Ensure our imported translations exist in the file.
169
    $this->assertRaw("msgid \"Monday\"\nmsgstr \"Ponedjeljak\"", 'Croatian translations present in exported file.');
170
    // Check for plural export specifically.
171
    $this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count sat\"\nmsgstr[1] \"@count sata\"\nmsgstr[2] \"@count sati\"", 'Plural translations exported properly.');
172 173 174

    // Check if the source appears on the translation page.
    $this->drupalGet('admin/config/regional/translate');
175 176
    $this->assertText("1 hour");
    $this->assertText("@count hours");
177 178

    // Look up editing page for this plural string and check fields.
179
    $path = 'admin/config/regional/translate/';
180 181 182
    $search = array(
      'langcode' => 'hr',
    );
183
    $this->drupalPostForm($path, $search, t('Filter'));
184
    // Labels for plural editing elements.
185 186 187 188 189 190 191 192 193 194 195
    $this->assertText('Singular form');
    $this->assertText('First plural form');
    $this->assertText('2. plural form');
    $this->assertNoText('3. plural form');

    // Plural values for langcode hr.
    $this->assertText('@count sat');
    $this->assertText('@count sata');
    $this->assertText('@count sati');

    // Edit langcode hr translations and see if that took effect.
196
    $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", array(':source' => "1 hour" . LOCALE_PLURAL_DELIMITER . "@count hours"))->fetchField();
197
    $edit = array(
198
      "strings[$lid][translations][1]" => '@count sata edited',
199
    );
200
    $this->drupalPostForm($path, $edit, t('Save translations'));
201 202 203 204

    $search = array(
      'langcode' => 'fr',
    );
205
    $this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
206
    // Plural values for the langcode fr.
207
    $this->assertText('@count heure');
208 209 210 211 212
    $this->assertText('@count heures');
    $this->assertNoText('2. plural form');

    // Edit langcode fr translations and see if that took effect.
    $edit = array(
213
      "strings[$lid][translations][0]" => '@count heure edited',
214
    );
215
    $this->drupalPostForm($path, $edit, t('Save translations'));
216 217 218 219 220

    // Inject a plural source string to the database. We need to use a specific
    // langcode here because the language will be English by default and will
    // not save our source string for performance optimization if we do not ask
    // specifically for a language.
221
    \Drupal::translation()->formatPlural(1, '1 day', '@count days', array(), array('langcode' => 'fr'));
222
    $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", array(':source' => "1 day" . LOCALE_PLURAL_DELIMITER . "@count days"))->fetchField();
223 224 225 226 227
    // Look up editing page for this plural string and check fields.
    $search = array(
      'string' => '1 day',
      'langcode' => 'fr',
    );
228
    $this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
229

230
    // Save complete translations for the string in langcode fr.
231
    $edit = array(
232 233
      "strings[$lid][translations][0]" => '1 jour',
      "strings[$lid][translations][1]" => '@count jours',
234
    );
235
    $this->drupalPostForm($path, $edit, t('Save translations'));
236

237 238 239 240 241
    // Save complete translations for the string in langcode hr.
    $search = array(
      'string' => '1 day',
      'langcode' => 'hr',
    );
242
    $this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
243 244 245 246 247 248

    $edit = array(
      "strings[$lid][translations][0]" => '@count dan',
      "strings[$lid][translations][1]" => '@count dana',
      "strings[$lid][translations][2]" => '@count dana',
    );
249
    $this->drupalPostForm($path, $edit, t('Save translations'));
250

251
    // Get the French translations.
252
    $this->drupalPostForm('admin/config/regional/translate/export', array(
253 254 255
      'langcode' => 'fr',
    ), t('Export'));
    // Check for plural export specifically.
256
    $this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count heure edited\"\nmsgstr[1] \"@count heures\"", 'Edited French plural translations for hours exported properly.');
257
    $this->assertRaw("msgid \"1 day\"\nmsgid_plural \"@count days\"\nmsgstr[0] \"1 jour\"\nmsgstr[1] \"@count jours\"", 'Added French plural translations for days exported properly.');
258 259

    // Get the Croatian translations.
260
    $this->drupalPostForm('admin/config/regional/translate/export', array(
261 262 263
      'langcode' => 'hr',
    ), t('Export'));
    // Check for plural export specifically.
264 265
    $this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count sat\"\nmsgstr[1] \"@count sata edited\"\nmsgstr[2] \"@count sati\"", 'Edited Croatian plural translations exported properly.');
    $this->assertRaw("msgid \"1 day\"\nmsgid_plural \"@count days\"\nmsgstr[0] \"@count dan\"\nmsgstr[1] \"@count dana\"\nmsgstr[2] \"@count dana\"", 'Added Croatian plural translations exported properly.');
266 267 268 269 270
  }

  /**
   * Imports a standalone .po file in a given language.
   *
271
   * @param string $contents
272
   *   Contents of the .po file to import.
273
   * @param array $options
274 275
   *   Additional options to pass to the translation import form.
   */
276
  public function importPoFile($contents, array $options = array()) {
277 278 279
    $name = tempnam('temporary://', "po_") . '.po';
    file_put_contents($name, $contents);
    $options['files[file]'] = $name;
280
    $this->drupalPostForm('admin/config/regional/translate/import', $options, t('Import'));
281 282 283 284 285 286
    drupal_unlink($name);
  }

  /**
   * Returns a .po file with a simple plural formula.
   */
287
  public function getPoFileWithSimplePlural() {
288 289 290 291 292 293 294 295 296 297 298
    return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 8\\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"
299
msgstr[0] "@count heure"
300 301 302 303 304 305 306 307 308 309
msgstr[1] "@count heures"

msgid "Monday"
msgstr "lundi"
EOF;
  }

  /**
   * Returns a .po file with a complex plural formula.
   */
310
  public function getPoFileWithComplexPlural() {
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
    return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 8\\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;
  }

  /**
   * Returns a .po file with a missing plural formula.
   */
334
  public function getPoFileWithMissingPlural() {
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
    return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 8\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"

msgid "Monday"
msgstr "lundi"
EOF;
  }

  /**
   * Returns a .po file with a broken plural formula.
   */
351
  public function getPoFileWithBrokenPlural() {
352 353 354 355 356 357 358 359 360 361 362 363 364 365
    return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 8\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: broken, will not parse\\n"

msgid "Monday"
msgstr "Ponedjeljak"
EOF;
  }
}