3 namespace Drupal\locale\Form;
5 use Drupal\Core\Form\FormStateInterface;
6 use Drupal\Core\Render\Element;
7 use Drupal\locale\SourceString;
10 * Defines a translation edit form.
14 class TranslateEditForm extends TranslateFormBase {
19 public function getFormId() {
20 return 'locale_translate_edit_form';
26 public function buildForm(array $form, FormStateInterface $form_state) {
27 $filter_values = $this->translateFilterValues();
28 $langcode = $filter_values['langcode'];
30 $this->languageManager->reset();
31 $languages = $this->languageManager->getLanguages();
33 $langname = isset($langcode) ? $languages[$langcode]->getName() : "- None -";
35 $form['#attached']['library'][] = 'locale/drupal.locale.admin';
39 '#value' => $filter_values['langcode'],
45 '#language' => $langname,
47 $this->t('Source string'),
48 $this->t('Translation for @language', ['@language' => $langname]),
50 '#empty' => $this->t('No strings available.'),
51 '#attributes' => ['class' => ['locale-translate-edit-table']],
54 if (isset($langcode)) {
55 $strings = $this->translateFilterLoadStrings();
57 $plurals = $this->getNumberOfPlurals($langcode);
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.
68 $form['strings'][$string->lid]['original'] = [
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>',
78 // Add original string value and mark as plural.
80 $original_singular = [
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>',
89 '#title' => $this->t('Plural form'),
90 '#plain_text' => $source_array[1],
91 '#preffix' => '<span lang="en">',
92 '#suffix' => '</span>',
94 $form['strings'][$string->lid]['original'] = [
96 ['#markup' => '<br>'],
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>',
105 'context_title' => $this->t('In Context'),
106 'context' => $string->context,
110 // Approximate the number of rows to use in the default textarea.
111 $rows = min(ceil(str_word_count($source_array[0]) / 12), 10);
113 $form['strings'][$string->lid]['translations'][0] = [
114 '#type' => 'textarea',
115 '#title' => $this->t('Translated string (@language)', ['@language' => $langname]),
116 '#title_display' => 'invisible',
118 '#default_value' => $translation_array[0],
119 '#attributes' => ['lang' => $langcode],
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')),
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>') : '',
136 // Simplify interface text for the most common case.
137 $form['strings'][$string->lid]['translations'][1]['#title'] = $this->t('Plural form');
141 if (count(Element::children($form['strings']))) {
142 $form['actions'] = ['#type' => 'actions'];
143 $form['actions']['submit'] = [
145 '#value' => $this->t('Save translations'),
149 $form['pager']['#type'] = 'pager';
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]);
172 public function submitForm(array &$form, FormStateInterface $form_state) {
173 $langcode = $form_state->getValue('langcode');
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;
183 foreach ($form_state->getValue('strings') as $lid => $new_translation) {
184 $existing_translation = isset($existing_translation_objects[$lid]);
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']);
191 // Generate an imploded string without delimiter, to be able to run
193 $new_translation_string = implode('', $new_translation['translations']);
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.
202 elseif (!$existing_translation && !empty($new_translation_string)) {
203 // Newly entered translation.
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'])
213 $updated[] = $target->getId();
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();
222 drupal_set_message($this->t('The strings have been saved.'));
224 // Keep the user on the current pager page.
225 $page = $this->getRequest()->query->get('page');
227 $form_state->setRedirect(
228 'locale.translate_page',
235 // Clear cache and force refresh of JavaScript translations.
236 _locale_refresh_translations([$langcode], $updated);
237 _locale_refresh_configuration([$langcode], $updated);