@@ -11,160 +11,160 @@ |
||
11 | 11 | */ |
12 | 12 | class PhpCode extends Extractor implements ExtractorInterface, ExtractorMultiInterface |
13 | 13 | { |
14 | - public static $options = [ |
|
15 | - // - false: to not extract comments |
|
16 | - // - empty string: to extract all comments |
|
17 | - // - non-empty string: to extract comments that start with that string |
|
18 | - // - array with strings to extract comments format. |
|
19 | - 'extractComments' => false, |
|
20 | - |
|
21 | - 'constants' => [], |
|
22 | - |
|
23 | - 'functions' => [ |
|
24 | - 'gettext' => 'gettext', |
|
25 | - '__' => 'gettext', |
|
26 | - 'ngettext' => 'ngettext', |
|
27 | - 'n__' => 'ngettext', |
|
28 | - 'pgettext' => 'pgettext', |
|
29 | - 'p__' => 'pgettext', |
|
30 | - 'dgettext' => 'dgettext', |
|
31 | - 'd__' => 'dgettext', |
|
32 | - 'dngettext' => 'dngettext', |
|
33 | - 'dn__' => 'dngettext', |
|
34 | - 'dpgettext' => 'dpgettext', |
|
35 | - 'dp__' => 'dpgettext', |
|
36 | - 'npgettext' => 'npgettext', |
|
37 | - 'np__' => 'npgettext', |
|
38 | - 'dnpgettext' => 'dnpgettext', |
|
39 | - 'dnp__' => 'dnpgettext', |
|
40 | - 'noop' => 'noop', |
|
41 | - 'noop__' => 'noop', |
|
42 | - ], |
|
43 | - ]; |
|
44 | - |
|
45 | - protected static $functionsScannerClass = 'Gettext\Utils\PhpFunctionsScanner'; |
|
46 | - |
|
47 | - /** |
|
48 | - * {@inheritdoc} |
|
49 | - * @throws Exception |
|
50 | - */ |
|
51 | - public static function fromString($string, Translations $translations, array $options = []) |
|
52 | - { |
|
53 | - static::fromStringMultiple($string, [$translations], $options); |
|
54 | - } |
|
55 | - |
|
56 | - /** |
|
57 | - * @inheritDoc |
|
58 | - * @throws Exception |
|
59 | - */ |
|
60 | - public static function fromStringMultiple($string, array $translations, array $options = []) |
|
61 | - { |
|
62 | - $options += static::$options; |
|
63 | - |
|
64 | - /** @var FunctionsScanner $functions */ |
|
65 | - $functions = new static::$functionsScannerClass($string); |
|
66 | - |
|
67 | - if ($options['extractComments'] !== false) { |
|
68 | - $functions->enableCommentsExtraction($options['extractComments']); |
|
69 | - } |
|
70 | - |
|
71 | - $functions->saveGettextFunctions($translations, $options); |
|
72 | - } |
|
73 | - |
|
74 | - /** |
|
75 | - * @inheritDoc |
|
76 | - */ |
|
77 | - public static function fromFileMultiple($file, array $translations, array $options = []) |
|
78 | - { |
|
79 | - foreach (static::getFiles($file) as $file) { |
|
80 | - $options['file'] = $file; |
|
81 | - static::fromStringMultiple(static::readFile($file), $translations, $options); |
|
82 | - } |
|
83 | - } |
|
84 | - |
|
85 | - |
|
86 | - /** |
|
87 | - * Decodes a T_CONSTANT_ENCAPSED_STRING string. |
|
88 | - * |
|
89 | - * @param string $value |
|
90 | - * |
|
91 | - * @return string |
|
92 | - */ |
|
93 | - public static function convertString($value) |
|
94 | - { |
|
95 | - if (strpos($value, '\\') === false) { |
|
96 | - return substr($value, 1, -1); |
|
97 | - } |
|
98 | - |
|
99 | - if ($value[0] === "'") { |
|
100 | - return strtr(substr($value, 1, -1), ['\\\\' => '\\', '\\\'' => '\'']); |
|
101 | - } |
|
102 | - |
|
103 | - $value = substr($value, 1, -1); |
|
104 | - |
|
105 | - return preg_replace_callback( |
|
106 | - '/\\\(n|r|t|v|e|f|\$|"|\\\|x[0-9A-Fa-f]{1,2}|u{[0-9a-f]{1,6}}|[0-7]{1,3})/', |
|
107 | - function ($match) { |
|
108 | - switch ($match[1][0]) { |
|
109 | - case 'n': |
|
110 | - return "\n"; |
|
111 | - case 'r': |
|
112 | - return "\r"; |
|
113 | - case 't': |
|
114 | - return "\t"; |
|
115 | - case 'v': |
|
116 | - return "\v"; |
|
117 | - case 'e': |
|
118 | - return "\e"; |
|
119 | - case 'f': |
|
120 | - return "\f"; |
|
121 | - case '$': |
|
122 | - return '$'; |
|
123 | - case '"': |
|
124 | - return '"'; |
|
125 | - case '\\': |
|
126 | - return '\\'; |
|
127 | - case 'x': |
|
128 | - return chr(hexdec(substr($match[1], 1))); |
|
129 | - case 'u': |
|
130 | - return static::unicodeChar(hexdec(substr($match[1], 1))); |
|
131 | - default: |
|
132 | - return chr(octdec($match[1])); |
|
133 | - } |
|
134 | - }, |
|
135 | - $value |
|
136 | - ); |
|
137 | - } |
|
138 | - |
|
139 | - /** |
|
140 | - * @param $dec |
|
141 | - * @return string|null |
|
142 | - * @see http://php.net/manual/en/function.chr.php#118804 |
|
143 | - */ |
|
144 | - protected static function unicodeChar($dec) |
|
145 | - { |
|
146 | - if ($dec < 0x80) { |
|
147 | - return chr($dec); |
|
148 | - } |
|
149 | - |
|
150 | - if ($dec < 0x0800) { |
|
151 | - return chr(0xC0 + ($dec >> 6)) |
|
152 | - . chr(0x80 + ($dec & 0x3f)); |
|
153 | - } |
|
154 | - |
|
155 | - if ($dec < 0x010000) { |
|
156 | - return chr(0xE0 + ($dec >> 12)) |
|
157 | - . chr(0x80 + (($dec >> 6) & 0x3f)) |
|
158 | - . chr(0x80 + ($dec & 0x3f)); |
|
159 | - } |
|
160 | - |
|
161 | - if ($dec < 0x200000) { |
|
162 | - return chr(0xF0 + ($dec >> 18)) |
|
163 | - . chr(0x80 + (($dec >> 12) & 0x3f)) |
|
164 | - . chr(0x80 + (($dec >> 6) & 0x3f)) |
|
165 | - . chr(0x80 + ($dec & 0x3f)); |
|
166 | - } |
|
167 | - |
|
168 | - return null; |
|
169 | - } |
|
14 | + public static $options = [ |
|
15 | + // - false: to not extract comments |
|
16 | + // - empty string: to extract all comments |
|
17 | + // - non-empty string: to extract comments that start with that string |
|
18 | + // - array with strings to extract comments format. |
|
19 | + 'extractComments' => false, |
|
20 | + |
|
21 | + 'constants' => [], |
|
22 | + |
|
23 | + 'functions' => [ |
|
24 | + 'gettext' => 'gettext', |
|
25 | + '__' => 'gettext', |
|
26 | + 'ngettext' => 'ngettext', |
|
27 | + 'n__' => 'ngettext', |
|
28 | + 'pgettext' => 'pgettext', |
|
29 | + 'p__' => 'pgettext', |
|
30 | + 'dgettext' => 'dgettext', |
|
31 | + 'd__' => 'dgettext', |
|
32 | + 'dngettext' => 'dngettext', |
|
33 | + 'dn__' => 'dngettext', |
|
34 | + 'dpgettext' => 'dpgettext', |
|
35 | + 'dp__' => 'dpgettext', |
|
36 | + 'npgettext' => 'npgettext', |
|
37 | + 'np__' => 'npgettext', |
|
38 | + 'dnpgettext' => 'dnpgettext', |
|
39 | + 'dnp__' => 'dnpgettext', |
|
40 | + 'noop' => 'noop', |
|
41 | + 'noop__' => 'noop', |
|
42 | + ], |
|
43 | + ]; |
|
44 | + |
|
45 | + protected static $functionsScannerClass = 'Gettext\Utils\PhpFunctionsScanner'; |
|
46 | + |
|
47 | + /** |
|
48 | + * {@inheritdoc} |
|
49 | + * @throws Exception |
|
50 | + */ |
|
51 | + public static function fromString($string, Translations $translations, array $options = []) |
|
52 | + { |
|
53 | + static::fromStringMultiple($string, [$translations], $options); |
|
54 | + } |
|
55 | + |
|
56 | + /** |
|
57 | + * @inheritDoc |
|
58 | + * @throws Exception |
|
59 | + */ |
|
60 | + public static function fromStringMultiple($string, array $translations, array $options = []) |
|
61 | + { |
|
62 | + $options += static::$options; |
|
63 | + |
|
64 | + /** @var FunctionsScanner $functions */ |
|
65 | + $functions = new static::$functionsScannerClass($string); |
|
66 | + |
|
67 | + if ($options['extractComments'] !== false) { |
|
68 | + $functions->enableCommentsExtraction($options['extractComments']); |
|
69 | + } |
|
70 | + |
|
71 | + $functions->saveGettextFunctions($translations, $options); |
|
72 | + } |
|
73 | + |
|
74 | + /** |
|
75 | + * @inheritDoc |
|
76 | + */ |
|
77 | + public static function fromFileMultiple($file, array $translations, array $options = []) |
|
78 | + { |
|
79 | + foreach (static::getFiles($file) as $file) { |
|
80 | + $options['file'] = $file; |
|
81 | + static::fromStringMultiple(static::readFile($file), $translations, $options); |
|
82 | + } |
|
83 | + } |
|
84 | + |
|
85 | + |
|
86 | + /** |
|
87 | + * Decodes a T_CONSTANT_ENCAPSED_STRING string. |
|
88 | + * |
|
89 | + * @param string $value |
|
90 | + * |
|
91 | + * @return string |
|
92 | + */ |
|
93 | + public static function convertString($value) |
|
94 | + { |
|
95 | + if (strpos($value, '\\') === false) { |
|
96 | + return substr($value, 1, -1); |
|
97 | + } |
|
98 | + |
|
99 | + if ($value[0] === "'") { |
|
100 | + return strtr(substr($value, 1, -1), ['\\\\' => '\\', '\\\'' => '\'']); |
|
101 | + } |
|
102 | + |
|
103 | + $value = substr($value, 1, -1); |
|
104 | + |
|
105 | + return preg_replace_callback( |
|
106 | + '/\\\(n|r|t|v|e|f|\$|"|\\\|x[0-9A-Fa-f]{1,2}|u{[0-9a-f]{1,6}}|[0-7]{1,3})/', |
|
107 | + function ($match) { |
|
108 | + switch ($match[1][0]) { |
|
109 | + case 'n': |
|
110 | + return "\n"; |
|
111 | + case 'r': |
|
112 | + return "\r"; |
|
113 | + case 't': |
|
114 | + return "\t"; |
|
115 | + case 'v': |
|
116 | + return "\v"; |
|
117 | + case 'e': |
|
118 | + return "\e"; |
|
119 | + case 'f': |
|
120 | + return "\f"; |
|
121 | + case '$': |
|
122 | + return '$'; |
|
123 | + case '"': |
|
124 | + return '"'; |
|
125 | + case '\\': |
|
126 | + return '\\'; |
|
127 | + case 'x': |
|
128 | + return chr(hexdec(substr($match[1], 1))); |
|
129 | + case 'u': |
|
130 | + return static::unicodeChar(hexdec(substr($match[1], 1))); |
|
131 | + default: |
|
132 | + return chr(octdec($match[1])); |
|
133 | + } |
|
134 | + }, |
|
135 | + $value |
|
136 | + ); |
|
137 | + } |
|
138 | + |
|
139 | + /** |
|
140 | + * @param $dec |
|
141 | + * @return string|null |
|
142 | + * @see http://php.net/manual/en/function.chr.php#118804 |
|
143 | + */ |
|
144 | + protected static function unicodeChar($dec) |
|
145 | + { |
|
146 | + if ($dec < 0x80) { |
|
147 | + return chr($dec); |
|
148 | + } |
|
149 | + |
|
150 | + if ($dec < 0x0800) { |
|
151 | + return chr(0xC0 + ($dec >> 6)) |
|
152 | + . chr(0x80 + ($dec & 0x3f)); |
|
153 | + } |
|
154 | + |
|
155 | + if ($dec < 0x010000) { |
|
156 | + return chr(0xE0 + ($dec >> 12)) |
|
157 | + . chr(0x80 + (($dec >> 6) & 0x3f)) |
|
158 | + . chr(0x80 + ($dec & 0x3f)); |
|
159 | + } |
|
160 | + |
|
161 | + if ($dec < 0x200000) { |
|
162 | + return chr(0xF0 + ($dec >> 18)) |
|
163 | + . chr(0x80 + (($dec >> 12) & 0x3f)) |
|
164 | + . chr(0x80 + (($dec >> 6) & 0x3f)) |
|
165 | + . chr(0x80 + ($dec & 0x3f)); |
|
166 | + } |
|
167 | + |
|
168 | + return null; |
|
169 | + } |
|
170 | 170 | } |
@@ -10,17 +10,17 @@ |
||
10 | 10 | */ |
11 | 11 | class JsonDictionary extends Extractor implements ExtractorInterface |
12 | 12 | { |
13 | - use DictionaryTrait; |
|
13 | + use DictionaryTrait; |
|
14 | 14 | |
15 | - /** |
|
16 | - * {@inheritdoc} |
|
17 | - */ |
|
18 | - public static function fromString($string, Translations $translations, array $options = []) |
|
19 | - { |
|
20 | - $messages = json_decode($string, true); |
|
15 | + /** |
|
16 | + * {@inheritdoc} |
|
17 | + */ |
|
18 | + public static function fromString($string, Translations $translations, array $options = []) |
|
19 | + { |
|
20 | + $messages = json_decode($string, true); |
|
21 | 21 | |
22 | - if (is_array($messages)) { |
|
23 | - static::fromArray($messages, $translations); |
|
24 | - } |
|
25 | - } |
|
22 | + if (is_array($messages)) { |
|
23 | + static::fromArray($messages, $translations); |
|
24 | + } |
|
25 | + } |
|
26 | 26 | } |
@@ -11,121 +11,121 @@ |
||
11 | 11 | */ |
12 | 12 | class Mo extends Extractor implements ExtractorInterface |
13 | 13 | { |
14 | - const MAGIC1 = -1794895138; |
|
15 | - const MAGIC2 = -569244523; |
|
16 | - const MAGIC3 = 2500072158; |
|
17 | - |
|
18 | - protected static $stringReaderClass = 'Gettext\Utils\StringReader'; |
|
19 | - |
|
20 | - /** |
|
21 | - * {@inheritdoc} |
|
22 | - */ |
|
23 | - public static function fromString($string, Translations $translations, array $options = []) |
|
24 | - { |
|
25 | - /** @var StringReader $stream */ |
|
26 | - $stream = new static::$stringReaderClass($string); |
|
27 | - $magic = static::readInt($stream, 'V'); |
|
28 | - |
|
29 | - if (($magic === static::MAGIC1) || ($magic === static::MAGIC3)) { //to make sure it works for 64-bit platforms |
|
30 | - $byteOrder = 'V'; //low endian |
|
31 | - } elseif ($magic === (static::MAGIC2 & 0xFFFFFFFF)) { |
|
32 | - $byteOrder = 'N'; //big endian |
|
33 | - } else { |
|
34 | - throw new Exception('Not MO file'); |
|
35 | - } |
|
36 | - |
|
37 | - static::readInt($stream, $byteOrder); |
|
38 | - |
|
39 | - $total = static::readInt($stream, $byteOrder); //total string count |
|
40 | - $originals = static::readInt($stream, $byteOrder); //offset of original table |
|
41 | - $tran = static::readInt($stream, $byteOrder); //offset of translation table |
|
42 | - |
|
43 | - $stream->seekto($originals); |
|
44 | - $table_originals = static::readIntArray($stream, $byteOrder, $total * 2); |
|
45 | - |
|
46 | - $stream->seekto($tran); |
|
47 | - $table_translations = static::readIntArray($stream, $byteOrder, $total * 2); |
|
48 | - |
|
49 | - for ($i = 0; $i < $total; ++$i) { |
|
50 | - $next = $i * 2; |
|
51 | - |
|
52 | - $stream->seekto($table_originals[$next + 2]); |
|
53 | - $original = $stream->read($table_originals[$next + 1]); |
|
54 | - |
|
55 | - $stream->seekto($table_translations[$next + 2]); |
|
56 | - $translated = $stream->read($table_translations[$next + 1]); |
|
57 | - |
|
58 | - if ($original === '') { |
|
59 | - // Headers |
|
60 | - foreach (explode("\n", $translated) as $headerLine) { |
|
61 | - if ($headerLine === '') { |
|
62 | - continue; |
|
63 | - } |
|
64 | - |
|
65 | - $headerChunks = preg_split('/:\s*/', $headerLine, 2); |
|
66 | - $translations->setHeader($headerChunks[0], isset($headerChunks[1]) ? $headerChunks[1] : ''); |
|
67 | - } |
|
68 | - |
|
69 | - continue; |
|
70 | - } |
|
71 | - |
|
72 | - $chunks = explode("\x04", $original, 2); |
|
73 | - |
|
74 | - if (isset($chunks[1])) { |
|
75 | - $context = $chunks[0]; |
|
76 | - $original = $chunks[1]; |
|
77 | - } else { |
|
78 | - $context = ''; |
|
79 | - } |
|
80 | - |
|
81 | - $chunks = explode("\x00", $original, 2); |
|
82 | - |
|
83 | - if (isset($chunks[1])) { |
|
84 | - $original = $chunks[0]; |
|
85 | - $plural = $chunks[1]; |
|
86 | - } else { |
|
87 | - $plural = ''; |
|
88 | - } |
|
89 | - |
|
90 | - $translation = $translations->insert($context, $original, $plural); |
|
91 | - |
|
92 | - if ($translated === '') { |
|
93 | - continue; |
|
94 | - } |
|
95 | - |
|
96 | - if ($plural === '') { |
|
97 | - $translation->setTranslation($translated); |
|
98 | - continue; |
|
99 | - } |
|
100 | - |
|
101 | - $v = explode("\x00", $translated); |
|
102 | - $translation->setTranslation(array_shift($v)); |
|
103 | - $translation->setPluralTranslations($v); |
|
104 | - } |
|
105 | - } |
|
106 | - |
|
107 | - /** |
|
108 | - * @param StringReader $stream |
|
109 | - * @param string $byteOrder |
|
110 | - */ |
|
111 | - protected static function readInt(StringReader $stream, $byteOrder) |
|
112 | - { |
|
113 | - if (($read = $stream->read(4)) === false) { |
|
114 | - return false; |
|
115 | - } |
|
116 | - |
|
117 | - $read = unpack($byteOrder, $read); |
|
118 | - |
|
119 | - return array_shift($read); |
|
120 | - } |
|
121 | - |
|
122 | - /** |
|
123 | - * @param StringReader $stream |
|
124 | - * @param string $byteOrder |
|
125 | - * @param int $count |
|
126 | - */ |
|
127 | - protected static function readIntArray(StringReader $stream, $byteOrder, $count) |
|
128 | - { |
|
129 | - return unpack($byteOrder.$count, $stream->read(4 * $count)); |
|
130 | - } |
|
14 | + const MAGIC1 = -1794895138; |
|
15 | + const MAGIC2 = -569244523; |
|
16 | + const MAGIC3 = 2500072158; |
|
17 | + |
|
18 | + protected static $stringReaderClass = 'Gettext\Utils\StringReader'; |
|
19 | + |
|
20 | + /** |
|
21 | + * {@inheritdoc} |
|
22 | + */ |
|
23 | + public static function fromString($string, Translations $translations, array $options = []) |
|
24 | + { |
|
25 | + /** @var StringReader $stream */ |
|
26 | + $stream = new static::$stringReaderClass($string); |
|
27 | + $magic = static::readInt($stream, 'V'); |
|
28 | + |
|
29 | + if (($magic === static::MAGIC1) || ($magic === static::MAGIC3)) { //to make sure it works for 64-bit platforms |
|
30 | + $byteOrder = 'V'; //low endian |
|
31 | + } elseif ($magic === (static::MAGIC2 & 0xFFFFFFFF)) { |
|
32 | + $byteOrder = 'N'; //big endian |
|
33 | + } else { |
|
34 | + throw new Exception('Not MO file'); |
|
35 | + } |
|
36 | + |
|
37 | + static::readInt($stream, $byteOrder); |
|
38 | + |
|
39 | + $total = static::readInt($stream, $byteOrder); //total string count |
|
40 | + $originals = static::readInt($stream, $byteOrder); //offset of original table |
|
41 | + $tran = static::readInt($stream, $byteOrder); //offset of translation table |
|
42 | + |
|
43 | + $stream->seekto($originals); |
|
44 | + $table_originals = static::readIntArray($stream, $byteOrder, $total * 2); |
|
45 | + |
|
46 | + $stream->seekto($tran); |
|
47 | + $table_translations = static::readIntArray($stream, $byteOrder, $total * 2); |
|
48 | + |
|
49 | + for ($i = 0; $i < $total; ++$i) { |
|
50 | + $next = $i * 2; |
|
51 | + |
|
52 | + $stream->seekto($table_originals[$next + 2]); |
|
53 | + $original = $stream->read($table_originals[$next + 1]); |
|
54 | + |
|
55 | + $stream->seekto($table_translations[$next + 2]); |
|
56 | + $translated = $stream->read($table_translations[$next + 1]); |
|
57 | + |
|
58 | + if ($original === '') { |
|
59 | + // Headers |
|
60 | + foreach (explode("\n", $translated) as $headerLine) { |
|
61 | + if ($headerLine === '') { |
|
62 | + continue; |
|
63 | + } |
|
64 | + |
|
65 | + $headerChunks = preg_split('/:\s*/', $headerLine, 2); |
|
66 | + $translations->setHeader($headerChunks[0], isset($headerChunks[1]) ? $headerChunks[1] : ''); |
|
67 | + } |
|
68 | + |
|
69 | + continue; |
|
70 | + } |
|
71 | + |
|
72 | + $chunks = explode("\x04", $original, 2); |
|
73 | + |
|
74 | + if (isset($chunks[1])) { |
|
75 | + $context = $chunks[0]; |
|
76 | + $original = $chunks[1]; |
|
77 | + } else { |
|
78 | + $context = ''; |
|
79 | + } |
|
80 | + |
|
81 | + $chunks = explode("\x00", $original, 2); |
|
82 | + |
|
83 | + if (isset($chunks[1])) { |
|
84 | + $original = $chunks[0]; |
|
85 | + $plural = $chunks[1]; |
|
86 | + } else { |
|
87 | + $plural = ''; |
|
88 | + } |
|
89 | + |
|
90 | + $translation = $translations->insert($context, $original, $plural); |
|
91 | + |
|
92 | + if ($translated === '') { |
|
93 | + continue; |
|
94 | + } |
|
95 | + |
|
96 | + if ($plural === '') { |
|
97 | + $translation->setTranslation($translated); |
|
98 | + continue; |
|
99 | + } |
|
100 | + |
|
101 | + $v = explode("\x00", $translated); |
|
102 | + $translation->setTranslation(array_shift($v)); |
|
103 | + $translation->setPluralTranslations($v); |
|
104 | + } |
|
105 | + } |
|
106 | + |
|
107 | + /** |
|
108 | + * @param StringReader $stream |
|
109 | + * @param string $byteOrder |
|
110 | + */ |
|
111 | + protected static function readInt(StringReader $stream, $byteOrder) |
|
112 | + { |
|
113 | + if (($read = $stream->read(4)) === false) { |
|
114 | + return false; |
|
115 | + } |
|
116 | + |
|
117 | + $read = unpack($byteOrder, $read); |
|
118 | + |
|
119 | + return array_shift($read); |
|
120 | + } |
|
121 | + |
|
122 | + /** |
|
123 | + * @param StringReader $stream |
|
124 | + * @param string $byteOrder |
|
125 | + * @param int $count |
|
126 | + */ |
|
127 | + protected static function readIntArray(StringReader $stream, $byteOrder, $count) |
|
128 | + { |
|
129 | + return unpack($byteOrder.$count, $stream->read(4 * $count)); |
|
130 | + } |
|
131 | 131 | } |
@@ -9,47 +9,47 @@ |
||
9 | 9 | */ |
10 | 10 | class Jed extends Extractor implements ExtractorInterface |
11 | 11 | { |
12 | - /** |
|
13 | - * {@inheritdoc} |
|
14 | - */ |
|
15 | - public static function fromString($string, Translations $translations, array $options = []) |
|
16 | - { |
|
17 | - static::extract(json_decode($string, true), $translations); |
|
18 | - } |
|
19 | - |
|
20 | - /** |
|
21 | - * Handle an array of translations and append to the Translations instance. |
|
22 | - * |
|
23 | - * @param array $content |
|
24 | - * @param Translations $translations |
|
25 | - */ |
|
26 | - public static function extract(array $content, Translations $translations) |
|
27 | - { |
|
28 | - $messages = current($content); |
|
29 | - $headers = isset($messages['']) ? $messages[''] : null; |
|
30 | - unset($messages['']); |
|
31 | - |
|
32 | - if (!empty($headers['domain'])) { |
|
33 | - $translations->setDomain($headers['domain']); |
|
34 | - } |
|
35 | - |
|
36 | - if (!empty($headers['lang'])) { |
|
37 | - $translations->setLanguage($headers['lang']); |
|
38 | - } |
|
39 | - |
|
40 | - if (!empty($headers['plural-forms'])) { |
|
41 | - $translations->setHeader(Translations::HEADER_PLURAL, $headers['plural-forms']); |
|
42 | - } |
|
43 | - |
|
44 | - $context_glue = '\u0004'; |
|
45 | - |
|
46 | - foreach ($messages as $key => $translation) { |
|
47 | - $key = explode($context_glue, $key); |
|
48 | - $context = isset($key[1]) ? array_shift($key) : ''; |
|
49 | - |
|
50 | - $translations->insert($context, array_shift($key)) |
|
51 | - ->setTranslation(array_shift($translation)) |
|
52 | - ->setPluralTranslations($translation); |
|
53 | - } |
|
54 | - } |
|
12 | + /** |
|
13 | + * {@inheritdoc} |
|
14 | + */ |
|
15 | + public static function fromString($string, Translations $translations, array $options = []) |
|
16 | + { |
|
17 | + static::extract(json_decode($string, true), $translations); |
|
18 | + } |
|
19 | + |
|
20 | + /** |
|
21 | + * Handle an array of translations and append to the Translations instance. |
|
22 | + * |
|
23 | + * @param array $content |
|
24 | + * @param Translations $translations |
|
25 | + */ |
|
26 | + public static function extract(array $content, Translations $translations) |
|
27 | + { |
|
28 | + $messages = current($content); |
|
29 | + $headers = isset($messages['']) ? $messages[''] : null; |
|
30 | + unset($messages['']); |
|
31 | + |
|
32 | + if (!empty($headers['domain'])) { |
|
33 | + $translations->setDomain($headers['domain']); |
|
34 | + } |
|
35 | + |
|
36 | + if (!empty($headers['lang'])) { |
|
37 | + $translations->setLanguage($headers['lang']); |
|
38 | + } |
|
39 | + |
|
40 | + if (!empty($headers['plural-forms'])) { |
|
41 | + $translations->setHeader(Translations::HEADER_PLURAL, $headers['plural-forms']); |
|
42 | + } |
|
43 | + |
|
44 | + $context_glue = '\u0004'; |
|
45 | + |
|
46 | + foreach ($messages as $key => $translation) { |
|
47 | + $key = explode($context_glue, $key); |
|
48 | + $context = isset($key[1]) ? array_shift($key) : ''; |
|
49 | + |
|
50 | + $translations->insert($context, array_shift($key)) |
|
51 | + ->setTranslation(array_shift($translation)) |
|
52 | + ->setPluralTranslations($translation); |
|
53 | + } |
|
54 | + } |
|
55 | 55 | } |
@@ -11,205 +11,205 @@ |
||
11 | 11 | */ |
12 | 12 | class Po extends Extractor implements ExtractorInterface |
13 | 13 | { |
14 | - use HeadersExtractorTrait; |
|
15 | - |
|
16 | - /** |
|
17 | - * Parses a .po file and append the translations found in the Translations instance. |
|
18 | - * |
|
19 | - * {@inheritdoc} |
|
20 | - */ |
|
21 | - public static function fromString($string, Translations $translations, array $options = []) |
|
22 | - { |
|
23 | - $lines = explode("\n", $string); |
|
24 | - $i = 0; |
|
25 | - |
|
26 | - $translation = $translations->createNewTranslation('', ''); |
|
27 | - |
|
28 | - for ($n = count($lines); $i < $n; ++$i) { |
|
29 | - $line = trim($lines[$i]); |
|
30 | - $line = static::fixMultiLines($line, $lines, $i); |
|
31 | - |
|
32 | - if ($line === '') { |
|
33 | - if ($translation->is('', '')) { |
|
34 | - static::extractHeaders($translation->getTranslation(), $translations); |
|
35 | - } elseif ($translation->hasOriginal()) { |
|
36 | - $translations[] = $translation; |
|
37 | - } |
|
38 | - |
|
39 | - $translation = $translations->createNewTranslation('', ''); |
|
40 | - continue; |
|
41 | - } |
|
42 | - |
|
43 | - $splitLine = preg_split('/\s+/', $line, 2); |
|
44 | - $key = $splitLine[0]; |
|
45 | - $data = isset($splitLine[1]) ? $splitLine[1] : ''; |
|
46 | - |
|
47 | - if ($key === '#~') { |
|
48 | - $translation->setDisabled(true); |
|
49 | - |
|
50 | - $splitLine = preg_split('/\s+/', $data, 2); |
|
51 | - $key = $splitLine[0]; |
|
52 | - $data = isset($splitLine[1]) ? $splitLine[1] : ''; |
|
53 | - } |
|
54 | - |
|
55 | - switch ($key) { |
|
56 | - case '#': |
|
57 | - $translation->addComment($data); |
|
58 | - $append = null; |
|
59 | - break; |
|
60 | - |
|
61 | - case '#.': |
|
62 | - $translation->addExtractedComment($data); |
|
63 | - $append = null; |
|
64 | - break; |
|
65 | - |
|
66 | - case '#,': |
|
67 | - foreach (array_map('trim', explode(',', trim($data))) as $value) { |
|
68 | - $translation->addFlag($value); |
|
69 | - } |
|
70 | - $append = null; |
|
71 | - break; |
|
72 | - |
|
73 | - case '#:': |
|
74 | - foreach (preg_split('/\s+/', trim($data)) as $value) { |
|
75 | - if (preg_match('/^(.+)(:(\d*))?$/U', $value, $matches)) { |
|
76 | - $translation->addReference($matches[1], isset($matches[3]) ? $matches[3] : null); |
|
77 | - } |
|
78 | - } |
|
79 | - $append = null; |
|
80 | - break; |
|
81 | - |
|
82 | - case 'msgctxt': |
|
83 | - $translation = $translation->getClone(static::convertString($data)); |
|
84 | - $append = 'Context'; |
|
85 | - break; |
|
86 | - |
|
87 | - case 'msgid': |
|
88 | - $translation = $translation->getClone(null, static::convertString($data)); |
|
89 | - $append = 'Original'; |
|
90 | - break; |
|
91 | - |
|
92 | - case 'msgid_plural': |
|
93 | - $translation->setPlural(static::convertString($data)); |
|
94 | - $append = 'Plural'; |
|
95 | - break; |
|
96 | - |
|
97 | - case 'msgstr': |
|
98 | - case 'msgstr[0]': |
|
99 | - $translation->setTranslation(static::convertString($data)); |
|
100 | - $append = 'Translation'; |
|
101 | - break; |
|
102 | - |
|
103 | - case 'msgstr[1]': |
|
104 | - $translation->setPluralTranslations([static::convertString($data)]); |
|
105 | - $append = 'PluralTranslation'; |
|
106 | - break; |
|
107 | - |
|
108 | - default: |
|
109 | - if (strpos($key, 'msgstr[') === 0) { |
|
110 | - $p = $translation->getPluralTranslations(); |
|
111 | - $p[] = static::convertString($data); |
|
112 | - |
|
113 | - $translation->setPluralTranslations($p); |
|
114 | - $append = 'PluralTranslation'; |
|
115 | - break; |
|
116 | - } |
|
117 | - |
|
118 | - if (isset($append)) { |
|
119 | - if ($append === 'Context') { |
|
120 | - $translation = $translation->getClone($translation->getContext() |
|
121 | - ."\n" |
|
122 | - .static::convertString($data)); |
|
123 | - break; |
|
124 | - } |
|
125 | - |
|
126 | - if ($append === 'Original') { |
|
127 | - $translation = $translation->getClone(null, $translation->getOriginal() |
|
128 | - ."\n" |
|
129 | - .static::convertString($data)); |
|
130 | - break; |
|
131 | - } |
|
132 | - |
|
133 | - if ($append === 'PluralTranslation') { |
|
134 | - $p = $translation->getPluralTranslations(); |
|
135 | - $p[] = array_pop($p)."\n".static::convertString($data); |
|
136 | - $translation->setPluralTranslations($p); |
|
137 | - break; |
|
138 | - } |
|
139 | - |
|
140 | - $getMethod = 'get'.$append; |
|
141 | - $setMethod = 'set'.$append; |
|
142 | - $translation->$setMethod($translation->$getMethod()."\n".static::convertString($data)); |
|
143 | - } |
|
144 | - break; |
|
145 | - } |
|
146 | - } |
|
147 | - |
|
148 | - if ($translation->hasOriginal() && !in_array($translation, iterator_to_array($translations))) { |
|
149 | - $translations[] = $translation; |
|
150 | - } |
|
151 | - } |
|
152 | - |
|
153 | - /** |
|
154 | - * Gets one string from multiline strings. |
|
155 | - * |
|
156 | - * @param string $line |
|
157 | - * @param array $lines |
|
158 | - * @param int &$i |
|
159 | - * |
|
160 | - * @return string |
|
161 | - */ |
|
162 | - protected static function fixMultiLines($line, array $lines, &$i) |
|
163 | - { |
|
164 | - for ($j = $i, $t = count($lines); $j < $t; ++$j) { |
|
165 | - if (substr($line, -1, 1) == '"' && isset($lines[$j + 1])) { |
|
166 | - $nextLine = trim($lines[$j + 1]); |
|
167 | - if (substr($nextLine, 0, 1) == '"') { |
|
168 | - $line = substr($line, 0, -1).substr($nextLine, 1); |
|
169 | - continue; |
|
170 | - } |
|
171 | - if (substr($nextLine, 0, 4) == '#~ "') { |
|
172 | - $line = substr($line, 0, -1).substr($nextLine, 4); |
|
173 | - continue; |
|
174 | - } |
|
175 | - } |
|
176 | - $i = $j; |
|
177 | - break; |
|
178 | - } |
|
179 | - |
|
180 | - return $line; |
|
181 | - } |
|
182 | - |
|
183 | - /** |
|
184 | - * Convert a string from its PO representation. |
|
185 | - * |
|
186 | - * @param string $value |
|
187 | - * |
|
188 | - * @return string |
|
189 | - */ |
|
190 | - public static function convertString($value) |
|
191 | - { |
|
192 | - if (!$value) { |
|
193 | - return ''; |
|
194 | - } |
|
195 | - |
|
196 | - if ($value[0] === '"') { |
|
197 | - $value = substr($value, 1, -1); |
|
198 | - } |
|
199 | - |
|
200 | - return strtr( |
|
201 | - $value, |
|
202 | - [ |
|
203 | - '\\\\' => '\\', |
|
204 | - '\\a' => "\x07", |
|
205 | - '\\b' => "\x08", |
|
206 | - '\\t' => "\t", |
|
207 | - '\\n' => "\n", |
|
208 | - '\\v' => "\x0b", |
|
209 | - '\\f' => "\x0c", |
|
210 | - '\\r' => "\r", |
|
211 | - '\\"' => '"', |
|
212 | - ] |
|
213 | - ); |
|
214 | - } |
|
14 | + use HeadersExtractorTrait; |
|
15 | + |
|
16 | + /** |
|
17 | + * Parses a .po file and append the translations found in the Translations instance. |
|
18 | + * |
|
19 | + * {@inheritdoc} |
|
20 | + */ |
|
21 | + public static function fromString($string, Translations $translations, array $options = []) |
|
22 | + { |
|
23 | + $lines = explode("\n", $string); |
|
24 | + $i = 0; |
|
25 | + |
|
26 | + $translation = $translations->createNewTranslation('', ''); |
|
27 | + |
|
28 | + for ($n = count($lines); $i < $n; ++$i) { |
|
29 | + $line = trim($lines[$i]); |
|
30 | + $line = static::fixMultiLines($line, $lines, $i); |
|
31 | + |
|
32 | + if ($line === '') { |
|
33 | + if ($translation->is('', '')) { |
|
34 | + static::extractHeaders($translation->getTranslation(), $translations); |
|
35 | + } elseif ($translation->hasOriginal()) { |
|
36 | + $translations[] = $translation; |
|
37 | + } |
|
38 | + |
|
39 | + $translation = $translations->createNewTranslation('', ''); |
|
40 | + continue; |
|
41 | + } |
|
42 | + |
|
43 | + $splitLine = preg_split('/\s+/', $line, 2); |
|
44 | + $key = $splitLine[0]; |
|
45 | + $data = isset($splitLine[1]) ? $splitLine[1] : ''; |
|
46 | + |
|
47 | + if ($key === '#~') { |
|
48 | + $translation->setDisabled(true); |
|
49 | + |
|
50 | + $splitLine = preg_split('/\s+/', $data, 2); |
|
51 | + $key = $splitLine[0]; |
|
52 | + $data = isset($splitLine[1]) ? $splitLine[1] : ''; |
|
53 | + } |
|
54 | + |
|
55 | + switch ($key) { |
|
56 | + case '#': |
|
57 | + $translation->addComment($data); |
|
58 | + $append = null; |
|
59 | + break; |
|
60 | + |
|
61 | + case '#.': |
|
62 | + $translation->addExtractedComment($data); |
|
63 | + $append = null; |
|
64 | + break; |
|
65 | + |
|
66 | + case '#,': |
|
67 | + foreach (array_map('trim', explode(',', trim($data))) as $value) { |
|
68 | + $translation->addFlag($value); |
|
69 | + } |
|
70 | + $append = null; |
|
71 | + break; |
|
72 | + |
|
73 | + case '#:': |
|
74 | + foreach (preg_split('/\s+/', trim($data)) as $value) { |
|
75 | + if (preg_match('/^(.+)(:(\d*))?$/U', $value, $matches)) { |
|
76 | + $translation->addReference($matches[1], isset($matches[3]) ? $matches[3] : null); |
|
77 | + } |
|
78 | + } |
|
79 | + $append = null; |
|
80 | + break; |
|
81 | + |
|
82 | + case 'msgctxt': |
|
83 | + $translation = $translation->getClone(static::convertString($data)); |
|
84 | + $append = 'Context'; |
|
85 | + break; |
|
86 | + |
|
87 | + case 'msgid': |
|
88 | + $translation = $translation->getClone(null, static::convertString($data)); |
|
89 | + $append = 'Original'; |
|
90 | + break; |
|
91 | + |
|
92 | + case 'msgid_plural': |
|
93 | + $translation->setPlural(static::convertString($data)); |
|
94 | + $append = 'Plural'; |
|
95 | + break; |
|
96 | + |
|
97 | + case 'msgstr': |
|
98 | + case 'msgstr[0]': |
|
99 | + $translation->setTranslation(static::convertString($data)); |
|
100 | + $append = 'Translation'; |
|
101 | + break; |
|
102 | + |
|
103 | + case 'msgstr[1]': |
|
104 | + $translation->setPluralTranslations([static::convertString($data)]); |
|
105 | + $append = 'PluralTranslation'; |
|
106 | + break; |
|
107 | + |
|
108 | + default: |
|
109 | + if (strpos($key, 'msgstr[') === 0) { |
|
110 | + $p = $translation->getPluralTranslations(); |
|
111 | + $p[] = static::convertString($data); |
|
112 | + |
|
113 | + $translation->setPluralTranslations($p); |
|
114 | + $append = 'PluralTranslation'; |
|
115 | + break; |
|
116 | + } |
|
117 | + |
|
118 | + if (isset($append)) { |
|
119 | + if ($append === 'Context') { |
|
120 | + $translation = $translation->getClone($translation->getContext() |
|
121 | + ."\n" |
|
122 | + .static::convertString($data)); |
|
123 | + break; |
|
124 | + } |
|
125 | + |
|
126 | + if ($append === 'Original') { |
|
127 | + $translation = $translation->getClone(null, $translation->getOriginal() |
|
128 | + ."\n" |
|
129 | + .static::convertString($data)); |
|
130 | + break; |
|
131 | + } |
|
132 | + |
|
133 | + if ($append === 'PluralTranslation') { |
|
134 | + $p = $translation->getPluralTranslations(); |
|
135 | + $p[] = array_pop($p)."\n".static::convertString($data); |
|
136 | + $translation->setPluralTranslations($p); |
|
137 | + break; |
|
138 | + } |
|
139 | + |
|
140 | + $getMethod = 'get'.$append; |
|
141 | + $setMethod = 'set'.$append; |
|
142 | + $translation->$setMethod($translation->$getMethod()."\n".static::convertString($data)); |
|
143 | + } |
|
144 | + break; |
|
145 | + } |
|
146 | + } |
|
147 | + |
|
148 | + if ($translation->hasOriginal() && !in_array($translation, iterator_to_array($translations))) { |
|
149 | + $translations[] = $translation; |
|
150 | + } |
|
151 | + } |
|
152 | + |
|
153 | + /** |
|
154 | + * Gets one string from multiline strings. |
|
155 | + * |
|
156 | + * @param string $line |
|
157 | + * @param array $lines |
|
158 | + * @param int &$i |
|
159 | + * |
|
160 | + * @return string |
|
161 | + */ |
|
162 | + protected static function fixMultiLines($line, array $lines, &$i) |
|
163 | + { |
|
164 | + for ($j = $i, $t = count($lines); $j < $t; ++$j) { |
|
165 | + if (substr($line, -1, 1) == '"' && isset($lines[$j + 1])) { |
|
166 | + $nextLine = trim($lines[$j + 1]); |
|
167 | + if (substr($nextLine, 0, 1) == '"') { |
|
168 | + $line = substr($line, 0, -1).substr($nextLine, 1); |
|
169 | + continue; |
|
170 | + } |
|
171 | + if (substr($nextLine, 0, 4) == '#~ "') { |
|
172 | + $line = substr($line, 0, -1).substr($nextLine, 4); |
|
173 | + continue; |
|
174 | + } |
|
175 | + } |
|
176 | + $i = $j; |
|
177 | + break; |
|
178 | + } |
|
179 | + |
|
180 | + return $line; |
|
181 | + } |
|
182 | + |
|
183 | + /** |
|
184 | + * Convert a string from its PO representation. |
|
185 | + * |
|
186 | + * @param string $value |
|
187 | + * |
|
188 | + * @return string |
|
189 | + */ |
|
190 | + public static function convertString($value) |
|
191 | + { |
|
192 | + if (!$value) { |
|
193 | + return ''; |
|
194 | + } |
|
195 | + |
|
196 | + if ($value[0] === '"') { |
|
197 | + $value = substr($value, 1, -1); |
|
198 | + } |
|
199 | + |
|
200 | + return strtr( |
|
201 | + $value, |
|
202 | + [ |
|
203 | + '\\\\' => '\\', |
|
204 | + '\\a' => "\x07", |
|
205 | + '\\b' => "\x08", |
|
206 | + '\\t' => "\t", |
|
207 | + '\\n' => "\n", |
|
208 | + '\\v' => "\x0b", |
|
209 | + '\\f' => "\x0c", |
|
210 | + '\\r' => "\r", |
|
211 | + '\\"' => '"', |
|
212 | + ] |
|
213 | + ); |
|
214 | + } |
|
215 | 215 | } |
@@ -16,408 +16,408 @@ |
||
16 | 16 | */ |
17 | 17 | class VueJs extends Extractor implements ExtractorInterface, ExtractorMultiInterface |
18 | 18 | { |
19 | - public static $options = [ |
|
20 | - 'constants' => [], |
|
21 | - |
|
22 | - 'functions' => [ |
|
23 | - 'gettext' => 'gettext', |
|
24 | - '__' => 'gettext', |
|
25 | - 'ngettext' => 'ngettext', |
|
26 | - 'n__' => 'ngettext', |
|
27 | - 'pgettext' => 'pgettext', |
|
28 | - 'p__' => 'pgettext', |
|
29 | - 'dgettext' => 'dgettext', |
|
30 | - 'd__' => 'dgettext', |
|
31 | - 'dngettext' => 'dngettext', |
|
32 | - 'dn__' => 'dngettext', |
|
33 | - 'dpgettext' => 'dpgettext', |
|
34 | - 'dp__' => 'dpgettext', |
|
35 | - 'npgettext' => 'npgettext', |
|
36 | - 'np__' => 'npgettext', |
|
37 | - 'dnpgettext' => 'dnpgettext', |
|
38 | - 'dnp__' => 'dnpgettext', |
|
39 | - 'noop' => 'noop', |
|
40 | - 'noop__' => 'noop', |
|
41 | - ], |
|
42 | - ]; |
|
43 | - |
|
44 | - protected static $functionsScannerClass = 'Gettext\Utils\JsFunctionsScanner'; |
|
45 | - |
|
46 | - /** |
|
47 | - * @inheritDoc |
|
48 | - * @throws Exception |
|
49 | - */ |
|
50 | - public static function fromFileMultiple($file, array $translations, array $options = []) |
|
51 | - { |
|
52 | - foreach (static::getFiles($file) as $file) { |
|
53 | - $options['file'] = $file; |
|
54 | - static::fromStringMultiple(static::readFile($file), $translations, $options); |
|
55 | - } |
|
56 | - } |
|
57 | - |
|
58 | - /** |
|
59 | - * @inheritdoc |
|
60 | - * @throws Exception |
|
61 | - */ |
|
62 | - public static function fromString($string, Translations $translations, array $options = []) |
|
63 | - { |
|
64 | - static::fromStringMultiple($string, [$translations], $options); |
|
65 | - } |
|
66 | - |
|
67 | - /** |
|
68 | - * @inheritDoc |
|
69 | - * @throws Exception |
|
70 | - */ |
|
71 | - public static function fromStringMultiple($string, array $translations, array $options = []) |
|
72 | - { |
|
73 | - $options += static::$options; |
|
74 | - $options += [ |
|
75 | - // HTML attribute prefixes we parse as JS which could contain translations (are JS expressions) |
|
76 | - 'attributePrefixes' => [ |
|
77 | - ':', |
|
78 | - 'v-bind:', |
|
79 | - 'v-on:', |
|
80 | - 'v-text', |
|
81 | - ], |
|
82 | - // HTML Tags to parse |
|
83 | - 'tagNames' => [ |
|
84 | - 'translate', |
|
85 | - ], |
|
86 | - // HTML tags to parse when attribute exists |
|
87 | - 'tagAttributes' => [ |
|
88 | - 'v-translate', |
|
89 | - ], |
|
90 | - // Comments |
|
91 | - 'commentAttributes' => [ |
|
92 | - 'translate-comment', |
|
93 | - ], |
|
94 | - 'contextAttributes' => [ |
|
95 | - 'translate-context', |
|
96 | - ], |
|
97 | - // Attribute with plural content |
|
98 | - 'pluralAttributes' => [ |
|
99 | - 'translate-plural', |
|
100 | - ], |
|
101 | - ]; |
|
102 | - |
|
103 | - // Ok, this is the weirdest hack, but let me explain: |
|
104 | - // On Linux (Mac is fine), when converting HTML to DOM, new lines get trimmed after the first tag. |
|
105 | - // So if there are new lines between <template> and next element, they are lost |
|
106 | - // So we insert a "." which is a text node, and it will prevent that newlines are stripped between elements. |
|
107 | - // Same thing happens between template and script tag. |
|
108 | - $string = str_replace('<template>', '<template>.', $string); |
|
109 | - $string = str_replace('</template>', '</template>.', $string); |
|
110 | - |
|
111 | - // Normalize newlines |
|
112 | - $string = str_replace(["\r\n", "\n\r", "\r"], "\n", $string); |
|
113 | - |
|
114 | - // VueJS files are valid HTML files, we will operate with the DOM here |
|
115 | - $dom = static::convertHtmlToDom($string); |
|
116 | - |
|
117 | - $script = static::extractScriptTag($string); |
|
118 | - |
|
119 | - // Parse the script part as a regular JS code |
|
120 | - if ($script) { |
|
121 | - $scriptLineNumber = $dom->getElementsByTagName('script')->item(0)->getLineNo(); |
|
122 | - static::getScriptTranslationsFromString( |
|
123 | - $script, |
|
124 | - $translations, |
|
125 | - $options, |
|
126 | - $scriptLineNumber - 1 |
|
127 | - ); |
|
128 | - } |
|
129 | - |
|
130 | - // Template part is parsed separately, all variables will be extracted |
|
131 | - // and handled as a regular JS code |
|
132 | - $template = $dom->getElementsByTagName('template')->item(0); |
|
133 | - if ($template) { |
|
134 | - static::getTemplateTranslations( |
|
135 | - $template, |
|
136 | - $translations, |
|
137 | - $options, |
|
138 | - $template->getLineNo() - 1 |
|
139 | - ); |
|
140 | - } |
|
141 | - } |
|
142 | - |
|
143 | - /** |
|
144 | - * Extracts script tag contents using regex instead of DOM operations. |
|
145 | - * If we parse using DOM, some contents may change, for example, tags within strings will be stripped |
|
146 | - * |
|
147 | - * @param $string |
|
148 | - * @return bool|string |
|
149 | - */ |
|
150 | - protected static function extractScriptTag($string) |
|
151 | - { |
|
152 | - if (preg_match('#<\s*?script\b[^>]*>(.*?)</script\b[^>]*>#s', $string, $matches)) { |
|
153 | - return $matches[1]; |
|
154 | - } |
|
155 | - |
|
156 | - return ''; |
|
157 | - } |
|
158 | - |
|
159 | - /** |
|
160 | - * @param string $html |
|
161 | - * @return DOMDocument |
|
162 | - */ |
|
163 | - protected static function convertHtmlToDom($html) |
|
164 | - { |
|
165 | - $dom = new DOMDocument; |
|
166 | - |
|
167 | - libxml_use_internal_errors(true); |
|
168 | - |
|
169 | - // Prepend xml encoding so DOMDocument document handles UTF8 correctly. |
|
170 | - // Assuming that vue template files will not have any xml encoding tags, because duplicate tags may be ignored. |
|
171 | - $dom->loadHTML('<?xml encoding="utf-8"?>' . $html); |
|
172 | - |
|
173 | - libxml_clear_errors(); |
|
174 | - |
|
175 | - return $dom; |
|
176 | - } |
|
177 | - |
|
178 | - /** |
|
179 | - * Extract translations from script part |
|
180 | - * |
|
181 | - * @param string $scriptContents Only script tag contents, not the whole template |
|
182 | - * @param Translations|Translations[] $translations One or multiple domain Translation objects |
|
183 | - * @param array $options |
|
184 | - * @param int $lineOffset Number of lines the script is offset in the vue template file |
|
185 | - * @throws Exception |
|
186 | - */ |
|
187 | - protected static function getScriptTranslationsFromString( |
|
188 | - $scriptContents, |
|
189 | - $translations, |
|
190 | - array $options = [], |
|
191 | - $lineOffset = 0 |
|
192 | - ) { |
|
193 | - /** @var FunctionsScanner $functions */ |
|
194 | - $functions = new static::$functionsScannerClass($scriptContents); |
|
195 | - $options['lineOffset'] = $lineOffset; |
|
196 | - $functions->saveGettextFunctions($translations, $options); |
|
197 | - } |
|
198 | - |
|
199 | - /** |
|
200 | - * Parse template to extract all translations (element content and dynamic element attributes) |
|
201 | - * |
|
202 | - * @param DOMNode $dom |
|
203 | - * @param Translations|Translations[] $translations One or multiple domain Translation objects |
|
204 | - * @param array $options |
|
205 | - * @param int $lineOffset Line number where the template part starts in the vue file |
|
206 | - * @throws Exception |
|
207 | - */ |
|
208 | - protected static function getTemplateTranslations( |
|
209 | - DOMNode $dom, |
|
210 | - $translations, |
|
211 | - array $options, |
|
212 | - $lineOffset = 0 |
|
213 | - ) { |
|
214 | - // Build a JS string from all template attribute expressions |
|
215 | - $fakeAttributeJs = static::getTemplateAttributeFakeJs($options, $dom); |
|
216 | - |
|
217 | - // 1 line offset is necessary because parent template element was ignored when converting to DOM |
|
218 | - static::getScriptTranslationsFromString($fakeAttributeJs, $translations, $options, $lineOffset); |
|
219 | - |
|
220 | - // Build a JS string from template element content expressions |
|
221 | - $fakeTemplateJs = static::getTemplateFakeJs($dom); |
|
222 | - static::getScriptTranslationsFromString($fakeTemplateJs, $translations, $options, $lineOffset); |
|
223 | - |
|
224 | - static::getTagTranslations($options, $dom, $translations); |
|
225 | - } |
|
226 | - |
|
227 | - /** |
|
228 | - * @param array $options |
|
229 | - * @param DOMNode $dom |
|
230 | - * @param Translations|Translations[] $translations |
|
231 | - */ |
|
232 | - protected static function getTagTranslations(array $options, DOMNode $dom, $translations) |
|
233 | - { |
|
234 | - // Since tag scanning does not support domains, we always use the first translation given |
|
235 | - $translations = is_array($translations) ? reset($translations) : $translations; |
|
236 | - |
|
237 | - $children = $dom->childNodes; |
|
238 | - for ($i = 0; $i < $children->length; $i++) { |
|
239 | - $node = $children->item($i); |
|
240 | - |
|
241 | - if (!($node instanceof DOMElement)) { |
|
242 | - continue; |
|
243 | - } |
|
244 | - |
|
245 | - $translatable = false; |
|
246 | - |
|
247 | - if (in_array($node->tagName, $options['tagNames'], true)) { |
|
248 | - $translatable = true; |
|
249 | - } |
|
250 | - |
|
251 | - $attrList = $node->attributes; |
|
252 | - $context = null; |
|
253 | - $plural = ""; |
|
254 | - $comment = null; |
|
255 | - |
|
256 | - for ($j = 0; $j < $attrList->length; $j++) { |
|
257 | - /** @var DOMAttr $domAttr */ |
|
258 | - $domAttr = $attrList->item($j); |
|
259 | - // Check if this is a dynamic vue attribute |
|
260 | - if (in_array($domAttr->name, $options['tagAttributes'])) { |
|
261 | - $translatable = true; |
|
262 | - } |
|
263 | - if (in_array($domAttr->name, $options['contextAttributes'])) { |
|
264 | - $context = $domAttr->value; |
|
265 | - } |
|
266 | - if (in_array($domAttr->name, $options['pluralAttributes'])) { |
|
267 | - $plural = $domAttr->value; |
|
268 | - } |
|
269 | - if (in_array($domAttr->name, $options['commentAttributes'])) { |
|
270 | - $comment = $domAttr->value; |
|
271 | - } |
|
272 | - } |
|
273 | - |
|
274 | - if ($translatable) { |
|
275 | - $translation = $translations->insert($context, trim($node->textContent), $plural); |
|
276 | - $translation->addReference($options['file'], $node->getLineNo()); |
|
277 | - if ($comment) { |
|
278 | - $translation->addExtractedComment($comment); |
|
279 | - } |
|
280 | - } |
|
281 | - |
|
282 | - if ($node->hasChildNodes()) { |
|
283 | - static::getTagTranslations($options, $node, $translations); |
|
284 | - } |
|
285 | - } |
|
286 | - } |
|
287 | - |
|
288 | - /** |
|
289 | - * Extract JS expressions from element attribute bindings (excluding text within elements) |
|
290 | - * For example: <span :title="__('extract this')"> skip element content </span> |
|
291 | - * |
|
292 | - * @param array $options |
|
293 | - * @param DOMNode $dom |
|
294 | - * @return string JS code |
|
295 | - */ |
|
296 | - protected static function getTemplateAttributeFakeJs(array $options, DOMNode $dom) |
|
297 | - { |
|
298 | - $expressionsByLine = static::getVueAttributeExpressions($options['attributePrefixes'], $dom); |
|
299 | - |
|
300 | - if (empty($expressionsByLine)) { |
|
301 | - return ''; |
|
302 | - } |
|
303 | - |
|
304 | - $maxLines = max(array_keys($expressionsByLine)); |
|
305 | - $fakeJs = ''; |
|
306 | - |
|
307 | - for ($line = 1; $line <= $maxLines; $line++) { |
|
308 | - if (isset($expressionsByLine[$line])) { |
|
309 | - $fakeJs .= implode("; ", $expressionsByLine[$line]); |
|
310 | - } |
|
311 | - $fakeJs .= "\n"; |
|
312 | - } |
|
313 | - |
|
314 | - return $fakeJs; |
|
315 | - } |
|
316 | - |
|
317 | - /** |
|
318 | - * Loop DOM element recursively and parse out all dynamic vue attributes which are basically JS expressions |
|
319 | - * |
|
320 | - * @param array $attributePrefixes List of attribute prefixes we parse as JS (may contain translations) |
|
321 | - * @param DOMNode $dom |
|
322 | - * @param array $expressionByLine [lineNumber => [jsExpression, ..], ..] |
|
323 | - * @return array [lineNumber => [jsExpression, ..], ..] |
|
324 | - */ |
|
325 | - protected static function getVueAttributeExpressions( |
|
326 | - array $attributePrefixes, |
|
327 | - DOMNode $dom, |
|
328 | - array &$expressionByLine = [] |
|
329 | - ) { |
|
330 | - $children = $dom->childNodes; |
|
331 | - |
|
332 | - for ($i = 0; $i < $children->length; $i++) { |
|
333 | - $node = $children->item($i); |
|
334 | - |
|
335 | - if (!($node instanceof DOMElement)) { |
|
336 | - continue; |
|
337 | - } |
|
338 | - $attrList = $node->attributes; |
|
339 | - |
|
340 | - for ($j = 0; $j < $attrList->length; $j++) { |
|
341 | - /** @var DOMAttr $domAttr */ |
|
342 | - $domAttr = $attrList->item($j); |
|
343 | - |
|
344 | - // Check if this is a dynamic vue attribute |
|
345 | - if (static::isAttributeMatching($domAttr->name, $attributePrefixes)) { |
|
346 | - $line = $domAttr->getLineNo(); |
|
347 | - $expressionByLine += [$line => []]; |
|
348 | - $expressionByLine[$line][] = $domAttr->value; |
|
349 | - } |
|
350 | - } |
|
351 | - |
|
352 | - if ($node->hasChildNodes()) { |
|
353 | - $expressionByLine = static::getVueAttributeExpressions($attributePrefixes, $node, $expressionByLine); |
|
354 | - } |
|
355 | - } |
|
356 | - |
|
357 | - return $expressionByLine; |
|
358 | - } |
|
359 | - |
|
360 | - /** |
|
361 | - * Check if this attribute name should be parsed for translations |
|
362 | - * |
|
363 | - * @param string $attributeName |
|
364 | - * @param string[] $attributePrefixes |
|
365 | - * @return bool |
|
366 | - */ |
|
367 | - protected static function isAttributeMatching($attributeName, $attributePrefixes) |
|
368 | - { |
|
369 | - foreach ($attributePrefixes as $prefix) { |
|
370 | - if (strpos($attributeName, $prefix) === 0) { |
|
371 | - return true; |
|
372 | - } |
|
373 | - } |
|
374 | - return false; |
|
375 | - } |
|
376 | - |
|
377 | - /** |
|
378 | - * Extract JS expressions from within template elements (excluding attributes) |
|
379 | - * For example: <span :title="skip attributes"> {{__("extract element content")}} </span> |
|
380 | - * |
|
381 | - * @param DOMNode $dom |
|
382 | - * @return string JS code |
|
383 | - */ |
|
384 | - protected static function getTemplateFakeJs(DOMNode $dom) |
|
385 | - { |
|
386 | - $fakeJs = ''; |
|
387 | - $lines = explode("\n", $dom->textContent); |
|
388 | - |
|
389 | - // Build a fake JS file from template by extracting JS expressions within each template line |
|
390 | - foreach ($lines as $line) { |
|
391 | - $expressionMatched = static::parseOneTemplateLine($line); |
|
392 | - |
|
393 | - $fakeJs .= implode("; ", $expressionMatched) . "\n"; |
|
394 | - } |
|
395 | - |
|
396 | - return $fakeJs; |
|
397 | - } |
|
398 | - |
|
399 | - /** |
|
400 | - * Match JS expressions in a template line |
|
401 | - * |
|
402 | - * @param string $line |
|
403 | - * @return string[] |
|
404 | - */ |
|
405 | - protected static function parseOneTemplateLine($line) |
|
406 | - { |
|
407 | - $line = trim($line); |
|
408 | - |
|
409 | - if (!$line) { |
|
410 | - return []; |
|
411 | - } |
|
412 | - |
|
413 | - $regex = '#\{\{(.*?)\}\}#'; |
|
414 | - |
|
415 | - preg_match_all($regex, $line, $matches); |
|
416 | - |
|
417 | - $matched = array_map(function ($v) { |
|
418 | - return trim($v, '\'"{}'); |
|
419 | - }, $matches[1]); |
|
420 | - |
|
421 | - return $matched; |
|
422 | - } |
|
19 | + public static $options = [ |
|
20 | + 'constants' => [], |
|
21 | + |
|
22 | + 'functions' => [ |
|
23 | + 'gettext' => 'gettext', |
|
24 | + '__' => 'gettext', |
|
25 | + 'ngettext' => 'ngettext', |
|
26 | + 'n__' => 'ngettext', |
|
27 | + 'pgettext' => 'pgettext', |
|
28 | + 'p__' => 'pgettext', |
|
29 | + 'dgettext' => 'dgettext', |
|
30 | + 'd__' => 'dgettext', |
|
31 | + 'dngettext' => 'dngettext', |
|
32 | + 'dn__' => 'dngettext', |
|
33 | + 'dpgettext' => 'dpgettext', |
|
34 | + 'dp__' => 'dpgettext', |
|
35 | + 'npgettext' => 'npgettext', |
|
36 | + 'np__' => 'npgettext', |
|
37 | + 'dnpgettext' => 'dnpgettext', |
|
38 | + 'dnp__' => 'dnpgettext', |
|
39 | + 'noop' => 'noop', |
|
40 | + 'noop__' => 'noop', |
|
41 | + ], |
|
42 | + ]; |
|
43 | + |
|
44 | + protected static $functionsScannerClass = 'Gettext\Utils\JsFunctionsScanner'; |
|
45 | + |
|
46 | + /** |
|
47 | + * @inheritDoc |
|
48 | + * @throws Exception |
|
49 | + */ |
|
50 | + public static function fromFileMultiple($file, array $translations, array $options = []) |
|
51 | + { |
|
52 | + foreach (static::getFiles($file) as $file) { |
|
53 | + $options['file'] = $file; |
|
54 | + static::fromStringMultiple(static::readFile($file), $translations, $options); |
|
55 | + } |
|
56 | + } |
|
57 | + |
|
58 | + /** |
|
59 | + * @inheritdoc |
|
60 | + * @throws Exception |
|
61 | + */ |
|
62 | + public static function fromString($string, Translations $translations, array $options = []) |
|
63 | + { |
|
64 | + static::fromStringMultiple($string, [$translations], $options); |
|
65 | + } |
|
66 | + |
|
67 | + /** |
|
68 | + * @inheritDoc |
|
69 | + * @throws Exception |
|
70 | + */ |
|
71 | + public static function fromStringMultiple($string, array $translations, array $options = []) |
|
72 | + { |
|
73 | + $options += static::$options; |
|
74 | + $options += [ |
|
75 | + // HTML attribute prefixes we parse as JS which could contain translations (are JS expressions) |
|
76 | + 'attributePrefixes' => [ |
|
77 | + ':', |
|
78 | + 'v-bind:', |
|
79 | + 'v-on:', |
|
80 | + 'v-text', |
|
81 | + ], |
|
82 | + // HTML Tags to parse |
|
83 | + 'tagNames' => [ |
|
84 | + 'translate', |
|
85 | + ], |
|
86 | + // HTML tags to parse when attribute exists |
|
87 | + 'tagAttributes' => [ |
|
88 | + 'v-translate', |
|
89 | + ], |
|
90 | + // Comments |
|
91 | + 'commentAttributes' => [ |
|
92 | + 'translate-comment', |
|
93 | + ], |
|
94 | + 'contextAttributes' => [ |
|
95 | + 'translate-context', |
|
96 | + ], |
|
97 | + // Attribute with plural content |
|
98 | + 'pluralAttributes' => [ |
|
99 | + 'translate-plural', |
|
100 | + ], |
|
101 | + ]; |
|
102 | + |
|
103 | + // Ok, this is the weirdest hack, but let me explain: |
|
104 | + // On Linux (Mac is fine), when converting HTML to DOM, new lines get trimmed after the first tag. |
|
105 | + // So if there are new lines between <template> and next element, they are lost |
|
106 | + // So we insert a "." which is a text node, and it will prevent that newlines are stripped between elements. |
|
107 | + // Same thing happens between template and script tag. |
|
108 | + $string = str_replace('<template>', '<template>.', $string); |
|
109 | + $string = str_replace('</template>', '</template>.', $string); |
|
110 | + |
|
111 | + // Normalize newlines |
|
112 | + $string = str_replace(["\r\n", "\n\r", "\r"], "\n", $string); |
|
113 | + |
|
114 | + // VueJS files are valid HTML files, we will operate with the DOM here |
|
115 | + $dom = static::convertHtmlToDom($string); |
|
116 | + |
|
117 | + $script = static::extractScriptTag($string); |
|
118 | + |
|
119 | + // Parse the script part as a regular JS code |
|
120 | + if ($script) { |
|
121 | + $scriptLineNumber = $dom->getElementsByTagName('script')->item(0)->getLineNo(); |
|
122 | + static::getScriptTranslationsFromString( |
|
123 | + $script, |
|
124 | + $translations, |
|
125 | + $options, |
|
126 | + $scriptLineNumber - 1 |
|
127 | + ); |
|
128 | + } |
|
129 | + |
|
130 | + // Template part is parsed separately, all variables will be extracted |
|
131 | + // and handled as a regular JS code |
|
132 | + $template = $dom->getElementsByTagName('template')->item(0); |
|
133 | + if ($template) { |
|
134 | + static::getTemplateTranslations( |
|
135 | + $template, |
|
136 | + $translations, |
|
137 | + $options, |
|
138 | + $template->getLineNo() - 1 |
|
139 | + ); |
|
140 | + } |
|
141 | + } |
|
142 | + |
|
143 | + /** |
|
144 | + * Extracts script tag contents using regex instead of DOM operations. |
|
145 | + * If we parse using DOM, some contents may change, for example, tags within strings will be stripped |
|
146 | + * |
|
147 | + * @param $string |
|
148 | + * @return bool|string |
|
149 | + */ |
|
150 | + protected static function extractScriptTag($string) |
|
151 | + { |
|
152 | + if (preg_match('#<\s*?script\b[^>]*>(.*?)</script\b[^>]*>#s', $string, $matches)) { |
|
153 | + return $matches[1]; |
|
154 | + } |
|
155 | + |
|
156 | + return ''; |
|
157 | + } |
|
158 | + |
|
159 | + /** |
|
160 | + * @param string $html |
|
161 | + * @return DOMDocument |
|
162 | + */ |
|
163 | + protected static function convertHtmlToDom($html) |
|
164 | + { |
|
165 | + $dom = new DOMDocument; |
|
166 | + |
|
167 | + libxml_use_internal_errors(true); |
|
168 | + |
|
169 | + // Prepend xml encoding so DOMDocument document handles UTF8 correctly. |
|
170 | + // Assuming that vue template files will not have any xml encoding tags, because duplicate tags may be ignored. |
|
171 | + $dom->loadHTML('<?xml encoding="utf-8"?>' . $html); |
|
172 | + |
|
173 | + libxml_clear_errors(); |
|
174 | + |
|
175 | + return $dom; |
|
176 | + } |
|
177 | + |
|
178 | + /** |
|
179 | + * Extract translations from script part |
|
180 | + * |
|
181 | + * @param string $scriptContents Only script tag contents, not the whole template |
|
182 | + * @param Translations|Translations[] $translations One or multiple domain Translation objects |
|
183 | + * @param array $options |
|
184 | + * @param int $lineOffset Number of lines the script is offset in the vue template file |
|
185 | + * @throws Exception |
|
186 | + */ |
|
187 | + protected static function getScriptTranslationsFromString( |
|
188 | + $scriptContents, |
|
189 | + $translations, |
|
190 | + array $options = [], |
|
191 | + $lineOffset = 0 |
|
192 | + ) { |
|
193 | + /** @var FunctionsScanner $functions */ |
|
194 | + $functions = new static::$functionsScannerClass($scriptContents); |
|
195 | + $options['lineOffset'] = $lineOffset; |
|
196 | + $functions->saveGettextFunctions($translations, $options); |
|
197 | + } |
|
198 | + |
|
199 | + /** |
|
200 | + * Parse template to extract all translations (element content and dynamic element attributes) |
|
201 | + * |
|
202 | + * @param DOMNode $dom |
|
203 | + * @param Translations|Translations[] $translations One or multiple domain Translation objects |
|
204 | + * @param array $options |
|
205 | + * @param int $lineOffset Line number where the template part starts in the vue file |
|
206 | + * @throws Exception |
|
207 | + */ |
|
208 | + protected static function getTemplateTranslations( |
|
209 | + DOMNode $dom, |
|
210 | + $translations, |
|
211 | + array $options, |
|
212 | + $lineOffset = 0 |
|
213 | + ) { |
|
214 | + // Build a JS string from all template attribute expressions |
|
215 | + $fakeAttributeJs = static::getTemplateAttributeFakeJs($options, $dom); |
|
216 | + |
|
217 | + // 1 line offset is necessary because parent template element was ignored when converting to DOM |
|
218 | + static::getScriptTranslationsFromString($fakeAttributeJs, $translations, $options, $lineOffset); |
|
219 | + |
|
220 | + // Build a JS string from template element content expressions |
|
221 | + $fakeTemplateJs = static::getTemplateFakeJs($dom); |
|
222 | + static::getScriptTranslationsFromString($fakeTemplateJs, $translations, $options, $lineOffset); |
|
223 | + |
|
224 | + static::getTagTranslations($options, $dom, $translations); |
|
225 | + } |
|
226 | + |
|
227 | + /** |
|
228 | + * @param array $options |
|
229 | + * @param DOMNode $dom |
|
230 | + * @param Translations|Translations[] $translations |
|
231 | + */ |
|
232 | + protected static function getTagTranslations(array $options, DOMNode $dom, $translations) |
|
233 | + { |
|
234 | + // Since tag scanning does not support domains, we always use the first translation given |
|
235 | + $translations = is_array($translations) ? reset($translations) : $translations; |
|
236 | + |
|
237 | + $children = $dom->childNodes; |
|
238 | + for ($i = 0; $i < $children->length; $i++) { |
|
239 | + $node = $children->item($i); |
|
240 | + |
|
241 | + if (!($node instanceof DOMElement)) { |
|
242 | + continue; |
|
243 | + } |
|
244 | + |
|
245 | + $translatable = false; |
|
246 | + |
|
247 | + if (in_array($node->tagName, $options['tagNames'], true)) { |
|
248 | + $translatable = true; |
|
249 | + } |
|
250 | + |
|
251 | + $attrList = $node->attributes; |
|
252 | + $context = null; |
|
253 | + $plural = ""; |
|
254 | + $comment = null; |
|
255 | + |
|
256 | + for ($j = 0; $j < $attrList->length; $j++) { |
|
257 | + /** @var DOMAttr $domAttr */ |
|
258 | + $domAttr = $attrList->item($j); |
|
259 | + // Check if this is a dynamic vue attribute |
|
260 | + if (in_array($domAttr->name, $options['tagAttributes'])) { |
|
261 | + $translatable = true; |
|
262 | + } |
|
263 | + if (in_array($domAttr->name, $options['contextAttributes'])) { |
|
264 | + $context = $domAttr->value; |
|
265 | + } |
|
266 | + if (in_array($domAttr->name, $options['pluralAttributes'])) { |
|
267 | + $plural = $domAttr->value; |
|
268 | + } |
|
269 | + if (in_array($domAttr->name, $options['commentAttributes'])) { |
|
270 | + $comment = $domAttr->value; |
|
271 | + } |
|
272 | + } |
|
273 | + |
|
274 | + if ($translatable) { |
|
275 | + $translation = $translations->insert($context, trim($node->textContent), $plural); |
|
276 | + $translation->addReference($options['file'], $node->getLineNo()); |
|
277 | + if ($comment) { |
|
278 | + $translation->addExtractedComment($comment); |
|
279 | + } |
|
280 | + } |
|
281 | + |
|
282 | + if ($node->hasChildNodes()) { |
|
283 | + static::getTagTranslations($options, $node, $translations); |
|
284 | + } |
|
285 | + } |
|
286 | + } |
|
287 | + |
|
288 | + /** |
|
289 | + * Extract JS expressions from element attribute bindings (excluding text within elements) |
|
290 | + * For example: <span :title="__('extract this')"> skip element content </span> |
|
291 | + * |
|
292 | + * @param array $options |
|
293 | + * @param DOMNode $dom |
|
294 | + * @return string JS code |
|
295 | + */ |
|
296 | + protected static function getTemplateAttributeFakeJs(array $options, DOMNode $dom) |
|
297 | + { |
|
298 | + $expressionsByLine = static::getVueAttributeExpressions($options['attributePrefixes'], $dom); |
|
299 | + |
|
300 | + if (empty($expressionsByLine)) { |
|
301 | + return ''; |
|
302 | + } |
|
303 | + |
|
304 | + $maxLines = max(array_keys($expressionsByLine)); |
|
305 | + $fakeJs = ''; |
|
306 | + |
|
307 | + for ($line = 1; $line <= $maxLines; $line++) { |
|
308 | + if (isset($expressionsByLine[$line])) { |
|
309 | + $fakeJs .= implode("; ", $expressionsByLine[$line]); |
|
310 | + } |
|
311 | + $fakeJs .= "\n"; |
|
312 | + } |
|
313 | + |
|
314 | + return $fakeJs; |
|
315 | + } |
|
316 | + |
|
317 | + /** |
|
318 | + * Loop DOM element recursively and parse out all dynamic vue attributes which are basically JS expressions |
|
319 | + * |
|
320 | + * @param array $attributePrefixes List of attribute prefixes we parse as JS (may contain translations) |
|
321 | + * @param DOMNode $dom |
|
322 | + * @param array $expressionByLine [lineNumber => [jsExpression, ..], ..] |
|
323 | + * @return array [lineNumber => [jsExpression, ..], ..] |
|
324 | + */ |
|
325 | + protected static function getVueAttributeExpressions( |
|
326 | + array $attributePrefixes, |
|
327 | + DOMNode $dom, |
|
328 | + array &$expressionByLine = [] |
|
329 | + ) { |
|
330 | + $children = $dom->childNodes; |
|
331 | + |
|
332 | + for ($i = 0; $i < $children->length; $i++) { |
|
333 | + $node = $children->item($i); |
|
334 | + |
|
335 | + if (!($node instanceof DOMElement)) { |
|
336 | + continue; |
|
337 | + } |
|
338 | + $attrList = $node->attributes; |
|
339 | + |
|
340 | + for ($j = 0; $j < $attrList->length; $j++) { |
|
341 | + /** @var DOMAttr $domAttr */ |
|
342 | + $domAttr = $attrList->item($j); |
|
343 | + |
|
344 | + // Check if this is a dynamic vue attribute |
|
345 | + if (static::isAttributeMatching($domAttr->name, $attributePrefixes)) { |
|
346 | + $line = $domAttr->getLineNo(); |
|
347 | + $expressionByLine += [$line => []]; |
|
348 | + $expressionByLine[$line][] = $domAttr->value; |
|
349 | + } |
|
350 | + } |
|
351 | + |
|
352 | + if ($node->hasChildNodes()) { |
|
353 | + $expressionByLine = static::getVueAttributeExpressions($attributePrefixes, $node, $expressionByLine); |
|
354 | + } |
|
355 | + } |
|
356 | + |
|
357 | + return $expressionByLine; |
|
358 | + } |
|
359 | + |
|
360 | + /** |
|
361 | + * Check if this attribute name should be parsed for translations |
|
362 | + * |
|
363 | + * @param string $attributeName |
|
364 | + * @param string[] $attributePrefixes |
|
365 | + * @return bool |
|
366 | + */ |
|
367 | + protected static function isAttributeMatching($attributeName, $attributePrefixes) |
|
368 | + { |
|
369 | + foreach ($attributePrefixes as $prefix) { |
|
370 | + if (strpos($attributeName, $prefix) === 0) { |
|
371 | + return true; |
|
372 | + } |
|
373 | + } |
|
374 | + return false; |
|
375 | + } |
|
376 | + |
|
377 | + /** |
|
378 | + * Extract JS expressions from within template elements (excluding attributes) |
|
379 | + * For example: <span :title="skip attributes"> {{__("extract element content")}} </span> |
|
380 | + * |
|
381 | + * @param DOMNode $dom |
|
382 | + * @return string JS code |
|
383 | + */ |
|
384 | + protected static function getTemplateFakeJs(DOMNode $dom) |
|
385 | + { |
|
386 | + $fakeJs = ''; |
|
387 | + $lines = explode("\n", $dom->textContent); |
|
388 | + |
|
389 | + // Build a fake JS file from template by extracting JS expressions within each template line |
|
390 | + foreach ($lines as $line) { |
|
391 | + $expressionMatched = static::parseOneTemplateLine($line); |
|
392 | + |
|
393 | + $fakeJs .= implode("; ", $expressionMatched) . "\n"; |
|
394 | + } |
|
395 | + |
|
396 | + return $fakeJs; |
|
397 | + } |
|
398 | + |
|
399 | + /** |
|
400 | + * Match JS expressions in a template line |
|
401 | + * |
|
402 | + * @param string $line |
|
403 | + * @return string[] |
|
404 | + */ |
|
405 | + protected static function parseOneTemplateLine($line) |
|
406 | + { |
|
407 | + $line = trim($line); |
|
408 | + |
|
409 | + if (!$line) { |
|
410 | + return []; |
|
411 | + } |
|
412 | + |
|
413 | + $regex = '#\{\{(.*?)\}\}#'; |
|
414 | + |
|
415 | + preg_match_all($regex, $line, $matches); |
|
416 | + |
|
417 | + $matched = array_map(function ($v) { |
|
418 | + return trim($v, '\'"{}'); |
|
419 | + }, $matches[1]); |
|
420 | + |
|
421 | + return $matched; |
|
422 | + } |
|
423 | 423 | } |
@@ -8,73 +8,73 @@ |
||
8 | 8 | |
9 | 9 | abstract class Extractor implements ExtractorInterface |
10 | 10 | { |
11 | - /** |
|
12 | - * {@inheritdoc} |
|
13 | - */ |
|
14 | - public static function fromFile($file, Translations $translations, array $options = []) |
|
15 | - { |
|
16 | - foreach (static::getFiles($file) as $file) { |
|
17 | - $options['file'] = $file; |
|
18 | - static::fromString(static::readFile($file), $translations, $options); |
|
19 | - } |
|
20 | - } |
|
11 | + /** |
|
12 | + * {@inheritdoc} |
|
13 | + */ |
|
14 | + public static function fromFile($file, Translations $translations, array $options = []) |
|
15 | + { |
|
16 | + foreach (static::getFiles($file) as $file) { |
|
17 | + $options['file'] = $file; |
|
18 | + static::fromString(static::readFile($file), $translations, $options); |
|
19 | + } |
|
20 | + } |
|
21 | 21 | |
22 | - /** |
|
23 | - * Checks and returns all files. |
|
24 | - * |
|
25 | - * @param string|array $file The file/s |
|
26 | - * |
|
27 | - * @return array The file paths |
|
28 | - */ |
|
29 | - protected static function getFiles($file) |
|
30 | - { |
|
31 | - if (empty($file)) { |
|
32 | - throw new InvalidArgumentException('There is not any file defined'); |
|
33 | - } |
|
22 | + /** |
|
23 | + * Checks and returns all files. |
|
24 | + * |
|
25 | + * @param string|array $file The file/s |
|
26 | + * |
|
27 | + * @return array The file paths |
|
28 | + */ |
|
29 | + protected static function getFiles($file) |
|
30 | + { |
|
31 | + if (empty($file)) { |
|
32 | + throw new InvalidArgumentException('There is not any file defined'); |
|
33 | + } |
|
34 | 34 | |
35 | - if (is_string($file)) { |
|
36 | - if (!is_file($file)) { |
|
37 | - throw new InvalidArgumentException("'$file' is not a valid file"); |
|
38 | - } |
|
35 | + if (is_string($file)) { |
|
36 | + if (!is_file($file)) { |
|
37 | + throw new InvalidArgumentException("'$file' is not a valid file"); |
|
38 | + } |
|
39 | 39 | |
40 | - if (!is_readable($file)) { |
|
41 | - throw new InvalidArgumentException("'$file' is not a readable file"); |
|
42 | - } |
|
40 | + if (!is_readable($file)) { |
|
41 | + throw new InvalidArgumentException("'$file' is not a readable file"); |
|
42 | + } |
|
43 | 43 | |
44 | - return [$file]; |
|
45 | - } |
|
44 | + return [$file]; |
|
45 | + } |
|
46 | 46 | |
47 | - if (is_array($file)) { |
|
48 | - $files = []; |
|
47 | + if (is_array($file)) { |
|
48 | + $files = []; |
|
49 | 49 | |
50 | - foreach ($file as $f) { |
|
51 | - $files = array_merge($files, static::getFiles($f)); |
|
52 | - } |
|
50 | + foreach ($file as $f) { |
|
51 | + $files = array_merge($files, static::getFiles($f)); |
|
52 | + } |
|
53 | 53 | |
54 | - return $files; |
|
55 | - } |
|
54 | + return $files; |
|
55 | + } |
|
56 | 56 | |
57 | - throw new InvalidArgumentException('The first argument must be string or array'); |
|
58 | - } |
|
57 | + throw new InvalidArgumentException('The first argument must be string or array'); |
|
58 | + } |
|
59 | 59 | |
60 | - /** |
|
61 | - * Reads and returns the content of a file. |
|
62 | - * |
|
63 | - * @param string $file |
|
64 | - * |
|
65 | - * @return string |
|
66 | - */ |
|
67 | - protected static function readFile($file) |
|
68 | - { |
|
69 | - $length = filesize($file); |
|
60 | + /** |
|
61 | + * Reads and returns the content of a file. |
|
62 | + * |
|
63 | + * @param string $file |
|
64 | + * |
|
65 | + * @return string |
|
66 | + */ |
|
67 | + protected static function readFile($file) |
|
68 | + { |
|
69 | + $length = filesize($file); |
|
70 | 70 | |
71 | - if (!($fd = fopen($file, 'rb'))) { |
|
72 | - throw new Exception("Cannot read the file '$file', probably permissions"); |
|
73 | - } |
|
71 | + if (!($fd = fopen($file, 'rb'))) { |
|
72 | + throw new Exception("Cannot read the file '$file', probably permissions"); |
|
73 | + } |
|
74 | 74 | |
75 | - $content = $length ? fread($fd, $length) : ''; |
|
76 | - fclose($fd); |
|
75 | + $content = $length ? fread($fd, $length) : ''; |
|
76 | + fclose($fd); |
|
77 | 77 | |
78 | - return $content; |
|
79 | - } |
|
78 | + return $content; |
|
79 | + } |
|
80 | 80 | } |
@@ -10,17 +10,17 @@ |
||
10 | 10 | */ |
11 | 11 | class Json extends Extractor implements ExtractorInterface |
12 | 12 | { |
13 | - use MultidimensionalArrayTrait; |
|
13 | + use MultidimensionalArrayTrait; |
|
14 | 14 | |
15 | - /** |
|
16 | - * {@inheritdoc} |
|
17 | - */ |
|
18 | - public static function fromString($string, Translations $translations, array $options = []) |
|
19 | - { |
|
20 | - $messages = json_decode($string, true); |
|
15 | + /** |
|
16 | + * {@inheritdoc} |
|
17 | + */ |
|
18 | + public static function fromString($string, Translations $translations, array $options = []) |
|
19 | + { |
|
20 | + $messages = json_decode($string, true); |
|
21 | 21 | |
22 | - if (is_array($messages)) { |
|
23 | - static::fromArray($messages, $translations); |
|
24 | - } |
|
25 | - } |
|
22 | + if (is_array($messages)) { |
|
23 | + static::fromArray($messages, $translations); |
|
24 | + } |
|
25 | + } |
|
26 | 26 | } |
@@ -11,17 +11,17 @@ |
||
11 | 11 | */ |
12 | 12 | class Yaml extends Extractor implements ExtractorInterface |
13 | 13 | { |
14 | - use MultidimensionalArrayTrait; |
|
14 | + use MultidimensionalArrayTrait; |
|
15 | 15 | |
16 | - /** |
|
17 | - * {@inheritdoc} |
|
18 | - */ |
|
19 | - public static function fromString($string, Translations $translations, array $options = []) |
|
20 | - { |
|
21 | - $messages = YamlParser::parse($string); |
|
16 | + /** |
|
17 | + * {@inheritdoc} |
|
18 | + */ |
|
19 | + public static function fromString($string, Translations $translations, array $options = []) |
|
20 | + { |
|
21 | + $messages = YamlParser::parse($string); |
|
22 | 22 | |
23 | - if (is_array($messages)) { |
|
24 | - static::fromArray($messages, $translations); |
|
25 | - } |
|
26 | - } |
|
23 | + if (is_array($messages)) { |
|
24 | + static::fromArray($messages, $translations); |
|
25 | + } |
|
26 | + } |
|
27 | 27 | } |