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