5 * Tests for locale.module.
7 * The test file includes:
8 * - a functional test for the language configuration forms;
9 * - functional tests for the translation functionalities, including searching;
10 * - a functional test for the PO files import feature, including validation;
11 * - functional tests for translations and templates export feature;
12 * - functional tests for the uninstall process;
13 * - a functional test for the language switching feature;
14 * - a functional test for a user's ability to change their default language;
15 * - a functional test for configuring a different path alias per language;
16 * - a functional test for configuring a different path alias per language;
17 * - a functional test for multilingual support by content type and on nodes.
18 * - a functional test for multilingual fields.
19 * - a functional test for comment language.
20 * - a functional test fot language types/negotiation info.
25 * Functional tests for language configuration's effect on negotiation setup.
27 class LocaleConfigurationTest
extends DrupalWebTestCase
{
28 public static
function getInfo() {
30 'name' => 'Language negotiation autoconfiguration',
31 'description' => 'Adds and configures languages to check negotiation changes.',
37 parent
::setUp('locale');
41 * Functional tests for adding, editing and deleting languages.
43 function testLanguageConfiguration() {
46 // User to add and remove language.
47 $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
48 $this->drupalLogin($admin_user);
50 // Check if the Default English language has no path prefix.
51 $this->drupalGet('admin/config/regional/language/detection/url');
52 $this->assertFieldByXPath('//input[@name="prefix[en]"]', '', t('Default English has no path prefix.'));
54 // Add predefined language.
56 'predefined_langcode' => 'fr',
58 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
59 $this->assertText('fr', t('Language added successfully.'));
60 $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE
)), t('Correct page redirection.'));
62 // Check if the Default English language has no path prefix.
63 $this->drupalGet('admin/config/regional/language/detection/url');
64 $this->assertFieldByXPath('//input[@name="prefix[en]"]', '', t('Default English has no path prefix.'));
65 // Check if French has a path prefix.
66 $this->drupalGet('admin/config/regional/language/detection/url');
67 $this->assertFieldByXPath('//input[@name="prefix[fr]"]', 'fr', t('French has a path prefix.'));
69 // Check if we can change the default language.
70 $this->drupalGet('admin/config/regional/language');
71 $this->assertFieldChecked('edit-site-default-en', t('English is the default language.'));
72 // Change the default language.
74 'site_default' => 'fr',
76 $this->drupalPost(NULL
, $edit, t('Save configuration'));
77 $this->assertNoFieldChecked('edit-site-default-en', t('Default language updated.'));
78 $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE
)), t('Correct page redirection.'));
80 // Check if a valid language prefix is added afrer changing the default
82 $this->drupalGet('admin/config/regional/language/detection/url');
83 $this->assertFieldByXPath('//input[@name="prefix[en]"]', 'en', t('A valid path prefix has been added to the previous default language.'));
84 // Check if French still has a path prefix.
85 $this->drupalGet('admin/config/regional/language/detection/url');
86 $this->assertFieldByXPath('//input[@name="prefix[fr]"]', 'fr', t('French still has a path prefix.'));
91 * Functional tests for JavaScript parsing for translatable strings.
93 class LocaleJavascriptTranslationTest
extends DrupalWebTestCase
{
94 public static
function getInfo() {
96 'name' => 'Javascript translation',
97 'description' => 'Tests parsing js files for translatable strings',
103 parent
::setUp('locale', 'locale_test');
106 function testFileParsing() {
108 $filename = drupal_get_path('module', 'locale_test') .
'/locale_test.js';
110 // Parse the file to look for source strings.
111 _locale_parse_js_file($filename);
113 // Get all of the source strings that were found.
114 $source_strings = db_select('locales_source', 's')
115 ->fields('s', array('source', 'context'))
116 ->condition('s.location', $filename)
120 // List of all strings that should be in the file.
121 $test_strings = array(
122 "Standard Call t" => '',
123 "Whitespace Call t" => '',
125 "Single Quote t" => '',
126 "Single Quote \\'Escaped\\' t" => '',
127 "Single Quote Concat strings t" => '',
129 "Double Quote t" => '',
130 "Double Quote \\\"Escaped\\\" t" => '',
131 "Double Quote Concat strings t" => '',
133 "Context !key Args t" => "Context string",
135 "Context Unquoted t" => "Context string unquoted",
136 "Context Single Quoted t" => "Context string single quoted",
137 "Context Double Quoted t" => "Context string double quoted",
139 "Standard Call plural" => '',
140 "Standard Call @count plural" => '',
141 "Whitespace Call plural" => '',
142 "Whitespace Call @count plural" => '',
144 "Single Quote plural" => '',
145 "Single Quote @count plural" => '',
146 "Single Quote \\'Escaped\\' plural" => '',
147 "Single Quote \\'Escaped\\' @count plural" => '',
149 "Double Quote plural" => '',
150 "Double Quote @count plural" => '',
151 "Double Quote \\\"Escaped\\\" plural" => '',
152 "Double Quote \\\"Escaped\\\" @count plural" => '',
154 "Context !key Args plural" => "Context string",
155 "Context !key Args @count plural" => "Context string",
157 "Context Unquoted plural" => "Context string unquoted",
158 "Context Unquoted @count plural" => "Context string unquoted",
159 "Context Single Quoted plural" => "Context string single quoted",
160 "Context Single Quoted @count plural" => "Context string single quoted",
161 "Context Double Quoted plural" => "Context string double quoted",
162 "Context Double Quoted @count plural" => "Context string double quoted",
165 // Assert that all strings were found properly.
166 foreach ($test_strings as
$str => $context) {
167 $args = array('%source' => $str, '%context' => $context);
169 // Make sure that the string was found in the file.
170 $this->assertTrue(isset($source_strings[$str]), t("Found source string: %source", $args));
172 // Make sure that the proper context was matched.
173 $this->assertTrue(isset($source_strings[$str]) && $source_strings[$str] === $context, strlen($context) > 0 ?
t("Context for %source is %context", $args) : t("Context for %source is blank", $args));
176 $this->assertEqual(count($source_strings), count($test_strings), t("Found correct number of source strings."));
180 * Functional test for string translation and validation.
182 class LocaleTranslationFunctionalTest
extends DrupalWebTestCase
{
183 public static
function getInfo() {
185 'name' => 'String translate, search and validate',
186 'description' => 'Adds a new locale and translates its name. Checks the validation of translation strings and search results.',
192 parent
::setUp('locale');
196 * Adds a language and tests string translation by users with the appropriate permissions.
198 function testStringTranslation() {
201 // User to add and remove language.
202 $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
203 // User to translate and delete string.
204 $translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
205 // Code for the language.
207 // The English name for the language. This will be translated.
208 $name = $this->randomName(16);
209 // This is the language indicator on the translation search screen for
210 // untranslated strings. Copied straight from locale.inc.
211 $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
212 // This will be the translation of $name.
213 $translation = $this->randomName(16);
214 $translation_to_en = $this->randomName(16);
216 // Add custom language.
217 $this->drupalLogin($admin_user);
219 'predefined_langcode' => 'custom',
220 'langcode' => $langcode,
224 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
226 t($name, array(), array('langcode' => $langcode));
227 // Reset locale cache.
229 $this->assertRaw('"edit-site-default-' .
$langcode .
'"', t('Language code found.'));
230 $this->assertText(t($name), t('Test language added.'));
231 $this->drupalLogout();
233 // Search for the name and translate it.
234 $this->drupalLogin($translate_user);
238 'translation' => 'all',
240 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
241 // assertText() seems to remove the input field where $name always could be
242 // found, so this is not a false assert. See how assertNoText succeeds
244 $this->assertText($name, t('Search found the name.'));
245 $this->assertRaw($language_indicator, t('Name is untranslated.'));
246 // Assume this is the only result, given the random name.
247 $this->clickLink(t('edit'));
248 // We save the lid from the path.
250 preg_match('!admin/config/regional/translate/edit/(\d+)!', $this->getUrl(), $matches);
252 // No t() here, it's surely not translated yet.
253 $this->assertText($name, t('name found on edit screen.'));
254 $this->assertNoText('English', t('No way to translate the string to English.'));
255 $this->drupalLogout();
256 $this->drupalLogin($admin_user);
257 $this->drupalPost('admin/config/regional/language/edit/en', array('locale_translate_english' => TRUE
), t('Save language'));
258 $this->drupalLogout();
259 $this->drupalLogin($translate_user);
260 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
261 // assertText() seems to remove the input field where $name always could be
262 // found, so this is not a false assert. See how assertNoText succeeds
264 $this->assertText($name, t('Search found the name.'));
265 $this->assertRaw($language_indicator, t('Name is untranslated.'));
266 // Assume this is the only result, given the random name.
267 $this->clickLink(t('edit'));
268 $string_edit_url = $this->getUrl();
270 "translations[$langcode]" => $translation,
271 'translations[en]' => $translation_to_en,
273 $this->drupalPost(NULL
, $edit, t('Save translations'));
274 $this->assertText(t('The string has been saved.'), t('The string has been saved.'));
275 $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE
)), t('Correct page redirection.'));
276 $this->drupalGet($string_edit_url);
277 $this->assertRaw($translation, t('Non-English translation properly saved.'));
278 $this->assertRaw($translation_to_en, t('English translation properly saved.'));
279 $this->assertTrue($name != $translation && t($name, array(), array('langcode' => $langcode)) == $translation, t('t() works for non-English.'));
280 // Refresh the locale() cache to get fresh data from t() below. We are in
281 // the same HTTP request and therefore t() is not refreshed by saving the
282 // translation above.
284 // Now we should get the proper fresh translation from t().
285 $this->assertTrue($name != $translation_to_en && t($name, array(), array('langcode' => 'en')) == $translation_to_en, t('t() works for English.'));
286 $this->assertTrue(t($name, array(), array('langcode' => LANGUAGE_SYSTEM
)) == $name, t('t() works for LANGUAGE_SYSTEM.'));
287 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
288 // The indicator should not be here.
289 $this->assertNoRaw($language_indicator, t('String is translated.'));
291 // Try to edit a non-existent string and ensure we're redirected correctly.
292 // Assuming we don't have 999,999 strings already.
293 $random_lid = 999999;
294 $this->drupalGet('admin/config/regional/translate/edit/' .
$random_lid);
295 $this->assertText(t('String not found'), t('String not found.'));
296 $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE
)), t('Correct page redirection.'));
297 $this->drupalLogout();
299 // Delete the language.
300 $this->drupalLogin($admin_user);
301 $path = 'admin/config/regional/language/delete/' .
$langcode;
302 // This a confirm form, we do not need any fields changed.
303 $this->drupalPost($path, array(), t('Delete'));
304 // We need raw here because %language and %langcode will add HTML.
305 $t_args = array('%language' => $name, '%langcode' => $langcode);
306 $this->assertRaw(t('The %language (%langcode) language has been removed.', $t_args), t('The test language has been removed.'));
307 // Reload to remove $name.
308 $this->drupalGet($path);
309 // Verify that language is no longer found.
310 $this->assertResponse(404, t('Language no longer found.'));
311 $this->drupalLogout();
313 // Delete the string.
314 $this->drupalLogin($translate_user);
318 'translation' => 'all',
320 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
321 // Assume this is the only result, given the random name.
322 $this->clickLink(t('delete'));
323 $this->assertText(t('Are you sure you want to delete the string'), t('"delete" link is correct.'));
324 // Delete the string.
325 $path = 'admin/config/regional/translate/delete/' .
$lid;
326 $this->drupalGet($path);
327 // First test the 'cancel' link.
328 $this->clickLink(t('Cancel'));
329 $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE
)), t('Correct page redirection.'));
330 $this->assertRaw($name, t('The string was not deleted.'));
331 // Delete the name string.
332 $this->drupalPost('admin/config/regional/translate/delete/' .
$lid, array(), t('Delete'));
333 $this->assertText(t('The string has been removed.'), t('The string has been removed message.'));
334 $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE
)), t('Correct page redirection.'));
335 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
336 $this->assertNoText($name, t('Search now can not find the name.'));
340 * Adds a language and checks that the JavaScript translation files are
341 * properly created and rebuilt on deletion.
343 function testJavaScriptTranslation() {
344 $user = $this->drupalCreateUser(array('translate interface', 'administer languages', 'access administration pages'));
345 $this->drupalLogin($user);
348 // The English name for the language. This will be translated.
349 $name = $this->randomName(16);
351 // Add custom language.
353 'predefined_langcode' => 'custom',
354 'langcode' => $langcode,
358 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
359 drupal_static_reset('language_list');
361 // Build the JavaScript translation file.
362 $this->drupalGet('admin/config/regional/translate/translate');
364 // Retrieve the id of the first string available in the {locales_source}
365 // table and translate it.
366 $query = db_select('locales_source', 'l');
367 $query->addExpression('min(l.lid)', 'lid');
368 $result = $query->condition('l.location', '%.js%', 'LIKE')->execute();
369 $url = 'admin/config/regional/translate/edit/' .
$result->fetchObject()->lid
;
370 $edit = array('translations['.
$langcode .
']' => $this->randomName());
371 $this->drupalPost($url, $edit, t('Save translations'));
373 // Trigger JavaScript translation parsing and building.
374 require_once DRUPAL_ROOT .
'/core/includes/locale.inc';
375 _locale_rebuild_js($langcode);
377 $locale_javascripts = variable_get('locale_translation_javascript', array());
378 $js_file = 'public://' .
variable_get('locale_js_directory', 'languages') .
'/' .
$langcode .
'_' .
$locale_javascripts[$langcode] .
'.js';
379 $this->assertTrue($result = file_exists($js_file), t('JavaScript file created: %file', array('%file' => $result ?
$js_file : t('not found'))));
381 // Test JavaScript translation rebuilding.
382 file_unmanaged_delete($js_file);
383 $this->assertTrue($result = !file_exists($js_file), t('JavaScript file deleted: %file', array('%file' => $result ?
$js_file : t('found'))));
385 _locale_rebuild_js($langcode);
386 $this->assertTrue($result = file_exists($js_file), t('JavaScript file rebuilt: %file', array('%file' => $result ?
$js_file : t('not found'))));
390 * Tests the validation of the translation input.
392 function testStringValidation() {
395 // User to add language and strings.
396 $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'translate interface'));
397 $this->drupalLogin($admin_user);
399 // The English name for the language. This will be translated.
400 $name = $this->randomName(16);
401 // This is the language indicator on the translation search screen for
402 // untranslated strings. Copied straight from locale.inc.
403 $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
404 // These will be the invalid translations of $name.
405 $key = $this->randomName(16);
406 $bad_translations[$key] = "<script>alert('xss');</script>" .
$key;
407 $key = $this->randomName(16);
408 $bad_translations[$key] = '<img SRC="javascript:alert(\'xss\');">' .
$key;
409 $key = $this->randomName(16);
410 $bad_translations[$key] = '<<SCRIPT>alert("xss");//<</SCRIPT>' .
$key;
411 $key = $this->randomName(16);
412 $bad_translations[$key] ="<BODY ONLOAD=alert('xss')>" .
$key;
414 // Add custom language.
416 'predefined_langcode' => 'custom',
417 'langcode' => $langcode,
421 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
423 t($name, array(), array('langcode' => $langcode));
424 // Reset locale cache.
428 'translation' => 'all',
430 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
431 // Find the edit path.
432 $content = $this->drupalGetContent();
433 $this->assertTrue(preg_match('@(admin/config/regional/translate/edit/[0-9]+)@', $content, $matches), t('Found the edit path.'));
435 foreach ($bad_translations as
$key => $translation) {
437 "translations[$langcode]" => $translation,
439 $this->drupalPost($path, $edit, t('Save translations'));
440 // Check for a form error on the textarea.
441 $form_class = $this->xpath('//form[@id="locale-translate-edit-form"]//textarea/@class');
442 $this->assertNotIdentical(FALSE
, strpos($form_class[0], 'error'), t('The string was rejected as unsafe.'));
443 $this->assertNoText(t('The string has been saved.'), t('The string was not saved.'));
448 * Tests translation search form.
450 function testStringSearch() {
453 // User to add and remove language.
454 $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
455 // User to translate and delete string.
456 $translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
458 // Code for the language.
460 // The English name for the language. This will be translated.
461 $name = $this->randomName(16);
462 // This is the language indicator on the translation search screen for
463 // untranslated strings. Copied straight from locale.inc.
464 $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
465 // This will be the translation of $name.
466 $translation = $this->randomName(16);
468 // Add custom language.
469 $this->drupalLogin($admin_user);
471 'predefined_langcode' => 'custom',
472 'langcode' => $langcode,
476 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
478 t($name, array(), array('langcode' => $langcode));
479 // Reset locale cache.
481 $this->drupalLogout();
483 // Search for the name.
484 $this->drupalLogin($translate_user);
488 'translation' => 'all',
490 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
491 // assertText() seems to remove the input field where $name always could be
492 // found, so this is not a false assert. See how assertNoText succeeds
494 $this->assertText($name, t('Search found the string.'));
496 // Ensure untranslated string doesn't appear if searching on 'only
497 // translated strings'.
501 'translation' => 'translated',
503 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
504 $this->assertText(t('No strings available.'), t("Search didn't find the string."));
506 // Ensure untranslated string appears if searching on 'only untranslated
511 'translation' => 'untranslated',
513 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
514 $this->assertNoText(t('No strings available.'), t('Search found the string.'));
517 // Assume this is the only result, given the random name.
518 $this->clickLink(t('edit'));
519 // We save the lid from the path.
521 preg_match('!admin/config/regional/translate/edit/(\d)+!', $this->getUrl(), $matches);
524 "translations[$langcode]" => $translation,
526 $this->drupalPost(NULL
, $edit, t('Save translations'));
528 // Ensure translated string does appear if searching on 'only
529 // translated strings'.
531 'string' => $translation,
533 'translation' => 'translated',
535 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
536 $this->assertNoText(t('No strings available.'), t('Search found the translation.'));
538 // Ensure translated source string doesn't appear if searching on 'only
539 // untranslated strings'.
543 'translation' => 'untranslated',
545 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
546 $this->assertText(t('No strings available.'), t("Search didn't find the source string."));
548 // Ensure translated string doesn't appear if searching on 'only
549 // untranslated strings'.
551 'string' => $translation,
553 'translation' => 'untranslated',
555 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
556 $this->assertText(t('No strings available.'), t("Search didn't find the translation."));
558 // Ensure translated string does appear if searching on the custom language.
560 'string' => $translation,
561 'language' => $langcode,
562 'translation' => 'all',
564 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
565 $this->assertNoText(t('No strings available.'), t('Search found the translation.'));
567 // Ensure translated string doesn't appear if searching in System (English).
569 'string' => $translation,
570 'language' => LANGUAGE_SYSTEM
,
571 'translation' => 'all',
573 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
574 $this->assertText(t('No strings available.'), t("Search didn't find the translation."));
576 // Search for a string that isn't in the system.
577 $unavailable_string = $this->randomName(16);
579 'string' => $unavailable_string,
581 'translation' => 'all',
583 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
584 $this->assertText(t('No strings available.'), t("Search didn't find the invalid string."));
589 * Tests plural index computation functionality.
591 class LocalePluralFormatTest
extends DrupalWebTestCase
{
592 public static
function getInfo() {
594 'name' => 'Plural formula evaluation',
595 'description' => 'Tests plural formula evaluation for various languages.',
601 parent
::setUp('locale', 'locale_test');
603 $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
604 $this->drupalLogin($admin_user);
608 * Tests locale_get_plural() functionality.
610 function testGetPluralFormat() {
611 // Import some .po files with formulas to set up the environment.
612 // These will also add the languages to the system and enable them.
613 $this->importPoFile($this->getPoFileWithSimplePlural(), array(
616 $this->importPoFile($this->getPoFileWithComplexPlural(), array(
620 // Reset static caches from locale_get_plural() to ensure we get fresh data.
621 drupal_static_reset('locale_get_plural');
622 drupal_static_reset('locale_get_plural:plurals');
624 // Test locale_get_plural() for English (no formula presnt).
625 $this->assertIdentical(locale_get_plural(1, 'en'), 0, t("Computed plural index for 'en' with count 1 is 0."));
626 $this->assertIdentical(locale_get_plural(0, 'en'), 1, t("Computed plural index for 'en' with count 0 is 1."));
627 $this->assertIdentical(locale_get_plural(5, 'en'), 1, t("Computed plural index for 'en' with count 5 is 1."));
629 // Test locale_get_plural() for French (simpler formula).
630 $this->assertIdentical(locale_get_plural(1, 'fr'), 0, t("Computed plural index for 'fr' with count 1 is 0."));
631 $this->assertIdentical(locale_get_plural(0, 'fr'), 0, t("Computed plural index for 'fr' with count 0 is 0."));
632 $this->assertIdentical(locale_get_plural(5, 'fr'), 1, t("Computed plural index for 'fr' with count 5 is 1."));
634 // Test locale_get_plural() for Croatian (more complex formula).
635 $this->assertIdentical(locale_get_plural( 1, 'hr'), 0, t("Computed plural index for 'hr' with count 1 is 0."));
636 $this->assertIdentical(locale_get_plural(21, 'hr'), 0, t("Computed plural index for 'hr' with count 21 is 0."));
637 $this->assertIdentical(locale_get_plural( 0, 'hr'), 2, t("Computed plural index for 'hr' with count 0 is 2."));
638 $this->assertIdentical(locale_get_plural( 2, 'hr'), 1, t("Computed plural index for 'hr' with count 2 is 1."));
639 $this->assertIdentical(locale_get_plural( 8, 'hr'), 2, t("Computed plural index for 'hr' with count 8 is 2."));
641 // Test locale_get_plural() for Hungarian (nonexistent language).
642 $this->assertIdentical(locale_get_plural( 1, 'hu'), -1, t("Computed plural index for 'hu' with count 1 is -1."));
643 $this->assertIdentical(locale_get_plural(21, 'hu'), -1, t("Computed plural index for 'hu' with count 21 is -1."));
644 $this->assertIdentical(locale_get_plural( 0, 'hu'), -1, t("Computed plural index for 'hu' with count 0 is -1."));
648 * Imports a standalone .po file in a given language.
651 * Contents of the .po file to import.
653 * Additional options to pass to the translation import form.
655 function importPoFile($contents, array $options = array()) {
656 $name = tempnam('temporary://', "po_") .
'.po';
657 file_put_contents($name, $contents);
658 $options['files[file]'] = $name;
659 $this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
660 drupal_unlink($name);
664 * Returns a .po file with a simple plural formula.
666 function getPoFileWithSimplePlural() {
670 "Project-Id-Version: Drupal 7\\n"
671 "MIME-Version: 1.0\\n"
672 "Content-Type: text/plain; charset=UTF-8\\n"
673 "Content-Transfer-Encoding: 8bit\\n"
674 "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
677 msgid_plural
"@count hours"
679 msgstr
[1] "@count heures"
687 * Returns a .po file with a complex plural formula.
689 function getPoFileWithComplexPlural() {
693 "Project-Id-Version: Drupal 7\\n"
694 "MIME-Version: 1.0\\n"
695 "Content-Type: text/plain; charset=UTF-8\\n"
696 "Content-Transfer-Encoding: 8bit\\n"
697 "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"
700 msgid_plural
"@count hours"
701 msgstr
[0] "@count sat"
702 msgstr
[1] "@count sata"
703 msgstr
[2] "@count sati"
712 * Functional tests for the import of translation files.
714 class LocaleImportFunctionalTest
extends DrupalWebTestCase
{
715 public static
function getInfo() {
717 'name' => 'Translation import',
718 'description' => 'Tests the import of locale files.',
724 * A user able to create languages and import translations.
726 protected
$admin_user = NULL
;
729 parent
::setUp('locale', 'locale_test');
731 // Set the translation file directory.
732 variable_set('locale_translate_file_directory', drupal_get_path('module', 'locale_test'));
734 $this->admin_user
= $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
735 $this->drupalLogin($this->admin_user
);
739 * Test import of standalone .po files.
741 function testStandalonePoFile() {
742 // Try importing a .po file.
743 $this->importPoFile($this->getPoFile(), array(
747 // The import should automatically create the corresponding language.
748 $this->assertRaw(t('The language %language has been created.', array('%language' => 'French')), t('The language has been automatically created.'));
750 // The import should have created 7 strings.
751 $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 9, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
753 // This import should have saved plural forms to have 2 variants.
754 $locale_plurals = variable_get('locale_translation_plurals', array());
755 $this->assert($locale_plurals['fr']['plurals'] == 2, t('Plural number initialized.'));
757 // Ensure we were redirected correctly.
758 $this->assertEqual($this->getUrl(), url('admin/config/regional/translate', array('absolute' => TRUE
)), t('Correct page redirection.'));
761 // Try importing a .po file with invalid tags.
762 $this->importPoFile($this->getBadPoFile(), array(
766 // The import should have created 1 string and rejected 2.
767 $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
768 $skip_message = format_plural(2, 'A translation string was skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', '@count translation strings were skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', array('@url' => url('admin/reports/dblog')));
769 $this->assertRaw($skip_message, t('Unsafe strings were skipped.'));
772 // Try importing a .po file which doesn't exist.
773 $name = $this->randomName(16);
774 $this->drupalPost('admin/config/regional/translate/import', array(
776 'files[file]' => $name,
778 $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/import', array('absolute' => TRUE
)), t('Correct page redirection.'));
779 $this->assertText(t('File to import not found.'), t('File to import not found message.'));
782 // Try importing a .po file with overriding strings, and ensure existing
784 $this->importPoFile($this->getOverwritePoFile(), array(
786 'mode' => 1, // Existing strings are kept, only new strings are added.
789 // The import should have created 1 string.
790 $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
791 // Ensure string wasn't overwritten.
793 'string' => 'Montag',
795 'translation' => 'translated',
797 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
798 $this->assertText(t('No strings available.'), t('String not overwritten by imported string.'));
800 // This import should not have changed number of plural forms.
801 $locale_plurals = variable_get('locale_translation_plurals', array());
802 $this->assert($locale_plurals['fr']['plurals'] == 2, t('Plural numbers untouched.'));
804 // Try importing a .po file with overriding strings, and ensure existing
805 // strings are overwritten.
806 $this->importPoFile($this->getOverwritePoFile(), array(
808 'mode' => 0, // Strings in the uploaded file replace existing ones, new ones are added.
811 // The import should have updated 2 strings.
812 $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 2, '%delete' => 0)), t('The translation file was successfully imported.'));
813 // Ensure string was overwritten.
815 'string' => 'Montag',
817 'translation' => 'translated',
819 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
820 $this->assertNoText(t('No strings available.'), t('String overwritten by imported string.'));
821 // This import should have changed number of plural forms.
822 $locale_plurals = variable_get('locale_translation_plurals', array());
823 $this->assert($locale_plurals['fr']['plurals'] == 3, t('Plural numbers changed.'));
827 * Test automatic import of a module's translation files.
829 function testAutomaticModuleTranslationImportLanguageEnable() {
830 // Code for the language - manually set to match the test translation file.
832 // The English name for the language.
833 $name = $this->randomName(16);
835 // Create a custom language.
837 'predefined_langcode' => 'custom',
838 'langcode' => $langcode,
842 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
844 // Ensure the translation file was automatically imported when language was
846 $this->assertText(t('One translation file imported.'), t('Language file automatically imported.'));
848 // Ensure strings were successfully imported.
851 'language' => $langcode,
852 'translation' => 'translated',
854 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
855 $this->assertNoText(t('No strings available.'), t('String successfully imported.'));
859 * Test msgctxt context support.
861 function testLanguageContext() {
862 // Try importing a .po file.
863 $this->importPoFile($this->getPoFileWithContext(), array(
867 $this->assertIdentical(t('May', array(), array('langcode' => 'hr', 'context' => 'Long month name')), 'Svibanj', t('Long month name context is working.'));
868 $this->assertIdentical(t('May', array(), array('langcode' => 'hr')), 'Svi.', t('Default context is working.'));
872 * Test empty msgstr at end of .po file see #611786.
874 function testEmptyMsgstr() {
877 // Try importing a .po file.
878 $this->importPoFile($this->getPoFileWithMsgstr(), array(
879 'langcode' => $langcode,
882 $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
883 $this->assertIdentical(t('Operations', array(), array('langcode' => $langcode)), 'MĹąveletek', t('String imported and translated.'));
885 // Try importing a .po file.
886 $this->importPoFile($this->getPoFileWithEmptyMsgstr(), array(
887 'langcode' => $langcode,
890 $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 0, '%delete' => 1)), t('The translation file was successfully imported.'));
891 // This is the language indicator on the translation search screen for
892 // untranslated strings. Copied straight from locale.inc.
893 $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
898 'translation' => 'all',
900 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
901 // assertText() seems to remove the input field where $str always could be
902 // found, so this is not a false assert.
903 $this->assertText($str, t('Search found the string.'));
904 $this->assertRaw($language_indicator, t('String is untranslated again.'));
908 * Helper function: import a standalone .po file in a given language.
911 * Contents of the .po file to import.
913 * Additional options to pass to the translation import form.
915 function importPoFile($contents, array $options = array()) {
916 $name = tempnam('temporary://', "po_") .
'.po';
917 file_put_contents($name, $contents);
918 $options['files[file]'] = $name;
919 $this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
920 drupal_unlink($name);
924 * Helper function that returns a proper .po file.
926 function getPoFile() {
930 "Project-Id-Version: Drupal 7\\n"
931 "MIME-Version: 1.0\\n"
932 "Content-Type: text/plain; charset=UTF-8\\n"
933 "Content-Transfer-Encoding: 8bit\\n"
934 "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
937 msgid_plural
"@count sheep"
938 msgstr
[0] "un mouton"
939 msgstr
[1] "@count moutons"
965 * Helper function that returns a bad .po file.
967 function getBadPoFile() {
971 "Project-Id-Version: Drupal 7\\n"
972 "MIME-Version: 1.0\\n"
973 "Content-Type: text/plain; charset=UTF-8\\n"
974 "Content-Transfer-Encoding: 8bit\\n"
975 "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
977 msgid
"Save configuration"
978 msgstr
"Enregistrer la configuration"
981 msgstr
"modifier<img SRC="javascript
:alert(\'xss\'
);">"
984 msgstr
"supprimer<script>alert('xss');</script>"
990 * Helper function that returns a proper .po file for testing.
992 function getOverwritePoFile() {
996 "Project-Id-Version: Drupal 7\\n"
997 "MIME-Version: 1.0\\n"
998 "Content-Type: text/plain; charset=UTF-8\\n"
999 "Content-Transfer-Encoding: 8bit\\n"
1000 "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"
1011 * Helper function that returns a .po file with context.
1013 function getPoFileWithContext() {
1014 // Croatian (code hr) is one the the languages that have a different
1015 // form for the full name and the abbreviated name for the month May.
1019 "Project-Id-Version: Drupal 7\\n"
1020 "MIME-Version: 1.0\\n"
1021 "Content-Type: text/plain; charset=UTF-8\\n"
1022 "Content-Transfer-Encoding: 8bit\\n"
1023 "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"
1025 msgctxt
"Long month name"
1035 * Helper function that returns a .po file with an empty last item.
1037 function getPoFileWithEmptyMsgstr() {
1041 "Project-Id-Version: Drupal 7\\n"
1042 "MIME-Version: 1.0\\n"
1043 "Content-Type: text/plain; charset=UTF-8\\n"
1044 "Content-Transfer-Encoding: 8bit\\n"
1045 "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1053 * Helper function that returns a .po file with an empty last item.
1055 function getPoFileWithMsgstr() {
1059 "Project-Id-Version: Drupal 7\\n"
1060 "MIME-Version: 1.0\\n"
1061 "Content-Type: text/plain; charset=UTF-8\\n"
1062 "Content-Transfer-Encoding: 8bit\\n"
1063 "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1068 msgid
"Will not appear in Drupal core, so we can ensure the test passes"
1077 * Functional tests for the export of translation files.
1079 class LocaleExportFunctionalTest
extends DrupalWebTestCase
{
1080 public static
function getInfo() {
1082 'name' => 'Translation export',
1083 'description' => 'Tests the exportation of locale files.',
1084 'group' => 'Locale',
1089 * A user able to create languages and export translations.
1091 protected
$admin_user = NULL
;
1094 parent
::setUp('locale', 'locale_test');
1096 $this->admin_user
= $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
1097 $this->drupalLogin($this->admin_user
);
1101 * Test exportation of translations.
1103 function testExportTranslation() {
1104 // First import some known translations.
1105 // This will also automatically enable the 'fr' language.
1106 $name = tempnam('temporary://', "po_") .
'.po';
1107 file_put_contents($name, $this->getPoFile());
1108 $this->drupalPost('admin/config/regional/translate/import', array(
1110 'files[file]' => $name,
1112 drupal_unlink($name);
1114 // Get the French translations.
1115 $this->drupalPost('admin/config/regional/translate/export', array(
1119 // Ensure we have a translation file.
1120 $this->assertRaw('# French translation of Drupal', t('Exported French translation file.'));
1121 // Ensure our imported translations exist in the file.
1122 $this->assertRaw('msgstr "lundi"', t('French translations present in exported file.'));
1126 * Test exportation of translation template file.
1128 function testExportTranslationTemplateFile() {
1129 // Get the translation template file.
1130 // There are two 'Export' buttons on this page, but it somehow works. It'd
1131 // be better if we could use the submit button id like documented but that
1133 $this->drupalPost('admin/config/regional/translate/export', array(), t('Export'));
1134 // Ensure we have a translation file.
1135 $this->assertRaw('# LANGUAGE translation of PROJECT', t('Exported translation template file.'));
1139 * Helper function that returns a proper .po file.
1141 function getPoFile() {
1145 "Project-Id-Version: Drupal 6\\n"
1146 "MIME-Version: 1.0\\n"
1147 "Content-Type: text/plain; charset=UTF-8\\n"
1148 "Content-Transfer-Encoding: 8bit\\n"
1149 "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1159 * Tests for the st() function.
1161 class LocaleInstallTest
extends DrupalWebTestCase
{
1162 public static
function getInfo() {
1164 'name' => 'String translation using st()',
1165 'description' => 'Tests that st() works like t().',
1166 'group' => 'Locale',
1171 parent
::setUp('locale');
1173 // st() lives in install.inc, so ensure that it is loaded for all tests.
1174 require_once DRUPAL_ROOT .
'/core/includes/install.inc';
1178 * Verify that function signatures of t() and st() are equal.
1180 function testFunctionSignatures() {
1181 $reflector_t = new
ReflectionFunction('t');
1182 $reflector_st = new
ReflectionFunction('st');
1183 $this->assertEqual($reflector_t->getParameters(), $reflector_st->getParameters(), t('Function signatures of t() and st() are equal.'));
1188 * Locale uninstall with English UI functional test.
1190 class LocaleUninstallFunctionalTest
extends DrupalWebTestCase
{
1191 public static
function getInfo() {
1193 'name' => 'Locale uninstall (EN)',
1194 'description' => 'Tests the uninstall process using the built-in UI language.',
1195 'group' => 'Locale',
1200 * The default language set for the UI before uninstall.
1202 protected
$language;
1205 parent
::setUp('locale');
1206 $this->langcode
= 'en';
1210 * Check if the values of the Locale variables are correct after uninstall.
1212 function testUninstallProcess() {
1213 $locale_module = array('locale', 'language');
1215 // Add a new language and optionally set it as default.
1216 require_once DRUPAL_ROOT .
'/core/includes/locale.inc';
1218 $language = (object) array(
1221 'default' => $this->langcode
== 'fr',
1223 language_save($language);
1225 // Check the UI language.
1226 drupal_language_initialize();
1227 $this->assertEqual($GLOBALS['language']->langcode
, $this->langcode
, t('Current language: %lang', array('%lang' => $GLOBALS['language']->langcode
)));
1229 // Enable multilingual workflow option for articles.
1230 variable_set('node_type_language_article', 1);
1232 // Change JavaScript translations directory.
1233 variable_set('locale_js_directory', 'js_translations');
1235 // Build the JavaScript translation file for French.
1236 $user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
1237 $this->drupalLogin($user);
1238 $this->drupalGet('admin/config/regional/translate/translate');
1239 $string = db_query('SELECT min(lid) AS lid FROM {locales_source} WHERE location LIKE :location', array(
1240 ':location' => '%.js%',
1242 $edit = array('translations[fr]' => 'french translation');
1243 $this->drupalPost('admin/config/regional/translate/edit/' .
$string->lid
, $edit, t('Save translations'));
1244 _locale_rebuild_js('fr');
1245 $locale_javascripts = variable_get('locale_translation_javascript', array());
1246 $js_file = 'public://' .
variable_get('locale_js_directory', 'languages') .
'/fr_' .
$locale_javascripts['fr'] .
'.js';
1247 $this->assertTrue($result = file_exists($js_file), t('JavaScript file created: %file', array('%file' => $result ?
$js_file : t('none'))));
1249 // Disable string caching.
1250 variable_set('locale_cache_strings', 0);
1252 // Change language negotiation options.
1253 drupal_load('module', 'locale');
1254 variable_set('language_types', drupal_language_types() + array('language_custom' => TRUE
));
1255 variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE
, locale_language_negotiation_info());
1256 variable_set('language_negotiation_' . LANGUAGE_TYPE_CONTENT
, locale_language_negotiation_info());
1257 variable_set('language_negotiation_' . LANGUAGE_TYPE_URL
, locale_language_negotiation_info());
1259 // Change language providers settings.
1260 variable_set('locale_language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX
);
1261 variable_set('locale_language_negotiation_session_param', TRUE
);
1263 // Uninstall Locale.
1264 module_disable($locale_module);
1265 drupal_uninstall_modules($locale_module);
1267 // Visit the front page.
1268 $this->drupalGet('');
1270 // Check the init language logic.
1271 drupal_language_initialize();
1272 $this->assertEqual($GLOBALS['language']->langcode
, 'en', t('Language after uninstall: %lang', array('%lang' => $GLOBALS['language']->langcode
)));
1274 // Check JavaScript files deletion.
1275 $this->assertTrue($result = !file_exists($js_file), t('JavaScript file deleted: %file', array('%file' => $result ?
$js_file : t('found'))));
1277 // Check language count.
1278 $language_count = variable_get('language_count', 1);
1279 $this->assertEqual($language_count, 1, t('Language count: %count', array('%count' => $language_count)));
1281 // Check language negotiation.
1282 require_once DRUPAL_ROOT .
'/core/includes/language.inc';
1283 $this->assertTrue(count(language_types()) == count(drupal_language_types()), t('Language types reset'));
1284 $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_INTERFACE
) == LANGUAGE_NEGOTIATION_DEFAULT
;
1285 $this->assertTrue($language_negotiation, t('Interface language negotiation: %setting', array('%setting' => t($language_negotiation ?
'none' : 'set'))));
1286 $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_CONTENT
) == LANGUAGE_NEGOTIATION_DEFAULT
;
1287 $this->assertTrue($language_negotiation, t('Content language negotiation: %setting', array('%setting' => t($language_negotiation ?
'none' : 'set'))));
1288 $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_URL
) == LANGUAGE_NEGOTIATION_DEFAULT
;
1289 $this->assertTrue($language_negotiation, t('URL language negotiation: %setting', array('%setting' => t($language_negotiation ?
'none' : 'set'))));
1291 // Check language providers settings.
1292 $this->assertFalse(variable_get('locale_language_negotiation_url_part', FALSE
), t('URL language provider indicator settings cleared.'));
1293 $this->assertFalse(variable_get('locale_language_negotiation_session_param', FALSE
), t('Visit language provider settings cleared.'));
1295 // Check JavaScript parsed.
1296 $javascript_parsed_count = count(variable_get('javascript_parsed', array()));
1297 $this->assertEqual($javascript_parsed_count, 0, t('JavaScript parsed count: %count', array('%count' => $javascript_parsed_count)));
1299 // Check JavaScript translations directory.
1300 $locale_js_directory = variable_get('locale_js_directory', 'languages');
1301 $this->assertEqual($locale_js_directory, 'languages', t('JavaScript translations directory: %dir', array('%dir' => $locale_js_directory)));
1303 // Check string caching.
1304 $locale_cache_strings = variable_get('locale_cache_strings', 1);
1305 $this->assertEqual($locale_cache_strings, 1, t('String caching: %status', array('%status' => t($locale_cache_strings ?
'enabled': 'disabled'))));
1310 * Locale uninstall with French UI functional test.
1312 * Because this class extends LocaleUninstallFunctionalTest, it doesn't require a new
1313 * test of its own. Rather, it switches the default UI language in setUp and then
1314 * runs the testUninstallProcess (which it inherits from LocaleUninstallFunctionalTest)
1315 * to test with this new language.
1317 class LocaleUninstallFrenchFunctionalTest
extends LocaleUninstallFunctionalTest
{
1318 public static
function getInfo() {
1320 'name' => 'Locale uninstall (FR)',
1321 'description' => 'Tests the uninstall process using French as interface language.',
1322 'group' => 'Locale',
1328 $this->langcode
= 'fr';
1334 * Functional tests for the language switching feature.
1336 class LocaleLanguageSwitchingFunctionalTest
extends DrupalWebTestCase
{
1338 public static
function getInfo() {
1340 'name' => 'Language switching',
1341 'description' => 'Tests for the language switching feature.',
1342 'group' => 'Locale',
1347 parent
::setUp('locale');
1349 // Create and login user.
1350 $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer languages', 'translate interface', 'access administration pages'));
1351 $this->drupalLogin($admin_user);
1355 * Functional tests for the language switcher block.
1357 function testLanguageBlock() {
1358 // Enable the language switching block.
1359 $language_type = LANGUAGE_TYPE_INTERFACE
;
1361 "blocks[locale_{$language_type}][region]" => 'sidebar_first',
1363 $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
1367 'predefined_langcode' => 'fr',
1369 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
1371 // Enable URL language detection and selection.
1372 $edit = array('language[enabled][locale-url]' => '1');
1373 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
1375 // Assert that the language switching block is displayed on the frontpage.
1376 $this->drupalGet('');
1377 $this->assertText(t('Languages'), t('Language switcher block found.'));
1379 // Assert that only the current language is marked as active.
1380 list($language_switcher) = $this->xpath('//div[@id=:id]/div[@class="content"]', array(':id' => 'block-locale-' .
$language_type));
1382 'active' => array(),
1383 'inactive' => array(),
1386 'active' => array(),
1387 'inactive' => array(),
1389 foreach ($language_switcher->ul
->li as
$link) {
1390 $classes = explode(" ", (string) $link['class']);
1391 list($langcode) = array_intersect($classes, array('en', 'fr'));
1392 if (in_array('active', $classes)) {
1393 $links['active'][] = $langcode;
1396 $links['inactive'][] = $langcode;
1398 $anchor_classes = explode(" ", (string) $link->a
['class']);
1399 if (in_array('active', $anchor_classes)) {
1400 $anchors['active'][] = $langcode;
1403 $anchors['inactive'][] = $langcode;
1406 $this->assertIdentical($links, array('active' => array('en'), 'inactive' => array('fr')), t('Only the current language list item is marked as active on the language switcher block.'));
1407 $this->assertIdentical($anchors, array('active' => array('en'), 'inactive' => array('fr')), t('Only the current language anchor is marked as active on the language switcher block.'));
1412 * Test browser language detection.
1414 class LocaleBrowserDetectionTest
extends DrupalUnitTestCase
{
1416 public static
function getInfo() {
1418 'name' => 'Browser language detection',
1419 'description' => 'Tests for the browser language detection.',
1420 'group' => 'Locale',
1425 * Unit tests for the locale_language_from_browser() function.
1427 function testLanguageFromBrowser() {
1428 // Load the required functions.
1429 require_once DRUPAL_ROOT .
'/core/includes/locale.inc';
1432 // In our test case, 'en' has priority over 'en-US'.
1433 'en' => (object) array(
1436 'en-US' => (object) array(
1437 'langcode' => 'en-US',
1439 // But 'fr-CA' has priority over 'fr'.
1440 'fr-CA' => (object) array(
1441 'langcode' => 'fr-CA',
1443 'fr' => (object) array(
1446 // 'es-MX' is alone.
1447 'es-MX' => (object) array(
1448 'langcode' => 'es-MX',
1451 'pt' => (object) array(
1454 // Language codes with more then one dash are actually valid.
1455 // eh-oh-laa-laa is the official language code of the Teletubbies.
1456 'eh-oh-laa-laa' => (object) array(
1457 'langcode' => 'eh-oh-laa-laa',
1461 $test_cases = array(
1462 // Equal qvalue for each language, choose the site prefered one.
1463 'en,en-US,fr-CA,fr,es-MX' => 'en',
1464 'en-US,en,fr-CA,fr,es-MX' => 'en',
1469 'fr,fr-CA' => 'fr-CA',
1470 'fr-CA,fr' => 'fr-CA',
1472 'fr;q=1' => 'fr-CA',
1473 'fr,es-MX' => 'fr-CA',
1476 'es-MX,de' => 'es-MX',
1477 'de,es-MX' => 'es-MX',
1479 // Different cases and whitespace.
1486 // A less specific language from the browser matches a more specific one
1487 // from the website, and the other way around for compatibility with
1488 // some versions of Internet Explorer.
1493 'pt-PT;q=0.5,pt-BR;q=1,en;q=0.7' => 'en',
1494 'pt-PT;q=1,pt-BR;q=0.5,en;q=0.7' => 'en',
1495 'pt-PT;q=0.4,pt-BR;q=0.1,en;q=0.7' => 'en',
1496 'pt-PT;q=0.1,pt-BR;q=0.4,en;q=0.7' => 'en',
1498 // Language code with several dashes are valid. The less specific language
1499 // from the browser matches the more specific one from the website.
1500 'eh-oh-laa-laa' => 'eh-oh-laa-laa',
1501 'eh-oh-laa' => 'eh-oh-laa-laa',
1502 'eh-oh' => 'eh-oh-laa-laa',
1503 'eh' => 'eh-oh-laa-laa',
1505 // Different qvalues.
1506 'en-US,en;q=0.5,fr;q=0.25' => 'en-US',
1507 'fr,en;q=0.5' => 'fr-CA',
1508 'fr,en;q=0.5,fr-CA;q=0.25' => 'fr',
1510 // Silly wildcards are also valid.
1511 '*,fr-CA;q=0.5' => 'en',
1512 '*,en;q=0.25' => 'fr-CA',
1513 'en,en-US;q=0.5,fr;q=0.25' => 'en',
1514 'en-US,en;q=0.5,fr;q=0.25' => 'en-US',
1516 // Unresolvable cases.
1519 $this->randomName(10) => FALSE
,
1522 foreach ($test_cases as
$accept_language => $expected_result) {
1523 $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $accept_language;
1524 $result = locale_language_from_browser($languages);
1525 $this->assertIdentical($result, $expected_result, t("Language selection '@accept-language' selects '@result', result = '@actual'", array('@accept-language' => $accept_language, '@result' => $expected_result, '@actual' => isset($result) ?
$result : 'none')));
1531 * Functional tests for a user's ability to change their default language.
1533 class LocaleUserLanguageFunctionalTest
extends DrupalWebTestCase
{
1534 public static
function getInfo() {
1536 'name' => 'User language settings',
1537 'description' => "Tests user's ability to change their default language.",
1538 'group' => 'Locale',
1543 parent
::setUp('locale');
1547 * Test if user can change their default language.
1549 function testUserLanguageConfiguration() {
1552 // User to add and remove language.
1553 $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
1554 // User to change their default language.
1555 $web_user = $this->drupalCreateUser();
1557 // Add custom language.
1558 $this->drupalLogin($admin_user);
1559 // Code for the language.
1561 // The English name for the language.
1562 $name = $this->randomName(16);
1564 'predefined_langcode' => 'custom',
1565 'langcode' => $langcode,
1569 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1571 // Add custom language and disable it.
1572 // Code for the language.
1573 $langcode_disabled = 'xx-yy';
1574 // The English name for the language. This will be translated.
1575 $name_disabled = $this->randomName(16);
1577 'predefined_langcode' => 'custom',
1578 'langcode' => $langcode_disabled,
1579 'name' => $name_disabled,
1582 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1583 // Disable the language.
1585 'languages[' .
$langcode_disabled .
'][enabled]' => FALSE
,
1587 $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
1588 $this->drupalLogout();
1590 // Login as normal user and edit account settings.
1591 $this->drupalLogin($web_user);
1592 $path = 'user/' .
$web_user->uid .
'/edit';
1593 $this->drupalGet($path);
1594 // Ensure language settings fieldset is available.
1595 $this->assertText(t('Language'), t('Language selector available.'));
1596 // Ensure custom language is present.
1597 $this->assertText($name, t('Language present on form.'));
1598 // Ensure disabled language isn't present.
1599 $this->assertNoText($name_disabled, t('Disabled language not present on form.'));
1600 // Switch to our custom language.
1602 'language' => $langcode,
1604 $this->drupalPost($path, $edit, t('Save'));
1605 // Ensure form was submitted successfully.
1606 $this->assertText(t('The changes have been saved.'), t('Changes were saved.'));
1607 // Check if language was changed.
1608 $elements = $this->xpath('//input[@id=:id]', array(':id' => 'edit-language-' .
$langcode));
1609 $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Default language successfully updated.'));
1611 $this->drupalLogout();
1616 * Functional test for language handling during user creation.
1618 class LocaleUserCreationTest
extends DrupalWebTestCase
{
1620 public static
function getInfo() {
1622 'name' => 'User creation',
1623 'description' => 'Tests whether proper language is stored for new users and access to language selector.',
1624 'group' => 'Locale',
1629 parent
::setUp('locale');
1630 variable_set('user_register', USER_REGISTER_VISITORS
);
1634 * Functional test for language handling during user creation.
1636 function testLocalUserCreation() {
1637 // User to add and remove language and create new users.
1638 $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'administer users'));
1639 $this->drupalLogin($admin_user);
1641 // Add predefined language.
1644 'predefined_langcode' => 'fr',
1646 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
1647 $this->assertText($langcode, t('Language added successfully.'));
1648 $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE
)), t('Correct page redirection.'));
1650 // Set language negotiation.
1652 'language[enabled][locale-url]' => TRUE
,
1654 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
1655 $this->assertText(t('Language negotiation configuration saved.'), t('Set language negotiation.'));
1657 // Check if the language selector is available on admin/people/create and
1658 // set to the currently active language.
1659 $this->drupalGet($langcode .
'/admin/people/create');
1660 $this->assertFieldChecked("edit-language-$langcode", t('Global language set in the language selector.'));
1662 // Create a user with the admin/people/create form and check if the correct
1664 $username = $this->randomName(10);
1666 'name' => $username,
1667 'mail' => $this->randomName(4) .
'@example.com',
1668 'pass[pass1]' => $username,
1669 'pass[pass2]' => $username,
1672 $this->drupalPost($langcode .
'/admin/people/create', $edit, t('Create new account'));
1674 $user = user_load_by_name($username);
1675 $this->assertEqual($user->language
, $langcode, t('New user has correct language set.'));
1677 // Register a new user and check if the language selector is hidden.
1678 $this->drupalLogout();
1680 $this->drupalGet($langcode .
'/user/register');
1681 $this->assertNoFieldByName('language[fr]', t('Language selector is not accessible.'));
1683 $username = $this->randomName(10);
1685 'name' => $username,
1686 'mail' => $this->randomName(4) .
'@example.com',
1689 $this->drupalPost($langcode .
'/user/register', $edit, t('Create new account'));
1691 $user = user_load_by_name($username);
1692 $this->assertEqual($user->language
, $langcode, t('New user has correct language set.'));
1694 // Test if the admin can use the language selector and if the
1695 // correct language is was saved.
1696 $user_edit = $langcode .
'/user/' .
$user->uid .
'/edit';
1698 $this->drupalLogin($admin_user);
1699 $this->drupalGet($user_edit);
1700 $this->assertFieldChecked("edit-language-$langcode", t('Language selector is accessible and correct language is selected.'));
1702 // Set pass_raw so we can login the new user.
1703 $user->pass_raw
= $this->randomName(10);
1705 'pass[pass1]' => $user->pass_raw
,
1706 'pass[pass2]' => $user->pass_raw
,
1709 $this->drupalPost($user_edit, $edit, t('Save'));
1711 $this->drupalLogin($user);
1712 $this->drupalGet($user_edit);
1713 $this->assertFieldChecked("edit-language-$langcode", t('Language selector is accessible and correct language is selected.'));
1718 * Functional tests for configuring a different path alias per language.
1720 class LocalePathFunctionalTest
extends DrupalWebTestCase
{
1721 public static
function getInfo() {
1723 'name' => 'Path language settings',
1724 'description' => 'Checks you can configure a language for individual url aliases.',
1725 'group' => 'Locale',
1730 parent
::setUp('locale', 'path');
1734 * Test if a language can be associated with a path alias.
1736 function testPathLanguageConfiguration() {
1739 // User to add and remove language.
1740 $admin_user = $this->drupalCreateUser(array('administer languages', 'create page content', 'administer url aliases', 'create url aliases', 'access administration pages'));
1742 // Add custom language.
1743 $this->drupalLogin($admin_user);
1744 // Code for the language.
1746 // The English name for the language.
1747 $name = $this->randomName(16);
1748 // The domain prefix.
1749 $prefix = $langcode;
1751 'predefined_langcode' => 'custom',
1752 'langcode' => $langcode,
1756 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1759 $edit = array( "prefix[$langcode]" => $prefix );
1760 $this->drupalPost('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
1762 // Check that the "xx" front page is readily available because path prefix
1763 // negotiation is pre-configured.
1764 $this->drupalGet($prefix);
1765 $this->assertText(t('Welcome to Drupal'), t('The "xx" front page is readibly available.'));
1768 $node = $this->drupalCreateNode(array('type' => 'page'));
1770 // Create a path alias in default language (English).
1771 $path = 'admin/config/search/path/add';
1772 $english_path = $this->randomName(8);
1774 'source' => 'node/' .
$node->nid
,
1775 'alias' => $english_path,
1778 $this->drupalPost($path, $edit, t('Save'));
1780 // Create a path alias in new custom language.
1781 $custom_language_path = $this->randomName(8);
1783 'source' => 'node/' .
$node->nid
,
1784 'alias' => $custom_language_path,
1785 'langcode' => $langcode,
1787 $this->drupalPost($path, $edit, t('Save'));
1789 // Confirm English language path alias works.
1790 $this->drupalGet($english_path);
1791 $this->assertText($node->title
, t('English alias works.'));
1793 // Confirm custom language path alias works.
1794 $this->drupalGet($prefix .
'/' .
$custom_language_path);
1795 $this->assertText($node->title
, t('Custom language alias works.'));
1797 // Create a custom path.
1798 $custom_path = $this->randomName(8);
1800 // Check priority of language for alias by source path.
1802 'source' => 'node/' .
$node->nid
,
1803 'alias' => $custom_path,
1804 'langcode' => LANGUAGE_NONE
,
1807 $lookup_path = drupal_lookup_path('alias', 'node/' .
$node->nid
, 'en');
1808 $this->assertEqual($english_path, $lookup_path, t('English language alias has priority.'));
1809 // Same check for language 'xx'.
1810 $lookup_path = drupal_lookup_path('alias', 'node/' .
$node->nid
, $prefix);
1811 $this->assertEqual($custom_language_path, $lookup_path, t('Custom language alias has priority.'));
1814 // Create language nodes to check priority of aliases.
1815 $first_node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
1816 $second_node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
1818 // Assign a custom path alias to the first node with the English language.
1820 'source' => 'node/' .
$first_node->nid
,
1821 'alias' => $custom_path,
1826 // Assign a custom path alias to second node with LANGUAGE_NONE.
1828 'source' => 'node/' .
$second_node->nid
,
1829 'alias' => $custom_path,
1830 'langcode' => LANGUAGE_NONE
,
1834 // Test that both node titles link to our path alias.
1835 $this->drupalGet('<front>');
1836 $custom_path_url = base_path() .
(variable_get('clean_url', 0) ?
$custom_path : '?q=' .
$custom_path);
1837 $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $first_node->title
));
1838 $this->assertTrue(!empty($elements), t('First node links to the path alias.'));
1839 $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $second_node->title
));
1840 $this->assertTrue(!empty($elements), t('Second node links to the path alias.'));
1842 // Confirm that the custom path leads to the first node.
1843 $this->drupalGet($custom_path);
1844 $this->assertText($first_node->title
, t('Custom alias returns first node.'));
1846 // Confirm that the custom path with prefix leads to the second node.
1847 $this->drupalGet($prefix .
'/' .
$custom_path);
1848 $this->assertText($second_node->title
, t('Custom alias with prefix returns second node.'));
1854 * Functional tests for multilingual support on nodes.
1856 class LocaleContentFunctionalTest
extends DrupalWebTestCase
{
1857 public static
function getInfo() {
1859 'name' => 'Content language settings',
1860 'description' => 'Checks you can enable multilingual support on content types and configure a language for a node.',
1861 'group' => 'Locale',
1866 parent
::setUp('locale');
1870 * Verifies that machine name fields are always LTR.
1872 function testMachineNameLTR() {
1873 // User to add and remove language.
1874 $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages'));
1877 $this->drupalLogin($admin_user);
1879 // Verify that the machine name field is LTR for a new content type.
1880 $this->drupalGet('admin/structure/types/add');
1881 $this->assertFieldByXpath('//input[@name="type" and @dir="ltr"]', NULL
, 'The machine name field is LTR when no additional language is configured.');
1883 // Install the Arabic language (which is RTL) and configure as the default.
1885 $edit['predefined_langcode'] = 'ar';
1886 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
1889 $edit['site_default'] = 'ar';
1890 $this->drupalPost(NULL
, $edit, t('Save configuration'));
1892 // Verify that the machine name field is still LTR for a new content type.
1893 $this->drupalGet('admin/structure/types/add');
1894 $this->assertFieldByXpath('//input[@name="type" and @dir="ltr"]', NULL
, 'The machine name field is LTR when the default language is RTL.');
1898 * Test if a content type can be set to multilingual and language is present.
1900 function testContentTypeLanguageConfiguration() {
1903 // User to add and remove language.
1904 $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages'));
1905 // User to create a node.
1906 $web_user = $this->drupalCreateUser(array('create article content', 'create page content', 'edit any page content'));
1908 // Add custom language.
1909 $this->drupalLogin($admin_user);
1910 // Code for the language.
1912 // The English name for the language.
1913 $name = $this->randomName(16);
1915 'predefined_langcode' => 'custom',
1916 'langcode' => $langcode,
1920 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1922 // Add disabled custom language.
1923 // Code for the language.
1924 $langcode_disabled = 'xx-yy';
1925 // The English name for the language.
1926 $name_disabled = $this->randomName(16);
1928 'predefined_langcode' => 'custom',
1929 'langcode' => $langcode_disabled,
1930 'name' => $name_disabled,
1933 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1934 // Disable second custom language.
1935 $path = 'admin/config/regional/language';
1937 'languages[' .
$langcode_disabled .
'][enabled]' => FALSE
,
1939 $this->drupalPost($path, $edit, t('Save configuration'));
1941 // Set "Basic page" content type to use multilingual support.
1942 $this->drupalGet('admin/structure/types/manage/page');
1943 $this->assertText(t('Multilingual support'), t('Multilingual support fieldset present on content type configuration form.'));
1945 'node_type_language' => 1,
1947 $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
1948 $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), t('Basic page content type has been updated.'));
1949 $this->drupalLogout();
1951 // Verify language selection is not present on add article form.
1952 $this->drupalLogin($web_user);
1953 $this->drupalGet('node/add/article');
1954 // Verify language select list is not present.
1955 $this->assertNoFieldByName('language', NULL
, t('Language select not present on add article form.'));
1957 // Verify language selection appears on add "Basic page" form.
1958 $this->drupalGet('node/add/page');
1959 // Verify language select list is present.
1960 $this->assertFieldByName('language', NULL
, t('Language select present on add Basic page form.'));
1961 // Ensure enabled language appears.
1962 $this->assertText($name, t('Enabled language present.'));
1963 // Ensure disabled language doesn't appear.
1964 $this->assertNoText($name_disabled, t('Disabled language not present.'));
1966 // Create "Basic page" content.
1967 $node_title = $this->randomName();
1968 $node_body = $this->randomName();
1971 'title' => $node_title,
1972 'body' => array($langcode => array(array('value' => $node_body))),
1973 'language' => $langcode,
1975 $node = $this->drupalCreateNode($edit);
1976 // Edit the content and ensure correct language is selected.
1977 $path = 'node/' .
$node->nid .
'/edit';
1978 $this->drupalGet($path);
1979 $this->assertRaw('<option value="' .
$langcode .
'" selected="selected">' .
$name .
'</option>', t('Correct language selected.'));
1980 // Ensure we can change the node language.
1984 $this->drupalPost($path, $edit, t('Save'));
1985 $this->assertRaw(t('%title has been updated.', array('%title' => $node_title)), t('Basic page content updated.'));
1987 $this->drupalLogout();
1991 * Test if a dir and lang tags exist in node's attributes.
1993 function testContentTypeDirLang() {
1994 // User to add and remove language.
1995 $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages'));
1996 // User to create a node.
1997 $web_user = $this->drupalCreateUser(array('create article content', 'edit own article content'));
2000 $this->drupalLogin($admin_user);
2002 // Install Arabic language.
2004 $edit['predefined_langcode'] = 'ar';
2005 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2007 // Install Spanish language.
2009 $edit['predefined_langcode'] = 'es';
2010 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2012 // Set "Article" content type to use multilingual support.
2013 $this->drupalGet('admin/structure/types/manage/article');
2015 'node_type_language' => 1,
2017 $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type'));
2018 $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Article')), t('Article content type has been updated.'));
2019 $this->drupalLogout();
2021 // Login as web user to add new article.
2022 $this->drupalLogin($web_user);
2024 // Create three nodes: English, Arabic and Spanish.
2025 $node_en = $this->createNodeArticle('en');
2026 $node_ar = $this->createNodeArticle('ar');
2027 $node_es = $this->createNodeArticle('es');
2029 $this->drupalGet('node');
2031 // Check if English node does not have lang tag.
2032 $pattern = '|id="node-' .
$node_en->nid .
'"[^<>]*lang="en"|';
2033 $this->assertNoPattern($pattern, t('The lang tag has not been assigned to the English node.'));
2035 // Check if English node does not have dir tag.
2036 $pattern = '|id="node-' .
$node_en->nid .
'"[^<>]*dir="ltr"|';
2037 $this->assertNoPattern($pattern, t('The dir tag has not been assigned to the English node.'));
2039 // Check if Arabic node has lang="ar" & dir="rtl" tags.
2040 $pattern = '|id="node-' .
$node_ar->nid .
'"[^<>]*lang="ar" dir="rtl"|';
2041 $this->assertPattern($pattern, t('The lang and dir tags have been assigned correctly to the Arabic node.'));
2043 // Check if Spanish node has lang="es" tag.
2044 $pattern = '|id="node-' .
$node_es->nid .
'"[^<>]*lang="es"|';
2045 $this->assertPattern($pattern, t('The lang tag has been assigned correctly to the Spanish node.'));
2047 // Check if Spanish node does not have dir="ltr" tag.
2048 $pattern = '|id="node-' .
$node_es->nid .
'"[^<>]*lang="es" dir="ltr"|';
2049 $this->assertNoPattern($pattern, t('The dir tag has not been assigned to the Spanish node.'));
2051 $this->drupalLogout();
2055 * Create node in a specific language.
2057 protected
function createNodeArticle($langcode) {
2058 $this->drupalGet('node/add/article');
2059 $node_title = $this->randomName();
2060 $node_body = $this->randomName();
2062 'type' => 'article',
2063 'title' => $node_title,
2064 'body' => array($langcode => array(array('value' => $node_body))),
2065 'language' => $langcode,
2068 return $this->drupalCreateNode($edit);
2073 * Test UI language negotiation
2075 * 1. URL (PATH) > DEFAULT
2076 * UI Language base on URL prefix, browser language preference has no
2079 * UI in site default language
2080 * zh-hans/admin/config
2082 * blah-blah/admin/config
2084 * 2. URL (PATH) > BROWSER > DEFAULT
2086 * UI in user's browser language preference if the site has that
2087 * language enabled, if not, the default language
2088 * zh-hans/admin/config
2090 * blah-blah/admin/config
2092 * 3. URL (DOMAIN) > DEFAULT
2093 * http://example.com/admin/config
2094 * UI language in site default
2095 * http://example.cn/admin/config
2096 * UI language in Chinese
2098 class LocaleUILanguageNegotiationTest
extends DrupalWebTestCase
{
2099 public static
function getInfo() {
2101 'name' => 'UI language negotiation',
2102 'description' => 'Test UI language switching by url path prefix and domain.',
2103 'group' => 'Locale',
2108 parent
::setUp('locale', 'locale_test');
2109 require_once DRUPAL_ROOT .
'/core/includes/language.inc';
2110 drupal_load('module', 'locale');
2111 $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages', 'administer blocks'));
2112 $this->drupalLogin($admin_user);
2116 * Tests for language switching by URL path.
2118 function testUILanguageNegotiation() {
2119 // A few languages to switch to.
2120 // This one is unknown, should get the default lang version.
2121 $langcode_unknown = 'blah-blah';
2122 // For testing browser lang preference.
2123 $langcode_browser_fallback = 'vi';
2124 // For testing path prefix.
2125 $langcode = 'zh-hans';
2126 // For setting browser language preference to 'vi'.
2127 $http_header_browser_fallback = array("Accept-Language: $langcode_browser_fallback;q=1");
2128 // For setting browser language preference to some unknown.
2129 $http_header_blah = array("Accept-Language: blah;q=1");
2131 // This domain should switch the UI to Chinese.
2132 $language_domain = 'example.cn';
2134 // Setup the site languages by installing two languages.
2135 require_once DRUPAL_ROOT .
'/core/includes/locale.inc';
2136 $language = (object) array(
2137 'langcode' => $langcode_browser_fallback,
2139 language_save($language);
2140 $language = (object) array(
2141 'langcode' => $langcode,
2143 language_save($language);
2145 // We will look for this string in the admin/config screen to see if the
2146 // corresponding translated string is shown.
2147 $default_string = 'Configure languages for content and the user interface';
2149 // Set the default language in order for the translated string to be registered
2150 // into database when seen by t(). Without doing this, our target string
2151 // is for some reason not found when doing translate search. This might
2153 drupal_static_reset('language_list');
2154 $languages = language_list(TRUE
);
2155 variable_set('language_default', $languages['vi']);
2156 // First visit this page to make sure our target string is searchable.
2157 $this->drupalGet('admin/config');
2158 // Now the t()'ed string is in db so switch the language back to default.
2159 variable_del('language_default');
2161 // Translate the string.
2162 $language_browser_fallback_string = "In $langcode_browser_fallback In $langcode_browser_fallback In $langcode_browser_fallback";
2163 $language_string = "In $langcode In $langcode In $langcode";
2164 // Do a translate search of our target string.
2165 $edit = array( 'string' => $default_string);
2166 $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Filter'));
2167 // Should find the string and now click edit to post translated string.
2168 $this->clickLink('edit');
2170 "translations[$langcode_browser_fallback]" => $language_browser_fallback_string,
2171 "translations[$langcode]" => $language_string,
2173 $this->drupalPost(NULL
, $edit, t('Save translations'));
2175 // Configure URL language rewrite.
2176 variable_set('locale_language_negotiation_url_type', LANGUAGE_TYPE_INTERFACE
);
2179 // Default, browser preference should have no influence.
2181 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL
, LANGUAGE_NEGOTIATION_DEFAULT
),
2182 'path' => 'admin/config',
2183 'expect' => $default_string,
2184 'expected_provider' => LANGUAGE_NEGOTIATION_DEFAULT
,
2185 'http_header' => $http_header_browser_fallback,
2186 'message' => 'URL (PATH) > DEFAULT: no language prefix, UI language is default and the browser language preference setting is not used.',
2190 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL
, LANGUAGE_NEGOTIATION_DEFAULT
),
2191 'path' => "$langcode/admin/config",
2192 'expect' => $language_string,
2193 'expected_provider' => LANGUAGE_NEGOTIATION_URL
,
2194 'http_header' => $http_header_browser_fallback,
2195 'message' => 'URL (PATH) > DEFAULT: with language prefix, UI language is switched based on path prefix',
2197 // Default, go by browser preference.
2199 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL
, LANGUAGE_NEGOTIATION_BROWSER
),
2200 'path' => 'admin/config',
2201 'expect' => $language_browser_fallback_string,
2202 'expected_provider' => LANGUAGE_NEGOTIATION_BROWSER
,
2203 'http_header' => $http_header_browser_fallback,
2204 'message' => 'URL (PATH) > BROWSER: no language prefix, UI language is determined by browser language preference',
2206 // Prefix, switch to the language.
2208 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL
, LANGUAGE_NEGOTIATION_BROWSER
),
2209 'path' => "$langcode/admin/config",
2210 'expect' => $language_string,
2211 'expected_provider' => LANGUAGE_NEGOTIATION_URL
,
2212 'http_header' => $http_header_browser_fallback,
2213 'message' => 'URL (PATH) > BROWSER: with langage prefix, UI language is based on path prefix',
2215 // Default, browser language preference is not one of site's lang.
2217 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL
, LANGUAGE_NEGOTIATION_BROWSER
, LANGUAGE_NEGOTIATION_DEFAULT
),
2218 'path' => 'admin/config',
2219 'expect' => $default_string,
2220 'expected_provider' => LANGUAGE_NEGOTIATION_DEFAULT
,
2221 'http_header' => $http_header_blah,
2222 'message' => 'URL (PATH) > BROWSER > DEFAULT: no language prefix and browser language preference set to unknown language should use default language',
2226 foreach ($tests as
$test) {
2227 $this->runTest($test);
2230 // Unknown language prefix should return 404.
2231 variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE
, locale_language_negotiation_info());
2232 $this->drupalGet("$langcode_unknown/admin/config", array(), $http_header_browser_fallback);
2233 $this->assertResponse(404, "Unknown language path prefix should return 404");
2235 // Setup for domain negotiation, first configure the language to have domain
2236 // URL. We use https and a port to make sure that only the domain name is used.
2237 $edit = array("domain[$langcode]" => "https://$language_domain:99");
2238 $this->drupalPost("admin/config/regional/language/detection/url", $edit, t('Save configuration'));
2239 // Set the site to use domain language negotiation.
2242 // Default domain, browser preference should have no influence.
2244 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL
, LANGUAGE_NEGOTIATION_DEFAULT
),
2245 'locale_language_negotiation_url_part' => LANGUAGE_NEGOTIATION_URL_DOMAIN
,
2246 'path' => 'admin/config',
2247 'expect' => $default_string,
2248 'expected_provider' => LANGUAGE_NEGOTIATION_DEFAULT
,
2249 'http_header' => $http_header_browser_fallback,
2250 'message' => 'URL (DOMAIN) > DEFAULT: default domain should get default language',
2252 // Language domain specific URL, we set the $_SERVER['HTTP_HOST'] in
2253 // locale_test.module hook_boot() to simulate this.
2255 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL
, LANGUAGE_NEGOTIATION_DEFAULT
),
2256 'locale_language_negotiation_url_part' => LANGUAGE_NEGOTIATION_URL_DOMAIN
,
2257 'locale_test_domain' => $language_domain,
2258 'path' => 'admin/config',
2259 'expect' => $language_string,
2260 'expected_provider' => LANGUAGE_NEGOTIATION_URL
,
2261 'http_header' => $http_header_browser_fallback,
2262 'message' => 'URL (DOMAIN) > DEFAULT: domain example.cn should switch to Chinese',
2266 foreach ($tests as
$test) {
2267 $this->runTest($test);
2271 protected
function runTest($test) {
2272 if (!empty($test['language_negotiation'])) {
2273 $negotiation = array_flip($test['language_negotiation']);
2274 language_negotiation_set(LANGUAGE_TYPE_INTERFACE
, $negotiation);
2276 if (!empty($test['locale_language_negotiation_url_part'])) {
2277 variable_set('locale_language_negotiation_url_part', $test['locale_language_negotiation_url_part']);
2279 if (!empty($test['locale_test_domain'])) {
2280 variable_set('locale_test_domain', $test['locale_test_domain']);
2282 $this->drupalGet($test['path'], array(), $test['http_header']);
2283 $this->assertText($test['expect'], $test['message']);
2284 $this->assertText(t('Language negotiation provider: @name', array('@name' => $test['expected_provider'])));
2288 * Test URL language detection when the requested URL has no language.
2290 function testUrlLanguageFallback() {
2291 // Add the Italian language.
2292 $langcode_browser_fallback = 'it';
2293 $language = (object) array(
2294 'langcode' => $langcode_browser_fallback,
2296 language_save($language);
2297 $languages = language_list();
2299 // Enable the path prefix for the default language: this way any unprefixed
2300 // URL must have a valid fallback value.
2301 $edit = array('prefix[en]' => 'en');
2302 $this->drupalPost('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
2304 // Enable browser and URL language detection.
2306 'language[enabled][locale-browser]' => TRUE
,
2307 'language[enabled][locale-url]' => TRUE
,
2308 'language[weight][locale-browser]' => -8,
2309 'language[weight][locale-url]' => -10,
2311 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
2312 $this->drupalGet('admin/config/regional/language/detection');
2314 // Enable the language switcher block.
2315 $edit = array('blocks[locale_language][region]' => 'sidebar_first');
2316 $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
2318 // Access the front page without specifying any valid URL language prefix
2319 // and having as browser language preference a non-default language.
2320 $http_header = array("Accept-Language: $langcode_browser_fallback;q=1");
2321 $language = (object) array('langcode' => '');
2322 $this->drupalGet('', array('language' => $language), $http_header);
2324 // Check that the language switcher active link matches the given browser
2326 $args = array(':url' => base_path() .
(!empty($GLOBALS['conf']['clean_url']) ?
$langcode_browser_fallback : "?q=$langcode_browser_fallback"));
2327 $fields = $this->xpath('//div[@id="block-locale-language"]//a[@class="language-link active" and @href=:url]', $args);
2328 $this->assertTrue($fields[0] == $languages[$langcode_browser_fallback]->name
, t('The browser language is the URL active language'));
2330 // Check that URLs are rewritten using the given browser language.
2331 $fields = $this->xpath('//div[@id="site-name"]//a[@rel="home" and @href=:url]//span', $args);
2332 $this->assertTrue($fields[0] == 'Drupal', t('URLs are rewritten using the browser language.'));
2336 * Test if the url function returns the right url when using different domains for different languages.
2338 function testLanguageDomain() {
2339 // Add the Italian language.
2341 $language = (object) array(
2342 'langcode' => $langcode,
2344 language_save($language);
2345 $languages = language_list();
2347 // Enable browser and URL language detection.
2349 'language[enabled][locale-url]' => TRUE
,
2350 'language[weight][locale-url]' => -10,
2352 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
2354 // Change the domain for the Italian language.
2356 'locale_language_negotiation_url_part' => 1,
2357 'domain[it]' => 'it.example.com',
2359 $this->drupalPost('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
2361 // Build the link we're going to test based on the clean url setting.
2362 $link = (!empty($GLOBALS['conf']['clean_url'])) ?
'it.example.com/admin' : 'it.example.com/?q=admin';
2365 // Test URL in another language: http://it.example.com/?q=admin.
2366 // Base path gives problems on the testbot, so $correct_link is hard-coded.
2367 // @see UrlAlterFunctionalTest::assertUrlOutboundAlter (path.test).
2368 $italian_url = url('admin', array('language' => $languages['it']));
2369 $url_scheme = ($is_https) ?
'https://' : 'http://';
2370 $correct_link = $url_scheme .
$link;
2371 $this->assertTrue($italian_url == $correct_link, t('The url() function returns the right url (@url) in accordance with the chosen language', array('@url' => $italian_url)));
2373 // Test https via options.
2374 variable_set('https', TRUE
);
2375 $italian_url = url('admin', array('https' => TRUE
, 'language' => $languages['it']));
2376 $correct_link = 'https://' .
$link;
2377 $this->assertTrue($italian_url == $correct_link, t('The url() function returns the right https url (via options) (@url) in accordance with the chosen language', array('@url' => $italian_url)));
2378 variable_set('https', FALSE
);
2380 // Test https via current url scheme.
2381 $temp_https = $is_https;
2383 $italian_url = url('admin', array('language' => $languages['it']));
2384 $correct_link = 'https://' .
$link;
2385 $this->assertTrue($italian_url == $correct_link, t('The url() function returns the right url (via current url scheme) (@url) in accordance with the chosen language', array('@url' => $italian_url)));
2386 $is_https = $temp_https;
2391 * Test that URL rewriting works as expected.
2393 class LocaleUrlRewritingTest
extends DrupalWebTestCase
{
2394 public static
function getInfo() {
2396 'name' => 'URL rewriting',
2397 'description' => 'Test that URL rewriting works as expected.',
2398 'group' => 'Locale',
2403 parent
::setUp('locale');
2405 // Create and login user.
2406 $this->web_user
= $this->drupalCreateUser(array('administer languages', 'access administration pages'));
2407 $this->drupalLogin($this->web_user
);
2409 // Install French language.
2411 $edit['predefined_langcode'] = 'fr';
2412 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2414 // Install Italian language.
2416 $edit['predefined_langcode'] = 'it';
2417 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2419 // Disable Italian language.
2420 $edit = array('languages[it][enabled]' => FALSE
);
2421 $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
2423 // Enable URL language detection and selection.
2424 $edit = array('language[enabled][locale-url]' => 1);
2425 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
2427 // Reset static caching.
2428 drupal_static_reset('language_list');
2429 drupal_static_reset('locale_url_outbound_alter');
2430 drupal_static_reset('locale_language_url_rewrite_url');
2434 * Check that disabled or non-installed languages are not considered.
2436 function testUrlRewritingEdgeCases() {
2437 // Check URL rewriting with a disabled language.
2438 $languages = language_list();
2439 $this->checkUrl($languages['it'], t('Path language is ignored if language is disabled.'), t('URL language negotiation does not work with disabled languages'));
2441 // Check URL rewriting with a non-installed language.
2442 $non_existing = language_default();
2443 $non_existing->langcode
= $this->randomName();
2444 $this->checkUrl($non_existing, t('Path language is ignored if language is not installed.'), t('URL language negotiation does not work with non-installed languages'));
2448 * Check URL rewriting for the given language.
2450 * The test is performed with a fixed URL (the default front page) to simply
2451 * check that language prefixes are not added to it and that the prefixed URL
2452 * is actually not working.
2454 private
function checkUrl($language, $message1, $message2) {
2455 $options = array('language' => $language);
2456 $base_path = trim(base_path(), '/');
2457 $rewritten_path = trim(str_replace(array('?q=', $base_path), '', url('node', $options)), '/');
2458 $segments = explode('/', $rewritten_path, 2);
2459 $prefix = $segments[0];
2460 $path = isset($segments[1]) ?
$segments[1] : $prefix;
2462 // If the rewritten URL has not a language prefix we pick a random prefix so
2463 // we can always check the prefixed URL.
2464 $prefixes = locale_language_negotiation_url_prefixes();
2465 $stored_prefix = isset($prefixes[$language->langcode
]) ?
$prefixes[$language->langcode
] : $this->randomName();
2466 if ($this->assertNotEqual($stored_prefix, $prefix, $message1)) {
2467 $prefix = $stored_prefix;
2470 $this->drupalGet("$prefix/$path");
2471 $this->assertResponse(404, $message2);
2476 * Functional test for multilingual fields.
2478 class LocaleMultilingualFieldsFunctionalTest
extends DrupalWebTestCase
{
2479 public static
function getInfo() {
2481 'name' => 'Multilingual fields',
2482 'description' => 'Test multilingual support for fields.',
2483 'group' => 'Locale',
2488 parent
::setUp('locale');
2490 $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages', 'create page content', 'edit own page content'));
2491 $this->drupalLogin($admin_user);
2493 // Add a new language.
2494 require_once DRUPAL_ROOT .
'/core/includes/locale.inc';
2495 $language = (object) array(
2497 'name' => 'Italian',
2499 language_save($language);
2501 // Enable URL language detection and selection.
2502 $edit = array('language[enabled][locale-url]' => '1');
2503 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
2505 // Set "Basic page" content type to use multilingual support.
2507 'node_type_language' => 1,
2509 $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
2510 $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), t('Basic page content type has been updated.'));
2512 // Make node body translatable.
2513 $field = field_info_field('body');
2514 $field['translatable'] = TRUE
;
2515 field_update_field($field);
2519 * Test if field languages are correctly set through the node form.
2521 function testMultilingualNodeForm() {
2522 // Create "Basic page" content.
2523 $langcode = LANGUAGE_NONE
;
2524 $title_key = "title";
2525 $title_value = $this->randomName(8);
2526 $body_key = "body[$langcode][0][value]";
2527 $body_value = $this->randomName(16);
2529 // Create node to edit.
2531 $edit[$title_key] = $title_value;
2532 $edit[$body_key] = $body_value;
2533 $edit['language'] = 'en';
2534 $this->drupalPost('node/add/page', $edit, t('Save'));
2536 // Check that the node exists in the database.
2537 $node = $this->drupalGetNodeByTitle($edit[$title_key]);
2538 $this->assertTrue($node, t('Node found in database.'));
2540 $assert = isset($node->body
['en']) && !isset($node->body
[LANGUAGE_NONE
]) && $node->body
['en'][0]['value'] == $body_value;
2541 $this->assertTrue($assert, t('Field language correctly set.'));
2543 // Change node language.
2544 $this->drupalGet("node/$node->nid/edit");
2546 $title_key => $this->randomName(8),
2549 $this->drupalPost(NULL
, $edit, t('Save'));
2550 $node = $this->drupalGetNodeByTitle($edit[$title_key]);
2551 $this->assertTrue($node, t('Node found in database.'));
2553 $assert = isset($node->body
['it']) && !isset($node->body
['en']) && $node->body
['it'][0]['value'] == $body_value;
2554 $this->assertTrue($assert, t('Field language correctly changed.'));
2556 // Enable content language URL detection.
2557 language_negotiation_set(LANGUAGE_TYPE_CONTENT
, array(LANGUAGE_NEGOTIATION_URL
=> 0));
2559 // Test multilingual field language fallback logic.
2560 $this->drupalGet("it/node/$node->nid");
2561 $this->assertRaw($body_value, t('Body correctly displayed using Italian as requested language'));
2563 $this->drupalGet("node/$node->nid");
2564 $this->assertRaw($body_value, t('Body correctly displayed using English as requested language'));
2568 * Test multilingual field display settings.
2570 function testMultilingualDisplaySettings() {
2571 // Create "Basic page" content.
2572 $langcode = LANGUAGE_NONE
;
2573 $title_key = "title";
2574 $title_value = $this->randomName(8);
2575 $body_key = "body[$langcode][0][value]";
2576 $body_value = $this->randomName(16);
2578 // Create node to edit.
2580 $edit[$title_key] = $title_value;
2581 $edit[$body_key] = $body_value;
2582 $edit['language'] = 'en';
2583 $this->drupalPost('node/add/page', $edit, t('Save'));
2585 // Check that the node exists in the database.
2586 $node = $this->drupalGetNodeByTitle($edit[$title_key]);
2587 $this->assertTrue($node, t('Node found in database.'));
2589 // Check if node body is showed.
2590 $this->drupalGet("node/$node->nid");
2591 $body = $this->xpath('//div[@id=:id]//div[@property="content:encoded"]/p', array(':id' => 'node-' .
$node->nid
));
2592 $this->assertEqual(current($body), $node->body
['en'][0]['value'], 'Node body is correctly showed.');
2597 * Functional tests for comment language.
2599 class LocaleCommentLanguageFunctionalTest
extends DrupalWebTestCase
{
2601 public static
function getInfo() {
2603 'name' => 'Comment language',
2604 'description' => 'Tests for comment language.',
2605 'group' => 'Locale',
2610 parent
::setUp('locale', 'locale_test');
2612 // Create and login user.
2613 $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'administer content types', 'create article content'));
2614 $this->drupalLogin($admin_user);
2617 $edit = array('predefined_langcode' => 'fr');
2618 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2620 // Set "Article" content type to use multilingual support.
2621 $edit = array('node_type_language' => 1);
2622 $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type'));
2624 // Enable content language negotiation UI.
2625 variable_set('locale_test_content_language_type', TRUE
);
2627 // Set interface language detection to user and content language detection
2628 // to URL. Disable inheritance from interface language to ensure content
2629 // language will fall back to the default language if no URL language can be
2632 'language[enabled][locale-user]' => TRUE
,
2633 'language_content[enabled][locale-url]' => TRUE
,
2634 'language_content[enabled][locale-interface]' => FALSE
,
2636 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
2638 // Change user language preference, this way interface language is always
2639 // French no matter what path prefix the URLs have.
2640 $edit = array('language' => 'fr');
2641 $this->drupalPost("user/{$admin_user->uid}/edit", $edit, t('Save'));
2645 * Test that comment language is properly set.
2647 function testCommentLanguage() {
2648 drupal_static_reset('language_list');
2650 // Create two nodes, one for english and one for french, and comment each
2651 // node using both english and french as content language by changing URL
2652 // language prefixes. Meanwhile interface language is always French, which
2653 // is the user language preference. This way we can ensure that node
2654 // language and interface language do not influence comment language, as
2655 // only content language has to.
2656 foreach (language_list() as
$node_langcode => $node_language) {
2657 $langcode_none = LANGUAGE_NONE
;
2659 // Create "Article" content.
2660 $title = $this->randomName();
2663 "body[$langcode_none][0][value]" => $this->randomName(),
2664 "language" => $node_langcode,
2666 $this->drupalPost("node/add/article", $edit, t('Save'));
2667 $node = $this->drupalGetNodeByTitle($title);
2669 $prefixes = locale_language_negotiation_url_prefixes();
2670 foreach (language_list() as
$langcode => $language) {
2671 // Post a comment with content language $langcode.
2672 $prefix = empty($prefixes[$langcode]) ?
'' : $prefixes[$langcode] .
'/';
2673 $edit = array("comment_body[$langcode_none][0][value]" => $this->randomName());
2674 $this->drupalPost("{$prefix}node/{$node->nid}", $edit, t('Save'));
2676 // Check that comment language matches the current content language.
2677 $comment = db_select('comment', 'c')
2679 ->condition('nid', $node->nid
)
2680 ->orderBy('cid', 'DESC')
2683 $args = array('%node_language' => $node_langcode, '%comment_language' => $comment->language
, '%langcode' => $langcode);
2684 $this->assertEqual($comment->language
, $langcode, t('The comment posted with content language %langcode and belonging to the node with language %node_language has language %comment_language', $args));
2691 * Functional tests for localizing date formats.
2693 class LocaleDateFormatsFunctionalTest
extends DrupalWebTestCase
{
2695 public static
function getInfo() {
2697 'name' => 'Localize date formats',
2698 'description' => 'Tests for the localization of date formats.',
2699 'group' => 'Locale',
2704 parent
::setUp('locale');
2706 // Create and login user.
2707 $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'create article content'));
2708 $this->drupalLogin($admin_user);
2712 * Functional tests for localizing date formats.
2714 function testLocalizeDateFormats() {
2717 'predefined_langcode' => 'fr',
2719 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2721 // Set language negotiation.
2722 $language_type = LANGUAGE_TYPE_INTERFACE
;
2724 "{$language_type}[enabled][locale-url]" => TRUE
,
2726 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
2728 // Configure date formats.
2729 $this->drupalGet('admin/config/regional/date-time/locale');
2730 $this->assertText('French', 'Configured languages appear.');
2732 'date_format_long' => 'd.m.Y - H:i',
2733 'date_format_medium' => 'd.m.Y - H:i',
2734 'date_format_short' => 'd.m.Y - H:i',
2736 $this->drupalPost('admin/config/regional/date-time/locale/fr/edit', $edit, t('Save configuration'));
2737 $this->assertText(t('Configuration saved.'), 'French date formats updated.');
2739 'date_format_long' => 'j M Y - g:ia',
2740 'date_format_medium' => 'j M Y - g:ia',
2741 'date_format_short' => 'j M Y - g:ia',
2743 $this->drupalPost('admin/config/regional/date-time/locale/en/edit', $edit, t('Save configuration'));
2744 $this->assertText(t('Configuration saved.'), 'English date formats updated.');
2746 // Create node content.
2747 $node = $this->drupalCreateNode(array('type' => 'article'));
2749 // Configure format for the node posted date changes with the language.
2750 $this->drupalGet('node/' .
$node->nid
);
2751 $english_date = format_date($node->created
, 'custom', 'j M Y');
2752 $this->assertText($english_date, t('English date format appears'));
2753 $this->drupalGet('fr/node/' .
$node->nid
);
2754 $french_date = format_date($node->created
, 'custom', 'd.m.Y');
2755 $this->assertText($french_date, t('French date format appears'));
2760 * Functional test for language types/negotiation info.
2762 class LocaleLanguageNegotiationInfoFunctionalTest
extends DrupalWebTestCase
{
2764 public static
function getInfo() {
2766 'name' => 'Language negotiation info',
2767 'description' => 'Tests alterations to language types/negotiation info.',
2768 'group' => 'Locale',
2773 parent
::setUp('locale');
2774 require_once DRUPAL_ROOT .
'/core/includes/language.inc';
2775 $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'view the administration theme'));
2776 $this->drupalLogin($admin_user);
2777 $this->drupalPost('admin/config/regional/language/add', array('predefined_langcode' => 'it'), t('Add language'));
2781 * Tests alterations to language types/negotiation info.
2783 function testInfoAlterations() {
2784 // Enable language type/negotiation info alterations.
2785 variable_set('locale_test_language_types', TRUE
);
2786 variable_set('locale_test_language_negotiation_info', TRUE
);
2787 $this->languageNegotiationUpdate();
2789 // Check that fixed language types are properly configured without the need
2790 // of saving the language negotiation settings.
2791 $this->checkFixedLanguageTypes();
2793 // Make the content language type configurable by updating the language
2794 // negotiation settings with the proper flag enabled.
2795 variable_set('locale_test_content_language_type', TRUE
);
2796 $this->languageNegotiationUpdate();
2797 $type = LANGUAGE_TYPE_CONTENT
;
2798 $language_types = variable_get('language_types', drupal_language_types());
2799 $this->assertTrue($language_types[$type], t('Content language type is configurable.'));
2801 // Enable some core and custom language providers. The test language type is
2802 // supposed to be configurable.
2803 $test_type = 'test_language_type';
2804 $provider = LANGUAGE_NEGOTIATION_INTERFACE
;
2805 $test_provider = 'test_language_provider';
2806 $form_field = $type .
'[enabled]['.
$provider .
']';
2808 $form_field => TRUE
,
2809 $type .
'[enabled][' .
$test_provider .
']' => TRUE
,
2810 $test_type .
'[enabled][' .
$test_provider .
']' => TRUE
,
2812 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
2814 // Remove the interface language provider by updating the language
2815 // negotiation settings with the proper flag enabled.
2816 variable_set('locale_test_language_negotiation_info_alter', TRUE
);
2817 $this->languageNegotiationUpdate();
2818 $negotiation = variable_get("language_negotiation_$type", array());
2819 $this->assertFalse(isset($negotiation[$provider]), t('Interface language provider removed from the stored settings.'));
2820 $this->assertNoFieldByXPath("//input[@name=\"$form_field\"]", NULL
, t('Interface language provider unavailable.'));
2822 // Check that type-specific language providers can be assigned only to the
2823 // corresponding language types.
2824 foreach (language_types_configurable() as
$type) {
2825 $form_field = $type .
'[enabled][test_language_provider_ts]';
2826 if ($type == $test_type) {
2827 $this->assertFieldByXPath("//input[@name=\"$form_field\"]", NULL
, t('Type-specific test language provider available for %type.', array('%type' => $type)));
2830 $this->assertNoFieldByXPath("//input[@name=\"$form_field\"]", NULL
, t('Type-specific test language provider unavailable for %type.', array('%type' => $type)));
2834 // Check language negotiation results.
2835 $this->drupalGet('');
2836 $last = variable_get('locale_test_language_negotiation_last', array());
2837 foreach (language_types() as
$type) {
2838 $langcode = $last[$type];
2839 $value = $type == LANGUAGE_TYPE_CONTENT
|| strpos($type, 'test') !== FALSE ?
'it' : 'en';
2840 $this->assertEqual($langcode, $value, t('The negotiated language for %type is %language', array('%type' => $type, '%language' => $langcode)));
2843 // Disable locale_test and check that everything is set back to the original
2845 $this->languageNegotiationUpdate('disable');
2847 // Check that only the core language types are available.
2848 foreach (language_types() as
$type) {
2849 $this->assertTrue(strpos($type, 'test') === FALSE
, t('The %type language is still available', array('%type' => $type)));
2852 // Check that fixed language types are properly configured, even those
2853 // previously set to configurable.
2854 $this->checkFixedLanguageTypes();
2856 // Check that unavailable language providers are not present in the
2857 // negotiation settings.
2858 $negotiation = variable_get("language_negotiation_$type", array());
2859 $this->assertFalse(isset($negotiation[$test_provider]), t('The disabled test language provider is not part of the content language negotiation settings.'));
2861 // Check that configuration page presents the correct options and settings.
2862 $this->assertNoRaw(t('Test language detection'), t('No test language type configuration available.'));
2863 $this->assertNoRaw(t('This is a test language provider'), t('No test language provider available.'));
2867 * Update language types/negotiation information.
2869 * Manually invoke locale_modules_enabled()/locale_modules_disabled() since
2870 * they would not be invoked after enabling/disabling locale_test the first
2873 protected
function languageNegotiationUpdate($op = 'enable') {
2874 static
$last_op = NULL
;
2875 $modules = array('locale_test');
2877 // Enable/disable locale_test only if we did not already before.
2878 if ($last_op != $op) {
2879 $function = "module_{$op}";
2880 $function($modules);
2881 // Reset hook implementation cache.
2882 module_implements_reset();
2885 drupal_static_reset('language_types_info');
2886 drupal_static_reset('language_negotiation_info');
2887 $function = "locale_modules_{$op}d";
2888 if (function_exists($function)) {
2889 $function($modules);
2892 $this->drupalGet('admin/config/regional/language/detection');
2896 * Check that language negotiation for fixed types matches the stored one.
2898 protected
function checkFixedLanguageTypes() {
2899 drupal_static_reset('language_types_info');
2900 foreach (language_types_info() as
$type => $info) {
2901 if (isset($info['fixed'])) {
2902 $negotiation = variable_get("language_negotiation_$type", array());
2903 $equal = count($info['fixed']) == count($negotiation);
2904 while ($equal && list($id) = each($negotiation)) {
2905 list(, $info_id) = each($info['fixed']);
2906 $equal = $info_id == $id;
2908 $this->assertTrue($equal, t('language negotiation for %type is properly set up', array('%type' => $type)));