Chris@0: timestampOld = REQUEST_TIME - 300; Chris@0: $this->timestampMedium = REQUEST_TIME - 200; Chris@0: $this->timestampNew = REQUEST_TIME - 100; Chris@0: $this->timestampNow = REQUEST_TIME; Chris@0: Chris@0: // Enable import of translations. By default this is disabled for automated Chris@0: // tests. Chris@0: $this->config('locale.settings') Chris@0: ->set('translation.import_enabled', TRUE) Chris@16: ->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_LOCAL) Chris@0: ->save(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the value of the default translations directory. Chris@0: * Chris@0: * @param string $path Chris@0: * Path of the translations directory relative to the drupal installation Chris@0: * directory. Chris@0: */ Chris@0: protected function setTranslationsDirectory($path) { Chris@18: \Drupal::service('file_system')->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY); Chris@0: $this->config('locale.settings')->set('translation.path', $path)->save(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds a language. Chris@0: * Chris@0: * @param string $langcode Chris@0: * The language code of the language to add. Chris@0: */ Chris@0: protected function addLanguage($langcode) { Chris@0: $edit = ['predefined_langcode' => $langcode]; Chris@0: $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language')); Chris@0: $this->container->get('language_manager')->reset(); Chris@17: $this->assertTrue(\Drupal::languageManager()->getLanguage($langcode), new FormattableMarkup('Language %langcode added.', ['%langcode' => $langcode])); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a translation file and tests its timestamp. Chris@0: * Chris@0: * @param string $path Chris@0: * Path of the file relative to the public file path. Chris@0: * @param string $filename Chris@0: * Name of the file to create. Chris@0: * @param int $timestamp Chris@0: * (optional) Timestamp to set the file to. Defaults to current time. Chris@0: * @param array $translations Chris@0: * (optional) Array of source/target value translation strings. Only Chris@0: * singular strings are supported, no plurals. No double quotes are allowed Chris@0: * in source and translations strings. Chris@0: */ Chris@0: protected function makePoFile($path, $filename, $timestamp = NULL, array $translations = []) { Chris@0: $timestamp = $timestamp ? $timestamp : REQUEST_TIME; Chris@0: $path = 'public://' . $path; Chris@0: $text = ''; Chris@0: $po_header = << 1);\\n" Chris@0: Chris@0: EOF; Chris@0: Chris@0: // Convert array of translations to Gettext source and translation strings. Chris@0: if ($translations) { Chris@0: foreach ($translations as $source => $target) { Chris@0: $text .= 'msgid "' . $source . '"' . "\n"; Chris@0: $text .= 'msgstr "' . $target . '"' . "\n"; Chris@0: } Chris@0: } Chris@0: Chris@18: \Drupal::service('file_system')->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY); Chris@0: $file = File::create([ Chris@0: 'uid' => 1, Chris@0: 'filename' => $filename, Chris@0: 'uri' => $path . '/' . $filename, Chris@0: 'filemime' => 'text/x-gettext-translation', Chris@0: 'timestamp' => $timestamp, Chris@0: 'status' => FILE_STATUS_PERMANENT, Chris@0: ]); Chris@0: file_put_contents($file->getFileUri(), $po_header . $text); Chris@14: touch(\Drupal::service('file_system')->realpath($file->getFileUri()), $timestamp); Chris@0: $file->save(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Setup the environment containing local and remote translation files. Chris@0: * Chris@0: * Update tests require a simulated environment for local and remote files. Chris@0: * Normally remote files are located at a remote server (e.g. ftp.drupal.org). Chris@0: * For testing we can not rely on this. A directory in the file system of the Chris@0: * test site is designated for remote files and is addressed using an absolute Chris@0: * URL. Because Drupal does not allow files with a po extension to be accessed Chris@0: * (denied in .htaccess) the translation files get a _po extension. Another Chris@0: * directory is designated for local translation files. Chris@0: * Chris@0: * The environment is set up with the following files. File creation times are Chris@0: * set to create different variations in test conditions. Chris@0: * contrib_module_one Chris@0: * - remote file: timestamp new Chris@0: * - local file: timestamp old Chris@0: * contrib_module_two Chris@0: * - remote file: timestamp old Chris@0: * - local file: timestamp new Chris@0: * contrib_module_three Chris@0: * - remote file: timestamp old Chris@0: * - local file: timestamp old Chris@0: * custom_module_one Chris@0: * - local file: timestamp new Chris@0: * Time stamp of current translation set by setCurrentTranslations() is always Chris@0: * timestamp medium. This makes it easy to predict which translation will be Chris@0: * imported. Chris@0: */ Chris@0: protected function setTranslationFiles() { Chris@0: $config = $this->config('locale.settings'); Chris@0: Chris@0: // A flag is set to let the locale_test module replace the project data with Chris@0: // a set of test projects which match the below project files. Chris@0: \Drupal::state()->set('locale.test_projects_alter', TRUE); Chris@0: \Drupal::state()->set('locale.remove_core_project', FALSE); Chris@0: Chris@0: // Setup the environment. Chris@0: $public_path = PublicStream::basePath(); Chris@0: $this->setTranslationsDirectory($public_path . '/local'); Chris@0: $config->set('translation.default_filename', '%project-%version.%language._po')->save(); Chris@0: Chris@0: // Setting up sets of translations for the translation files. Chris@0: $translations_one = ['January' => 'Januar_1', 'February' => 'Februar_1', 'March' => 'Marz_1']; Chris@0: $translations_two = ['February' => 'Februar_2', 'March' => 'Marz_2', 'April' => 'April_2']; Chris@0: $translations_three = ['April' => 'April_3', 'May' => 'Mai_3', 'June' => 'Juni_3']; Chris@0: Chris@0: // Add a number of files to the local file system to serve as remote Chris@0: // translation server and match the project definitions set in Chris@0: // locale_test_locale_translation_projects_alter(). Chris@0: $this->makePoFile('remote/8.x/contrib_module_one', 'contrib_module_one-8.x-1.1.de._po', $this->timestampNew, $translations_one); Chris@0: $this->makePoFile('remote/8.x/contrib_module_two', 'contrib_module_two-8.x-2.0-beta4.de._po', $this->timestampOld, $translations_two); Chris@0: $this->makePoFile('remote/8.x/contrib_module_three', 'contrib_module_three-8.x-1.0.de._po', $this->timestampOld, $translations_three); Chris@0: Chris@0: // Add a number of files to the local file system to serve as local Chris@0: // translation files and match the project definitions set in Chris@0: // locale_test_locale_translation_projects_alter(). Chris@0: $this->makePoFile('local', 'contrib_module_one-8.x-1.1.de._po', $this->timestampOld, $translations_one); Chris@0: $this->makePoFile('local', 'contrib_module_two-8.x-2.0-beta4.de._po', $this->timestampNew, $translations_two); Chris@0: $this->makePoFile('local', 'contrib_module_three-8.x-1.0.de._po', $this->timestampOld, $translations_three); Chris@0: $this->makePoFile('local', 'custom_module_one.de.po', $this->timestampNew); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Setup existing translations in the database and set up the status of Chris@0: * existing translations. Chris@0: */ Chris@0: protected function setCurrentTranslations() { Chris@0: // Add non customized translations to the database. Chris@0: $langcode = 'de'; Chris@0: $context = ''; Chris@0: $non_customized_translations = [ Chris@0: 'March' => 'Marz', Chris@0: 'June' => 'Juni', Chris@0: ]; Chris@0: foreach ($non_customized_translations as $source => $translation) { Chris@0: $string = $this->container->get('locale.storage')->createString([ Chris@0: 'source' => $source, Chris@0: 'context' => $context, Chris@0: ]) Chris@0: ->save(); Chris@0: $this->container->get('locale.storage')->createTranslation([ Chris@0: 'lid' => $string->getId(), Chris@0: 'language' => $langcode, Chris@0: 'translation' => $translation, Chris@0: 'customized' => LOCALE_NOT_CUSTOMIZED, Chris@0: ])->save(); Chris@0: } Chris@0: Chris@0: // Add customized translations to the database. Chris@0: $customized_translations = [ Chris@0: 'January' => 'Januar_customized', Chris@0: 'February' => 'Februar_customized', Chris@0: 'May' => 'Mai_customized', Chris@0: ]; Chris@0: foreach ($customized_translations as $source => $translation) { Chris@0: $string = $this->container->get('locale.storage')->createString([ Chris@0: 'source' => $source, Chris@0: 'context' => $context, Chris@0: ]) Chris@0: ->save(); Chris@0: $this->container->get('locale.storage')->createTranslation([ Chris@0: 'lid' => $string->getId(), Chris@0: 'language' => $langcode, Chris@0: 'translation' => $translation, Chris@0: 'customized' => LOCALE_CUSTOMIZED, Chris@0: ])->save(); Chris@0: } Chris@0: Chris@0: // Add a state of current translations in locale_files. Chris@0: $default = [ Chris@0: 'langcode' => $langcode, Chris@0: 'uri' => '', Chris@0: 'timestamp' => $this->timestampMedium, Chris@0: 'last_checked' => $this->timestampMedium, Chris@0: ]; Chris@0: $data[] = [ Chris@0: 'project' => 'contrib_module_one', Chris@0: 'filename' => 'contrib_module_one-8.x-1.1.de._po', Chris@0: 'version' => '8.x-1.1', Chris@0: ]; Chris@0: $data[] = [ Chris@0: 'project' => 'contrib_module_two', Chris@0: 'filename' => 'contrib_module_two-8.x-2.0-beta4.de._po', Chris@0: 'version' => '8.x-2.0-beta4', Chris@0: ]; Chris@0: $data[] = [ Chris@0: 'project' => 'contrib_module_three', Chris@0: 'filename' => 'contrib_module_three-8.x-1.0.de._po', Chris@0: 'version' => '8.x-1.0', Chris@0: ]; Chris@0: $data[] = [ Chris@0: 'project' => 'custom_module_one', Chris@0: 'filename' => 'custom_module_one.de.po', Chris@0: 'version' => '', Chris@0: ]; Chris@18: $connection = Database::getConnection(); Chris@0: foreach ($data as $file) { Chris@0: $file = array_merge($default, $file); Chris@18: $connection->insert('locale_file')->fields($file)->execute(); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks the translation of a string. Chris@0: * Chris@0: * @param string $source Chris@0: * Translation source string. Chris@0: * @param string $translation Chris@0: * Translation to check. Use empty string to check for a not existing Chris@0: * translation. Chris@0: * @param string $langcode Chris@0: * Language code of the language to translate to. Chris@0: * @param string $message Chris@0: * (optional) A message to display with the assertion. Chris@0: */ Chris@0: protected function assertTranslation($source, $translation, $langcode, $message = '') { Chris@0: $db_translation = db_query('SELECT translation FROM {locales_target} lt INNER JOIN {locales_source} ls ON ls.lid = lt.lid WHERE ls.source = :source AND lt.language = :langcode', [':source' => $source, ':langcode' => $langcode])->fetchField(); Chris@0: $db_translation = $db_translation == FALSE ? '' : $db_translation; Chris@0: $this->assertEqual($translation, $db_translation, $message ? $message : format_string('Correct translation of %source (%language)', ['%source' => $source, '%language' => $langcode])); Chris@0: } Chris@0: Chris@0: }