Newer
Older
<?php
Dries Buytaert
committed
* Tests for locale.module.
*
* The test file includes:
* - a functional test for the language configuration forms;
* - functional tests for the translation functionalities, including searching;
* - a functional test for the PO files import feature, including validation;
* - functional tests for translations and templates export feature;
* - functional tests for the uninstall process;
* - a functional test for the language switching feature;
* - a functional test for a user's ability to change their default language;
* - a functional test for configuring a different path alias per language;
* - a functional test for configuring a different path alias per language;
* - a functional test for multilingual support by content type and on nodes.
Angie Byron
committed
* - a functional test for multilingual fields.
* - a functional test for comment language.
Dries Buytaert
committed
* - a functional test fot language types/negotiation info.
/**
* Functional tests for language configuration's effect on negotiation setup.
*/
class LocaleConfigurationTest extends DrupalWebTestCase {
Angie Byron
committed
public static function getInfo() {
return array(
'name' => 'Language negotiation autoconfiguration',
'description' => 'Adds and configures languages to check negotiation changes.',
'group' => 'Locale',
);
}
function setUp() {
parent::setUp('locale');
}
/**
* Functional tests for adding, editing and deleting languages.
*/
function testLanguageConfiguration() {
global $base_url;
// User to add and remove language.
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
$this->drupalLogin($admin_user);
// Check if the Default English language has no path prefix.
$this->drupalGet('admin/config/regional/language/detection/url');
$this->assertFieldByXPath('//input[@name="prefix[en]"]', '', t('Default English has no path prefix.'));
// Add predefined language.
$edit = array(
'predefined_langcode' => 'fr',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
$this->assertText('fr', t('Language added successfully.'));
$this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.'));
// Check if the Default English language has no path prefix.
$this->drupalGet('admin/config/regional/language/detection/url');
$this->assertFieldByXPath('//input[@name="prefix[en]"]', '', t('Default English has no path prefix.'));
// Check if French has a path prefix.
$this->drupalGet('admin/config/regional/language/detection/url');
$this->assertFieldByXPath('//input[@name="prefix[fr]"]', 'fr', t('French has a path prefix.'));
// Check if we can change the default language.
$this->drupalGet('admin/config/regional/language');
$this->assertFieldChecked('edit-site-default-en', t('English is the default language.'));
// Change the default language.
$edit = array(
'site_default' => 'fr',
);
$this->drupalPost(NULL, $edit, t('Save configuration'));
$this->assertNoFieldChecked('edit-site-default-en', t('Default language updated.'));
$this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.'));
Dries Buytaert
committed
// Check if a valid language prefix is added afrer changing the default
Angie Byron
committed
// language.
$this->drupalGet('admin/config/regional/language/detection/url');
Dries Buytaert
committed
$this->assertFieldByXPath('//input[@name="prefix[en]"]', 'en', t('A valid path prefix has been added to the previous default language.'));
// Check if French still has a path prefix.
$this->drupalGet('admin/config/regional/language/detection/url');
$this->assertFieldByXPath('//input[@name="prefix[fr]"]', 'fr', t('French still has a path prefix.'));
}
}
Dries Buytaert
committed
/**
* Functional tests for JavaScript parsing for translatable strings.
*/
class LocaleJavascriptTranslationTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Javascript translation',
'description' => 'Tests parsing js files for translatable strings',
'group' => 'Locale',
);
}
function setUp() {
parent::setUp('locale', 'locale_test');
}
function testFileParsing() {
$filename = drupal_get_path('module', 'locale_test') . '/locale_test.js';
// Parse the file to look for source strings.
_locale_parse_js_file($filename);
// Get all of the source strings that were found.
$source_strings = db_select('locales_source', 's')
->fields('s', array('source', 'context'))
Dries Buytaert
committed
->condition('s.location', $filename)
->execute()
->fetchAllKeyed();
// List of all strings that should be in the file.
$test_strings = array(
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
"Standard Call t" => '',
"Whitespace Call t" => '',
"Single Quote t" => '',
"Single Quote \\'Escaped\\' t" => '',
"Single Quote Concat strings t" => '',
"Double Quote t" => '',
"Double Quote \\\"Escaped\\\" t" => '',
"Double Quote Concat strings t" => '',
"Context !key Args t" => "Context string",
"Context Unquoted t" => "Context string unquoted",
"Context Single Quoted t" => "Context string single quoted",
"Context Double Quoted t" => "Context string double quoted",
"Standard Call plural" => '',
"Standard Call @count plural" => '',
"Whitespace Call plural" => '',
"Whitespace Call @count plural" => '',
"Single Quote plural" => '',
"Single Quote @count plural" => '',
"Single Quote \\'Escaped\\' plural" => '',
"Single Quote \\'Escaped\\' @count plural" => '',
"Double Quote plural" => '',
"Double Quote @count plural" => '',
"Double Quote \\\"Escaped\\\" plural" => '',
"Double Quote \\\"Escaped\\\" @count plural" => '',
"Context !key Args plural" => "Context string",
"Context !key Args @count plural" => "Context string",
"Context Unquoted plural" => "Context string unquoted",
"Context Unquoted @count plural" => "Context string unquoted",
"Context Single Quoted plural" => "Context string single quoted",
"Context Single Quoted @count plural" => "Context string single quoted",
"Context Double Quoted plural" => "Context string double quoted",
"Context Double Quoted @count plural" => "Context string double quoted",
Dries Buytaert
committed
);
// Assert that all strings were found properly.
foreach ($test_strings as $str => $context) {
$args = array('%source' => $str, '%context' => $context);
// Make sure that the string was found in the file.
$this->assertTrue(isset($source_strings[$str]), t("Found source string: %source", $args));
// Make sure that the proper context was matched.
$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));
Dries Buytaert
committed
}
$this->assertEqual(count($source_strings), count($test_strings), t("Found correct number of source strings."));
}
}
/**
* Functional test for string translation and validation.
*/
class LocaleTranslationFunctionalTest extends DrupalWebTestCase {
Angie Byron
committed
public static function getInfo() {
return array(
'name' => 'String translate, search and validate',
'description' => 'Adds a new locale and translates its name. Checks the validation of translation strings and search results.',
'group' => 'Locale',
);
}
function setUp() {
parent::setUp('locale');
}
/**
* Adds a language and tests string translation by users with the appropriate permissions.
*/
function testStringTranslation() {
global $base_url;
// User to add and remove language.
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
// User to translate and delete string.
$translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
// Code for the language.
$langcode = 'xx';
// The English name for the language. This will be translated.
$name = $this->randomName(16);
// This is the language indicator on the translation search screen for
// untranslated strings. Copied straight from locale.inc.
$language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
// This will be the translation of $name.
$translation = $this->randomName(16);
catch
committed
$translation_to_en = $this->randomName(16);
// Add custom language.
$this->drupalLogin($admin_user);
Angie Byron
committed
$edit = array(
'predefined_langcode' => 'custom',
'langcode' => $langcode,
'name' => $name,
'direction' => '0',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
// Add string.
Dries Buytaert
committed
t($name, array(), array('langcode' => $langcode));
// Reset locale cache.
locale_reset();
Dries Buytaert
committed
$this->assertRaw('"edit-site-default-' . $langcode .'"', t('Language code found.'));
$this->assertText(t($name), t('Test language added.'));
$this->drupalLogout();
// Search for the name and translate it.
$this->drupalLogin($translate_user);
$search = array(
'string' => $name,
'language' => 'all',
'translation' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
// assertText() seems to remove the input field where $name always could be
// found, so this is not a false assert. See how assertNoText succeeds
// later.
$this->assertText($name, t('Search found the name.'));
$this->assertRaw($language_indicator, t('Name is untranslated.'));
// Assume this is the only result, given the random name.
$this->clickLink(t('edit'));
// We save the lid from the path.
Angie Byron
committed
$matches = array();
Angie Byron
committed
preg_match('!admin/config/regional/translate/edit/(\d+)!', $this->getUrl(), $matches);
Angie Byron
committed
$lid = $matches[1];
// No t() here, it's surely not translated yet.
$this->assertText($name, t('name found on edit screen.'));
catch
committed
$this->assertNoText('English', t('No way to translate the string to English.'));
$this->drupalLogout();
$this->drupalLogin($admin_user);
$this->drupalPost('admin/config/regional/language/edit/en', array('locale_translate_english' => TRUE), t('Save language'));
$this->drupalLogout();
$this->drupalLogin($translate_user);
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
// assertText() seems to remove the input field where $name always could be
// found, so this is not a false assert. See how assertNoText succeeds
// later.
$this->assertText($name, t('Search found the name.'));
$this->assertRaw($language_indicator, t('Name is untranslated.'));
// Assume this is the only result, given the random name.
$this->clickLink(t('edit'));
$string_edit_url = $this->getUrl();
$edit = array(
"translations[$langcode]" => $translation,
catch
committed
'translations[en]' => $translation_to_en,
);
$this->drupalPost(NULL, $edit, t('Save translations'));
$this->assertText(t('The string has been saved.'), t('The string has been saved.'));
$this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
catch
committed
$this->drupalGet($string_edit_url);
$this->assertRaw($translation, t('Non-English translation properly saved.'));
$this->assertRaw($translation_to_en, t('English translation properly saved.'));
$this->assertTrue($name != $translation && t($name, array(), array('langcode' => $langcode)) == $translation, t('t() works for non-English.'));
// Refresh the locale() cache to get fresh data from t() below. We are in
// the same HTTP request and therefore t() is not refreshed by saving the
// translation above.
locale_reset();
// Now we should get the proper fresh translation from t().
$this->assertTrue($name != $translation_to_en && t($name, array(), array('langcode' => 'en')) == $translation_to_en, t('t() works for English.'));
$this->assertTrue(t($name, array(), array('langcode' => LANGUAGE_SYSTEM)) == $name, t('t() works for LANGUAGE_SYSTEM.'));
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
// The indicator should not be here.
$this->assertNoRaw($language_indicator, t('String is translated.'));
// Try to edit a non-existent string and ensure we're redirected correctly.
// Assuming we don't have 999,999 strings already.
$random_lid = 999999;
Dries Buytaert
committed
$this->drupalGet('admin/config/regional/translate/edit/' . $random_lid);
$this->assertText(t('String not found'), t('String not found.'));
$this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
$this->drupalLogout();
// Delete the language.
$this->drupalLogin($admin_user);
Dries Buytaert
committed
$path = 'admin/config/regional/language/delete/' . $langcode;
// This a confirm form, we do not need any fields changed.
$this->drupalPost($path, array(), t('Delete'));
// We need raw here because %language and %langcode will add HTML.
$t_args = array('%language' => $name, '%langcode' => $langcode);
$this->assertRaw(t('The %language (%langcode) language has been removed.', $t_args), t('The test language has been removed.'));
// Reload to remove $name.
$this->drupalGet($path);
Dries Buytaert
committed
// Verify that language is no longer found.
$this->assertResponse(404, t('Language no longer found.'));
$this->drupalLogout();
// Delete the string.
$this->drupalLogin($translate_user);
$search = array(
'string' => $name,
'language' => 'all',
'translation' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
// Assume this is the only result, given the random name.
$this->clickLink(t('delete'));
$this->assertText(t('Are you sure you want to delete the string'), t('"delete" link is correct.'));
// Delete the string.
Dries Buytaert
committed
$path = 'admin/config/regional/translate/delete/' . $lid;
$this->drupalGet($path);
// First test the 'cancel' link.
$this->clickLink(t('Cancel'));
$this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
$this->assertRaw($name, t('The string was not deleted.'));
// Delete the name string.
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/delete/' . $lid, array(), t('Delete'));
$this->assertText(t('The string has been removed.'), t('The string has been removed message.'));
$this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertNoText($name, t('Search now can not find the name.'));
}
Dries Buytaert
committed
/*
* Adds a language and checks that the JavaScript translation files are
* properly created and rebuilt on deletion.
*/
function testJavaScriptTranslation() {
$user = $this->drupalCreateUser(array('translate interface', 'administer languages', 'access administration pages'));
$this->drupalLogin($user);
$langcode = 'xx';
// The English name for the language. This will be translated.
$name = $this->randomName(16);
// Add custom language.
$edit = array(
'predefined_langcode' => 'custom',
'langcode' => $langcode,
'name' => $name,
'direction' => '0',
);
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
drupal_static_reset('language_list');
// Build the JavaScript translation file.
$this->drupalGet('admin/config/regional/translate/translate');
// Retrieve the id of the first string available in the {locales_source}
// table and translate it.
$query = db_select('locales_source', 'l');
$query->addExpression('min(l.lid)', 'lid');
Dries Buytaert
committed
$result = $query->condition('l.location', '%.js%', 'LIKE')->execute();
$url = 'admin/config/regional/translate/edit/' . $result->fetchObject()->lid;
$edit = array('translations['. $langcode .']' => $this->randomName());
$this->drupalPost($url, $edit, t('Save translations'));
Dries Buytaert
committed
// Trigger JavaScript translation parsing and building.
Nate Lampton
committed
require_once DRUPAL_ROOT . '/core/includes/locale.inc';
_locale_rebuild_js($langcode);
$locale_javascripts = variable_get('locale_translation_javascript', array());
$js_file = 'public://' . variable_get('locale_js_directory', 'languages') . '/' . $langcode . '_' . $locale_javascripts[$langcode] . '.js';
$this->assertTrue($result = file_exists($js_file), t('JavaScript file created: %file', array('%file' => $result ? $js_file : t('not found'))));
// Test JavaScript translation rebuilding.
file_unmanaged_delete($js_file);
$this->assertTrue($result = !file_exists($js_file), t('JavaScript file deleted: %file', array('%file' => $result ? $js_file : t('found'))));
cache_clear_all();
_locale_rebuild_js($langcode);
$this->assertTrue($result = file_exists($js_file), t('JavaScript file rebuilt: %file', array('%file' => $result ? $js_file : t('not found'))));
}
/**
* Tests the validation of the translation input.
*/
function testStringValidation() {
Dries Buytaert
committed
global $base_url;
// User to add language and strings.
Dries Buytaert
committed
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'translate interface'));
$this->drupalLogin($admin_user);
$langcode = 'xx';
Dries Buytaert
committed
// The English name for the language. This will be translated.
$name = $this->randomName(16);
// This is the language indicator on the translation search screen for
// untranslated strings. Copied straight from locale.inc.
$language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
// These will be the invalid translations of $name.
$key = $this->randomName(16);
$bad_translations[$key] = "<script>alert('xss');</script>" . $key;
$key = $this->randomName(16);
$bad_translations[$key] = '<img SRC="javascript:alert(\'xss\');">' . $key;
$key = $this->randomName(16);
$bad_translations[$key] = '<<SCRIPT>alert("xss");//<</SCRIPT>' . $key;
$key = $this->randomName(16);
$bad_translations[$key] ="<BODY ONLOAD=alert('xss')>" . $key;
Dries Buytaert
committed
// Add custom language.
$edit = array(
'predefined_langcode' => 'custom',
Dries Buytaert
committed
'langcode' => $langcode,
'name' => $name,
'direction' => '0',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
Dries Buytaert
committed
// Add string.
Dries Buytaert
committed
t($name, array(), array('langcode' => $langcode));
Dries Buytaert
committed
// Reset locale cache.
$search = array(
Dries Buytaert
committed
'string' => $name,
'language' => 'all',
'translation' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
// Find the edit path.
Dries Buytaert
committed
$content = $this->drupalGetContent();
$this->assertTrue(preg_match('@(admin/config/regional/translate/edit/[0-9]+)@', $content, $matches), t('Found the edit path.'));
Dries Buytaert
committed
$path = $matches[0];
foreach ($bad_translations as $key => $translation) {
$edit = array(
Dries Buytaert
committed
"translations[$langcode]" => $translation,
);
$this->drupalPost($path, $edit, t('Save translations'));
// Check for a form error on the textarea.
$form_class = $this->xpath('//form[@id="locale-translate-edit-form"]//textarea/@class');
$this->assertNotIdentical(FALSE, strpos($form_class[0], 'error'), t('The string was rejected as unsafe.'));
$this->assertNoText(t('The string has been saved.'), t('The string was not saved.'));
Dries Buytaert
committed
}
}
/**
* Tests translation search form.
*/
function testStringSearch() {
global $base_url;
// User to add and remove language.
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
// User to translate and delete string.
$translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
// Code for the language.
$langcode = 'xx';
// The English name for the language. This will be translated.
$name = $this->randomName(16);
// This is the language indicator on the translation search screen for
// untranslated strings. Copied straight from locale.inc.
$language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
// This will be the translation of $name.
$translation = $this->randomName(16);
// Add custom language.
$this->drupalLogin($admin_user);
$edit = array(
'predefined_langcode' => 'custom',
'langcode' => $langcode,
'name' => $name,
'direction' => '0',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
// Add string.
Dries Buytaert
committed
t($name, array(), array('langcode' => $langcode));
// Reset locale cache.
locale_reset();
$this->drupalLogout();
// Search for the name.
$this->drupalLogin($translate_user);
$search = array(
'string' => $name,
'language' => 'all',
'translation' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
// assertText() seems to remove the input field where $name always could be
// found, so this is not a false assert. See how assertNoText succeeds
// later.
$this->assertText($name, t('Search found the string.'));
// Ensure untranslated string doesn't appear if searching on 'only
// translated strings'.
$search = array(
'string' => $name,
'language' => 'all',
'translation' => 'translated',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertText(t('No strings available.'), t("Search didn't find the string."));
// Ensure untranslated string appears if searching on 'only untranslated
// strings'.
$search = array(
'string' => $name,
'language' => 'all',
'translation' => 'untranslated',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertNoText(t('No strings available.'), t('Search found the string.'));
// Add translation.
// Assume this is the only result, given the random name.
$this->clickLink(t('edit'));
// We save the lid from the path.
$matches = array();
Dries Buytaert
committed
preg_match('!admin/config/regional/translate/edit/(\d)+!', $this->getUrl(), $matches);
$lid = $matches[1];
$edit = array(
"translations[$langcode]" => $translation,
);
$this->drupalPost(NULL, $edit, t('Save translations'));
// Ensure translated string does appear if searching on 'only
// translated strings'.
$search = array(
'string' => $translation,
'language' => 'all',
'translation' => 'translated',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertNoText(t('No strings available.'), t('Search found the translation.'));
// Ensure translated source string doesn't appear if searching on 'only
// untranslated strings'.
$search = array(
'string' => $name,
'language' => 'all',
'translation' => 'untranslated',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertText(t('No strings available.'), t("Search didn't find the source string."));
// Ensure translated string doesn't appear if searching on 'only
// untranslated strings'.
$search = array(
'string' => $translation,
'language' => 'all',
'translation' => 'untranslated',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertText(t('No strings available.'), t("Search didn't find the translation."));
// Ensure translated string does appear if searching on the custom language.
$search = array(
'string' => $translation,
'language' => $langcode,
'translation' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertNoText(t('No strings available.'), t('Search found the translation.'));
catch
committed
// Ensure translated string doesn't appear if searching in System (English).
$search = array(
'string' => $translation,
catch
committed
'language' => LANGUAGE_SYSTEM,
'translation' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertText(t('No strings available.'), t("Search didn't find the translation."));
// Search for a string that isn't in the system.
$unavailable_string = $this->randomName(16);
$search = array(
'string' => $unavailable_string,
'language' => 'all',
'translation' => 'all',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertText(t('No strings available.'), t("Search didn't find the invalid string."));
}
}
Dries Buytaert
committed
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
/**
* Tests plural index computation functionality.
*/
class LocalePluralFormatTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Plural formula evaluation',
'description' => 'Tests plural formula evaluation for various languages.',
'group' => 'Locale',
);
}
function setUp() {
parent::setUp('locale', 'locale_test');
$admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
$this->drupalLogin($admin_user);
}
/**
* Tests locale_get_plural() functionality.
*/
function testGetPluralFormat() {
// Import some .po files with formulas to set up the environment.
// These will also add the languages to the system and enable them.
$this->importPoFile($this->getPoFileWithSimplePlural(), array(
'langcode' => 'fr',
));
$this->importPoFile($this->getPoFileWithComplexPlural(), array(
'langcode' => 'hr',
));
// Reset static caches from locale_get_plural() to ensure we get fresh data.
drupal_static_reset('locale_get_plural');
drupal_static_reset('locale_get_plural:plurals');
// Test locale_get_plural() for English (no formula presnt).
$this->assertIdentical(locale_get_plural(1, 'en'), 0, t("Computed plural index for 'en' with count 1 is 0."));
$this->assertIdentical(locale_get_plural(0, 'en'), 1, t("Computed plural index for 'en' with count 0 is 1."));
$this->assertIdentical(locale_get_plural(5, 'en'), 1, t("Computed plural index for 'en' with count 5 is 1."));
// Test locale_get_plural() for French (simpler formula).
$this->assertIdentical(locale_get_plural(1, 'fr'), 0, t("Computed plural index for 'fr' with count 1 is 0."));
$this->assertIdentical(locale_get_plural(0, 'fr'), 0, t("Computed plural index for 'fr' with count 0 is 0."));
$this->assertIdentical(locale_get_plural(5, 'fr'), 1, t("Computed plural index for 'fr' with count 5 is 1."));
// Test locale_get_plural() for Croatian (more complex formula).
$this->assertIdentical(locale_get_plural( 1, 'hr'), 0, t("Computed plural index for 'hr' with count 1 is 0."));
$this->assertIdentical(locale_get_plural(21, 'hr'), 0, t("Computed plural index for 'hr' with count 21 is 0."));
$this->assertIdentical(locale_get_plural( 0, 'hr'), 2, t("Computed plural index for 'hr' with count 0 is 2."));
$this->assertIdentical(locale_get_plural( 2, 'hr'), 1, t("Computed plural index for 'hr' with count 2 is 1."));
$this->assertIdentical(locale_get_plural( 8, 'hr'), 2, t("Computed plural index for 'hr' with count 8 is 2."));
// Test locale_get_plural() for Hungarian (nonexistent language).
$this->assertIdentical(locale_get_plural( 1, 'hu'), -1, t("Computed plural index for 'hu' with count 1 is -1."));
$this->assertIdentical(locale_get_plural(21, 'hu'), -1, t("Computed plural index for 'hu' with count 21 is -1."));
$this->assertIdentical(locale_get_plural( 0, 'hu'), -1, t("Computed plural index for 'hu' with count 0 is -1."));
}
/**
* Imports a standalone .po file in a given language.
*
* @param $contents
* Contents of the .po file to import.
* @param $options
* Additional options to pass to the translation import form.
*/
function importPoFile($contents, array $options = array()) {
$name = tempnam('temporary://', "po_") . '.po';
file_put_contents($name, $contents);
$options['files[file]'] = $name;
$this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
drupal_unlink($name);
}
/**
* Returns a .po file with a simple plural formula.
*/
function getPoFileWithSimplePlural() {
return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 7\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
msgid "1 hour"
msgid_plural "@count hours"
msgstr[0] "1 heure"
msgstr[1] "@count heures"
msgid "Monday"
msgstr "lundi"
EOF;
}
/**
* Returns a .po file with a complex plural formula.
*/
function getPoFileWithComplexPlural() {
return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 7\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
msgid "1 hour"
msgid_plural "@count hours"
msgstr[0] "@count sat"
msgstr[1] "@count sata"
msgstr[2] "@count sati"
msgid "Monday"
msgstr "Ponedjeljak"
EOF;
}
}
Dries Buytaert
committed
/**
* Functional tests for the import of translation files.
*/
class LocaleImportFunctionalTest extends DrupalWebTestCase {
Angie Byron
committed
public static function getInfo() {
Dries Buytaert
committed
return array(
'name' => 'Translation import',
Dries Buytaert
committed
'description' => 'Tests the import of locale files.',
'group' => 'Locale',
Dries Buytaert
committed
);
}
/**
* A user able to create languages and import translations.
*/
protected $admin_user = NULL;
function setUp() {
Angie Byron
committed
parent::setUp('locale', 'locale_test');
Dries Buytaert
committed
// Set the translation file directory.
variable_set('locale_translate_file_directory', drupal_get_path('module', 'locale_test'));
Dries Buytaert
committed
$this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
$this->drupalLogin($this->admin_user);
}
/**
Dries Buytaert
committed
* Test import of standalone .po files.
Dries Buytaert
committed
*/
function testStandalonePoFile() {
// Try importing a .po file.
Dries Buytaert
committed
$this->importPoFile($this->getPoFile(), array(
Dries Buytaert
committed
'langcode' => 'fr',
Dries Buytaert
committed
));
Dries Buytaert
committed
// The import should automatically create the corresponding language.
$this->assertRaw(t('The language %language has been created.', array('%language' => 'French')), t('The language has been automatically created.'));
// The import should have created 7 strings.
Angie Byron
committed
$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.'));
Dries Buytaert
committed
Dries Buytaert
committed
// This import should have saved plural forms to have 2 variants.
$locale_plurals = variable_get('locale_translation_plurals', array());
$this->assert($locale_plurals['fr']['plurals'] == 2, t('Plural number initialized.'));
Dries Buytaert
committed
// Ensure we were redirected correctly.
$this->assertEqual($this->getUrl(), url('admin/config/regional/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
Dries Buytaert
committed
Dries Buytaert
committed
Dries Buytaert
committed
// Try importing a .po file with invalid tags.
Dries Buytaert
committed
$this->importPoFile($this->getBadPoFile(), array(
Dries Buytaert
committed
'langcode' => 'fr',
Dries Buytaert
committed
));
// The import should have created 1 string and rejected 2.
$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.'));
$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')));
$this->assertRaw($skip_message, t('Unsafe strings were skipped.'));
Dries Buytaert
committed
Dries Buytaert
committed
// Try importing a .po file which doesn't exist.
$name = $this->randomName(16);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/import', array(
'langcode' => 'fr',
'files[file]' => $name,
), t('Import'));
$this->assertEqual($this->getUrl(), url('admin/config/regional/translate/import', array('absolute' => TRUE)), t('Correct page redirection.'));
$this->assertText(t('File to import not found.'), t('File to import not found message.'));
Dries Buytaert
committed
// Try importing a .po file with overriding strings, and ensure existing
// strings are kept.
Dries Buytaert
committed
$this->importPoFile($this->getOverwritePoFile(), array(
'langcode' => 'fr',
'mode' => 1, // Existing strings are kept, only new strings are added.
Dries Buytaert
committed
));
// The import should have created 1 string.
$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.'));
// Ensure string wasn't overwritten.
$search = array(
'string' => 'Montag',
'language' => 'fr',
'translation' => 'translated',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertText(t('No strings available.'), t('String not overwritten by imported string.'));
Dries Buytaert
committed
// This import should not have changed number of plural forms.
$locale_plurals = variable_get('locale_translation_plurals', array());
$this->assert($locale_plurals['fr']['plurals'] == 2, t('Plural numbers untouched.'));
Dries Buytaert
committed
// Try importing a .po file with overriding strings, and ensure existing
// strings are overwritten.
Dries Buytaert
committed
$this->importPoFile($this->getOverwritePoFile(), array(
'langcode' => 'fr',
'mode' => 0, // Strings in the uploaded file replace existing ones, new ones are added.
Dries Buytaert
committed
));
// The import should have updated 2 strings.
$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.'));
// Ensure string was overwritten.
$search = array(
'string' => 'Montag',
'language' => 'fr',
'translation' => 'translated',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertNoText(t('No strings available.'), t('String overwritten by imported string.'));
Dries Buytaert
committed
// This import should have changed number of plural forms.
$locale_plurals = variable_get('locale_translation_plurals', array());
$this->assert($locale_plurals['fr']['plurals'] == 3, t('Plural numbers changed.'));
}
/**
* Test automatic import of a module's translation files.
*/
function testAutomaticModuleTranslationImportLanguageEnable() {
// Code for the language - manually set to match the test translation file.
$langcode = 'xx';
// The English name for the language.
$name = $this->randomName(16);
// Create a custom language.
$edit = array(
'predefined_langcode' => 'custom',
'langcode' => $langcode,
'name' => $name,
'direction' => '0',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
// Ensure the translation file was automatically imported when language was
// added.
$this->assertText(t('One translation file imported.'), t('Language file automatically imported.'));
// Ensure strings were successfully imported.
$search = array(
'string' => 'lundi',
'language' => $langcode,
'translation' => 'translated',
);
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->assertNoText(t('No strings available.'), t('String successfully imported.'));
Dries Buytaert
committed
}
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Test msgctxt context support.
Dries Buytaert
committed
*/
function testLanguageContext() {
// Try importing a .po file.
$this->importPoFile($this->getPoFileWithContext(), array(
'langcode' => 'hr',
));
$this->assertIdentical(t('May', array(), array('langcode' => 'hr', 'context' => 'Long month name')), 'Svibanj', t('Long month name context is working.'));
$this->assertIdentical(t('May', array(), array('langcode' => 'hr')), 'Svi.', t('Default context is working.'));
Dries Buytaert
committed
}
Dries Buytaert
committed
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
/**
* Test empty msgstr at end of .po file see #611786.
*/
function testEmptyMsgstr() {
$langcode = 'hu';
// Try importing a .po file.
$this->importPoFile($this->getPoFileWithMsgstr(), array(
'langcode' => $langcode,
));
$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.'));
$this->assertIdentical(t('Operations', array(), array('langcode' => $langcode)), 'Műveletek', t('String imported and translated.'));
// Try importing a .po file.
$this->importPoFile($this->getPoFileWithEmptyMsgstr(), array(
'langcode' => $langcode,
'mode' => 0,
));
$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.'));
// This is the language indicator on the translation search screen for
// untranslated strings. Copied straight from locale.inc.
$language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
$str = "Operations";
$search = array(
'string' => $str,
'language' => 'all',
'translation' => 'all',
);
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
// assertText() seems to remove the input field where $str always could be
// found, so this is not a false assert.
$this->assertText($str, t('Search found the string.'));
$this->assertRaw($language_indicator, t('String is untranslated again.'));
}
Dries Buytaert
committed
/**
* Helper function: import a standalone .po file in a given language.
*
* @param $contents
* Contents of the .po file to import.
* @param $options
* Additional options to pass to the translation import form.
*/
function importPoFile($contents, array $options = array()) {
$name = tempnam('temporary://', "po_") . '.po';
Dries Buytaert
committed
file_put_contents($name, $contents);
$options['files[file]'] = $name;
Dries Buytaert
committed
$this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
drupal_unlink($name);
Dries Buytaert
committed
}
Dries Buytaert
committed
/**
* Helper function that returns a proper .po file.
*/
function getPoFile() {
return <<< EOF
msgid ""
msgstr ""
Dries Buytaert
committed
"Project-Id-Version: Drupal 7\\n"
Dries Buytaert
committed
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
Angie Byron
committed
msgid "One sheep"
msgid_plural "@count sheep"
msgstr[0] "un mouton"
msgstr[1] "@count moutons"
Dries Buytaert
committed
msgid "Monday"
msgstr "lundi"
msgid "Tuesday"
msgstr "mardi"
msgid "Wednesday"
msgstr "mercredi"
msgid "Thursday"
msgstr "jeudi"
msgid "Friday"
msgstr "vendredi"
msgid "Saturday"
msgstr "samedi"
msgid "Sunday"
msgstr "dimanche"
Dries Buytaert
committed
EOF;
}
/**
* Helper function that returns a bad .po file.
Dries Buytaert
committed
*/
function getBadPoFile() {
return <<< EOF
msgid ""
msgstr ""
Dries Buytaert
committed
"Project-Id-Version: Drupal 7\\n"
Dries Buytaert
committed
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
msgid "Save configuration"
msgstr "Enregistrer la configuration"
msgid "edit"
msgstr "modifier<img SRC="javascript:alert(\'xss\');">"
msgid "delete"
msgstr "supprimer<script>alert('xss');</script>"
Dries Buytaert
committed
EOF;
}
/**
* Helper function that returns a proper .po file for testing.
*/
function getOverwritePoFile() {
return <<< EOF
msgid ""
msgstr ""
Dries Buytaert
committed
"Project-Id-Version: Drupal 7\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
Dries Buytaert
committed
"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"