Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Symfony\Component\Translation;
13
 
14
use Symfony\Contracts\Translation\TranslatorInterface;
15
 
16
/**
17
 * This translator should only be used in a development environment.
18
 */
19
final class PseudoLocalizationTranslator implements TranslatorInterface
20
{
21
    private const EXPANSION_CHARACTER = '~';
22
 
23
    private TranslatorInterface $translator;
24
    private bool $accents;
25
    private float $expansionFactor;
26
    private bool $brackets;
27
    private bool $parseHTML;
28
 
29
    /**
30
     * @var string[]
31
     */
32
    private array $localizableHTMLAttributes;
33
 
34
    /**
35
     * Available options:
36
     *  * accents:
37
     *      type: boolean
38
     *      default: true
39
     *      description: replace ASCII characters of the translated string with accented versions or similar characters
40
     *      example: if true, "foo" => "ƒöö".
41
     *
42
     *  * expansion_factor:
43
     *      type: float
44
     *      default: 1
45
     *      validation: it must be greater than or equal to 1
46
     *      description: expand the translated string by the given factor with spaces and tildes
47
     *      example: if 2, "foo" => "~foo ~"
48
     *
49
     *  * brackets:
50
     *      type: boolean
51
     *      default: true
52
     *      description: wrap the translated string with brackets
53
     *      example: if true, "foo" => "[foo]"
54
     *
55
     *  * parse_html:
56
     *      type: boolean
57
     *      default: false
58
     *      description: parse the translated string as HTML - looking for HTML tags has a performance impact but allows to preserve them from alterations - it also allows to compute the visible translated string length which is useful to correctly expand ot when it contains HTML
59
     *      warning: unclosed tags are unsupported, they will be fixed (closed) by the parser - eg, "foo <div>bar" => "foo <div>bar</div>"
60
     *
61
     *  * localizable_html_attributes:
62
     *      type: string[]
63
     *      default: []
64
     *      description: the list of HTML attributes whose values can be altered - it is only useful when the "parse_html" option is set to true
65
     *      example: if ["title"], and with the "accents" option set to true, "<a href="#" title="Go to your profile">Profile</a>" => "<a href="#" title="Ĝö ţö ýöûŕ þŕöƒîļé">Þŕöƒîļé</a>" - if "title" was not in the "localizable_html_attributes" list, the title attribute data would be left unchanged.
66
     */
67
    public function __construct(TranslatorInterface $translator, array $options = [])
68
    {
69
        $this->translator = $translator;
70
        $this->accents = $options['accents'] ?? true;
71
 
72
        if (1.0 > ($this->expansionFactor = $options['expansion_factor'] ?? 1.0)) {
73
            throw new \InvalidArgumentException('The expansion factor must be greater than or equal to 1.');
74
        }
75
 
76
        $this->brackets = $options['brackets'] ?? true;
77
 
78
        $this->parseHTML = $options['parse_html'] ?? false;
79
        if ($this->parseHTML && !$this->accents && 1.0 === $this->expansionFactor) {
80
            $this->parseHTML = false;
81
        }
82
 
83
        $this->localizableHTMLAttributes = $options['localizable_html_attributes'] ?? [];
84
    }
85
 
86
    public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string
87
    {
88
        $trans = '';
89
        $visibleText = '';
90
 
91
        foreach ($this->getParts($this->translator->trans($id, $parameters, $domain, $locale)) as [$visible, $localizable, $text]) {
92
            if ($visible) {
93
                $visibleText .= $text;
94
            }
95
 
96
            if (!$localizable) {
97
                $trans .= $text;
98
 
99
                continue;
100
            }
101
 
102
            $this->addAccents($trans, $text);
103
        }
104
 
105
        $this->expand($trans, $visibleText);
106
 
107
        $this->addBrackets($trans);
108
 
109
        return $trans;
110
    }
111
 
112
    public function getLocale(): string
113
    {
114
        return $this->translator->getLocale();
115
    }
116
 
117
    private function getParts(string $originalTrans): array
118
    {
119
        if (!$this->parseHTML) {
120
            return [[true, true, $originalTrans]];
121
        }
122
 
123
        $html = mb_encode_numericentity($originalTrans, [0x80, 0xFFFF, 0, 0xFFFF], mb_detect_encoding($originalTrans, null, true) ?: 'UTF-8');
124
 
125
        $useInternalErrors = libxml_use_internal_errors(true);
126
 
127
        $dom = new \DOMDocument();
128
        $dom->loadHTML('<trans>'.$html.'</trans>');
129
 
130
        libxml_clear_errors();
131
        libxml_use_internal_errors($useInternalErrors);
132
 
133
        return $this->parseNode($dom->childNodes->item(1)->childNodes->item(0)->childNodes->item(0));
134
    }
135
 
136
    private function parseNode(\DOMNode $node): array
137
    {
138
        $parts = [];
139
 
140
        foreach ($node->childNodes as $childNode) {
141
            if (!$childNode instanceof \DOMElement) {
142
                $parts[] = [true, true, $childNode->nodeValue];
143
 
144
                continue;
145
            }
146
 
147
            $parts[] = [false, false, '<'.$childNode->tagName];
148
 
149
            /** @var \DOMAttr $attribute */
150
            foreach ($childNode->attributes as $attribute) {
151
                $parts[] = [false, false, ' '.$attribute->nodeName.'="'];
152
 
153
                $localizableAttribute = \in_array($attribute->nodeName, $this->localizableHTMLAttributes, true);
154
                foreach (preg_split('/(&(?:amp|quot|#039|lt|gt);+)/', htmlspecialchars($attribute->nodeValue, \ENT_QUOTES, 'UTF-8'), -1, \PREG_SPLIT_DELIM_CAPTURE) as $i => $match) {
155
                    if ('' === $match) {
156
                        continue;
157
                    }
158
 
159
                    $parts[] = [false, $localizableAttribute && 0 === $i % 2, $match];
160
                }
161
 
162
                $parts[] = [false, false, '"'];
163
            }
164
 
165
            $parts[] = [false, false, '>'];
166
 
167
            $parts = array_merge($parts, $this->parseNode($childNode, $parts));
168
 
169
            $parts[] = [false, false, '</'.$childNode->tagName.'>'];
170
        }
171
 
172
        return $parts;
173
    }
174
 
175
    private function addAccents(string &$trans, string $text): void
176
    {
177
        $trans .= $this->accents ? strtr($text, [
178
            ' ' => ' ',
179
            '!' => '¡',
180
            '"' => '″',
181
            '#' => '♯',
182
            '$' => '€',
183
            '%' => '‰',
184
            '&' => '⅋',
185
            '\'' => '´',
186
            '(' => '{',
187
            ')' => '}',
188
            '*' => '⁎',
189
            '+' => '⁺',
190
            ',' => '،',
191
            '-' => '‐',
192
            '.' => '·',
193
            '/' => '⁄',
194
            '0' => '⓪',
195
            '1' => '①',
196
            '2' => 'â‘¡',
197
            '3' => 'â‘¢',
198
            '4' => 'â‘£',
199
            '5' => '⑤',
200
            '6' => 'â‘¥',
201
            '7' => '⑦',
202
            '8' => 'â‘§',
203
            '9' => '⑨',
204
            ':' => '∶',
205
            ';' => '⁏',
206
            '<' => '≤',
207
            '=' => '≂',
208
            '>' => '≥',
209
            '?' => '¿',
210
            '@' => 'Õž',
211
            'A' => 'Å',
212
            'B' => 'Ɓ',
213
            'C' => 'Ç',
214
            'D' => 'Ð',
215
            'E' => 'É',
216
            'F' => 'Æ‘',
217
            'G' => 'Ĝ',
218
            'H' => 'Ĥ',
219
            'I' => 'Î',
220
            'J' => 'Ä´',
221
            'K' => 'Ķ',
222
            'L' => 'Ä»',
223
            'M' => 'á¹€',
224
            'N' => 'Ñ',
225
            'O' => 'Ö',
226
            'P' => 'Þ',
227
            'Q' => 'Ǫ',
228
            'R' => 'Å”',
229
            'S' => 'Š',
230
            'T' => 'Å¢',
231
            'U' => 'Û',
232
            'V' => 'á¹¼',
233
            'W' => 'Å´',
234
            'X' => 'Ẋ',
235
            'Y' => 'Ý',
236
            'Z' => 'Ž',
237
            '[' => '⁅',
238
            '\\' => '∖',
239
            ']' => '⁆',
240
            '^' => 'Ë„',
241
            '_' => '‿',
242
            '`' => '‵',
243
            'a' => 'å',
244
            'b' => 'Æ€',
245
            'c' => 'ç',
246
            'd' => 'ð',
247
            'e' => 'é',
248
            'f' => 'Æ’',
249
            'g' => 'ĝ',
250
            'h' => 'Ä¥',
251
            'i' => 'î',
252
            'j' => 'ĵ',
253
            'k' => 'Ä·',
254
            'l' => 'ļ',
255
            'm' => 'ɱ',
256
            'n' => 'ñ',
257
            'o' => 'ö',
258
            'p' => 'þ',
259
            'q' => 'Ç«',
260
            'r' => 'Å•',
261
            's' => 'š',
262
            't' => 'Å£',
263
            'u' => 'û',
264
            'v' => 'á¹½',
265
            'w' => 'ŵ',
266
            'x' => 'ẋ',
267
            'y' => 'ý',
268
            'z' => 'ž',
269
            '{' => '(',
270
            '|' => '¦',
271
            '}' => ')',
272
            '~' => 'Ëž',
273
        ]) : $text;
274
    }
275
 
276
    private function expand(string &$trans, string $visibleText): void
277
    {
278
        if (1.0 >= $this->expansionFactor) {
279
            return;
280
        }
281
 
282
        $visibleLength = $this->strlen($visibleText);
283
        $missingLength = (int) ceil($visibleLength * $this->expansionFactor) - $visibleLength;
284
        if ($this->brackets) {
285
            $missingLength -= 2;
286
        }
287
 
288
        if (0 >= $missingLength) {
289
            return;
290
        }
291
 
292
        $words = [];
293
        $wordsCount = 0;
294
        foreach (preg_split('/ +/', $visibleText, -1, \PREG_SPLIT_NO_EMPTY) as $word) {
295
            $wordLength = $this->strlen($word);
296
 
297
            if ($wordLength >= $missingLength) {
298
                continue;
299
            }
300
 
301
            if (!isset($words[$wordLength])) {
302
                $words[$wordLength] = 0;
303
            }
304
 
305
            ++$words[$wordLength];
306
            ++$wordsCount;
307
        }
308
 
309
        if (!$words) {
310
            $trans .= 1 === $missingLength ? self::EXPANSION_CHARACTER : ' '.str_repeat(self::EXPANSION_CHARACTER, $missingLength - 1);
311
 
312
            return;
313
        }
314
 
315
        arsort($words, \SORT_NUMERIC);
316
 
317
        $longestWordLength = max(array_keys($words));
318
 
319
        while (true) {
320
            $r = mt_rand(1, $wordsCount);
321
 
322
            foreach ($words as $length => $count) {
323
                $r -= $count;
324
                if ($r <= 0) {
325
                    break;
326
                }
327
            }
328
 
329
            $trans .= ' '.str_repeat(self::EXPANSION_CHARACTER, $length);
330
 
331
            $missingLength -= $length + 1;
332
 
333
            if (0 === $missingLength) {
334
                return;
335
            }
336
 
337
            while ($longestWordLength >= $missingLength) {
338
                $wordsCount -= $words[$longestWordLength];
339
                unset($words[$longestWordLength]);
340
 
341
                if (!$words) {
342
                    $trans .= 1 === $missingLength ? self::EXPANSION_CHARACTER : ' '.str_repeat(self::EXPANSION_CHARACTER, $missingLength - 1);
343
 
344
                    return;
345
                }
346
 
347
                $longestWordLength = max(array_keys($words));
348
            }
349
        }
350
    }
351
 
352
    private function addBrackets(string &$trans): void
353
    {
354
        if (!$this->brackets) {
355
            return;
356
        }
357
 
358
        $trans = '['.$trans.']';
359
    }
360
 
361
    private function strlen(string $s): int
362
    {
363
        return false === ($encoding = mb_detect_encoding($s, null, true)) ? \strlen($s) : mb_strlen($s, $encoding);
364
    }
365
}