PageRenderTime 48ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/core/modules/locale/src/Form/TranslateEditForm.php

http://github.com/drupal/drupal
PHP | 242 lines | 178 code | 26 blank | 38 comment | 23 complexity | 4886c663def8baa0e84b97ebd2b7af40 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. namespace Drupal\locale\Form;
  3. use Drupal\Component\Gettext\PoItem;
  4. use Drupal\Core\Form\FormStateInterface;
  5. use Drupal\Core\Render\Element;
  6. use Drupal\locale\SourceString;
  7. /**
  8. * Defines a translation edit form.
  9. *
  10. * @internal
  11. */
  12. class TranslateEditForm extends TranslateFormBase {
  13. /**
  14. * {@inheritdoc}
  15. */
  16. public function getFormId() {
  17. return 'locale_translate_edit_form';
  18. }
  19. /**
  20. * {@inheritdoc}
  21. */
  22. public function buildForm(array $form, FormStateInterface $form_state) {
  23. $filter_values = $this->translateFilterValues();
  24. $langcode = $filter_values['langcode'];
  25. $this->languageManager->reset();
  26. $languages = $this->languageManager->getLanguages();
  27. $langname = isset($langcode) ? $languages[$langcode]->getName() : "- None -";
  28. $form['#attached']['library'][] = 'locale/drupal.locale.admin';
  29. $form['langcode'] = [
  30. '#type' => 'value',
  31. '#value' => $filter_values['langcode'],
  32. ];
  33. $form['strings'] = [
  34. '#type' => 'table',
  35. '#tree' => TRUE,
  36. '#language' => $langname,
  37. '#header' => [
  38. $this->t('Source string'),
  39. $this->t('Translation for @language', ['@language' => $langname]),
  40. ],
  41. '#empty' => $this->t('No strings available.'),
  42. '#attributes' => ['class' => ['locale-translate-edit-table']],
  43. ];
  44. if (isset($langcode)) {
  45. $strings = $this->translateFilterLoadStrings();
  46. $plurals = $this->getNumberOfPlurals($langcode);
  47. foreach ($strings as $string) {
  48. // Cast into source string, will do for our purposes.
  49. $source = new SourceString($string);
  50. // Split source to work with plural values.
  51. $source_array = $source->getPlurals();
  52. $translation_array = $string->getPlurals();
  53. if (count($source_array) == 1) {
  54. // Add original string value and mark as non-plural.
  55. $plural = FALSE;
  56. $form['strings'][$string->lid]['original'] = [
  57. '#type' => 'item',
  58. '#title' => $this->t('Source string (@language)', ['@language' => $this->t('Built-in English')]),
  59. '#title_display' => 'invisible',
  60. '#plain_text' => $source_array[0],
  61. '#preffix' => '<span lang="en">',
  62. '#suffix' => '</span>',
  63. ];
  64. }
  65. else {
  66. // Add original string value and mark as plural.
  67. $plural = TRUE;
  68. $original_singular = [
  69. '#type' => 'item',
  70. '#title' => $this->t('Singular form'),
  71. '#plain_text' => $source_array[0],
  72. '#prefix' => '<span class="visually-hidden">' . $this->t('Source string (@language)', ['@language' => $this->t('Built-in English')]) . '</span><span lang="en">',
  73. '#suffix' => '</span>',
  74. ];
  75. $original_plural = [
  76. '#type' => 'item',
  77. '#title' => $this->t('Plural form'),
  78. '#plain_text' => $source_array[1],
  79. '#preffix' => '<span lang="en">',
  80. '#suffix' => '</span>',
  81. ];
  82. $form['strings'][$string->lid]['original'] = [
  83. $original_singular,
  84. ['#markup' => '<br>'],
  85. $original_plural,
  86. ];
  87. }
  88. if (!empty($string->context)) {
  89. $form['strings'][$string->lid]['original'][] = [
  90. '#type' => 'inline_template',
  91. '#template' => '<br><small>{{ context_title }}: <span lang="en">{{ context }}</span></small>',
  92. '#context' => [
  93. 'context_title' => $this->t('In Context'),
  94. 'context' => $string->context,
  95. ],
  96. ];
  97. }
  98. // Approximate the number of rows to use in the default textarea.
  99. $rows = min(ceil(str_word_count($source_array[0]) / 12), 10);
  100. if (!$plural) {
  101. $form['strings'][$string->lid]['translations'][0] = [
  102. '#type' => 'textarea',
  103. '#title' => $this->t('Translated string (@language)', ['@language' => $langname]),
  104. '#title_display' => 'invisible',
  105. '#rows' => $rows,
  106. '#default_value' => $translation_array[0],
  107. '#attributes' => ['lang' => $langcode],
  108. ];
  109. }
  110. else {
  111. // Add a textarea for each plural variant.
  112. for ($i = 0; $i < $plurals; $i++) {
  113. $form['strings'][$string->lid]['translations'][$i] = [
  114. '#type' => 'textarea',
  115. // @todo Should use better labels https://www.drupal.org/node/2499639
  116. '#title' => ($i == 0 ? $this->t('Singular form') : $this->formatPlural($i, 'First plural form', '@count. plural form')),
  117. '#rows' => $rows,
  118. '#default_value' => isset($translation_array[$i]) ? $translation_array[$i] : '',
  119. '#attributes' => ['lang' => $langcode],
  120. '#prefix' => $i == 0 ? ('<span class="visually-hidden">' . $this->t('Translated string (@language)', ['@language' => $langname]) . '</span>') : '',
  121. ];
  122. }
  123. if ($plurals == 2) {
  124. // Simplify interface text for the most common case.
  125. $form['strings'][$string->lid]['translations'][1]['#title'] = $this->t('Plural form');
  126. }
  127. }
  128. }
  129. if (count(Element::children($form['strings']))) {
  130. $form['actions'] = ['#type' => 'actions'];
  131. $form['actions']['submit'] = [
  132. '#type' => 'submit',
  133. '#value' => $this->t('Save translations'),
  134. ];
  135. }
  136. }
  137. $form['pager']['#type'] = 'pager';
  138. return $form;
  139. }
  140. /**
  141. * {@inheritdoc}
  142. */
  143. public function validateForm(array &$form, FormStateInterface $form_state) {
  144. $langcode = $form_state->getValue('langcode');
  145. foreach ($form_state->getValue('strings') as $lid => $translations) {
  146. foreach ($translations['translations'] as $key => $value) {
  147. if (!locale_string_is_safe($value)) {
  148. $form_state->setErrorByName("strings][$lid][translations][$key", $this->t('The submitted string contains disallowed HTML: %string', ['%string' => $value]));
  149. $form_state->setErrorByName("translations][$langcode][$key", $this->t('The submitted string contains disallowed HTML: %string', ['%string' => $value]));
  150. $this->logger('locale')->warning('Attempted submission of a translation string with disallowed HTML: %string', ['%string' => $value]);
  151. }
  152. }
  153. }
  154. }
  155. /**
  156. * {@inheritdoc}
  157. */
  158. public function submitForm(array &$form, FormStateInterface $form_state) {
  159. $langcode = $form_state->getValue('langcode');
  160. $updated = [];
  161. // Preload all translations for strings in the form.
  162. $lids = array_keys($form_state->getValue('strings'));
  163. $existing_translation_objects = [];
  164. foreach ($this->localeStorage->getTranslations(['lid' => $lids, 'language' => $langcode, 'translated' => TRUE]) as $existing_translation_object) {
  165. $existing_translation_objects[$existing_translation_object->lid] = $existing_translation_object;
  166. }
  167. foreach ($form_state->getValue('strings') as $lid => $new_translation) {
  168. $existing_translation = isset($existing_translation_objects[$lid]);
  169. // Plural translations are saved in a delimited string. To be able to
  170. // compare the new strings with the existing strings a string in the same
  171. // format is created.
  172. $new_translation_string_delimited = implode(PoItem::DELIMITER, $new_translation['translations']);
  173. // Generate an imploded string without delimiter, to be able to run
  174. // empty() on it.
  175. $new_translation_string = implode('', $new_translation['translations']);
  176. $is_changed = FALSE;
  177. if ($existing_translation && $existing_translation_objects[$lid]->translation != $new_translation_string_delimited) {
  178. // If there is an existing translation in the DB and the new translation
  179. // is not the same as the existing one.
  180. $is_changed = TRUE;
  181. }
  182. elseif (!$existing_translation && !empty($new_translation_string)) {
  183. // Newly entered translation.
  184. $is_changed = TRUE;
  185. }
  186. if ($is_changed) {
  187. // Only update or insert if we have a value to use.
  188. $target = isset($existing_translation_objects[$lid]) ? $existing_translation_objects[$lid] : $this->localeStorage->createTranslation(['lid' => $lid, 'language' => $langcode]);
  189. $target->setPlurals($new_translation['translations'])
  190. ->setCustomized()
  191. ->save();
  192. $updated[] = $target->getId();
  193. }
  194. if (empty($new_translation_string) && isset($existing_translation_objects[$lid])) {
  195. // Empty new translation entered: remove existing entry from database.
  196. $existing_translation_objects[$lid]->delete();
  197. $updated[] = $lid;
  198. }
  199. }
  200. $this->messenger()->addStatus($this->t('The strings have been saved.'));
  201. // Keep the user on the current pager page.
  202. $page = $this->getRequest()->query->get('page');
  203. if (isset($page)) {
  204. $form_state->setRedirect(
  205. 'locale.translate_page',
  206. [],
  207. ['page' => $page]
  208. );
  209. }
  210. if ($updated) {
  211. // Clear cache and force refresh of JavaScript translations.
  212. _locale_refresh_translations([$langcode], $updated);
  213. _locale_refresh_configuration([$langcode], $updated);
  214. }
  215. }
  216. }