|
11 | 11 |
|
12 | 12 | namespace Translation\SymfonyStorage\Dumper; |
13 | 13 |
|
14 | | -use Symfony\Component\Translation\Dumper\XliffFileDumper; |
| 14 | +use Symfony\Component\Translation\Dumper\FileDumper; |
15 | 15 | use Symfony\Component\Translation\MessageCatalogue; |
| 16 | +use Symfony\Component\Translation\Exception\InvalidArgumentException; |
16 | 17 |
|
17 | 18 | /** |
| 19 | + * XliffFileDumper generates xliff files from a message catalogue. |
| 20 | + * Mostly borrowed from Symfony. |
| 21 | + * |
18 | 22 | * @author Tobias Nyholm <tobias.nyholm@gmail.com> |
| 23 | + * @author Michel Salib <michelsalib@hotmail.com> |
19 | 24 | */ |
20 | | -final class XliffDumper extends XliffFileDumper |
| 25 | +final class XliffDumper extends FileDumper |
21 | 26 | { |
22 | 27 | /** |
23 | | - * Alias for formatCatalogue to provide a BC bridge. |
| 28 | + * {@inheritdoc} |
| 29 | + */ |
| 30 | + public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = []) |
| 31 | + { |
| 32 | + $xliffVersion = '1.2'; |
| 33 | + if (array_key_exists('xliff_version', $options)) { |
| 34 | + $xliffVersion = $options['xliff_version']; |
| 35 | + } |
| 36 | + |
| 37 | + if (array_key_exists('default_locale', $options)) { |
| 38 | + $defaultLocale = $options['default_locale']; |
| 39 | + } else { |
| 40 | + $defaultLocale = \Locale::getDefault(); |
| 41 | + } |
| 42 | + |
| 43 | + if ('1.2' === $xliffVersion) { |
| 44 | + return $this->dumpXliff1($defaultLocale, $messages, $domain, $options); |
| 45 | + } |
| 46 | + if ('2.0' === $xliffVersion) { |
| 47 | + return $this->dumpXliff2($defaultLocale, $messages, $domain, $options); |
| 48 | + } |
| 49 | + |
| 50 | + throw new InvalidArgumentException( |
| 51 | + sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion) |
| 52 | + ); |
| 53 | + } |
| 54 | + |
| 55 | + /** |
| 56 | + * Symfony 2.7 support. |
24 | 57 | * |
25 | 58 | * @param MessageCatalogue $messages |
26 | 59 | * @param string $domain |
27 | | - * @param array $options |
28 | 60 | * |
29 | 61 | * @return string |
30 | 62 | */ |
31 | | - public function getFormattedCatalogue(MessageCatalogue $messages, $domain, array $options = []) |
| 63 | + protected function format(MessageCatalogue $messages, $domain) |
| 64 | + { |
| 65 | + return $this->dumpXliff2(\Locale::getDefault(), $messages, $domain); |
| 66 | + } |
| 67 | + |
| 68 | + /** |
| 69 | + * {@inheritdoc} |
| 70 | + */ |
| 71 | + protected function getExtension() |
| 72 | + { |
| 73 | + return 'xlf'; |
| 74 | + } |
| 75 | + |
| 76 | + private function dumpXliff1($defaultLocale, MessageCatalogue $messages, $domain, array $options = []) |
32 | 77 | { |
33 | | - if (method_exists($this, 'formatCatalogue')) { |
34 | | - return parent::formatCatalogue($messages, $domain, $options); |
| 78 | + $toolInfo = ['tool-id' => 'symfony', 'tool-name' => 'Symfony']; |
| 79 | + if (array_key_exists('tool_info', $options)) { |
| 80 | + $toolInfo = array_merge($toolInfo, $options['tool_info']); |
35 | 81 | } |
36 | 82 |
|
37 | | - return $this->format($messages, $domain); |
| 83 | + $dom = new \DOMDocument('1.0', 'utf-8'); |
| 84 | + $dom->formatOutput = true; |
| 85 | + |
| 86 | + $xliff = $dom->appendChild($dom->createElement('xliff')); |
| 87 | + $xliff->setAttribute('version', '1.2'); |
| 88 | + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:1.2'); |
| 89 | + |
| 90 | + $xliffFile = $xliff->appendChild($dom->createElement('file')); |
| 91 | + $xliffFile->setAttribute('source-language', str_replace('_', '-', $defaultLocale)); |
| 92 | + $xliffFile->setAttribute('target-language', str_replace('_', '-', $messages->getLocale())); |
| 93 | + $xliffFile->setAttribute('datatype', 'plaintext'); |
| 94 | + $xliffFile->setAttribute('original', 'file.ext'); |
| 95 | + |
| 96 | + $xliffHead = $xliffFile->appendChild($dom->createElement('header')); |
| 97 | + $xliffTool = $xliffHead->appendChild($dom->createElement('tool')); |
| 98 | + foreach ($toolInfo as $id => $value) { |
| 99 | + $xliffTool->setAttribute($id, $value); |
| 100 | + } |
| 101 | + |
| 102 | + $xliffBody = $xliffFile->appendChild($dom->createElement('body')); |
| 103 | + foreach ($messages->all($domain) as $source => $target) { |
| 104 | + $translation = $dom->createElement('trans-unit'); |
| 105 | + |
| 106 | + $translation->setAttribute( |
| 107 | + 'id', |
| 108 | + strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._') |
| 109 | + ); |
| 110 | + $translation->setAttribute('resname', $source); |
| 111 | + |
| 112 | + $s = $translation->appendChild($dom->createElement('source')); |
| 113 | + $s->appendChild($dom->createTextNode($source)); |
| 114 | + |
| 115 | + // Does the target contain characters requiring a CDATA section? |
| 116 | + $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode( |
| 117 | + $target |
| 118 | + ); |
| 119 | + |
| 120 | + $targetElement = $dom->createElement('target'); |
| 121 | + $metadata = $messages->getMetadata($source, $domain); |
| 122 | + if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { |
| 123 | + foreach ($metadata['target-attributes'] as $name => $value) { |
| 124 | + $targetElement->setAttribute($name, $value); |
| 125 | + } |
| 126 | + } |
| 127 | + $t = $translation->appendChild($targetElement); |
| 128 | + $t->appendChild($text); |
| 129 | + |
| 130 | + if ($this->hasMetadataArrayInfo('notes', $metadata)) { |
| 131 | + foreach ($metadata['notes'] as $note) { |
| 132 | + if (!isset($note['content'])) { |
| 133 | + continue; |
| 134 | + } |
| 135 | + |
| 136 | + $n = $translation->appendChild($dom->createElement('note')); |
| 137 | + $n->appendChild($dom->createTextNode($note['content'])); |
| 138 | + |
| 139 | + if (isset($note['priority'])) { |
| 140 | + $n->setAttribute('priority', $note['priority']); |
| 141 | + } |
| 142 | + |
| 143 | + if (isset($note['from'])) { |
| 144 | + $n->setAttribute('from', $note['from']); |
| 145 | + } |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + $xliffBody->appendChild($translation); |
| 150 | + } |
| 151 | + |
| 152 | + return $dom->saveXML(); |
| 153 | + } |
| 154 | + |
| 155 | + private function dumpXliff2($defaultLocale, MessageCatalogue $messages, $domain, array $options = []) |
| 156 | + { |
| 157 | + $dom = new \DOMDocument('1.0', 'utf-8'); |
| 158 | + $dom->formatOutput = true; |
| 159 | + |
| 160 | + $xliff = $dom->appendChild($dom->createElement('xliff')); |
| 161 | + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:2.0'); |
| 162 | + $xliff->setAttribute('version', '2.0'); |
| 163 | + $xliff->setAttribute('srcLang', str_replace('_', '-', $defaultLocale)); |
| 164 | + $xliff->setAttribute('trgLang', str_replace('_', '-', $messages->getLocale())); |
| 165 | + |
| 166 | + $xliffFile = $xliff->appendChild($dom->createElement('file')); |
| 167 | + $xliffFile->setAttribute('id', $domain.'.'.$messages->getLocale()); |
| 168 | + |
| 169 | + foreach ($messages->all($domain) as $source => $target) { |
| 170 | + $translation = $dom->createElement('unit'); |
| 171 | + $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._')); |
| 172 | + $metadata = $messages->getMetadata($source, $domain); |
| 173 | + |
| 174 | + // Add notes section |
| 175 | + if ($this->hasMetadataArrayInfo('notes', $metadata)) { |
| 176 | + $notesElement = $dom->createElement('notes'); |
| 177 | + foreach ($metadata['notes'] as $note) { |
| 178 | + $n = $dom->createElement('note'); |
| 179 | + $n->appendChild($dom->createTextNode(isset($note['content']) ? $note['content'] : '')); |
| 180 | + unset($note['content']); |
| 181 | + |
| 182 | + foreach ($note as $name => $value) { |
| 183 | + $n->setAttribute($name, $value); |
| 184 | + } |
| 185 | + $notesElement->appendChild($n); |
| 186 | + } |
| 187 | + $translation->appendChild($notesElement); |
| 188 | + } |
| 189 | + |
| 190 | + $segment = $translation->appendChild($dom->createElement('segment')); |
| 191 | + |
| 192 | + $s = $segment->appendChild($dom->createElement('source')); |
| 193 | + $s->appendChild($dom->createTextNode($source)); |
| 194 | + |
| 195 | + // Does the target contain characters requiring a CDATA section? |
| 196 | + $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode( |
| 197 | + $target |
| 198 | + ); |
| 199 | + |
| 200 | + $targetElement = $dom->createElement('target'); |
| 201 | + if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { |
| 202 | + foreach ($metadata['target-attributes'] as $name => $value) { |
| 203 | + $targetElement->setAttribute($name, $value); |
| 204 | + } |
| 205 | + } |
| 206 | + |
| 207 | + $t = $segment->appendChild($targetElement); |
| 208 | + $t->appendChild($text); |
| 209 | + |
| 210 | + $xliffFile->appendChild($translation); |
| 211 | + } |
| 212 | + |
| 213 | + return $dom->saveXML(); |
| 214 | + } |
| 215 | + |
| 216 | + /** |
| 217 | + * @param string $key |
| 218 | + * @param array|null $metadata |
| 219 | + * |
| 220 | + * @return bool |
| 221 | + */ |
| 222 | + private function hasMetadataArrayInfo($key, $metadata = null) |
| 223 | + { |
| 224 | + return null !== $metadata && |
| 225 | + array_key_exists($key, $metadata) && |
| 226 | + ($metadata[$key] instanceof \Traversable || is_array($metadata[$key])); |
38 | 227 | } |
39 | 228 | } |
0 commit comments