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