diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php index 555c198..403761b 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php @@ -122,25 +122,25 @@ function testUILanguageNegotiation() { 'string' => $default_string, 'langcode' => $langcode_browser_fallback, ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->drupalPost('admin/config/regional/translate', $search, t('Filter')); $textarea = current($this->xpath('//textarea')); $lid = (string) $textarea[0]['name']; $edit = array( $lid => $language_browser_fallback_string, ); - $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations')); + $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations')); $search = array( 'string' => $default_string, 'langcode' => $langcode, ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->drupalPost('admin/config/regional/translate', $search, t('Filter')); $textarea = current($this->xpath('//textarea')); $lid = (string) $textarea[0]['name']; $edit = array( $lid => $language_string, ); - $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations')); + $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations')); // Configure URL language rewrite. variable_set('language_negotiation_url_type', Language::TYPE_INTERFACE); diff --git a/core/modules/locale/lib/Drupal/locale/Controller/LocaleController.php b/core/modules/locale/lib/Drupal/locale/Controller/LocaleController.php index 7d95e3a..cccbcea 100644 --- a/core/modules/locale/lib/Drupal/locale/Controller/LocaleController.php +++ b/core/modules/locale/lib/Drupal/locale/Controller/LocaleController.php @@ -7,10 +7,15 @@ namespace Drupal\locale\Controller; use Drupal\Core\Controller\ControllerInterface; -use Drupal\Core\Routing\PathBasedGeneratorInterface; use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\KeyValueStore\KeyValueStoreInterface; +use Drupal\Core\Routing\PathBasedGeneratorInterface; +use Drupal\Core\StringTranslation\Translator\TranslatorInterface; +use Drupal\locale\Form\TranslateEditForm; +use Drupal\locale\Form\TranslateFilterForm; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; + /** * Return response for manual check translations. */ @@ -24,14 +29,26 @@ class LocaleController implements ControllerInterface { protected $moduleHandler; /** + * The container. + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $container; + + /** * Constructs a \Drupal\locale\Controller\LocaleController object. * * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. + * @param \Drupal\Core\Routing\PathBasedGeneratorInterface $url_generator + * The URL generator service. + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The service container this object should use. */ - public function __construct(ModuleHandlerInterface $module_handler, PathBasedGeneratorInterface $url_generator) { + public function __construct(ModuleHandlerInterface $module_handler, PathBasedGeneratorInterface $url_generator, ContainerInterface $container) { $this->moduleHandler = $module_handler; $this->urlGenerator = $url_generator; + $this->container = $container; } /** @@ -40,7 +57,8 @@ public function __construct(ModuleHandlerInterface $module_handler, PathBasedGen public static function create(ContainerInterface $container) { return new static( $container->get('module_handler'), - $container->get('url_generator') + $container->get('url_generator'), + $container ); } @@ -69,4 +87,17 @@ public function checkTranslation() { return new RedirectResponse($this->urlGenerator->generateFromPath('admin/reports/translations', array('absolute' => TRUE))); } + + /** + * Shows the string search screen. + * + * @see locale_menu() + */ + public function translatePage() { + $state = $this->container->get('keyvalue')->get('state'); + return array( + 'filter' => TranslateFilterForm::create($this->container), + 'form' => TranslateEditForm::create($this->container), + ); + } } diff --git a/core/modules/locale/lib/Drupal/locale/Form/TranslateEditForm.php b/core/modules/locale/lib/Drupal/locale/Form/TranslateEditForm.php new file mode 100644 index 0000000..8cceda8 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Form/TranslateEditForm.php @@ -0,0 +1,284 @@ +state = $state; + $this->translator = $translator; + $this->localeStorage = $locale_storage; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('keyvalue')->get('state'), + $container->get('string_translation'), + $container->get('locale.storage') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormID() { + return 'locale_translate_edit_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, array &$form_state) { + $filter_values = $this->translateFilterValues($this->request); + $langcode = $filter_values['langcode']; + + drupal_static_reset('language_list'); + $languages = language_list(); + + $langname = isset($langcode) ? $languages[$langcode]->name : "- None -"; + + $path = drupal_get_path('module', 'locale'); + $form['#attached']['css'] = array( + $path . '/css/locale.admin.css', + ); + $form['#attached']['library'][] = array('locale', 'drupal.locale.admin'); + + $form['langcode'] = array( + '#type' => 'value', + '#value' => $filter_values['langcode'], + ); + + $form['strings'] = array( + '#type' => 'item', + '#tree' => TRUE, + '#language' => $langname, + '#theme' => 'locale_translate_edit_form_strings', + ); + + if (isset($langcode)) { + $strings = $this->translateFilterLoadStrings($this->request); + + $plural_formulas = $this->state->get('locale.translation.plurals') ?: array(); + + foreach ($strings as $string) { + // Cast into source string, will do for our purposes. + $source = new SourceString($string); + // Split source to work with plural values. + $source_array = $source->getPlurals(); + $translation_array = $string->getPlurals(); + if (count($source_array) == 1) { + // Add original string value and mark as non-plural. + $form['strings'][$string->lid]['plural'] = array( + '#type' => 'value', + '#value' => 0, + ); + $form['strings'][$string->lid]['original'] = array( + '#type' => 'item', + '#title' => $this->translator->translate('Source string'), + '#title_display' => 'invisible', + '#markup' => String::checkPlain($source_array[0]), + ); + } + else { + // Add original string value and mark as plural. + $form['strings'][$string->lid]['plural'] = array( + '#type' => 'value', + '#value' => 1, + ); + $form['strings'][$string->lid]['original_singular'] = array( + '#type' => 'item', + '#title' => $this->translator->translate('Singular form'), + '#markup' => String::checkPlain($source_array[0]), + ); + $form['strings'][$string->lid]['original_plural'] = array( + '#type' => 'item', + '#title' => $this->translator->translate('Plural form'), + '#markup' => String::checkPlain($source_array[1]), + ); + } + if (!empty($string->context)) { + $form['strings'][$string->lid]['context'] = array( + '#type' => 'value', + '#value' => String::checkPlain($string->context), + ); + } + // Approximate the number of rows to use in the default textarea. + $rows = min(ceil(str_word_count($source_array[0]) / 12), 10); + if (empty($form['strings'][$string->lid]['plural']['#value'])) { + $form['strings'][$string->lid]['translations'][0] = array( + '#type' => 'textarea', + '#title' => $this->translator->translate('Translated string'), + '#title_display' => 'invisible', + '#rows' => $rows, + '#default_value' => $translation_array[0], + ); + } + else { + // Dealing with plural strings. + if (isset($plural_formulas[$langcode]['plurals']) && $plural_formulas[$langcode]['plurals'] > 2) { + // Add a textarea for each plural variant. + for ($i = 0; $i < $plural_formulas[$langcode]['plurals']; $i++) { + $form['strings'][$string->lid]['translations'][$i] = array( + '#type' => 'textarea', + '#title' => ($i == 0 ? $this->translator->translate('Singular form') : format_plural($i, 'First plural form', '@count. plural form')), + '#rows' => $rows, + '#default_value' => isset($translation_array[$i]) ? $translation_array[$i] : '', + ); + } + } + else { + // Fallback for unknown number of plurals. + $form['strings'][$string->lid]['translations'][0] = array( + '#type' => 'textarea', + '#title' => $this->translator->translate('Singular form'), + '#rows' => $rows, + '#default_value' => $translation_array[0], + ); + $form['strings'][$string->lid]['translations'][1] = array( + '#type' => 'textarea', + '#title' => $this->translator->translate('Plural form'), + '#rows' => $rows, + '#default_value' => isset($translation_array[1]) ? $translation_array[1] : '', + ); + } + } + } + if (count(element_children($form['strings']))) { + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => $this->translator->translate('Save translations'), + ); + } + } + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, array &$form_state) { + $langcode = $form_state['values']['langcode']; + foreach ($form_state['values']['strings'] as $lid => $translations) { + foreach ($translations['translations'] as $key => $value) { + if (!locale_string_is_safe($value)) { + form_set_error("strings][$lid][translations][$key", $this->translator->translate('The submitted string contains disallowed HTML: %string', array('%string' => $value))); + form_set_error("translations][$langcode][$key", $this->translator->translate('The submitted string contains disallowed HTML: %string', array('%string' => $value))); + watchdog('locale', 'Attempted submission of a translation string with disallowed HTML: %string', array('%string' => $value), WATCHDOG_WARNING); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, array &$form_state, Request $request = NULL) { + $langcode = $form_state['values']['langcode']; + $updated = array(); + + // Preload all translations for strings in the form. + $lids = array_keys($form_state['values']['strings']); + $existing_translation_objects = array(); + foreach ($this->localeStorage->getTranslations(array('lid' => $lids, 'language' => $langcode, 'translated' => TRUE)) as $existing_translation_object) { + $existing_translation_objects[$existing_translation_object->lid] = $existing_translation_object; + } + + foreach ($form_state['values']['strings'] as $lid => $new_translation) { + $existing_translation = isset($existing_translation_objects[$lid]); + + // Plural translations are saved in a delimited string. To be able to + // compare the new strings with the existing strings a string in the same format is created. + $new_translation_string_delimited = implode(LOCALE_PLURAL_DELIMITER, $new_translation['translations']); + + // Generate an imploded string without delimiter, to be able to run + // empty() on it. + $new_translation_string = implode('', $new_translation['translations']); + + $is_changed = FALSE; + + if ($existing_translation && $existing_translation_objects[$lid]->translation != $new_translation_string_delimited) { + // If there is an existing translation in the DB and the new translation + // is not the same as the existing one. + $is_changed = TRUE; + } + elseif (!$existing_translation && !empty($new_translation_string)) { + // Newly entered translation. + $is_changed = TRUE; + } + + if ($is_changed) { + // Only update or insert if we have a value to use. + $target = isset($existing_translation_objects[$lid]) ? $existing_translation_objects[$lid] : $this->localeStorage->createTranslation(array('lid' => $lid, 'language' => $langcode)); + $target->setPlurals($new_translation['translations']) + ->setCustomized() + ->save(); + $updated[] = $target->getId(); + } + if (empty($new_translation_string) && isset($existing_translation_objects[$lid])) { + // Empty new translation entered: remove existing entry from database. + $existing_translation_objects[$lid]->delete(); + $updated[] = $lid; + } + } + + drupal_set_message($this->translator->translate('The strings have been saved.')); + + // Keep the user on the current pager page. + $page = $request->query->get('page'); + if (isset($page)) { + $form_state['redirect'] = array('admin/config/regional/translate', array('query' => array('page' => $page))); + } + + if ($updated) { + // Clear cache and force refresh of JavaScript translations. + _locale_refresh_translations(array($langcode), $updated); + _locale_refresh_configuration(array($langcode), $updated); + } + } + +} diff --git a/core/modules/locale/lib/Drupal/locale/Form/TranslateFilterForm.php b/core/modules/locale/lib/Drupal/locale/Form/TranslateFilterForm.php new file mode 100644 index 0000000..92ca824 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Form/TranslateFilterForm.php @@ -0,0 +1,114 @@ +translateFilters(); + $filter_values = $this->translateFilterValues($this->request); + + $form['#attached']['css'] = array( + drupal_get_path('module', 'locale') . '/locale.admin.css', + ); + + $form['filters'] = array( + '#type' => 'details', + '#title' => t('Filter translatable strings'), + '#collapsed' => FALSE, + ); + foreach ($filters as $key => $filter) { + // Special case for 'string' filter. + if ($key == 'string') { + $form['filters']['status']['string'] = array( + '#type' => 'search', + '#title' => $filter['title'], + '#description' => $filter['description'], + '#default_value' => $filter_values[$key], + ); + } + else { + $empty_option = isset($filter['options'][$filter['default']]) ? $filter['options'][$filter['default']] : '- None -'; + $form['filters']['status'][$key] = array( + '#title' => $filter['title'], + '#type' => 'select', + '#empty_value' => $filter['default'], + '#empty_option' => $empty_option, + '#size' => 0, + '#options' => $filter['options'], + '#default_value' => $filter_values[$key], + ); + if (isset($filter['states'])) { + $form['filters']['status'][$key]['#states'] = $filter['states']; + } + } + } + + $form['filters']['actions'] = array( + '#type' => 'actions', + '#attributes' => array('class' => array('container-inline')), + ); + $form['filters']['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Filter'), + ); + if (!empty($_SESSION['locale_translate_filter'])) { + $form['filters']['actions']['reset'] = array( + '#type' => 'submit', + '#value' => t('Reset'), + ); + } + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, array &$form_state) { + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, array &$form_state) { + $op = $form_state['values']['op']; + $filters = $this->translateFilters(); + switch ($op) { + case t('Filter'): + foreach ($filters as $name => $filter) { + if (isset($form_state['values'][$name])) { + $_SESSION['locale_translate_filter'][$name] = $form_state['values'][$name]; + } + } + break; + + case t('Reset'): + $_SESSION['locale_translate_filter'] = array(); + break; + } + + $form_state['redirect'] = 'admin/config/regional/translate'; + } + +} diff --git a/core/modules/locale/lib/Drupal/locale/Form/TranslateFormBase.php b/core/modules/locale/lib/Drupal/locale/Form/TranslateFormBase.php new file mode 100644 index 0000000..a421285 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Form/TranslateFormBase.php @@ -0,0 +1,203 @@ +localeStorage = $locale_storage; + $this->request = $request; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('locale.storage'), + $container->get('request') + ); + } + + /** + * Builds a string search query and returns an array of string objects. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request object. + * + * @return array + * Array of \Drupal\locale\TranslationString objects. + */ + protected function translateFilterLoadStrings(Request $request) { + $filter_values = $this->translateFilterValues($request); + + // Language is sanitized to be one of the possible options in + // translateFilterValues(). + $conditions = array('language' => $filter_values['langcode']); + $options = array('pager limit' => 30, 'translated' => TRUE, 'untranslated' => TRUE); + + // Add translation status conditions and options. + switch ($filter_values['translation']) { + case 'translated': + $conditions['translated'] = TRUE; + if ($filter_values['customized'] != 'all') { + $conditions['customized'] = $filter_values['customized']; + } + break; + + case 'untranslated': + $conditions['translated'] = FALSE; + break; + + } + + if (!empty($filter_values['string'])) { + $options['filters']['source'] = $filter_values['string']; + if ($options['translated']) { + $options['filters']['translation'] = $filter_values['string']; + } + } + + return $this->localeStorage->getTranslations($conditions, $options); + } + + /** + * Builds an array out of search criteria specified in request variables. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request object. + * + * @return array $filter_values + * The filter values. + */ + protected function translateFilterValues(Request $request) { + $filter_values = &drupal_static(__FUNCTION__); + if (!isset($filter_values)) { + $filter_values = array(); + $filters = $this->translateFilters(); + foreach ($filters as $key => $filter) { + $filter_values[$key] = $filter['default']; + // Let the filter defaults be overwritten by parameters in the URL. + if ($request->query->has($key)) { + // Only allow this value if it was among the options, or + // if there were no fixed options to filter for. + $value = $request->query->get($key); + if (!isset($filter['options']) || isset($filter['options'][$value])) { + $filter_values[$key] = $value; + } + } + elseif (isset($_SESSION['locale_translate_filter'][$key])) { + // Only allow this value if it was among the options, or + // if there were no fixed options to filter for. + if (!isset($filter['options']) || isset($filter['options'][$_SESSION['locale_translate_filter'][$key]])) { + $filter_values[$key] = $_SESSION['locale_translate_filter'][$key]; + } + } + } + } + return $filter_values; + } + + /** + * List locale translation filters that can be applied. + */ + protected function translateFilters() { + $filters = array(); + + // Get all languages, except English. + drupal_static_reset('language_list'); + $languages = language_list(); + $language_options = array(); + foreach ($languages as $langcode => $language) { + if ($langcode != 'en' || locale_translate_english()) { + $language_options[$langcode] = $language->name; + } + } + + // Pick the current interface language code for the filter. + $default_langcode = language(Language::TYPE_INTERFACE)->id; + if (!isset($language_options[$default_langcode])) { + $available_langcodes = array_keys($language_options); + $default_langcode = array_shift($available_langcodes); + } + + $filters['string'] = array( + 'title' => t('String contains'), + 'description' => t('Leave blank to show all strings. The search is case sensitive.'), + 'default' => '', + ); + + $filters['langcode'] = array( + 'title' => t('Translation language'), + 'options' => $language_options, + 'default' => $default_langcode, + ); + + $filters['translation'] = array( + 'title' => t('Search in'), + 'options' => array( + 'all' => t('Both translated and untranslated strings'), + 'translated' => t('Only translated strings'), + 'untranslated' => t('Only untranslated strings'), + ), + 'default' => 'all', + ); + + $filters['customized'] = array( + 'title' => t('Translation type'), + 'options' => array( + 'all' => t('All'), + LOCALE_NOT_CUSTOMIZED => t('Non-customized translation'), + LOCALE_CUSTOMIZED => t('Customized translation'), + ), + 'states' => array( + 'visible' => array( + ':input[name=translation]' => array('value' => 'translated'), + ), + ), + 'default' => 'all', + ); + + return $filters; + } + +} diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigTranslationTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigTranslationTest.php index 414cde0..f0ffe79 100644 --- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigTranslationTest.php +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigTranslationTest.php @@ -69,14 +69,14 @@ function testConfigTranslation() { 'langcode' => $langcode, 'translation' => 'all', ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->drupalPost('admin/config/regional/translate', $search, t('Filter')); $textareas = $this->xpath('//textarea'); $textarea = current($textareas); $lid = (string) $textarea[0]['name']; $edit = array( $lid => $site_name, ); - $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations')); + $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations')); $wrapper = $this->container->get('locale.config.typed')->get('system.site'); @@ -115,13 +115,13 @@ function testConfigTranslation() { 'langcode' => $langcode, 'translation' => 'all', ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->drupalPost('admin/config/regional/translate', $search, t('Filter')); $textarea = current($this->xpath('//textarea')); $lid = (string) $textarea[0]['name']; $edit = array( $lid => $image_style_label, ); - $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations')); + $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations')); // Check the right single translation has been created. $translations = $this->storage->getTranslations(array('language' => $langcode, 'type' => 'configuration', 'name' => 'image.style.medium')); diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php index a03f1df..fcff653 100644 --- a/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php @@ -204,7 +204,7 @@ function testPluralEditExport() { $search = array( 'langcode' => 'fr', ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->drupalPost('admin/config/regional/translate', $search, t('Filter')); // Plural values for the langcode fr. $this->assertText('@count heure'); $this->assertText('@count heures'); @@ -227,7 +227,7 @@ function testPluralEditExport() { 'string' => '1 day', 'langcode' => 'fr', ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->drupalPost('admin/config/regional/translate', $search, t('Filter')); // Save complete translations for the string in langcode fr. $edit = array( @@ -241,7 +241,7 @@ function testPluralEditExport() { 'string' => '1 day', 'langcode' => 'hr', ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->drupalPost('admin/config/regional/translate', $search, t('Filter')); $edit = array( "strings[$lid][translations][0]" => '@count dan', diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php index e2d1109..922b764 100644 --- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php @@ -78,7 +78,7 @@ function testUninstallProcess() { // 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'); + $this->drupalGet('admin/config/regional/translate'); // Get any of the javascript strings to translate. $js_strings = $this->container->get('locale.storage')->getStrings(array('type' => 'javascript')); $string = reset($js_strings); diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 932b2a1..3a997f7 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -173,9 +173,7 @@ function locale_menu() { $items['admin/config/regional/translate'] = array( 'title' => 'User interface translation', 'description' => 'Translate the built-in user interface.', - 'page callback' => 'locale_translate_page', - 'access arguments' => array('translate interface'), - 'file' => 'locale.pages.inc', + 'route_name' => 'locale_translate_page', 'weight' => -5, ); $items['admin/config/regional/translate/translate'] = array( @@ -744,7 +742,7 @@ function locale_form_language_admin_overview_form_alter(&$form, &$form_state) { '@total' => $total_strings, '@ratio' => $stats[$langcode]['ratio'], )), - 'admin/config/regional/translate/translate', + 'admin/config/regional/translate', array('query' => array('langcode' => $langcode)) ), ); diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc index 83777b6..f5523cb 100644 --- a/core/modules/locale/locale.pages.inc +++ b/core/modules/locale/locale.pages.inc @@ -12,460 +12,27 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** - * Page callback: Shows the string search screen. + * Page callback: Checks for translation updates and displays the status. * - * @see locale_menu() - */ -function locale_translate_page() { - return array( - 'filter' => drupal_get_form('locale_translate_filter_form'), - 'form' => drupal_get_form('locale_translate_edit_form'), - ); -} - -/** - * Builds a string search query and returns an array of string objects. - * - * @return array - * Array of Drupal\locale\TranslationString objects. - */ -function locale_translate_filter_load_strings() { - $filter_values = locale_translate_filter_values(); - - // Language is sanitized to be one of the possible options in - // locale_translate_filter_values(). - $conditions = array('language' => $filter_values['langcode']); - $options = array('pager limit' => 30, 'translated' => TRUE, 'untranslated' => TRUE); - - // Add translation status conditions and options. - switch ($filter_values['translation']) { - case 'translated': - $conditions['translated'] = TRUE; - if ($filter_values['customized'] != 'all') { - $conditions['customized'] = $filter_values['customized']; - } - break; - - case 'untranslated': - $conditions['translated'] = FALSE; - break; - - } - - if (!empty($filter_values['string'])) { - $options['filters']['source'] = $filter_values['string']; - if ($options['translated']) { - $options['filters']['translation'] = $filter_values['string']; - } - } - - return Drupal::service('locale.storage')->getTranslations($conditions, $options); -} - -/** - * Build array out of search criteria specified in request variables. - */ -function locale_translate_filter_values() { - $filter_values = &drupal_static(__FUNCTION__); - if (!isset($filter_values)) { - $filter_values = array(); - $filters = locale_translate_filters(); - foreach ($filters as $key => $filter) { - $filter_values[$key] = $filter['default']; - // Let the filter defaults be overwritten by parameters in the URL. - if (isset($_GET[$key])) { - // Only allow this value if it was among the options, or - // if there were no fixed options to filter for. - if (!isset($filter['options']) || isset($filter['options'][$_GET[$key]])) { - $filter_values[$key] = $_GET[$key]; - } - } - elseif (isset($_SESSION['locale_translate_filter'][$key])) { - // Only allow this value if it was among the options, or - // if there were no fixed options to filter for. - if (!isset($filter['options']) || isset($filter['options'][$_SESSION['locale_translate_filter'][$key]])) { - $filter_values[$key] = $_SESSION['locale_translate_filter'][$key]; - } - } - } - } - return $filter_values; -} - -/** - * List locale translation filters that can be applied. - */ -function locale_translate_filters() { - $filters = array(); - - // Get all languages, except English. - drupal_static_reset('language_list'); - $languages = language_list(); - $language_options = array(); - foreach ($languages as $langcode => $language) { - if ($langcode != 'en' || locale_translate_english()) { - $language_options[$langcode] = $language->name; - } - } - - // Pick the current interface language code for the filter. - $default_langcode = language(Language::TYPE_INTERFACE)->id; - if (!isset($language_options[$default_langcode])) { - $available_langcodes = array_keys($language_options); - $default_langcode = array_shift($available_langcodes); - } - - $filters['string'] = array( - 'title' => t('String contains'), - 'description' => t('Leave blank to show all strings. The search is case sensitive.'), - 'default' => '', - ); - - $filters['langcode'] = array( - 'title' => t('Translation language'), - 'options' => $language_options, - 'default' => $default_langcode, - ); - - $filters['translation'] = array( - 'title' => t('Search in'), - 'options' => array( - 'all' => t('Both translated and untranslated strings'), - 'translated' => t('Only translated strings'), - 'untranslated' => t('Only untranslated strings'), - ), - 'default' => 'all', - ); - - $filters['customized'] = array( - 'title' => t('Translation type'), - 'options' => array( - 'all' => t('All'), - LOCALE_NOT_CUSTOMIZED => t('Non-customized translation'), - LOCALE_CUSTOMIZED => t('Customized translation'), - ), - 'states' => array( - 'visible' => array( - ':input[name=translation]' => array('value' => 'translated'), - ), - ), - 'default' => 'all', - ); - - return $filters; -} - -/** - * Return form for locale translation filters. - * - * @ingroup forms - */ -function locale_translate_filter_form($form, &$form_state) { - $filters = locale_translate_filters(); - $filter_values = locale_translate_filter_values(); - - $form['#attached']['css'] = array( - drupal_get_path('module', 'locale') . '/css/locale.admin.css', - ); - - $form['filters'] = array( - '#type' => 'details', - '#title' => t('Filter translatable strings'), - '#collapsed' => FALSE, - ); - foreach ($filters as $key => $filter) { - // Special case for 'string' filter. - if ($key == 'string') { - $form['filters']['status']['string'] = array( - '#type' => 'search', - '#title' => $filter['title'], - '#description' => $filter['description'], - '#default_value' => $filter_values[$key], - ); - } - else { - $empty_option = isset($filter['options'][$filter['default']]) ? $filter['options'][$filter['default']] : '- None -'; - $form['filters']['status'][$key] = array( - '#title' => $filter['title'], - '#type' => 'select', - '#empty_value' => $filter['default'], - '#empty_option' => $empty_option, - '#size' => 0, - '#options' => $filter['options'], - '#default_value' => $filter_values[$key], - ); - if (isset($filter['states'])) { - $form['filters']['status'][$key]['#states'] = $filter['states']; - } - } - } - - $form['filters']['actions'] = array( - '#type' => 'actions', - '#attributes' => array('class' => array('container-inline')), - ); - $form['filters']['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Filter'), - ); - if (!empty($_SESSION['locale_translate_filter'])) { - $form['filters']['actions']['reset'] = array( - '#type' => 'submit', - '#value' => t('Reset'), - ); - } - - return $form; -} - -/** - * Process result from locale translation filter form. - */ -function locale_translate_filter_form_submit($form, &$form_state) { - $op = $form_state['values']['op']; - $filters = locale_translate_filters(); - switch ($op) { - case t('Filter'): - foreach ($filters as $name => $filter) { - if (isset($form_state['values'][$name])) { - $_SESSION['locale_translate_filter'][$name] = $form_state['values'][$name]; - } - } - break; - - case t('Reset'): - $_SESSION['locale_translate_filter'] = array(); - break; - - } - - $form_state['redirect'] = 'admin/config/regional/translate/translate'; -} - -/** - * Form constructor for the string editing form. + * Manually checks the translation status without the use of cron. * * @see locale_menu() - * @see locale_translate_edit_form_validate() - * @see locale_translate_edit_form_submit() - * - * @ingroup forms */ -function locale_translate_edit_form($form, &$form_state) { - $filter_values = locale_translate_filter_values(); - $langcode = $filter_values['langcode']; - - drupal_static_reset('language_list'); - $languages = language_list(); - - $langname = isset($langcode) ? $languages[$langcode]->name : "- None -"; - - $path = drupal_get_path('module', 'locale'); - $form['#attached']['css'] = array( - $path . '/css/locale.admin.css', - ); - $form['#attached']['library'][] = array('locale', 'drupal.locale.admin'); - - $form['langcode'] = array( - '#type' => 'value', - '#value' => $filter_values['langcode'], - ); - - $form['strings'] = array( - '#type' => 'item', - '#tree' => TRUE, - '#language' => $langname, - '#theme' => 'locale_translate_edit_form_strings', - ); - - if (isset($langcode)) { - $strings = locale_translate_filter_load_strings(); - - $plural_formulas = Drupal::state()->get('locale.translation.plurals') ?: array(); - - foreach ($strings as $string) { - // Cast into source string, will do for our purposes. - $source = new SourceString($string); - // Split source to work with plural values. - $source_array = $source->getPlurals(); - $translation_array = $string->getPlurals(); - if (count($source_array) == 1) { - // Add original string value and mark as non-plural. - $form['strings'][$string->lid]['plural'] = array( - '#type' => 'value', - '#value' => 0, - ); - $form['strings'][$string->lid]['original'] = array( - '#type' => 'item', - '#title' => t('Source string (@language)', array('@language' => t('Built-in English'))), - '#title_display' => 'invisible', - '#markup' => '' . check_plain($source_array[0]) . '', - ); - } - else { - // Add original string value and mark as plural. - $form['strings'][$string->lid]['plural'] = array( - '#type' => 'value', - '#value' => 1, - ); - $form['strings'][$string->lid]['original_singular'] = array( - '#type' => 'item', - '#title' => t('Singular form'), - '#markup' => '' . check_plain($source_array[0]) . '', - '#prefix' => '' . t('Source string (@language)', array('@language' => t('Built-in English'))) . '', - ); - $form['strings'][$string->lid]['original_plural'] = array( - '#type' => 'item', - '#title' => t('Plural form'), - '#markup' => '' . check_plain($source_array[1]) . '', - ); - } - if (!empty($string->context)) { - $form['strings'][$string->lid]['context'] = array( - '#type' => 'value', - '#value' => '' . check_plain($string->context) . '', - ); - } - // Approximate the number of rows to use in the default textarea. - $rows = min(ceil(str_word_count($source_array[0]) / 12), 10); - if (empty($form['strings'][$string->lid]['plural']['#value'])) { - $form['strings'][$string->lid]['translations'][0] = array( - '#type' => 'textarea', - '#title' => t('Translated string (@language)', array('@language' => $langname)), - '#title_display' => 'invisible', - '#rows' => $rows, - '#default_value' => $translation_array[0], - '#attributes' => array('lang' => $langcode), - ); - } - else { - // Dealing with plural strings. - if (isset($plural_formulas[$langcode]['plurals']) && $plural_formulas[$langcode]['plurals'] > 2) { - // Add a textarea for each plural variant. - for ($i = 0; $i < $plural_formulas[$langcode]['plurals']; $i++) { - $form['strings'][$string->lid]['translations'][$i] = array( - '#type' => 'textarea', - '#title' => ($i == 0 ? t('Singular form') : format_plural($i, 'First plural form', '@count. plural form')), - '#rows' => $rows, - '#default_value' => isset($translation_array[$i]) ? $translation_array[$i] : '', - '#attributes' => array('lang' => $langcode), - '#prefix' => $i == 0 ? ('' . t('Translated string (@language)', array('@language' => $langname)) . '') : '', - ); - } - } - else { - // Fallback for unknown number of plurals. - $form['strings'][$string->lid]['translations'][0] = array( - '#type' => 'textarea', - '#title' => t('Singular form'), - '#rows' => $rows, - '#default_value' => $translation_array[0], - '#attributes' => array('lang' => $langcode), - '#prefix' => '' . t('Translated string (@language)', array('@language' => $langname)) . '', - ); - $form['strings'][$string->lid]['translations'][1] = array( - '#type' => 'textarea', - '#title' => t('Plural form'), - '#rows' => $rows, - '#default_value' => isset($translation_array[1]) ? $translation_array[1] : '', - '#attributes' => array('lang' => $langcode), - ); - } - } - } - if (count(element_children($form['strings']))) { - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save translations')); - } - } - return $form; -} - -/** - * Form validation handler for locale_translate_edit_form(). - * - * @see locale_translate_edit_form_submit() - */ -function locale_translate_edit_form_validate($form, &$form_state) { - $langcode = $form_state['values']['langcode']; - foreach ($form_state['values']['strings'] as $lid => $translations) { - foreach ($translations['translations'] as $key => $value) { - if (!locale_string_is_safe($value)) { - form_set_error("strings][$lid][translations][$key", t('The submitted string contains disallowed HTML: %string', array('%string' => $value))); - form_set_error("translations][$langcode][$key", t('The submitted string contains disallowed HTML: %string', array('%string' => $value))); - watchdog('locale', 'Attempted submission of a translation string with disallowed HTML: %string', array('%string' => $value), WATCHDOG_WARNING); - } - } - } -} - -/** - * Form submission handler for locale_translate_edit_form(). - * - * @see locale_translate_edit_form_validate() - */ -function locale_translate_edit_form_submit($form, &$form_state) { - $langcode = $form_state['values']['langcode']; - $updated = array(); - - // Preload all translations for strings in the form. - $lids = array_keys($form_state['values']['strings']); - $existing_translation_objects = array(); - foreach (Drupal::service('locale.storage')->getTranslations(array('lid' => $lids, 'language' => $langcode, 'translated' => TRUE)) as $existing_translation_object) { - $existing_translation_objects[$existing_translation_object->lid] = $existing_translation_object; - } - - foreach ($form_state['values']['strings'] as $lid => $new_translation) { - $existing_translation = isset($existing_translation_objects[$lid]); - - // Plural translations are saved in a delimited string. To be able to - // compare the new strings with the existing strings a string in the same format is created. - $new_translation_string_delimited = implode(LOCALE_PLURAL_DELIMITER, $new_translation['translations']); - - // Generate an imploded string without delimiter, to be able to run - // empty() on it. - $new_translation_string = implode('', $new_translation['translations']); - - $is_changed = FALSE; - - if ($existing_translation && $existing_translation_objects[$lid]->translation != $new_translation_string_delimited) { - // If there is an existing translation in the DB and the new translation - // is not the same as the existing one. - $is_changed = TRUE; - } - elseif (!$existing_translation && !empty($new_translation_string)) { - // Newly entered translation. - $is_changed = TRUE; - } - - if ($is_changed) { - // Only update or insert if we have a value to use. - $target = isset($existing_translation_objects[$lid]) ? $existing_translation_objects[$lid] : Drupal::service('locale.storage')->createTranslation(array('lid' => $lid, 'language' => $langcode)); - $target->setPlurals($new_translation['translations']) - ->setCustomized() - ->save(); - $updated[] = $target->getId(); - } - if (empty($new_translation_string) && isset($existing_translation_objects[$lid])) { - // Empty new translation entered: remove existing entry from database. - $existing_translation_objects[$lid]->delete(); - $updated[] = $lid; - } - } - - drupal_set_message(t('The strings have been saved.')); +function locale_translation_manual_status() { + module_load_include('compare.inc', 'locale'); - // Keep the user on the current pager page. - if (isset($_GET['page'])) { - $form_state['redirect'] = array('admin/config/regional/translate', array('query' => array('page' => $_GET['page']))); - } + // Check the translation status of all translatable projects in all languages. + // First we clear the cached list of projects. Although not strictly + // necessary, this is helpful in case the project list is out of sync. + locale_translation_flush_projects(); + locale_translation_check_projects(); - if ($updated) { - // Clear cache and refresh configuration and JavaScript translations. - _locale_refresh_translations(array($langcode), $updated); - _locale_refresh_configuration(array($langcode), $updated); + // Execute a batch if required. A batch is only used when remote files + // are checked. + if (batch_get()) { + return batch_process('admin/reports/translations'); } - + return new RedirectResponse(url('admin/reports/translations', array('absolute' => TRUE))); } /** diff --git a/core/modules/locale/locale.routing.yml b/core/modules/locale/locale.routing.yml index 2047229..4b7d50e 100644 --- a/core/modules/locale/locale.routing.yml +++ b/core/modules/locale/locale.routing.yml @@ -11,3 +11,10 @@ locale_check_translation: _controller: 'Drupal\locale\Controller\LocaleController::checkTranslation' requirements: _permission: 'translate interface' + +locale_translate_page: + pattern: '/admin/config/regional/translate' + defaults: + _content: 'Drupal\locale\Controller\LocaleController::translatePage' + requirements: + _permission: 'translate interface' diff --git a/core/modules/toolbar/lib/Drupal/toolbar/Tests/ToolbarMenuTranslationTest.php b/core/modules/toolbar/lib/Drupal/toolbar/Tests/ToolbarMenuTranslationTest.php index 02c01d4..949d565 100644 --- a/core/modules/toolbar/lib/Drupal/toolbar/Tests/ToolbarMenuTranslationTest.php +++ b/core/modules/toolbar/lib/Drupal/toolbar/Tests/ToolbarMenuTranslationTest.php @@ -59,7 +59,7 @@ function testToolbarClasses() { 'langcode' => $langcode, 'translation' => 'untranslated', ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->drupalPost('admin/config/regional/translate', $search, t('Filter')); // Make sure will be able to translate the menu item. $this->assertNoText('No strings available.', 'Search found the menu item as untranslated.'); @@ -74,7 +74,7 @@ function testToolbarClasses() { $edit = array( $lid => $menu_item_translated, ); - $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations')); + $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations')); // Search for the translated menu item. $search = array( @@ -82,7 +82,7 @@ function testToolbarClasses() { 'langcode' => $langcode, 'translation' => 'translated', ); - $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->drupalPost('admin/config/regional/translate', $search, t('Filter')); // Make sure the menu item string was translated. $this->assertText($menu_item_translated, 'Search found the menu item as translated: ' . $menu_item_translated . '.');