diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleCommentLanguageTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleCommentLanguageTest.php new file mode 100644 index 00000000000..16fc83ff778 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleCommentLanguageTest.php @@ -0,0 +1,108 @@ + 'Comment language', + 'description' => 'Tests for comment language.', + 'group' => 'Locale', + ); + } + + function setUp() { + // We also use language_test module here to be able to turn on content + // language negotiation. Drupal core does not provide a way in itself + // to do that. + parent::setUp('locale', 'language_test'); + + // Create and login user. + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'administer content types', 'create article content')); + $this->drupalLogin($admin_user); + + // Add language. + $edit = array('predefined_langcode' => 'fr'); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Set "Article" content type to use multilingual support. + $edit = array('node_type_language' => 1); + $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type')); + + // Enable content language negotiation UI. + variable_set('language_test_content_language_type', TRUE); + + // Set interface language detection to user and content language detection + // to URL. Disable inheritance from interface language to ensure content + // language will fall back to the default language if no URL language can be + // detected. + $edit = array( + 'language_interface[enabled][language-user]' => TRUE, + 'language_content[enabled][language-url]' => TRUE, + 'language_content[enabled][language-interface]' => FALSE, + ); + $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); + + // Change user language preference, this way interface language is always + // French no matter what path prefix the URLs have. + $edit = array('preferred_langcode' => 'fr'); + $this->drupalPost("user/{$admin_user->uid}/edit", $edit, t('Save')); + } + + /** + * Test that comment language is properly set. + */ + function testCommentLanguage() { + drupal_static_reset('language_list'); + + // Create two nodes, one for english and one for french, and comment each + // node using both english and french as content language by changing URL + // language prefixes. Meanwhile interface language is always French, which + // is the user language preference. This way we can ensure that node + // language and interface language do not influence comment language, as + // only content language has to. + foreach (language_list() as $node_langcode => $node_language) { + $langcode_not_specified = LANGUAGE_NOT_SPECIFIED; + + // Create "Article" content. + $title = $this->randomName(); + $edit = array( + "title" => $title, + "body[$langcode_not_specified][0][value]" => $this->randomName(), + "langcode" => $node_langcode, + ); + $this->drupalPost("node/add/article", $edit, t('Save')); + $node = $this->drupalGetNodeByTitle($title); + + $prefixes = language_negotiation_url_prefixes(); + foreach (language_list() as $langcode => $language) { + // Post a comment with content language $langcode. + $prefix = empty($prefixes[$langcode]) ? '' : $prefixes[$langcode] . '/'; + $edit = array("comment_body[$langcode_not_specified][0][value]" => $this->randomName()); + $this->drupalPost("{$prefix}node/{$node->nid}", $edit, t('Save')); + + // Check that comment language matches the current content language. + $comment = db_select('comment', 'c') + ->fields('c') + ->condition('nid', $node->nid) + ->orderBy('cid', 'DESC') + ->execute() + ->fetchObject(); + $args = array('%node_language' => $node_langcode, '%comment_language' => $comment->langcode, '%langcode' => $langcode); + $this->assertEqual($comment->langcode, $langcode, t('The comment posted with content language %langcode and belonging to the node with language %node_language has language %comment_language', $args)); + } + } + } +} diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleContentTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleContentTest.php new file mode 100644 index 00000000000..0c5cea3689d --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleContentTest.php @@ -0,0 +1,210 @@ + 'Content language settings', + 'description' => 'Checks you can enable multilingual support on content types and configure a language for a node.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + } + + /** + * Verifies that machine name fields are always LTR. + */ + function testMachineNameLTR() { + // User to add and remove language. + $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages')); + + // Log in as admin. + $this->drupalLogin($admin_user); + + // Verify that the machine name field is LTR for a new content type. + $this->drupalGet('admin/structure/types/add'); + $this->assertFieldByXpath('//input[@name="type" and @dir="ltr"]', NULL, 'The machine name field is LTR when no additional language is configured.'); + + // Install the Arabic language (which is RTL) and configure as the default. + $edit = array(); + $edit['predefined_langcode'] = 'ar'; + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + $edit = array(); + $edit['site_default'] = 'ar'; + $this->drupalPost(NULL, $edit, t('Save configuration')); + + // Verify that the machine name field is still LTR for a new content type. + $this->drupalGet('admin/structure/types/add'); + $this->assertFieldByXpath('//input[@name="type" and @dir="ltr"]', NULL, 'The machine name field is LTR when the default language is RTL.'); + } + + /** + * Test if a content type can be set to multilingual and language is present. + */ + function testContentTypeLanguageConfiguration() { + global $base_url; + + // User to add and remove language. + $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages')); + // User to create a node. + $web_user = $this->drupalCreateUser(array('create article content', 'create page content', 'edit any page content')); + + // Add custom language. + $this->drupalLogin($admin_user); + // Code for the language. + $langcode = 'xx'; + // The English name for the language. + $name = $this->randomName(16); + $edit = array( + 'predefined_langcode' => 'custom', + 'langcode' => $langcode, + 'name' => $name, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + + // Set "Basic page" content type to use multilingual support. + $this->drupalGet('admin/structure/types/manage/page'); + $this->assertText(t('Multilingual support'), t('Multilingual support fieldset present on content type configuration form.')); + $edit = array( + 'node_type_language' => 1, + ); + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), t('Basic page content type has been updated.')); + $this->drupalLogout(); + + // Verify language selection is not present on add article form. + $this->drupalLogin($web_user); + $this->drupalGet('node/add/article'); + // Verify language select list is not present. + $this->assertNoFieldByName('language', NULL, t('Language select not present on add article form.')); + + // Verify language selection appears on add "Basic page" form. + $this->drupalGet('node/add/page'); + // Verify language select list is present. + $this->assertFieldByName('langcode', NULL, t('Language select present on add Basic page form.')); + // Ensure language appears. + $this->assertText($name, t('Language present.')); + + // Create "Basic page" content. + $node_title = $this->randomName(); + $node_body = $this->randomName(); + $edit = array( + 'type' => 'page', + 'title' => $node_title, + 'body' => array($langcode => array(array('value' => $node_body))), + 'langcode' => $langcode, + ); + $node = $this->drupalCreateNode($edit); + // Edit the content and ensure correct language is selected. + $path = 'node/' . $node->nid . '/edit'; + $this->drupalGet($path); + $this->assertRaw('', t('Correct language selected.')); + // Ensure we can change the node language. + $edit = array( + 'langcode' => 'en', + ); + $this->drupalPost($path, $edit, t('Save')); + $this->assertRaw(t('%title has been updated.', array('%title' => $node_title)), t('Basic page content updated.')); + + $this->drupalLogout(); + } + + /** + * Test if a dir and lang tags exist in node's attributes. + */ + function testContentTypeDirLang() { + // User to add and remove language. + $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages')); + // User to create a node. + $web_user = $this->drupalCreateUser(array('create article content', 'edit own article content')); + + // Login as admin. + $this->drupalLogin($admin_user); + + // Install Arabic language. + $edit = array(); + $edit['predefined_langcode'] = 'ar'; + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Install Spanish language. + $edit = array(); + $edit['predefined_langcode'] = 'es'; + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Set "Article" content type to use multilingual support. + $this->drupalGet('admin/structure/types/manage/article'); + $edit = array( + 'node_type_language' => 1, + ); + $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type')); + $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Article')), t('Article content type has been updated.')); + $this->drupalLogout(); + + // Login as web user to add new article. + $this->drupalLogin($web_user); + + // Create three nodes: English, Arabic and Spanish. + $node_en = $this->createNodeArticle('en'); + $node_ar = $this->createNodeArticle('ar'); + $node_es = $this->createNodeArticle('es'); + + $this->drupalGet('node'); + + // Check if English node does not have lang tag. + $pattern = '|id="node-' . $node_en->nid . '"[^<>]*lang="en"|'; + $this->assertNoPattern($pattern, t('The lang tag has not been assigned to the English node.')); + + // Check if English node does not have dir tag. + $pattern = '|id="node-' . $node_en->nid . '"[^<>]*dir="ltr"|'; + $this->assertNoPattern($pattern, t('The dir tag has not been assigned to the English node.')); + + // Check if Arabic node has lang="ar" & dir="rtl" tags. + $pattern = '|id="node-' . $node_ar->nid . '"[^<>]*lang="ar" dir="rtl"|'; + $this->assertPattern($pattern, t('The lang and dir tags have been assigned correctly to the Arabic node.')); + + // Check if Spanish node has lang="es" tag. + $pattern = '|id="node-' . $node_es->nid . '"[^<>]*lang="es"|'; + $this->assertPattern($pattern, t('The lang tag has been assigned correctly to the Spanish node.')); + + // Check if Spanish node does not have dir="ltr" tag. + $pattern = '|id="node-' . $node_es->nid . '"[^<>]*lang="es" dir="ltr"|'; + $this->assertNoPattern($pattern, t('The dir tag has not been assigned to the Spanish node.')); + + $this->drupalLogout(); + } + + /** + * Create node in a specific language. + */ + protected function createNodeArticle($langcode) { + $this->drupalGet('node/add/article'); + $node_title = $this->randomName(); + $node_body = $this->randomName(); + $edit = array( + 'type' => 'article', + 'title' => $node_title, + 'body' => array($langcode => array(array('value' => $node_body))), + 'langcode' => $langcode, + 'promote' => 1, + ); + return $this->drupalCreateNode($edit); + } +} diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleDateFormatsTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleDateFormatsTest.php new file mode 100644 index 00000000000..e5514b8b1a9 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleDateFormatsTest.php @@ -0,0 +1,82 @@ + 'Localize date formats', + 'description' => 'Tests for the localization of date formats.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp(array('node', 'locale')); + + // Create Article node type. + $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); + + // Create and login user. + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'create article content')); + $this->drupalLogin($admin_user); + } + + /** + * Functional tests for localizing date formats. + */ + function testLocalizeDateFormats() { + // Add language. + $edit = array( + 'predefined_langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Set language negotiation. + $language_type = LANGUAGE_TYPE_INTERFACE; + $edit = array( + "{$language_type}[enabled][language-url]" => TRUE, + ); + $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); + + // Configure date formats. + $this->drupalGet('admin/config/regional/date-time/locale'); + $this->assertText('French', 'Configured languages appear.'); + $edit = array( + 'date_format_long' => 'd.m.Y - H:i', + 'date_format_medium' => 'd.m.Y - H:i', + 'date_format_short' => 'd.m.Y - H:i', + ); + $this->drupalPost('admin/config/regional/date-time/locale/fr/edit', $edit, t('Save configuration')); + $this->assertText(t('Configuration saved.'), 'French date formats updated.'); + $edit = array( + 'date_format_long' => 'j M Y - g:ia', + 'date_format_medium' => 'j M Y - g:ia', + 'date_format_short' => 'j M Y - g:ia', + ); + $this->drupalPost('admin/config/regional/date-time/locale/en/edit', $edit, t('Save configuration')); + $this->assertText(t('Configuration saved.'), 'English date formats updated.'); + + // Create node content. + $node = $this->drupalCreateNode(array('type' => 'article')); + + // Configure format for the node posted date changes with the language. + $this->drupalGet('node/' . $node->nid); + $english_date = format_date($node->created, 'custom', 'j M Y'); + $this->assertText($english_date, t('English date format appears')); + $this->drupalGet('fr/node/' . $node->nid); + $french_date = format_date($node->created, 'custom', 'd.m.Y'); + $this->assertText($french_date, t('French date format appears')); + } +} diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleExportTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleExportTest.php new file mode 100644 index 00000000000..4ad935669e4 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleExportTest.php @@ -0,0 +1,152 @@ + 'Translation export', + 'description' => 'Tests the exportation of locale files.', + 'group' => 'Locale', + ); + } + + /** + * A user able to create languages and export translations. + */ + protected $admin_user = NULL; + + function setUp() { + parent::setUp('locale'); + + $this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages')); + $this->drupalLogin($this->admin_user); + } + + /** + * Test exportation of translations. + */ + function testExportTranslation() { + // First import some known translations. + // This will also automatically enable the 'fr' language. + $name = tempnam('temporary://', "po_") . '.po'; + file_put_contents($name, $this->getPoFile()); + $this->drupalPost('admin/config/regional/translate/import', array( + 'langcode' => 'fr', + 'files[file]' => $name, + ), t('Import')); + drupal_unlink($name); + + // Get the French translations. + $this->drupalPost('admin/config/regional/translate/export', array( + 'langcode' => 'fr', + ), t('Export')); + + // Ensure we have a translation file. + $this->assertRaw('# French translation of Drupal', t('Exported French translation file.')); + // Ensure our imported translations exist in the file. + $this->assertRaw('msgstr "lundi"', t('French translations present in exported file.')); + + // Import some more French translations which will be marked as customized. + $name = tempnam('temporary://', "po2_") . '.po'; + file_put_contents($name, $this->getCustomPoFile()); + $this->drupalPost('admin/config/regional/translate/import', array( + 'langcode' => 'fr', + 'files[file]' => $name, + 'customized' => 1, + ), t('Import')); + drupal_unlink($name); + + // We can't import a string with an empty translation, but calling + // locale() for an new string creates an entry in the locales_source table. + locale('February', NULL, 'fr'); + + // Export only customized French translations. + $this->drupalPost('admin/config/regional/translate/export', array( + 'langcode' => 'fr', + 'content_options[not_customized]' => FALSE, + 'content_options[customized]' => TRUE, + 'content_options[not_translated]' => FALSE, + ), t('Export')); + + // Ensure we have a translation file. + $this->assertRaw('# French translation of Drupal', t('Exported French translation file with only customized strings.')); + // Ensure the customized translations exist in the file. + $this->assertRaw('msgstr "janvier"', t('French custom translation present in exported file.')); + // Ensure no untranslated strings exist in the file. + $this->assertNoRaw('msgid "February"', t('Untranslated string not present in exported file.')); + + // Export only untranslated French translations. + $this->drupalPost('admin/config/regional/translate/export', array( + 'langcode' => 'fr', + 'content_options[not_customized]' => FALSE, + 'content_options[customized]' => FALSE, + 'content_options[not_translated]' => TRUE, + ), t('Export')); + + // Ensure we have a translation file. + $this->assertRaw('# French translation of Drupal', t('Exported French translation file with only untranslated strings.')); + // Ensure no customized translations exist in the file. + $this->assertNoRaw('msgstr "janvier"', t('French custom translation not present in exported file.')); + // Ensure the untranslated strings exist in the file. + $this->assertRaw('msgid "February"', t('Untranslated string present in exported file.')); + } + + /** + * Test exportation of translation template file. + */ + function testExportTranslationTemplateFile() { + // Get the translation template file. + $this->drupalPost('admin/config/regional/translate/export', array(), t('Export')); + // Ensure we have a translation file. + $this->assertRaw('# LANGUAGE translation of PROJECT', t('Exported translation template file.')); + } + + /** + * Helper function that returns a proper .po file. + */ + function getPoFile() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "Monday" +msgstr "lundi" +EOF; + } + + /** + * Helper function that returns a .po file which strings will be marked + * as customized. + */ + function getCustomPoFile() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "January" +msgstr "janvier" +EOF; + } + +} diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php new file mode 100644 index 00000000000..3beee64b24a --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php @@ -0,0 +1,487 @@ + 'Translation import', + 'description' => 'Tests the import of locale files.', + 'group' => 'Locale', + ); + } + + /** + * A user able to create languages and import translations. + */ + protected $admin_user = NULL; + + function setUp() { + parent::setUp(array('locale', 'dblog')); + + // Set the translation file directory. + variable_set('locale_translate_file_directory', drupal_get_path('module', 'locale') . '/tests'); + + $this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages')); + $this->drupalLogin($this->admin_user); + } + + /** + * Test import of standalone .po files. + */ + function testStandalonePoFile() { + // Try importing a .po file. + $this->importPoFile($this->getPoFile(), array( + 'langcode' => 'fr', + )); + + // 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 8 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' => 8, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.')); + + // 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.')); + + // Ensure we were redirected correctly. + $this->assertEqual($this->getUrl(), url('admin/config/regional/translate', array('absolute' => TRUE)), t('Correct page redirection.')); + + + // Try importing a .po file with invalid tags. + $this->importPoFile($this->getBadPoFile(), array( + 'langcode' => 'fr', + )); + + // 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. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.', array('@url' => url('admin/reports/dblog'))); + $this->assertRaw($skip_message, t('Unsafe strings were skipped.')); + + + // Try importing a .po file which doesn't exist. + $name = $this->randomName(16); + $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.')); + + + // Try importing a .po file with overriding strings, and ensure existing + // strings are kept. + $this->importPoFile($this->getOverwritePoFile(), array( + 'langcode' => 'fr', + )); + + // 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', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertText(t('No strings available.'), t('String not overwritten by imported string.')); + + // 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.')); + + // Try importing a .po file with overriding strings, and ensure existing + // strings are overwritten. + $this->importPoFile($this->getOverwritePoFile(), array( + 'langcode' => 'fr', + 'overwrite_options[not_customized]' => TRUE, + )); + + // 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', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertNoText(t('No strings available.'), t('String overwritten by imported string.')); + // 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.')); + + // Importing a .po file and mark its strings as customized strings. + $this->importPoFile($this->getCustomPoFile(), array( + 'langcode' => 'fr', + 'customized' => TRUE, + )); + + // The import should have created 6 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' => 6, '%update' => 0, '%delete' => 0)), t('The customized translation file was successfully imported.')); + + // The database should now contain 6 customized strings (two imported + // strings are not translated). + $count = db_query('SELECT lid FROM {locales_target} WHERE customized = :custom', array(':custom' => 1))->rowCount(); + $this->assertEqual($count, 6, t('Customized translations succesfully imported.')); + + // Try importing a .po file with overriding strings, and ensure existing + // customized strings are kept. + $this->importPoFile($this->getCustomOverwritePoFile(), array( + 'langcode' => 'fr', + 'overwrite_options[not_customized]' => TRUE, + 'overwrite_options[customized]' => FALSE, + )); + + // 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 customized translation file was successfully imported.')); + // Ensure string wasn't overwritten. + $search = array( + 'string' => 'januari', + 'language' => 'fr', + 'translation' => 'translated', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertText(t('No strings available.'), t('Customized string not overwritten by imported string.')); + + // Try importing a .po file with overriding strings, and ensure existing + // customized strings are overwritten. + $this->importPoFile($this->getCustomOverwritePoFile(), array( + 'langcode' => 'fr', + 'overwrite_options[not_customized]' => FALSE, + 'overwrite_options[customized]' => TRUE, + )); + + // 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 customized translation file was successfully imported.')); + // Ensure string was overwritten. + $search = array( + 'string' => 'januari', + 'language' => 'fr', + 'translation' => 'translated', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertNoText(t('No strings available.'), t('Customized string overwritten by imported string.')); + + } + + /** + * 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', + ); + $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', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertNoText(t('No strings available.'), t('String successfully imported.')); + } + + /** + * Test msgctxt context support. + */ + 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.')); + } + + /** + * 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, + 'overwrite_options[not_customized]' => TRUE, + )); + $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. + $language_indicator = "$langcode "; + $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.')); + } + + /** + * 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'; + file_put_contents($name, $contents); + $options['files[file]'] = $name; + $this->drupalPost('admin/config/regional/translate/import', $options, t('Import')); + drupal_unlink($name); + } + + /** + * Helper function that returns a proper .po file. + */ + function getPoFile() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "One sheep" +msgid_plural "@count sheep" +msgstr[0] "un mouton" +msgstr[1] "@count moutons" + +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" +EOF; + } + + /** + * Helper function that returns a bad .po file. + */ + function getBadPoFile() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "Save configuration" +msgstr "Enregistrer la configuration" + +msgid "edit" +msgstr "modifier" + +msgid "delete" +msgstr "supprimer" + +EOF; + } + + /** + * Helper function that returns a proper .po file for testing. + */ + function getOverwritePoFile() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n" + +msgid "Monday" +msgstr "Montag" + +msgid "Day" +msgstr "Jour" +EOF; + } + + /** + * Helper function that returns a .po file which strings will be marked + * as customized. + */ + function getCustomPoFile() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "One dog" +msgid_plural "@count dogs" +msgstr[0] "un chien" +msgstr[1] "@count chiens" + +msgid "January" +msgstr "janvier" + +msgid "February" +msgstr "février" + +msgid "March" +msgstr "mars" + +msgid "April" +msgstr "avril" + +msgid "June" +msgstr "juin" +EOF; + } + + /** + * Helper function that returns a .po file for testing customized strings. + */ + function getCustomOverwritePoFile() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "January" +msgstr "januari" + +msgid "February" +msgstr "februari" + +msgid "July" +msgstr "juillet" +EOF; + } + + /** + * Helper function that returns a .po file with context. + */ + function getPoFileWithContext() { + // Croatian (code hr) is one the the languages that have a different + // form for the full name and the abbreviated name for the month May. + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n" + +msgctxt "Long month name" +msgid "May" +msgstr "Svibanj" + +msgid "May" +msgstr "Svi." +EOF; + } + + /** + * Helper function that returns a .po file with an empty last item. + */ + function getPoFileWithEmptyMsgstr() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "Operations" +msgstr "" + +EOF; + } + /** + * Helper function that returns a .po file with an empty last item. + */ + function getPoFileWithMsgstr() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "Operations" +msgstr "Műveletek" + +msgid "Will not appear in Drupal core, so we can ensure the test passes" +msgstr "" + +EOF; + } + +} diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleInstallTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleInstallTest.php new file mode 100644 index 00000000000..f1677eb21e4 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleInstallTest.php @@ -0,0 +1,40 @@ + 'String translation using st()', + 'description' => 'Tests that st() works like t().', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + + // st() lives in install.inc, so ensure that it is loaded for all tests. + require_once DRUPAL_ROOT . '/core/includes/install.inc'; + } + + /** + * Verify that function signatures of t() and st() are equal. + */ + function testFunctionSignatures() { + $reflector_t = new ReflectionFunction('t'); + $reflector_st = new ReflectionFunction('st'); + $this->assertEqual($reflector_t->getParameters(), $reflector_st->getParameters(), t('Function signatures of t() and st() are equal.')); + } +} diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleJavascriptTranslation.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleJavascriptTranslation.php new file mode 100644 index 00000000000..97395379f18 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleJavascriptTranslation.php @@ -0,0 +1,100 @@ + 'Javascript translation', + 'description' => 'Tests parsing js files for translatable strings', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + } + + function testFileParsing() { + + $filename = drupal_get_path('module', 'locale') . '/tests/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')) + ->condition('s.location', $filename) + ->execute() + ->fetchAllKeyed(); + + // List of all strings that should be in the file. + $test_strings = array( + "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", + ); + + // 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)); + } + + $this->assertEqual(count($source_strings), count($test_strings), t("Found correct number of source strings.")); + } +} diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleMultilingualFieldsTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleMultilingualFieldsTest.php new file mode 100644 index 00000000000..2a8299ade56 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleMultilingualFieldsTest.php @@ -0,0 +1,137 @@ + 'Multilingual fields', + 'description' => 'Test multilingual support for fields.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp(array('node', 'locale')); + + // Create Basic page node type. + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + + // Setup users. + $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages', 'create page content', 'edit own page content')); + $this->drupalLogin($admin_user); + + // Add a new language. + $language = (object) array( + 'langcode' => 'it', + 'name' => 'Italian', + ); + language_save($language); + + // Enable URL language detection and selection. + $edit = array('language_interface[enabled][language-url]' => '1'); + $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); + + // Set "Basic page" content type to use multilingual support. + $edit = array( + 'node_type_language' => 1, + ); + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), t('Basic page content type has been updated.')); + + // Make node body translatable. + $field = field_info_field('body'); + $field['translatable'] = TRUE; + field_update_field($field); + } + + /** + * Test if field languages are correctly set through the node form. + */ + function testMultilingualNodeForm() { + // Create "Basic page" content. + $langcode = LANGUAGE_NOT_SPECIFIED; + $title_key = "title"; + $title_value = $this->randomName(8); + $body_key = "body[$langcode][0][value]"; + $body_value = $this->randomName(16); + + // Create node to edit. + $edit = array(); + $edit[$title_key] = $title_value; + $edit[$body_key] = $body_value; + $edit['langcode'] = 'en'; + $this->drupalPost('node/add/page', $edit, t('Save')); + + // Check that the node exists in the database. + $node = $this->drupalGetNodeByTitle($edit[$title_key]); + $this->assertTrue($node, t('Node found in database.')); + + $assert = isset($node->body['en']) && !isset($node->body[LANGUAGE_NOT_SPECIFIED]) && $node->body['en'][0]['value'] == $body_value; + $this->assertTrue($assert, t('Field language correctly set.')); + + // Change node language. + $this->drupalGet("node/$node->nid/edit"); + $edit = array( + $title_key => $this->randomName(8), + 'langcode' => 'it' + ); + $this->drupalPost(NULL, $edit, t('Save')); + $node = $this->drupalGetNodeByTitle($edit[$title_key]); + $this->assertTrue($node, t('Node found in database.')); + + $assert = isset($node->body['it']) && !isset($node->body['en']) && $node->body['it'][0]['value'] == $body_value; + $this->assertTrue($assert, t('Field language correctly changed.')); + + // Enable content language URL detection. + language_negotiation_set(LANGUAGE_TYPE_CONTENT, array(LANGUAGE_NEGOTIATION_URL => 0)); + + // Test multilingual field language fallback logic. + $this->drupalGet("it/node/$node->nid"); + $this->assertRaw($body_value, t('Body correctly displayed using Italian as requested language')); + + $this->drupalGet("node/$node->nid"); + $this->assertRaw($body_value, t('Body correctly displayed using English as requested language')); + } + + /* + * Test multilingual field display settings. + */ + function testMultilingualDisplaySettings() { + // Create "Basic page" content. + $langcode = LANGUAGE_NOT_SPECIFIED; + $title_key = "title"; + $title_value = $this->randomName(8); + $body_key = "body[$langcode][0][value]"; + $body_value = $this->randomName(16); + + // Create node to edit. + $edit = array(); + $edit[$title_key] = $title_value; + $edit[$body_key] = $body_value; + $edit['langcode'] = 'en'; + $this->drupalPost('node/add/page', $edit, t('Save')); + + // Check that the node exists in the database. + $node = $this->drupalGetNodeByTitle($edit[$title_key]); + $this->assertTrue($node, t('Node found in database.')); + + // Check if node body is showed. + $this->drupalGet("node/$node->nid"); + $body = $this->xpath('//article[@id=:id]//div[@class=:class]/descendant::p', array( + ':id' => 'node-' . $node->nid, + ':class' => 'content', + )); + $this->assertEqual(current($body), $node->body['en'][0]['value'], 'Node body found.'); + } +} diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocalePathTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocalePathTest.php new file mode 100644 index 00000000000..61fd67fef87 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocalePathTest.php @@ -0,0 +1,149 @@ + 'Path language settings', + 'description' => 'Checks you can configure a language for individual url aliases.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp(array('node', 'locale', 'path')); + + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + variable_set('site_frontpage', 'node'); + } + + /** + * Test if a language can be associated with a path alias. + */ + function testPathLanguageConfiguration() { + global $base_url; + + // User to add and remove language. + $admin_user = $this->drupalCreateUser(array('administer languages', 'create page content', 'administer url aliases', 'create url aliases', 'access administration pages')); + + // Add custom language. + $this->drupalLogin($admin_user); + // Code for the language. + $langcode = 'xx'; + // The English name for the language. + $name = $this->randomName(16); + // The domain prefix. + $prefix = $langcode; + $edit = array( + 'predefined_langcode' => 'custom', + 'langcode' => $langcode, + 'name' => $name, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + + // Set path prefix. + $edit = array( "prefix[$langcode]" => $prefix ); + $this->drupalPost('admin/config/regional/language/detection/url', $edit, t('Save configuration')); + + // Check that the "xx" front page is readily available because path prefix + // negotiation is pre-configured. + $this->drupalGet($prefix); + $this->assertText(t('Welcome to Drupal'), t('The "xx" front page is readibly available.')); + + // Create a node. + $node = $this->drupalCreateNode(array('type' => 'page')); + + // Create a path alias in default language (English). + $path = 'admin/config/search/path/add'; + $english_path = $this->randomName(8); + $edit = array( + 'source' => 'node/' . $node->nid, + 'alias' => $english_path, + 'langcode' => 'en', + ); + $this->drupalPost($path, $edit, t('Save')); + + // Create a path alias in new custom language. + $custom_language_path = $this->randomName(8); + $edit = array( + 'source' => 'node/' . $node->nid, + 'alias' => $custom_language_path, + 'langcode' => $langcode, + ); + $this->drupalPost($path, $edit, t('Save')); + + // Confirm English language path alias works. + $this->drupalGet($english_path); + $this->assertText($node->title, t('English alias works.')); + + // Confirm custom language path alias works. + $this->drupalGet($prefix . '/' . $custom_language_path); + $this->assertText($node->title, t('Custom language alias works.')); + + // Create a custom path. + $custom_path = $this->randomName(8); + + // Check priority of language for alias by source path. + $edit = array( + 'source' => 'node/' . $node->nid, + 'alias' => $custom_path, + 'langcode' => LANGUAGE_NOT_SPECIFIED, + ); + path_save($edit); + $lookup_path = drupal_lookup_path('alias', 'node/' . $node->nid, 'en'); + $this->assertEqual($english_path, $lookup_path, t('English language alias has priority.')); + // Same check for language 'xx'. + $lookup_path = drupal_lookup_path('alias', 'node/' . $node->nid, $prefix); + $this->assertEqual($custom_language_path, $lookup_path, t('Custom language alias has priority.')); + path_delete($edit); + + // Create language nodes to check priority of aliases. + $first_node = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1)); + $second_node = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1)); + + // Assign a custom path alias to the first node with the English language. + $edit = array( + 'source' => 'node/' . $first_node->nid, + 'alias' => $custom_path, + 'langcode' => 'en', + ); + path_save($edit); + + // Assign a custom path alias to second node with LANGUAGE_NOT_SPECIFIED. + $edit = array( + 'source' => 'node/' . $second_node->nid, + 'alias' => $custom_path, + 'langcode' => LANGUAGE_NOT_SPECIFIED, + ); + path_save($edit); + + // Test that both node titles link to our path alias. + $this->drupalGet(''); + $custom_path_url = base_path() . $GLOBALS['script_path'] . $custom_path; + $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $first_node->title)); + $this->assertTrue(!empty($elements), t('First node links to the path alias.')); + $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $second_node->title)); + $this->assertTrue(!empty($elements), t('Second node links to the path alias.')); + + // Confirm that the custom path leads to the first node. + $this->drupalGet($custom_path); + $this->assertText($first_node->title, t('Custom alias returns first node.')); + + // Confirm that the custom path with prefix leads to the second node. + $this->drupalGet($prefix . '/' . $custom_path); + $this->assertText($second_node->title, t('Custom alias with prefix returns second node.')); + + } +} diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php new file mode 100644 index 00000000000..6d45d215225 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php @@ -0,0 +1,323 @@ + 'Plural handling', + 'description' => 'Tests plural handling for various languages.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + + $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages')); + $this->drupalLogin($admin_user); + } + + /** + * Tests locale_get_plural() and format_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', + )); + + // Attempt to import some broken .po files as well to prove that these + // will not overwrite the proper plural formula imported above. + $this->importPoFile($this->getPoFileWithMissingPlural(), array( + 'langcode' => 'fr', + 'overwrite_options[not_customized]' => TRUE, + )); + $this->importPoFile($this->getPoFileWithBrokenPlural(), array( + 'langcode' => 'hr', + 'overwrite_options[not_customized]' => TRUE, + )); + + // Reset static caches from locale_get_plural() to ensure we get fresh data. + drupal_static_reset('locale_get_plural'); + drupal_static_reset('locale_get_plural:plurals'); + drupal_static_reset('locale'); + + // Expected plural translation strings for each plural index. + $plural_strings = array( + // English is not imported in this case, so we assume built-in text + // and formulas. + 'en' => array( + 0 => '1 hour', + 1 => '@count hours', + ), + 'fr' => array( + 0 => '1 heure', + 1 => '@count heures', + ), + 'hr' => array( + 0 => '@count sat', + 1 => '@count sata', + 2 => '@count sati', + ), + // Hungarian is not imported, so it should assume the same text as + // English, but it will always pick the plural form as per the built-in + // logic, so only index -1 is relevant with the plural value. + 'hu' => array( + 0 => '1 hour', + -1 => '@count hours', + ), + ); + + // Expected plural indexes precomputed base on the plural formulas with + // given $count value. + $plural_tests = array( + 'en' => array( + 1 => 0, + 0 => 1, + 5 => 1, + ), + 'fr' => array( + 1 => 0, + 0 => 0, + 5 => 1, + ), + 'hr' => array( + 1 => 0, + 21 => 0, + 0 => 2, + 2 => 1, + 8 => 2, + ), + 'hu' => array( + 1 => -1, + 21 => -1, + 0 => -1, + ), + ); + + foreach ($plural_tests as $langcode => $tests) { + foreach ($tests as $count => $expected_plural_index) { + // Assert that the we get the right plural index. + $this->assertIdentical(locale_get_plural($count, $langcode), $expected_plural_index, 'Computed plural index for ' . $langcode . ' for count ' . $count . ' is ' . $expected_plural_index); + // Assert that the we get the right translation for that. Change the + // expected index as per the logic for translation lookups. + $expected_plural_index = ($count == 1) ? 0 : $expected_plural_index; + $expected_plural_string = str_replace('@count', $count, $plural_strings[$langcode][$expected_plural_index]); + $this->assertIdentical(format_plural($count, '1 hour', '@count hours', array(), array('langcode' => $langcode)), $expected_plural_string, 'Plural translation of 1 hours / @count hours for count ' . $count . ' in ' . $langcode . ' is ' . $expected_plural_string); + } + } + } + + /** + * Tests plural editing and export functionality. + */ + function testPluralEditExport() { + // 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', + )); + + // Get the French translations. + $this->drupalPost('admin/config/regional/translate/export', array( + 'langcode' => 'fr', + ), t('Export')); + // Ensure we have a translation file. + $this->assertRaw('# French translation of Drupal', t('Exported French translation file.')); + // Ensure our imported translations exist in the file. + $this->assertRaw("msgid \"Monday\"\nmsgstr \"lundi\"", t('French translations present in exported file.')); + // Check for plural export specifically. + $this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"1 heure\"\nmsgstr[1] \"@count heures\"", t('Plural translations exported properly.')); + + // Get the Croatian translations. + $this->drupalPost('admin/config/regional/translate/export', array( + 'langcode' => 'hr', + ), t('Export')); + // Ensure we have a translation file. + $this->assertRaw('# Croatian translation of Drupal', t('Exported Croatian translation file.')); + // Ensure our imported translations exist in the file. + $this->assertRaw("msgid \"Monday\"\nmsgstr \"Ponedjeljak\"", t('Croatian translations present in exported file.')); + // Check for plural export specifically. + $this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count sat\"\nmsgstr[1] \"@count sata\"\nmsgstr[2] \"@count sati\"", t('Plural translations exported properly.')); + + // Check if the source appears on the translation page. + $this->drupalGet('admin/config/regional/translate'); + $this->assertText("1 hour, @count hours"); + + // Look up editing page for this plural string and check fields. + $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", array(':source' => "1 hour" . LOCALE_PLURAL_DELIMITER . "@count hours"))->fetchField(); + $path = 'admin/config/regional/translate/edit/' . $lid; + $this->drupalGet($path); + // Labels for plural editing elements. + $this->assertText('Singular form'); + $this->assertText('First plural form'); + $this->assertText('2. plural form'); + // Plural values for both languages. + $this->assertFieldById('edit-translations-hr-0', '@count sat'); + $this->assertFieldById('edit-translations-hr-1', '@count sata'); + $this->assertFieldById('edit-translations-hr-2', '@count sati'); + $this->assertNoFieldById('edit-translations-hr-3'); + $this->assertFieldById('edit-translations-fr-0', '1 heure'); + $this->assertFieldById('edit-translations-fr-1', '@count heures'); + $this->assertNoFieldById('edit-translations-fr-2'); + + // Edit some translations and see if that took effect. + $edit = array( + 'translations[fr][0]' => '1 heure edited', + 'translations[hr][1]' => '@count sata edited', + ); + $this->drupalPost($path, $edit, t('Save translations')); + + // Inject a plural source string to the database. We need to use a specific + // langcode here because the language will be English by default and will + // not save our source string for performance optimization if we do not ask + // specifically for a language. + format_plural(1, '1 day', '@count days', array(), array('langcode' => 'fr')); + // Look up editing page for this plural string and check fields. + $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", array(':source' => "1 day" . LOCALE_PLURAL_DELIMITER . "@count days"))->fetchField(); + $path = 'admin/config/regional/translate/edit/' . $lid; + + // Save complete translations for the string in both languages. + $edit = array( + 'translations[fr][0]' => '1 jour', + 'translations[fr][1]' => '@count jours', + 'translations[hr][0]' => '@count dan', + 'translations[hr][1]' => '@count dana', + 'translations[hr][2]' => '@count dana', + ); + $this->drupalPost($path, $edit, t('Save translations')); + + // Get the French translations. + $this->drupalPost('admin/config/regional/translate/export', array( + 'langcode' => 'fr', + ), t('Export')); + // Check for plural export specifically. + $this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"1 heure edited\"\nmsgstr[1] \"@count heures\"", t('Edited French plural translations for hours exported properly.')); + $this->assertRaw("msgid \"1 day\"\nmsgid_plural \"@count days\"\nmsgstr[0] \"1 jour\"\nmsgstr[1] \"@count jours\"", t('Added French plural translations for days exported properly.')); + + // Get the Croatian translations. + $this->drupalPost('admin/config/regional/translate/export', array( + 'langcode' => 'hr', + ), t('Export')); + // Check for plural export specifically. + $this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count sat\"\nmsgstr[1] \"@count sata edited\"\nmsgstr[2] \"@count sati\"", t('Edited Croatian plural translations exported properly.')); + $this->assertRaw("msgid \"1 day\"\nmsgid_plural \"@count days\"\nmsgstr[0] \"@count dan\"\nmsgstr[1] \"@count dana\"\nmsgstr[2] \"@count dana\"", t('Added Croatian plural translations exported properly.')); + } + + + /** + * 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 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "1 hour" +msgid_plural "@count hours" +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 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n" + +msgid "1 hour" +msgid_plural "@count hours" +msgstr[0] "@count sat" +msgstr[1] "@count sata" +msgstr[2] "@count sati" + +msgid "Monday" +msgstr "Ponedjeljak" +EOF; + } + + /** + * Returns a .po file with a missing plural formula. + */ + function getPoFileWithMissingPlural() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" + +msgid "Monday" +msgstr "lundi" +EOF; + } + + /** + * Returns a .po file with a broken plural formula. + */ + function getPoFileWithBrokenPlural() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 8\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: broken, will not parse\\n" + +msgid "Monday" +msgstr "Ponedjeljak" +EOF; + } +} diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleTranslationTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleTranslationTest.php new file mode 100644 index 00000000000..28e0834ba48 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleTranslationTest.php @@ -0,0 +1,418 @@ + '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. + $language_indicator = "$langcode "; + // This will be the translation of $name. + $translation = $this->randomName(16); + $translation_to_en = $this->randomName(16); + + // Add custom language. + $this->drupalLogin($admin_user); + $edit = array( + 'predefined_langcode' => 'custom', + 'langcode' => $langcode, + 'name' => $name, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + // Add string. + t($name, array(), array('langcode' => $langcode)); + // Reset locale cache. + locale_reset(); + $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', + ); + $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. + $matches = array(); + preg_match('!admin/config/regional/translate/edit/(\d+)!', $this->getUrl(), $matches); + $lid = $matches[1]; + // No t() here, it's surely not translated yet. + $this->assertText($name, t('name found on edit screen.')); + $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][0]" => $translation, + 'translations[en][0]' => $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.')); + $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.')); + $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; + $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); + $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); + // 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', + ); + $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. + $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. + $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.')); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertNoText($name, t('Search now can not find the name.')); + } + + /* + * 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'); + $result = $query->condition('l.location', '%.js%', 'LIKE')->execute(); + $url = 'admin/config/regional/translate/edit/' . $result->fetchObject()->lid; + $edit = array('translations['. $langcode .'][0]' => $this->randomName()); + $this->drupalPost($url, $edit, t('Save translations')); + + // Trigger JavaScript translation parsing and building. + _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() { + global $base_url; + + // User to add language and strings. + $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'translate interface')); + $this->drupalLogin($admin_user); + $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. + $language_indicator = "$langcode "; + // These will be the invalid translations of $name. + $key = $this->randomName(16); + $bad_translations[$key] = "" . $key; + $key = $this->randomName(16); + $bad_translations[$key] = '' . $key; + $key = $this->randomName(16); + $bad_translations[$key] = '<' . $key; + $key = $this->randomName(16); + $bad_translations[$key] ="" . $key; + + // 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')); + // Add string. + t($name, array(), array('langcode' => $langcode)); + // Reset locale cache. + $search = array( + 'string' => $name, + 'language' => 'all', + 'translation' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + // Find the edit path. + $content = $this->drupalGetContent(); + $this->assertTrue(preg_match('@(admin/config/regional/translate/edit/[0-9]+)@', $content, $matches), t('Found the edit path.')); + $path = $matches[0]; + foreach ($bad_translations as $key => $translation) { + $edit = array( + "translations[$langcode][0]" => $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.')); + } + } + + /** + * 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. + $language_indicator = "$langcode "; + // 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', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + // Add string. + 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', + ); + $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', + ); + $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', + ); + $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(); + preg_match('!admin/config/regional/translate/edit/(\d)+!', $this->getUrl(), $matches); + $lid = $matches[1]; + $edit = array( + "translations[$langcode][0]" => $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', + ); + $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', + ); + $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', + ); + $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', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertNoText(t('No strings available.'), t('Search found the translation.')); + + // Ensure translated string doesn't appear if searching in System (English). + $search = array( + 'string' => $translation, + 'language' => LANGUAGE_SYSTEM, + 'translation' => 'all', + ); + $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', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertText(t('No strings available.'), t("Search didn't find the invalid string.")); + } +} diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallFrenchTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallFrenchTest.php new file mode 100644 index 00000000000..047ff630670 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallFrenchTest.php @@ -0,0 +1,31 @@ + 'Locale uninstall (FR)', + 'description' => 'Tests the uninstall process using French as interface language.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp(); + $this->langcode = 'fr'; + } +} diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php new file mode 100644 index 00000000000..72868e9e1bc --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php @@ -0,0 +1,132 @@ + 'Locale uninstall (EN)', + 'description' => 'Tests the uninstall process using the built-in UI language.', + 'group' => 'Locale', + ); + } + + /** + * The default language set for the UI before uninstall. + */ + protected $language; + + function setUp() { + parent::setUp(array('node', 'locale')); + $this->langcode = 'en'; + + // Create Article node type. + $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); + } + + /** + * Check if the values of the Locale variables are correct after uninstall. + */ + function testUninstallProcess() { + $locale_module = array('locale', 'language'); + + $language = (object) array( + 'langcode' => 'fr', + 'name' => 'French', + 'default' => $this->langcode == 'fr', + ); + language_save($language); + + // Check the UI language. + drupal_language_initialize(); + $this->assertEqual(drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode, $this->langcode, t('Current language: %lang', array('%lang' => drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode))); + + // Enable multilingual workflow option for articles. + variable_set('node_type_language_article', 1); + + // Change JavaScript translations directory. + variable_set('locale_js_directory', 'js_translations'); + + // Build the JavaScript translation file for French. + $user = $this->drupalCreateUser(array('translate interface', 'access administration pages')); + $this->drupalLogin($user); + $this->drupalGet('admin/config/regional/translate/translate'); + $string = db_query('SELECT min(lid) AS lid FROM {locales_source} WHERE location LIKE :location', array( + ':location' => '%.js%', + ))->fetchObject(); + $edit = array('translations[fr][0]' => 'french translation'); + $this->drupalPost('admin/config/regional/translate/edit/' . $string->lid, $edit, t('Save translations')); + _locale_rebuild_js('fr'); + $locale_javascripts = variable_get('locale_translation_javascript', array()); + $js_file = 'public://' . variable_get('locale_js_directory', 'languages') . '/fr_' . $locale_javascripts['fr'] . '.js'; + $this->assertTrue($result = file_exists($js_file), t('JavaScript file created: %file', array('%file' => $result ? $js_file : t('none')))); + + // Disable string caching. + variable_set('locale_cache_strings', 0); + + // Change language negotiation options. + drupal_load('module', 'locale'); + variable_set('language_types', language_types_get_default() + array('language_custom' => TRUE)); + variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, language_language_negotiation_info()); + variable_set('language_negotiation_' . LANGUAGE_TYPE_CONTENT, language_language_negotiation_info()); + variable_set('language_negotiation_' . LANGUAGE_TYPE_URL, language_language_negotiation_info()); + + // Change language negotiation settings. + variable_set('language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX); + variable_set('language_negotiation_session_param', TRUE); + + // Uninstall Locale. + module_disable($locale_module); + drupal_uninstall_modules($locale_module); + + // Visit the front page. + $this->drupalGet(''); + + // Check the init language logic. + drupal_language_initialize(); + $this->assertEqual(drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode, 'en', t('Language after uninstall: %lang', array('%lang' => drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode))); + + // Check JavaScript files deletion. + $this->assertTrue($result = !file_exists($js_file), t('JavaScript file deleted: %file', array('%file' => $result ? $js_file : t('found')))); + + // Check language count. + $language_count = variable_get('language_count', 1); + $this->assertEqual($language_count, 1, t('Language count: %count', array('%count' => $language_count))); + + // Check language negotiation. + require_once DRUPAL_ROOT . '/core/includes/language.inc'; + $this->assertTrue(count(language_types_get_all()) == count(language_types_get_default()), t('Language types reset')); + $language_negotiation = language_negotiation_method_get_first(LANGUAGE_TYPE_INTERFACE) == LANGUAGE_NEGOTIATION_DEFAULT; + $this->assertTrue($language_negotiation, t('Interface language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); + $language_negotiation = language_negotiation_method_get_first(LANGUAGE_TYPE_CONTENT) == LANGUAGE_NEGOTIATION_DEFAULT; + $this->assertTrue($language_negotiation, t('Content language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); + $language_negotiation = language_negotiation_method_get_first(LANGUAGE_TYPE_URL) == LANGUAGE_NEGOTIATION_DEFAULT; + $this->assertTrue($language_negotiation, t('URL language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); + + // Check language negotiation method settings. + $this->assertFalse(variable_get('language_negotiation_url_part', FALSE), t('URL language negotiation method indicator settings cleared.')); + $this->assertFalse(variable_get('language_negotiation_session_param', FALSE), t('Visit language negotiation method settings cleared.')); + + // Check JavaScript parsed. + $javascript_parsed_count = count(variable_get('javascript_parsed', array())); + $this->assertEqual($javascript_parsed_count, 0, t('JavaScript parsed count: %count', array('%count' => $javascript_parsed_count))); + + // Check JavaScript translations directory. + $locale_js_directory = variable_get('locale_js_directory', 'languages'); + $this->assertEqual($locale_js_directory, 'languages', t('JavaScript translations directory: %dir', array('%dir' => $locale_js_directory))); + + // Check string caching. + $locale_cache_strings = variable_get('locale_cache_strings', 1); + $this->assertEqual($locale_cache_strings, 1, t('String caching: %status', array('%status' => t($locale_cache_strings ? 'enabled': 'disabled')))); + } +} diff --git a/core/modules/locale/locale.info b/core/modules/locale/locale.info index 8b3e5f74aca..e0749dba9d6 100644 --- a/core/modules/locale/locale.info +++ b/core/modules/locale/locale.info @@ -4,4 +4,3 @@ package = Core version = VERSION core = 8.x dependencies[] = language -files[] = locale.test diff --git a/core/modules/locale/locale.test b/core/modules/locale/locale.test deleted file mode 100644 index 5e252bf4d82..00000000000 --- a/core/modules/locale/locale.test +++ /dev/null @@ -1,2263 +0,0 @@ - 'Javascript translation', - 'description' => 'Tests parsing js files for translatable strings', - 'group' => 'Locale', - ); - } - - function setUp() { - parent::setUp('locale'); - } - - function testFileParsing() { - - $filename = drupal_get_path('module', 'locale') . '/tests/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')) - ->condition('s.location', $filename) - ->execute() - ->fetchAllKeyed(); - - // List of all strings that should be in the file. - $test_strings = array( - "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", - ); - - // 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)); - } - - $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 WebTestBase { - 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. - $language_indicator = "$langcode "; - // This will be the translation of $name. - $translation = $this->randomName(16); - $translation_to_en = $this->randomName(16); - - // Add custom language. - $this->drupalLogin($admin_user); - $edit = array( - 'predefined_langcode' => 'custom', - 'langcode' => $langcode, - 'name' => $name, - 'direction' => '0', - ); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); - // Add string. - t($name, array(), array('langcode' => $langcode)); - // Reset locale cache. - locale_reset(); - $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', - ); - $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. - $matches = array(); - preg_match('!admin/config/regional/translate/edit/(\d+)!', $this->getUrl(), $matches); - $lid = $matches[1]; - // No t() here, it's surely not translated yet. - $this->assertText($name, t('name found on edit screen.')); - $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][0]" => $translation, - 'translations[en][0]' => $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.')); - $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.')); - $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; - $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); - $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); - // 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', - ); - $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. - $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. - $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.')); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); - $this->assertNoText($name, t('Search now can not find the name.')); - } - - /* - * 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'); - $result = $query->condition('l.location', '%.js%', 'LIKE')->execute(); - $url = 'admin/config/regional/translate/edit/' . $result->fetchObject()->lid; - $edit = array('translations['. $langcode .'][0]' => $this->randomName()); - $this->drupalPost($url, $edit, t('Save translations')); - - // Trigger JavaScript translation parsing and building. - _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() { - global $base_url; - - // User to add language and strings. - $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'translate interface')); - $this->drupalLogin($admin_user); - $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. - $language_indicator = "$langcode "; - // These will be the invalid translations of $name. - $key = $this->randomName(16); - $bad_translations[$key] = "" . $key; - $key = $this->randomName(16); - $bad_translations[$key] = '' . $key; - $key = $this->randomName(16); - $bad_translations[$key] = '<' . $key; - $key = $this->randomName(16); - $bad_translations[$key] ="" . $key; - - // 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')); - // Add string. - t($name, array(), array('langcode' => $langcode)); - // Reset locale cache. - $search = array( - 'string' => $name, - 'language' => 'all', - 'translation' => 'all', - ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); - // Find the edit path. - $content = $this->drupalGetContent(); - $this->assertTrue(preg_match('@(admin/config/regional/translate/edit/[0-9]+)@', $content, $matches), t('Found the edit path.')); - $path = $matches[0]; - foreach ($bad_translations as $key => $translation) { - $edit = array( - "translations[$langcode][0]" => $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.')); - } - } - - /** - * 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. - $language_indicator = "$langcode "; - // 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', - ); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); - // Add string. - 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', - ); - $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', - ); - $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', - ); - $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(); - preg_match('!admin/config/regional/translate/edit/(\d)+!', $this->getUrl(), $matches); - $lid = $matches[1]; - $edit = array( - "translations[$langcode][0]" => $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', - ); - $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', - ); - $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', - ); - $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', - ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); - $this->assertNoText(t('No strings available.'), t('Search found the translation.')); - - // Ensure translated string doesn't appear if searching in System (English). - $search = array( - 'string' => $translation, - 'language' => LANGUAGE_SYSTEM, - 'translation' => 'all', - ); - $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', - ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); - $this->assertText(t('No strings available.'), t("Search didn't find the invalid string.")); - } -} - -/** - * Tests plural format handling functionality. - */ -class LocalePluralFormatTest extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Plural handling', - 'description' => 'Tests plural handling for various languages.', - 'group' => 'Locale', - ); - } - - function setUp() { - parent::setUp('locale'); - - $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages')); - $this->drupalLogin($admin_user); - } - - /** - * Tests locale_get_plural() and format_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', - )); - - // Attempt to import some broken .po files as well to prove that these - // will not overwrite the proper plural formula imported above. - $this->importPoFile($this->getPoFileWithMissingPlural(), array( - 'langcode' => 'fr', - 'overwrite_options[not_customized]' => TRUE, - )); - $this->importPoFile($this->getPoFileWithBrokenPlural(), array( - 'langcode' => 'hr', - 'overwrite_options[not_customized]' => TRUE, - )); - - // Reset static caches from locale_get_plural() to ensure we get fresh data. - drupal_static_reset('locale_get_plural'); - drupal_static_reset('locale_get_plural:plurals'); - drupal_static_reset('locale'); - - // Expected plural translation strings for each plural index. - $plural_strings = array( - // English is not imported in this case, so we assume built-in text - // and formulas. - 'en' => array( - 0 => '1 hour', - 1 => '@count hours', - ), - 'fr' => array( - 0 => '1 heure', - 1 => '@count heures', - ), - 'hr' => array( - 0 => '@count sat', - 1 => '@count sata', - 2 => '@count sati', - ), - // Hungarian is not imported, so it should assume the same text as - // English, but it will always pick the plural form as per the built-in - // logic, so only index -1 is relevant with the plural value. - 'hu' => array( - 0 => '1 hour', - -1 => '@count hours', - ), - ); - - // Expected plural indexes precomputed base on the plural formulas with - // given $count value. - $plural_tests = array( - 'en' => array( - 1 => 0, - 0 => 1, - 5 => 1, - ), - 'fr' => array( - 1 => 0, - 0 => 0, - 5 => 1, - ), - 'hr' => array( - 1 => 0, - 21 => 0, - 0 => 2, - 2 => 1, - 8 => 2, - ), - 'hu' => array( - 1 => -1, - 21 => -1, - 0 => -1, - ), - ); - - foreach ($plural_tests as $langcode => $tests) { - foreach ($tests as $count => $expected_plural_index) { - // Assert that the we get the right plural index. - $this->assertIdentical(locale_get_plural($count, $langcode), $expected_plural_index, 'Computed plural index for ' . $langcode . ' for count ' . $count . ' is ' . $expected_plural_index); - // Assert that the we get the right translation for that. Change the - // expected index as per the logic for translation lookups. - $expected_plural_index = ($count == 1) ? 0 : $expected_plural_index; - $expected_plural_string = str_replace('@count', $count, $plural_strings[$langcode][$expected_plural_index]); - $this->assertIdentical(format_plural($count, '1 hour', '@count hours', array(), array('langcode' => $langcode)), $expected_plural_string, 'Plural translation of 1 hours / @count hours for count ' . $count . ' in ' . $langcode . ' is ' . $expected_plural_string); - } - } - } - - /** - * Tests plural editing and export functionality. - */ - function testPluralEditExport() { - // 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', - )); - - // Get the French translations. - $this->drupalPost('admin/config/regional/translate/export', array( - 'langcode' => 'fr', - ), t('Export')); - // Ensure we have a translation file. - $this->assertRaw('# French translation of Drupal', t('Exported French translation file.')); - // Ensure our imported translations exist in the file. - $this->assertRaw("msgid \"Monday\"\nmsgstr \"lundi\"", t('French translations present in exported file.')); - // Check for plural export specifically. - $this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"1 heure\"\nmsgstr[1] \"@count heures\"", t('Plural translations exported properly.')); - - // Get the Croatian translations. - $this->drupalPost('admin/config/regional/translate/export', array( - 'langcode' => 'hr', - ), t('Export')); - // Ensure we have a translation file. - $this->assertRaw('# Croatian translation of Drupal', t('Exported Croatian translation file.')); - // Ensure our imported translations exist in the file. - $this->assertRaw("msgid \"Monday\"\nmsgstr \"Ponedjeljak\"", t('Croatian translations present in exported file.')); - // Check for plural export specifically. - $this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count sat\"\nmsgstr[1] \"@count sata\"\nmsgstr[2] \"@count sati\"", t('Plural translations exported properly.')); - - // Check if the source appears on the translation page. - $this->drupalGet('admin/config/regional/translate'); - $this->assertText("1 hour, @count hours"); - - // Look up editing page for this plural string and check fields. - $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", array(':source' => "1 hour" . LOCALE_PLURAL_DELIMITER . "@count hours"))->fetchField(); - $path = 'admin/config/regional/translate/edit/' . $lid; - $this->drupalGet($path); - // Labels for plural editing elements. - $this->assertText('Singular form'); - $this->assertText('First plural form'); - $this->assertText('2. plural form'); - // Plural values for both languages. - $this->assertFieldById('edit-translations-hr-0', '@count sat'); - $this->assertFieldById('edit-translations-hr-1', '@count sata'); - $this->assertFieldById('edit-translations-hr-2', '@count sati'); - $this->assertNoFieldById('edit-translations-hr-3'); - $this->assertFieldById('edit-translations-fr-0', '1 heure'); - $this->assertFieldById('edit-translations-fr-1', '@count heures'); - $this->assertNoFieldById('edit-translations-fr-2'); - - // Edit some translations and see if that took effect. - $edit = array( - 'translations[fr][0]' => '1 heure edited', - 'translations[hr][1]' => '@count sata edited', - ); - $this->drupalPost($path, $edit, t('Save translations')); - - // Inject a plural source string to the database. We need to use a specific - // langcode here because the language will be English by default and will - // not save our source string for performance optimization if we do not ask - // specifically for a language. - format_plural(1, '1 day', '@count days', array(), array('langcode' => 'fr')); - // Look up editing page for this plural string and check fields. - $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", array(':source' => "1 day" . LOCALE_PLURAL_DELIMITER . "@count days"))->fetchField(); - $path = 'admin/config/regional/translate/edit/' . $lid; - - // Save complete translations for the string in both languages. - $edit = array( - 'translations[fr][0]' => '1 jour', - 'translations[fr][1]' => '@count jours', - 'translations[hr][0]' => '@count dan', - 'translations[hr][1]' => '@count dana', - 'translations[hr][2]' => '@count dana', - ); - $this->drupalPost($path, $edit, t('Save translations')); - - // Get the French translations. - $this->drupalPost('admin/config/regional/translate/export', array( - 'langcode' => 'fr', - ), t('Export')); - // Check for plural export specifically. - $this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"1 heure edited\"\nmsgstr[1] \"@count heures\"", t('Edited French plural translations for hours exported properly.')); - $this->assertRaw("msgid \"1 day\"\nmsgid_plural \"@count days\"\nmsgstr[0] \"1 jour\"\nmsgstr[1] \"@count jours\"", t('Added French plural translations for days exported properly.')); - - // Get the Croatian translations. - $this->drupalPost('admin/config/regional/translate/export', array( - 'langcode' => 'hr', - ), t('Export')); - // Check for plural export specifically. - $this->assertRaw("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count sat\"\nmsgstr[1] \"@count sata edited\"\nmsgstr[2] \"@count sati\"", t('Edited Croatian plural translations exported properly.')); - $this->assertRaw("msgid \"1 day\"\nmsgid_plural \"@count days\"\nmsgstr[0] \"@count dan\"\nmsgstr[1] \"@count dana\"\nmsgstr[2] \"@count dana\"", t('Added Croatian plural translations exported properly.')); - } - - - /** - * 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 8\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\\n" - -msgid "1 hour" -msgid_plural "@count hours" -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 8\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" -"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n" - -msgid "1 hour" -msgid_plural "@count hours" -msgstr[0] "@count sat" -msgstr[1] "@count sata" -msgstr[2] "@count sati" - -msgid "Monday" -msgstr "Ponedjeljak" -EOF; - } - - /** - * Returns a .po file with a missing plural formula. - */ - function getPoFileWithMissingPlural() { - return <<< EOF -msgid "" -msgstr "" -"Project-Id-Version: Drupal 8\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" - -msgid "Monday" -msgstr "lundi" -EOF; - } - - /** - * Returns a .po file with a broken plural formula. - */ - function getPoFileWithBrokenPlural() { - return <<< EOF -msgid "" -msgstr "" -"Project-Id-Version: Drupal 8\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" -"Plural-Forms: broken, will not parse\\n" - -msgid "Monday" -msgstr "Ponedjeljak" -EOF; - } -} - -/** - * Functional tests for the import of translation files. - */ -class LocaleImportFunctionalTest extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Translation import', - 'description' => 'Tests the import of locale files.', - 'group' => 'Locale', - ); - } - - /** - * A user able to create languages and import translations. - */ - protected $admin_user = NULL; - - function setUp() { - parent::setUp(array('locale', 'dblog')); - - // Set the translation file directory. - variable_set('locale_translate_file_directory', drupal_get_path('module', 'locale') . '/tests'); - - $this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages')); - $this->drupalLogin($this->admin_user); - } - - /** - * Test import of standalone .po files. - */ - function testStandalonePoFile() { - // Try importing a .po file. - $this->importPoFile($this->getPoFile(), array( - 'langcode' => 'fr', - )); - - // 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 8 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' => 8, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.')); - - // 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.')); - - // Ensure we were redirected correctly. - $this->assertEqual($this->getUrl(), url('admin/config/regional/translate', array('absolute' => TRUE)), t('Correct page redirection.')); - - - // Try importing a .po file with invalid tags. - $this->importPoFile($this->getBadPoFile(), array( - 'langcode' => 'fr', - )); - - // 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. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.', array('@url' => url('admin/reports/dblog'))); - $this->assertRaw($skip_message, t('Unsafe strings were skipped.')); - - - // Try importing a .po file which doesn't exist. - $name = $this->randomName(16); - $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.')); - - - // Try importing a .po file with overriding strings, and ensure existing - // strings are kept. - $this->importPoFile($this->getOverwritePoFile(), array( - 'langcode' => 'fr', - )); - - // 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', - ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); - $this->assertText(t('No strings available.'), t('String not overwritten by imported string.')); - - // 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.')); - - // Try importing a .po file with overriding strings, and ensure existing - // strings are overwritten. - $this->importPoFile($this->getOverwritePoFile(), array( - 'langcode' => 'fr', - 'overwrite_options[not_customized]' => TRUE, - )); - - // 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', - ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); - $this->assertNoText(t('No strings available.'), t('String overwritten by imported string.')); - // 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.')); - - // Importing a .po file and mark its strings as customized strings. - $this->importPoFile($this->getCustomPoFile(), array( - 'langcode' => 'fr', - 'customized' => TRUE, - )); - - // The import should have created 6 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' => 6, '%update' => 0, '%delete' => 0)), t('The customized translation file was successfully imported.')); - - // The database should now contain 6 customized strings (two imported - // strings are not translated). - $count = db_query('SELECT lid FROM {locales_target} WHERE customized = :custom', array(':custom' => 1))->rowCount(); - $this->assertEqual($count, 6, t('Customized translations succesfully imported.')); - - // Try importing a .po file with overriding strings, and ensure existing - // customized strings are kept. - $this->importPoFile($this->getCustomOverwritePoFile(), array( - 'langcode' => 'fr', - 'overwrite_options[not_customized]' => TRUE, - 'overwrite_options[customized]' => FALSE, - )); - - // 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 customized translation file was successfully imported.')); - // Ensure string wasn't overwritten. - $search = array( - 'string' => 'januari', - 'language' => 'fr', - 'translation' => 'translated', - ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); - $this->assertText(t('No strings available.'), t('Customized string not overwritten by imported string.')); - - // Try importing a .po file with overriding strings, and ensure existing - // customized strings are overwritten. - $this->importPoFile($this->getCustomOverwritePoFile(), array( - 'langcode' => 'fr', - 'overwrite_options[not_customized]' => FALSE, - 'overwrite_options[customized]' => TRUE, - )); - - // 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 customized translation file was successfully imported.')); - // Ensure string was overwritten. - $search = array( - 'string' => 'januari', - 'language' => 'fr', - 'translation' => 'translated', - ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); - $this->assertNoText(t('No strings available.'), t('Customized string overwritten by imported string.')); - - } - - /** - * 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', - ); - $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', - ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); - $this->assertNoText(t('No strings available.'), t('String successfully imported.')); - } - - /** - * Test msgctxt context support. - */ - 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.')); - } - - /** - * 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, - 'overwrite_options[not_customized]' => TRUE, - )); - $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. - $language_indicator = "$langcode "; - $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.')); - } - - /** - * 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'; - file_put_contents($name, $contents); - $options['files[file]'] = $name; - $this->drupalPost('admin/config/regional/translate/import', $options, t('Import')); - drupal_unlink($name); - } - - /** - * Helper function that returns a proper .po file. - */ - function getPoFile() { - return <<< EOF -msgid "" -msgstr "" -"Project-Id-Version: Drupal 8\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\\n" - -msgid "One sheep" -msgid_plural "@count sheep" -msgstr[0] "un mouton" -msgstr[1] "@count moutons" - -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" -EOF; - } - - /** - * Helper function that returns a bad .po file. - */ - function getBadPoFile() { - return <<< EOF -msgid "" -msgstr "" -"Project-Id-Version: Drupal 8\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\\n" - -msgid "Save configuration" -msgstr "Enregistrer la configuration" - -msgid "edit" -msgstr "modifier" - -msgid "delete" -msgstr "supprimer" - -EOF; - } - - /** - * Helper function that returns a proper .po file for testing. - */ - function getOverwritePoFile() { - return <<< EOF -msgid "" -msgstr "" -"Project-Id-Version: Drupal 8\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" -"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n" - -msgid "Monday" -msgstr "Montag" - -msgid "Day" -msgstr "Jour" -EOF; - } - - /** - * Helper function that returns a .po file which strings will be marked - * as customized. - */ - function getCustomPoFile() { - return <<< EOF -msgid "" -msgstr "" -"Project-Id-Version: Drupal 8\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\\n" - -msgid "One dog" -msgid_plural "@count dogs" -msgstr[0] "un chien" -msgstr[1] "@count chiens" - -msgid "January" -msgstr "janvier" - -msgid "February" -msgstr "février" - -msgid "March" -msgstr "mars" - -msgid "April" -msgstr "avril" - -msgid "June" -msgstr "juin" -EOF; - } - - /** - * Helper function that returns a .po file for testing customized strings. - */ - function getCustomOverwritePoFile() { - return <<< EOF -msgid "" -msgstr "" -"Project-Id-Version: Drupal 8\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\\n" - -msgid "January" -msgstr "januari" - -msgid "February" -msgstr "februari" - -msgid "July" -msgstr "juillet" -EOF; - } - - /** - * Helper function that returns a .po file with context. - */ - function getPoFileWithContext() { - // Croatian (code hr) is one the the languages that have a different - // form for the full name and the abbreviated name for the month May. - return <<< EOF -msgid "" -msgstr "" -"Project-Id-Version: Drupal 8\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" -"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n" - -msgctxt "Long month name" -msgid "May" -msgstr "Svibanj" - -msgid "May" -msgstr "Svi." -EOF; - } - - /** - * Helper function that returns a .po file with an empty last item. - */ - function getPoFileWithEmptyMsgstr() { - return <<< EOF -msgid "" -msgstr "" -"Project-Id-Version: Drupal 8\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\\n" - -msgid "Operations" -msgstr "" - -EOF; - } - /** - * Helper function that returns a .po file with an empty last item. - */ - function getPoFileWithMsgstr() { - return <<< EOF -msgid "" -msgstr "" -"Project-Id-Version: Drupal 8\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\\n" - -msgid "Operations" -msgstr "Műveletek" - -msgid "Will not appear in Drupal core, so we can ensure the test passes" -msgstr "" - -EOF; - } - -} - -/** - * Functional tests for the export of translation files. - */ -class LocaleExportFunctionalTest extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Translation export', - 'description' => 'Tests the exportation of locale files.', - 'group' => 'Locale', - ); - } - - /** - * A user able to create languages and export translations. - */ - protected $admin_user = NULL; - - function setUp() { - parent::setUp('locale'); - - $this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages')); - $this->drupalLogin($this->admin_user); - } - - /** - * Test exportation of translations. - */ - function testExportTranslation() { - // First import some known translations. - // This will also automatically enable the 'fr' language. - $name = tempnam('temporary://', "po_") . '.po'; - file_put_contents($name, $this->getPoFile()); - $this->drupalPost('admin/config/regional/translate/import', array( - 'langcode' => 'fr', - 'files[file]' => $name, - ), t('Import')); - drupal_unlink($name); - - // Get the French translations. - $this->drupalPost('admin/config/regional/translate/export', array( - 'langcode' => 'fr', - ), t('Export')); - - // Ensure we have a translation file. - $this->assertRaw('# French translation of Drupal', t('Exported French translation file.')); - // Ensure our imported translations exist in the file. - $this->assertRaw('msgstr "lundi"', t('French translations present in exported file.')); - - // Import some more French translations which will be marked as customized. - $name = tempnam('temporary://', "po2_") . '.po'; - file_put_contents($name, $this->getCustomPoFile()); - $this->drupalPost('admin/config/regional/translate/import', array( - 'langcode' => 'fr', - 'files[file]' => $name, - 'customized' => 1, - ), t('Import')); - drupal_unlink($name); - - // We can't import a string with an empty translation, but calling - // locale() for an new string creates an entry in the locales_source table. - locale('February', NULL, 'fr'); - - // Export only customized French translations. - $this->drupalPost('admin/config/regional/translate/export', array( - 'langcode' => 'fr', - 'content_options[not_customized]' => FALSE, - 'content_options[customized]' => TRUE, - 'content_options[not_translated]' => FALSE, - ), t('Export')); - - // Ensure we have a translation file. - $this->assertRaw('# French translation of Drupal', t('Exported French translation file with only customized strings.')); - // Ensure the customized translations exist in the file. - $this->assertRaw('msgstr "janvier"', t('French custom translation present in exported file.')); - // Ensure no untranslated strings exist in the file. - $this->assertNoRaw('msgid "February"', t('Untranslated string not present in exported file.')); - - // Export only untranslated French translations. - $this->drupalPost('admin/config/regional/translate/export', array( - 'langcode' => 'fr', - 'content_options[not_customized]' => FALSE, - 'content_options[customized]' => FALSE, - 'content_options[not_translated]' => TRUE, - ), t('Export')); - - // Ensure we have a translation file. - $this->assertRaw('# French translation of Drupal', t('Exported French translation file with only untranslated strings.')); - // Ensure no customized translations exist in the file. - $this->assertNoRaw('msgstr "janvier"', t('French custom translation not present in exported file.')); - // Ensure the untranslated strings exist in the file. - $this->assertRaw('msgid "February"', t('Untranslated string present in exported file.')); - } - - /** - * Test exportation of translation template file. - */ - function testExportTranslationTemplateFile() { - // Get the translation template file. - $this->drupalPost('admin/config/regional/translate/export', array(), t('Export')); - // Ensure we have a translation file. - $this->assertRaw('# LANGUAGE translation of PROJECT', t('Exported translation template file.')); - } - - /** - * Helper function that returns a proper .po file. - */ - function getPoFile() { - return <<< EOF -msgid "" -msgstr "" -"Project-Id-Version: Drupal 8\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\\n" - -msgid "Monday" -msgstr "lundi" -EOF; - } - - /** - * Helper function that returns a .po file which strings will be marked - * as customized. - */ - function getCustomPoFile() { - return <<< EOF -msgid "" -msgstr "" -"Project-Id-Version: Drupal 8\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=UTF-8\\n" -"Content-Transfer-Encoding: 8bit\\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\\n" - -msgid "January" -msgstr "janvier" -EOF; - } - -} - -/** - * Tests for the st() function. - */ -class LocaleInstallTest extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'String translation using st()', - 'description' => 'Tests that st() works like t().', - 'group' => 'Locale', - ); - } - - function setUp() { - parent::setUp('locale'); - - // st() lives in install.inc, so ensure that it is loaded for all tests. - require_once DRUPAL_ROOT . '/core/includes/install.inc'; - } - - /** - * Verify that function signatures of t() and st() are equal. - */ - function testFunctionSignatures() { - $reflector_t = new ReflectionFunction('t'); - $reflector_st = new ReflectionFunction('st'); - $this->assertEqual($reflector_t->getParameters(), $reflector_st->getParameters(), t('Function signatures of t() and st() are equal.')); - } -} - -/** - * Locale uninstall with English UI functional test. - */ -class LocaleUninstallFunctionalTest extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Locale uninstall (EN)', - 'description' => 'Tests the uninstall process using the built-in UI language.', - 'group' => 'Locale', - ); - } - - /** - * The default language set for the UI before uninstall. - */ - protected $language; - - function setUp() { - parent::setUp(array('node', 'locale')); - $this->langcode = 'en'; - - // Create Article node type. - $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); - } - - /** - * Check if the values of the Locale variables are correct after uninstall. - */ - function testUninstallProcess() { - $locale_module = array('locale', 'language'); - - $language = (object) array( - 'langcode' => 'fr', - 'name' => 'French', - 'default' => $this->langcode == 'fr', - ); - language_save($language); - - // Check the UI language. - drupal_language_initialize(); - $this->assertEqual(drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode, $this->langcode, t('Current language: %lang', array('%lang' => drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode))); - - // Enable multilingual workflow option for articles. - variable_set('node_type_language_article', 1); - - // Change JavaScript translations directory. - variable_set('locale_js_directory', 'js_translations'); - - // Build the JavaScript translation file for French. - $user = $this->drupalCreateUser(array('translate interface', 'access administration pages')); - $this->drupalLogin($user); - $this->drupalGet('admin/config/regional/translate/translate'); - $string = db_query('SELECT min(lid) AS lid FROM {locales_source} WHERE location LIKE :location', array( - ':location' => '%.js%', - ))->fetchObject(); - $edit = array('translations[fr][0]' => 'french translation'); - $this->drupalPost('admin/config/regional/translate/edit/' . $string->lid, $edit, t('Save translations')); - _locale_rebuild_js('fr'); - $locale_javascripts = variable_get('locale_translation_javascript', array()); - $js_file = 'public://' . variable_get('locale_js_directory', 'languages') . '/fr_' . $locale_javascripts['fr'] . '.js'; - $this->assertTrue($result = file_exists($js_file), t('JavaScript file created: %file', array('%file' => $result ? $js_file : t('none')))); - - // Disable string caching. - variable_set('locale_cache_strings', 0); - - // Change language negotiation options. - drupal_load('module', 'locale'); - variable_set('language_types', language_types_get_default() + array('language_custom' => TRUE)); - variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, language_language_negotiation_info()); - variable_set('language_negotiation_' . LANGUAGE_TYPE_CONTENT, language_language_negotiation_info()); - variable_set('language_negotiation_' . LANGUAGE_TYPE_URL, language_language_negotiation_info()); - - // Change language negotiation settings. - variable_set('language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX); - variable_set('language_negotiation_session_param', TRUE); - - // Uninstall Locale. - module_disable($locale_module); - drupal_uninstall_modules($locale_module); - - // Visit the front page. - $this->drupalGet(''); - - // Check the init language logic. - drupal_language_initialize(); - $this->assertEqual(drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode, 'en', t('Language after uninstall: %lang', array('%lang' => drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode))); - - // Check JavaScript files deletion. - $this->assertTrue($result = !file_exists($js_file), t('JavaScript file deleted: %file', array('%file' => $result ? $js_file : t('found')))); - - // Check language count. - $language_count = variable_get('language_count', 1); - $this->assertEqual($language_count, 1, t('Language count: %count', array('%count' => $language_count))); - - // Check language negotiation. - require_once DRUPAL_ROOT . '/core/includes/language.inc'; - $this->assertTrue(count(language_types_get_all()) == count(language_types_get_default()), t('Language types reset')); - $language_negotiation = language_negotiation_method_get_first(LANGUAGE_TYPE_INTERFACE) == LANGUAGE_NEGOTIATION_DEFAULT; - $this->assertTrue($language_negotiation, t('Interface language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); - $language_negotiation = language_negotiation_method_get_first(LANGUAGE_TYPE_CONTENT) == LANGUAGE_NEGOTIATION_DEFAULT; - $this->assertTrue($language_negotiation, t('Content language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); - $language_negotiation = language_negotiation_method_get_first(LANGUAGE_TYPE_URL) == LANGUAGE_NEGOTIATION_DEFAULT; - $this->assertTrue($language_negotiation, t('URL language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); - - // Check language negotiation method settings. - $this->assertFalse(variable_get('language_negotiation_url_part', FALSE), t('URL language negotiation method indicator settings cleared.')); - $this->assertFalse(variable_get('language_negotiation_session_param', FALSE), t('Visit language negotiation method settings cleared.')); - - // Check JavaScript parsed. - $javascript_parsed_count = count(variable_get('javascript_parsed', array())); - $this->assertEqual($javascript_parsed_count, 0, t('JavaScript parsed count: %count', array('%count' => $javascript_parsed_count))); - - // Check JavaScript translations directory. - $locale_js_directory = variable_get('locale_js_directory', 'languages'); - $this->assertEqual($locale_js_directory, 'languages', t('JavaScript translations directory: %dir', array('%dir' => $locale_js_directory))); - - // Check string caching. - $locale_cache_strings = variable_get('locale_cache_strings', 1); - $this->assertEqual($locale_cache_strings, 1, t('String caching: %status', array('%status' => t($locale_cache_strings ? 'enabled': 'disabled')))); - } -} - -/** - * Locale uninstall with French UI functional test. - * - * Because this class extends LocaleUninstallFunctionalTest, it doesn't require a new - * test of its own. Rather, it switches the default UI language in setUp and then - * runs the testUninstallProcess (which it inherits from LocaleUninstallFunctionalTest) - * to test with this new language. - */ -class LocaleUninstallFrenchFunctionalTest extends LocaleUninstallFunctionalTest { - public static function getInfo() { - return array( - 'name' => 'Locale uninstall (FR)', - 'description' => 'Tests the uninstall process using French as interface language.', - 'group' => 'Locale', - ); - } - - function setUp() { - parent::setUp(); - $this->langcode = 'fr'; - } -} - -/** - * Functional tests for configuring a different path alias per language. - */ -class LocalePathFunctionalTest extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Path language settings', - 'description' => 'Checks you can configure a language for individual url aliases.', - 'group' => 'Locale', - ); - } - - function setUp() { - parent::setUp(array('node', 'locale', 'path')); - - $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - variable_set('site_frontpage', 'node'); - } - - /** - * Test if a language can be associated with a path alias. - */ - function testPathLanguageConfiguration() { - global $base_url; - - // User to add and remove language. - $admin_user = $this->drupalCreateUser(array('administer languages', 'create page content', 'administer url aliases', 'create url aliases', 'access administration pages')); - - // Add custom language. - $this->drupalLogin($admin_user); - // Code for the language. - $langcode = 'xx'; - // The English name for the language. - $name = $this->randomName(16); - // The domain prefix. - $prefix = $langcode; - $edit = array( - 'predefined_langcode' => 'custom', - 'langcode' => $langcode, - 'name' => $name, - 'direction' => '0', - ); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); - - // Set path prefix. - $edit = array( "prefix[$langcode]" => $prefix ); - $this->drupalPost('admin/config/regional/language/detection/url', $edit, t('Save configuration')); - - // Check that the "xx" front page is readily available because path prefix - // negotiation is pre-configured. - $this->drupalGet($prefix); - $this->assertText(t('Welcome to Drupal'), t('The "xx" front page is readibly available.')); - - // Create a node. - $node = $this->drupalCreateNode(array('type' => 'page')); - - // Create a path alias in default language (English). - $path = 'admin/config/search/path/add'; - $english_path = $this->randomName(8); - $edit = array( - 'source' => 'node/' . $node->nid, - 'alias' => $english_path, - 'langcode' => 'en', - ); - $this->drupalPost($path, $edit, t('Save')); - - // Create a path alias in new custom language. - $custom_language_path = $this->randomName(8); - $edit = array( - 'source' => 'node/' . $node->nid, - 'alias' => $custom_language_path, - 'langcode' => $langcode, - ); - $this->drupalPost($path, $edit, t('Save')); - - // Confirm English language path alias works. - $this->drupalGet($english_path); - $this->assertText($node->title, t('English alias works.')); - - // Confirm custom language path alias works. - $this->drupalGet($prefix . '/' . $custom_language_path); - $this->assertText($node->title, t('Custom language alias works.')); - - // Create a custom path. - $custom_path = $this->randomName(8); - - // Check priority of language for alias by source path. - $edit = array( - 'source' => 'node/' . $node->nid, - 'alias' => $custom_path, - 'langcode' => LANGUAGE_NOT_SPECIFIED, - ); - path_save($edit); - $lookup_path = drupal_lookup_path('alias', 'node/' . $node->nid, 'en'); - $this->assertEqual($english_path, $lookup_path, t('English language alias has priority.')); - // Same check for language 'xx'. - $lookup_path = drupal_lookup_path('alias', 'node/' . $node->nid, $prefix); - $this->assertEqual($custom_language_path, $lookup_path, t('Custom language alias has priority.')); - path_delete($edit); - - // Create language nodes to check priority of aliases. - $first_node = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1)); - $second_node = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1)); - - // Assign a custom path alias to the first node with the English language. - $edit = array( - 'source' => 'node/' . $first_node->nid, - 'alias' => $custom_path, - 'langcode' => 'en', - ); - path_save($edit); - - // Assign a custom path alias to second node with LANGUAGE_NOT_SPECIFIED. - $edit = array( - 'source' => 'node/' . $second_node->nid, - 'alias' => $custom_path, - 'langcode' => LANGUAGE_NOT_SPECIFIED, - ); - path_save($edit); - - // Test that both node titles link to our path alias. - $this->drupalGet(''); - $custom_path_url = base_path() . $GLOBALS['script_path'] . $custom_path; - $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $first_node->title)); - $this->assertTrue(!empty($elements), t('First node links to the path alias.')); - $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $second_node->title)); - $this->assertTrue(!empty($elements), t('Second node links to the path alias.')); - - // Confirm that the custom path leads to the first node. - $this->drupalGet($custom_path); - $this->assertText($first_node->title, t('Custom alias returns first node.')); - - // Confirm that the custom path with prefix leads to the second node. - $this->drupalGet($prefix . '/' . $custom_path); - $this->assertText($second_node->title, t('Custom alias with prefix returns second node.')); - - } -} - -/** - * Functional tests for multilingual support on nodes. - */ -class LocaleContentFunctionalTest extends WebTestBase { - protected $profile = 'standard'; - - public static function getInfo() { - return array( - 'name' => 'Content language settings', - 'description' => 'Checks you can enable multilingual support on content types and configure a language for a node.', - 'group' => 'Locale', - ); - } - - function setUp() { - parent::setUp('locale'); - } - - /** - * Verifies that machine name fields are always LTR. - */ - function testMachineNameLTR() { - // User to add and remove language. - $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages')); - - // Log in as admin. - $this->drupalLogin($admin_user); - - // Verify that the machine name field is LTR for a new content type. - $this->drupalGet('admin/structure/types/add'); - $this->assertFieldByXpath('//input[@name="type" and @dir="ltr"]', NULL, 'The machine name field is LTR when no additional language is configured.'); - - // Install the Arabic language (which is RTL) and configure as the default. - $edit = array(); - $edit['predefined_langcode'] = 'ar'; - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - - $edit = array(); - $edit['site_default'] = 'ar'; - $this->drupalPost(NULL, $edit, t('Save configuration')); - - // Verify that the machine name field is still LTR for a new content type. - $this->drupalGet('admin/structure/types/add'); - $this->assertFieldByXpath('//input[@name="type" and @dir="ltr"]', NULL, 'The machine name field is LTR when the default language is RTL.'); - } - - /** - * Test if a content type can be set to multilingual and language is present. - */ - function testContentTypeLanguageConfiguration() { - global $base_url; - - // User to add and remove language. - $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages')); - // User to create a node. - $web_user = $this->drupalCreateUser(array('create article content', 'create page content', 'edit any page content')); - - // Add custom language. - $this->drupalLogin($admin_user); - // Code for the language. - $langcode = 'xx'; - // The English name for the language. - $name = $this->randomName(16); - $edit = array( - 'predefined_langcode' => 'custom', - 'langcode' => $langcode, - 'name' => $name, - 'direction' => '0', - ); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); - - // Set "Basic page" content type to use multilingual support. - $this->drupalGet('admin/structure/types/manage/page'); - $this->assertText(t('Multilingual support'), t('Multilingual support fieldset present on content type configuration form.')); - $edit = array( - 'node_type_language' => 1, - ); - $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); - $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), t('Basic page content type has been updated.')); - $this->drupalLogout(); - - // Verify language selection is not present on add article form. - $this->drupalLogin($web_user); - $this->drupalGet('node/add/article'); - // Verify language select list is not present. - $this->assertNoFieldByName('language', NULL, t('Language select not present on add article form.')); - - // Verify language selection appears on add "Basic page" form. - $this->drupalGet('node/add/page'); - // Verify language select list is present. - $this->assertFieldByName('langcode', NULL, t('Language select present on add Basic page form.')); - // Ensure language appears. - $this->assertText($name, t('Language present.')); - - // Create "Basic page" content. - $node_title = $this->randomName(); - $node_body = $this->randomName(); - $edit = array( - 'type' => 'page', - 'title' => $node_title, - 'body' => array($langcode => array(array('value' => $node_body))), - 'langcode' => $langcode, - ); - $node = $this->drupalCreateNode($edit); - // Edit the content and ensure correct language is selected. - $path = 'node/' . $node->nid . '/edit'; - $this->drupalGet($path); - $this->assertRaw('', t('Correct language selected.')); - // Ensure we can change the node language. - $edit = array( - 'langcode' => 'en', - ); - $this->drupalPost($path, $edit, t('Save')); - $this->assertRaw(t('%title has been updated.', array('%title' => $node_title)), t('Basic page content updated.')); - - $this->drupalLogout(); - } - - /** - * Test if a dir and lang tags exist in node's attributes. - */ - function testContentTypeDirLang() { - // User to add and remove language. - $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages')); - // User to create a node. - $web_user = $this->drupalCreateUser(array('create article content', 'edit own article content')); - - // Login as admin. - $this->drupalLogin($admin_user); - - // Install Arabic language. - $edit = array(); - $edit['predefined_langcode'] = 'ar'; - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - - // Install Spanish language. - $edit = array(); - $edit['predefined_langcode'] = 'es'; - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - - // Set "Article" content type to use multilingual support. - $this->drupalGet('admin/structure/types/manage/article'); - $edit = array( - 'node_type_language' => 1, - ); - $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type')); - $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Article')), t('Article content type has been updated.')); - $this->drupalLogout(); - - // Login as web user to add new article. - $this->drupalLogin($web_user); - - // Create three nodes: English, Arabic and Spanish. - $node_en = $this->createNodeArticle('en'); - $node_ar = $this->createNodeArticle('ar'); - $node_es = $this->createNodeArticle('es'); - - $this->drupalGet('node'); - - // Check if English node does not have lang tag. - $pattern = '|id="node-' . $node_en->nid . '"[^<>]*lang="en"|'; - $this->assertNoPattern($pattern, t('The lang tag has not been assigned to the English node.')); - - // Check if English node does not have dir tag. - $pattern = '|id="node-' . $node_en->nid . '"[^<>]*dir="ltr"|'; - $this->assertNoPattern($pattern, t('The dir tag has not been assigned to the English node.')); - - // Check if Arabic node has lang="ar" & dir="rtl" tags. - $pattern = '|id="node-' . $node_ar->nid . '"[^<>]*lang="ar" dir="rtl"|'; - $this->assertPattern($pattern, t('The lang and dir tags have been assigned correctly to the Arabic node.')); - - // Check if Spanish node has lang="es" tag. - $pattern = '|id="node-' . $node_es->nid . '"[^<>]*lang="es"|'; - $this->assertPattern($pattern, t('The lang tag has been assigned correctly to the Spanish node.')); - - // Check if Spanish node does not have dir="ltr" tag. - $pattern = '|id="node-' . $node_es->nid . '"[^<>]*lang="es" dir="ltr"|'; - $this->assertNoPattern($pattern, t('The dir tag has not been assigned to the Spanish node.')); - - $this->drupalLogout(); - } - - /** - * Create node in a specific language. - */ - protected function createNodeArticle($langcode) { - $this->drupalGet('node/add/article'); - $node_title = $this->randomName(); - $node_body = $this->randomName(); - $edit = array( - 'type' => 'article', - 'title' => $node_title, - 'body' => array($langcode => array(array('value' => $node_body))), - 'langcode' => $langcode, - 'promote' => 1, - ); - return $this->drupalCreateNode($edit); - } -} - -/** - * Functional test for multilingual fields. - */ -class LocaleMultilingualFieldsFunctionalTest extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Multilingual fields', - 'description' => 'Test multilingual support for fields.', - 'group' => 'Locale', - ); - } - - function setUp() { - parent::setUp(array('node', 'locale')); - - // Create Basic page node type. - $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - - // Setup users. - $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages', 'create page content', 'edit own page content')); - $this->drupalLogin($admin_user); - - // Add a new language. - $language = (object) array( - 'langcode' => 'it', - 'name' => 'Italian', - ); - language_save($language); - - // Enable URL language detection and selection. - $edit = array('language_interface[enabled][language-url]' => '1'); - $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); - - // Set "Basic page" content type to use multilingual support. - $edit = array( - 'node_type_language' => 1, - ); - $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); - $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), t('Basic page content type has been updated.')); - - // Make node body translatable. - $field = field_info_field('body'); - $field['translatable'] = TRUE; - field_update_field($field); - } - - /** - * Test if field languages are correctly set through the node form. - */ - function testMultilingualNodeForm() { - // Create "Basic page" content. - $langcode = LANGUAGE_NOT_SPECIFIED; - $title_key = "title"; - $title_value = $this->randomName(8); - $body_key = "body[$langcode][0][value]"; - $body_value = $this->randomName(16); - - // Create node to edit. - $edit = array(); - $edit[$title_key] = $title_value; - $edit[$body_key] = $body_value; - $edit['langcode'] = 'en'; - $this->drupalPost('node/add/page', $edit, t('Save')); - - // Check that the node exists in the database. - $node = $this->drupalGetNodeByTitle($edit[$title_key]); - $this->assertTrue($node, t('Node found in database.')); - - $assert = isset($node->body['en']) && !isset($node->body[LANGUAGE_NOT_SPECIFIED]) && $node->body['en'][0]['value'] == $body_value; - $this->assertTrue($assert, t('Field language correctly set.')); - - // Change node language. - $this->drupalGet("node/$node->nid/edit"); - $edit = array( - $title_key => $this->randomName(8), - 'langcode' => 'it' - ); - $this->drupalPost(NULL, $edit, t('Save')); - $node = $this->drupalGetNodeByTitle($edit[$title_key]); - $this->assertTrue($node, t('Node found in database.')); - - $assert = isset($node->body['it']) && !isset($node->body['en']) && $node->body['it'][0]['value'] == $body_value; - $this->assertTrue($assert, t('Field language correctly changed.')); - - // Enable content language URL detection. - language_negotiation_set(LANGUAGE_TYPE_CONTENT, array(LANGUAGE_NEGOTIATION_URL => 0)); - - // Test multilingual field language fallback logic. - $this->drupalGet("it/node/$node->nid"); - $this->assertRaw($body_value, t('Body correctly displayed using Italian as requested language')); - - $this->drupalGet("node/$node->nid"); - $this->assertRaw($body_value, t('Body correctly displayed using English as requested language')); - } - - /* - * Test multilingual field display settings. - */ - function testMultilingualDisplaySettings() { - // Create "Basic page" content. - $langcode = LANGUAGE_NOT_SPECIFIED; - $title_key = "title"; - $title_value = $this->randomName(8); - $body_key = "body[$langcode][0][value]"; - $body_value = $this->randomName(16); - - // Create node to edit. - $edit = array(); - $edit[$title_key] = $title_value; - $edit[$body_key] = $body_value; - $edit['langcode'] = 'en'; - $this->drupalPost('node/add/page', $edit, t('Save')); - - // Check that the node exists in the database. - $node = $this->drupalGetNodeByTitle($edit[$title_key]); - $this->assertTrue($node, t('Node found in database.')); - - // Check if node body is showed. - $this->drupalGet("node/$node->nid"); - $body = $this->xpath('//article[@id=:id]//div[@class=:class]/descendant::p', array( - ':id' => 'node-' . $node->nid, - ':class' => 'content', - )); - $this->assertEqual(current($body), $node->body['en'][0]['value'], 'Node body found.'); - } -} - -/** - * Functional tests for comment language. - */ -class LocaleCommentLanguageFunctionalTest extends WebTestBase { - protected $profile = 'standard'; - - public static function getInfo() { - return array( - 'name' => 'Comment language', - 'description' => 'Tests for comment language.', - 'group' => 'Locale', - ); - } - - function setUp() { - // We also use language_test module here to be able to turn on content - // language negotiation. Drupal core does not provide a way in itself - // to do that. - parent::setUp('locale', 'language_test'); - - // Create and login user. - $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'administer content types', 'create article content')); - $this->drupalLogin($admin_user); - - // Add language. - $edit = array('predefined_langcode' => 'fr'); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - - // Set "Article" content type to use multilingual support. - $edit = array('node_type_language' => 1); - $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type')); - - // Enable content language negotiation UI. - variable_set('language_test_content_language_type', TRUE); - - // Set interface language detection to user and content language detection - // to URL. Disable inheritance from interface language to ensure content - // language will fall back to the default language if no URL language can be - // detected. - $edit = array( - 'language_interface[enabled][language-user]' => TRUE, - 'language_content[enabled][language-url]' => TRUE, - 'language_content[enabled][language-interface]' => FALSE, - ); - $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); - - // Change user language preference, this way interface language is always - // French no matter what path prefix the URLs have. - $edit = array('preferred_langcode' => 'fr'); - $this->drupalPost("user/{$admin_user->uid}/edit", $edit, t('Save')); - } - - /** - * Test that comment language is properly set. - */ - function testCommentLanguage() { - drupal_static_reset('language_list'); - - // Create two nodes, one for english and one for french, and comment each - // node using both english and french as content language by changing URL - // language prefixes. Meanwhile interface language is always French, which - // is the user language preference. This way we can ensure that node - // language and interface language do not influence comment language, as - // only content language has to. - foreach (language_list() as $node_langcode => $node_language) { - $langcode_not_specified = LANGUAGE_NOT_SPECIFIED; - - // Create "Article" content. - $title = $this->randomName(); - $edit = array( - "title" => $title, - "body[$langcode_not_specified][0][value]" => $this->randomName(), - "langcode" => $node_langcode, - ); - $this->drupalPost("node/add/article", $edit, t('Save')); - $node = $this->drupalGetNodeByTitle($title); - - $prefixes = language_negotiation_url_prefixes(); - foreach (language_list() as $langcode => $language) { - // Post a comment with content language $langcode. - $prefix = empty($prefixes[$langcode]) ? '' : $prefixes[$langcode] . '/'; - $edit = array("comment_body[$langcode_not_specified][0][value]" => $this->randomName()); - $this->drupalPost("{$prefix}node/{$node->nid}", $edit, t('Save')); - - // Check that comment language matches the current content language. - $comment = db_select('comment', 'c') - ->fields('c') - ->condition('nid', $node->nid) - ->orderBy('cid', 'DESC') - ->execute() - ->fetchObject(); - $args = array('%node_language' => $node_langcode, '%comment_language' => $comment->langcode, '%langcode' => $langcode); - $this->assertEqual($comment->langcode, $langcode, t('The comment posted with content language %langcode and belonging to the node with language %node_language has language %comment_language', $args)); - } - } - } -} - -/** - * Functional tests for localizing date formats. - */ -class LocaleDateFormatsFunctionalTest extends WebTestBase { - - public static function getInfo() { - return array( - 'name' => 'Localize date formats', - 'description' => 'Tests for the localization of date formats.', - 'group' => 'Locale', - ); - } - - function setUp() { - parent::setUp(array('node', 'locale')); - - // Create Article node type. - $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); - - // Create and login user. - $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'create article content')); - $this->drupalLogin($admin_user); - } - - /** - * Functional tests for localizing date formats. - */ - function testLocalizeDateFormats() { - // Add language. - $edit = array( - 'predefined_langcode' => 'fr', - ); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - - // Set language negotiation. - $language_type = LANGUAGE_TYPE_INTERFACE; - $edit = array( - "{$language_type}[enabled][language-url]" => TRUE, - ); - $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); - - // Configure date formats. - $this->drupalGet('admin/config/regional/date-time/locale'); - $this->assertText('French', 'Configured languages appear.'); - $edit = array( - 'date_format_long' => 'd.m.Y - H:i', - 'date_format_medium' => 'd.m.Y - H:i', - 'date_format_short' => 'd.m.Y - H:i', - ); - $this->drupalPost('admin/config/regional/date-time/locale/fr/edit', $edit, t('Save configuration')); - $this->assertText(t('Configuration saved.'), 'French date formats updated.'); - $edit = array( - 'date_format_long' => 'j M Y - g:ia', - 'date_format_medium' => 'j M Y - g:ia', - 'date_format_short' => 'j M Y - g:ia', - ); - $this->drupalPost('admin/config/regional/date-time/locale/en/edit', $edit, t('Save configuration')); - $this->assertText(t('Configuration saved.'), 'English date formats updated.'); - - // Create node content. - $node = $this->drupalCreateNode(array('type' => 'article')); - - // Configure format for the node posted date changes with the language. - $this->drupalGet('node/' . $node->nid); - $english_date = format_date($node->created, 'custom', 'j M Y'); - $this->assertText($english_date, t('English date format appears')); - $this->drupalGet('fr/node/' . $node->nid); - $french_date = format_date($node->created, 'custom', 'd.m.Y'); - $this->assertText($french_date, t('French date format appears')); - } -}