]>
Commit | Line | Data |
---|---|---|
4f5b44bd NL |
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\Loader; | |
13 | ||
14 | use Symfony\Component\Translation\Exception\InvalidResourceException; | |
15 | use Symfony\Component\Translation\Exception\NotFoundResourceException; | |
16 | use Symfony\Component\Config\Resource\FileResource; | |
17 | ||
18 | /** | |
19 | * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/) | |
20 | * @copyright Copyright (c) 2012, Clemens Tolboom | |
21 | */ | |
22 | class PoFileLoader extends ArrayLoader implements LoaderInterface | |
23 | { | |
24 | public function load($resource, $locale, $domain = 'messages') | |
25 | { | |
26 | if (!stream_is_local($resource)) { | |
27 | throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); | |
28 | } | |
29 | ||
30 | if (!file_exists($resource)) { | |
31 | throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); | |
32 | } | |
33 | ||
34 | $messages = $this->parse($resource); | |
35 | ||
36 | // empty file | |
37 | if (null === $messages) { | |
38 | $messages = array(); | |
39 | } | |
40 | ||
41 | // not an array | |
42 | if (!is_array($messages)) { | |
43 | throw new InvalidResourceException(sprintf('The file "%s" must contain a valid po file.', $resource)); | |
44 | } | |
45 | ||
46 | $catalogue = parent::load($messages, $locale, $domain); | |
47 | $catalogue->addResource(new FileResource($resource)); | |
48 | ||
49 | return $catalogue; | |
50 | } | |
51 | ||
52 | /** | |
53 | * Parses portable object (PO) format. | |
54 | * | |
55 | * From http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files | |
56 | * we should be able to parse files having: | |
57 | * | |
58 | * white-space | |
59 | * # translator-comments | |
60 | * #. extracted-comments | |
61 | * #: reference... | |
62 | * #, flag... | |
63 | * #| msgid previous-untranslated-string | |
64 | * msgid untranslated-string | |
65 | * msgstr translated-string | |
66 | * | |
67 | * extra or different lines are: | |
68 | * | |
69 | * #| msgctxt previous-context | |
70 | * #| msgid previous-untranslated-string | |
71 | * msgctxt context | |
72 | * | |
73 | * #| msgid previous-untranslated-string-singular | |
74 | * #| msgid_plural previous-untranslated-string-plural | |
75 | * msgid untranslated-string-singular | |
76 | * msgid_plural untranslated-string-plural | |
77 | * msgstr[0] translated-string-case-0 | |
78 | * ... | |
79 | * msgstr[N] translated-string-case-n | |
80 | * | |
81 | * The definition states: | |
82 | * - white-space and comments are optional. | |
83 | * - msgid "" that an empty singleline defines a header. | |
84 | * | |
85 | * This parser sacrifices some features of the reference implementation the | |
86 | * differences to that implementation are as follows. | |
87 | * - No support for comments spanning multiple lines. | |
88 | * - Translator and extracted comments are treated as being the same type. | |
89 | * - Message IDs are allowed to have other encodings as just US-ASCII. | |
90 | * | |
91 | * Items with an empty id are ignored. | |
92 | * | |
93 | * @param resource $resource | |
94 | * | |
95 | * @return array | |
96 | */ | |
97 | private function parse($resource) | |
98 | { | |
99 | $stream = fopen($resource, 'r'); | |
100 | ||
101 | $defaults = array( | |
102 | 'ids' => array(), | |
103 | 'translated' => null, | |
104 | ); | |
105 | ||
106 | $messages = array(); | |
107 | $item = $defaults; | |
108 | ||
109 | while ($line = fgets($stream)) { | |
110 | $line = trim($line); | |
111 | ||
112 | if ($line === '') { | |
113 | // Whitespace indicated current item is done | |
114 | $this->addMessage($messages, $item); | |
115 | $item = $defaults; | |
116 | } elseif (substr($line, 0, 7) === 'msgid "') { | |
117 | // We start a new msg so save previous | |
118 | // TODO: this fails when comments or contexts are added | |
119 | $this->addMessage($messages, $item); | |
120 | $item = $defaults; | |
121 | $item['ids']['singular'] = substr($line, 7, -1); | |
122 | } elseif (substr($line, 0, 8) === 'msgstr "') { | |
123 | $item['translated'] = substr($line, 8, -1); | |
124 | } elseif ($line[0] === '"') { | |
125 | $continues = isset($item['translated']) ? 'translated' : 'ids'; | |
126 | ||
127 | if (is_array($item[$continues])) { | |
128 | end($item[$continues]); | |
129 | $item[$continues][key($item[$continues])] .= substr($line, 1, -1); | |
130 | } else { | |
131 | $item[$continues] .= substr($line, 1, -1); | |
132 | } | |
133 | } elseif (substr($line, 0, 14) === 'msgid_plural "') { | |
134 | $item['ids']['plural'] = substr($line, 14, -1); | |
135 | } elseif (substr($line, 0, 7) === 'msgstr[') { | |
136 | $size = strpos($line, ']'); | |
137 | $item['translated'][(integer) substr($line, 7, 1)] = substr($line, $size + 3, -1); | |
138 | } | |
139 | ||
140 | } | |
141 | // save last item | |
142 | $this->addMessage($messages, $item); | |
143 | fclose($stream); | |
144 | ||
145 | return $messages; | |
146 | } | |
147 | ||
148 | /** | |
149 | * Save a translation item to the messeages. | |
150 | * | |
151 | * A .po file could contain by error missing plural indexes. We need to | |
152 | * fix these before saving them. | |
153 | * | |
154 | * @param array $messages | |
155 | * @param array $item | |
156 | */ | |
157 | private function addMessage(array &$messages, array $item) | |
158 | { | |
159 | if (is_array($item['translated'])) { | |
160 | $messages[$item['ids']['singular']] = stripslashes($item['translated'][0]); | |
161 | if (isset($item['ids']['plural'])) { | |
162 | $plurals = $item['translated']; | |
163 | // PO are by definition indexed so sort by index. | |
164 | ksort($plurals); | |
165 | // Make sure every index is filled. | |
166 | end($plurals); | |
167 | $count = key($plurals); | |
168 | // Fill missing spots with '-'. | |
169 | $empties = array_fill(0, $count+1, '-'); | |
170 | $plurals += $empties; | |
171 | ksort($plurals); | |
172 | $messages[$item['ids']['plural']] = stripcslashes(implode('|', $plurals)); | |
173 | } | |
174 | } elseif (!empty($item['ids']['singular'])) { | |
175 | $messages[$item['ids']['singular']] = stripslashes($item['translated']); | |
176 | } | |
177 | } | |
178 | } |