1<?php
2
3namespace Drupal\Core\Validation;
4
5use Drupal\Component\Render\MarkupInterface;
6use Drupal\Core\StringTranslation\TranslatableMarkup;
7
8/**
9 * Translates strings using Drupal's translation system.
10 *
11 * This class is used by the Symfony validator to translate violation messages.
12 */
13class DrupalTranslator implements TranslatorInterface {
14
15  /**
16   * The locale used for translating.
17   *
18   * @var string
19   */
20  protected $locale;
21
22  /**
23   * {@inheritdoc}
24   */
25  public function trans($id, array $parameters = [], $domain = NULL, $locale = NULL) {
26    // If a TranslatableMarkup object is passed in as $id, return it since the
27    // message has already been translated.
28    if ($id instanceof TranslatableMarkup) {
29      return $id;
30    }
31    return new TranslatableMarkup($id, $this->processParameters($parameters), $this->getOptions($domain, $locale));
32  }
33
34  /**
35   * {@inheritdoc}
36   */
37  public function transChoice($id, $number, array $parameters = [], $domain = NULL, $locale = NULL) {
38    // Violation messages can separated singular and plural versions by "|".
39    $ids = explode('|', $id);
40
41    if (!isset($ids[1])) {
42      throw new \InvalidArgumentException(sprintf('The message "%s" cannot be pluralized, because it is missing a plural (e.g. "There is one apple|There are @count apples").', $id));
43    }
44
45    // Normally, calls to formatPlural() need to use literal strings, like
46    //   formatPlural($count, '1 item', '@count items')
47    // so that the Drupal project POTX string extractor will correctly
48    // extract the strings for translation and save them in a format that
49    // formatPlural() can work with. However, this is a special case, because
50    // Drupal is supporting a constraint message format from Symfony. So
51    // although $id looks like a variable here, it is actually coming from a
52    // static string in a constraint class that the POTX extractor knows about
53    // and has processed to work with formatPlural(), so this specific call to
54    // formatPlural() will work correctly.
55    return \Drupal::translation()->formatPlural($number, $ids[0], $ids[1], $this->processParameters($parameters), $this->getOptions($domain, $locale));
56  }
57
58  /**
59   * {@inheritdoc}
60   */
61  public function setLocale($locale) {
62    $this->locale = $locale;
63  }
64
65  /**
66   * {@inheritdoc}
67   */
68  public function getLocale() {
69    return $this->locale ? $this->locale : \Drupal::languageManager()->getCurrentLanguage()->getId();
70  }
71
72  /**
73   * Processes the parameters array for use with TranslatableMarkup.
74   */
75  protected function processParameters(array $parameters) {
76    $return = [];
77    foreach ($parameters as $key => $value) {
78      // We allow the values in the parameters to be safe string objects. This
79      // can be useful when we want to use parameter values that are
80      // TranslatableMarkup.
81      if ($value instanceof MarkupInterface) {
82        $value = (string) $value;
83      }
84      if (is_object($value)) {
85        // TranslatableMarkup does not work with objects being passed as
86        // replacement strings.
87      }
88      // Check for symfony replacement patterns in the form "{{ name }}".
89      elseif (strpos($key, '{{ ') === 0 && strrpos($key, ' }}') == strlen($key) - 3) {
90        // Transform it into a Drupal pattern using the format %name.
91        $key = '%' . substr($key, 3, strlen($key) - 6);
92        $return[$key] = $value;
93      }
94      else {
95        $return[$key] = $value;
96      }
97    }
98    return $return;
99  }
100
101  /**
102   * Returns options suitable for use with TranslatableMarkup.
103   */
104  protected function getOptions($domain = NULL, $locale = NULL) {
105    // We do not support domains, so we ignore this parameter.
106    // If locale is left NULL, TranslatableMarkup will default to the interface
107    // language.
108    $locale = isset($locale) ? $locale : $this->locale;
109    return ['langcode' => $locale];
110  }
111
112}
113