Make WordPress Core

Ticket #10165: mo.php

File mo.php, 8.1 KB (added by soletan, 17 years ago)

Drop-In replacement for wp-includes/pomo/mo.php

Line 
1<?php
2
3
4/**
5 * Improved MO file reader for Wordpress.
6 *
7 * @author Thomas Urban <thomas.urban@toxa.de>
8 */
9
10
11require_once dirname(__FILE__) . '/translations.php';
12
13
14class MO extends Translations
15{
16
17        protected $nplurals = 2;
18
19
20
21        public function __construct( $headers = array(), $hash = array() )
22        {
23
24                $this->headers = $headers;
25                $this->entries = $hash;
26
27                if ( $this->headers['Plural-Forms'] )
28                {
29
30                        $temp = strtok( $this->headers['Plural-Forms'], ';' );
31                        if ( preg_match( '/^nplurals=(\d+)$/i', trim( $temp ), $matches ) )
32                                $this->nplurals = intval( $matches[1] );
33
34                }
35        }
36
37
38        /**
39         * Reads provided MO file.
40         *
41         * @param string $filename name of MO file to read
42         * @return MOX
43         */
44
45        public function import_from_file( $filename )
46        {
47
48                /*
49                 * check cache for existing record first
50                 */
51
52                // currently defunct due to internal WP_Object_Cache not saving anything
53                $set = wp_cache_get( $filename, 'l10n' );
54                if ( is_array( $set ) )
55                {
56
57                        list( $this->headers, $this->entries ) = $set;
58
59                        return true;
60
61                }
62
63
64
65                /**
66                 * read header from file
67                 */
68
69                $file = fopen( $filename, 'r' );
70                if ( !$file )
71                        return false;
72
73                $header = fread( $file, 28 );
74                if ( strlen( $header ) != 28 )
75                        return false;
76
77                // detect endianess
78                $endian = unpack( 'Nendian', substr( $header, 0, 4 ) );
79                if ( $endian['endian'] == intval( hexdec( '950412de' ) ) )
80                        $endian = 'N';
81                else if ( $endian['endian'] == intval( hexdec( 'de120495' ) ) )
82                        $endian = 'V';
83                else
84                        return false;
85
86                // parse header
87                $header = unpack( "{$endian}Hrevision/{$endian}Hcount/{$endian}HposOriginals/{$endian}HposTranslations/{$endian}HsizeHash/{$endian}HposHash", substr( $header, 4 ) );
88                if ( !is_array( $header ) )
89                        return false;
90
91                extract( $header );
92
93                // support revision 0 of MO format specs, only
94                if ( $Hrevision != 0 )
95                        return false;
96
97
98
99                /*
100                 * read index tables on originals and translations
101                 */
102
103                // seek to data blocks
104                fseek( $file, $HposOriginals, SEEK_SET );
105
106                // read originals' indices
107                $HsizeOriginals = $HposTranslations - $HposOriginals;
108                if ( $HsizeOriginals != $Hcount * 8 )
109                        return false;
110
111                $originals = fread( $file, $HsizeOriginals );
112                if ( strlen( $originals ) != $HsizeOriginals )
113                        return false;
114
115                // read translations' indices
116                $HsizeTranslations = $HposHash - $HposTranslations;
117                if ( $HsizeTranslations != $Hcount * 8 )
118                        return false;
119
120                $translations = fread( $file, $HsizeTranslations );
121                if ( strlen( $translations ) != $HsizeTranslations )
122                        return false;
123
124                // transform raw data into set of indices
125                $originals    = str_split( $originals, 8 );
126                $translations = str_split( $translations, 8 );
127
128
129
130                /*
131                 * read set of strings to separate string
132                 */
133
134                // skip hash table
135                $HposStrings = $HposHash + $HsizeHash * 4;
136
137                fseek( $file, $HposStrings, SEEK_SET );
138
139                // read strings expected in rest of file
140                $strings = '';
141                while ( !feof( $file ) )
142                        $strings .= fread( $file, 4096 );
143
144                fclose( $file );
145
146
147
148                // collect hash records
149                $hash = $header = array();
150
151                for ( $i = 0; $i < $Hcount; $i++ )
152                {
153
154                        // parse index records on original and related translation
155                        $o = unpack( "{$endian}length/{$endian}pos", $originals[$i] );
156                        $t = unpack( "{$endian}length/{$endian}pos", $translations[$i] );
157
158                        if ( !$o || !$t )
159                                return false;
160
161                        // adjust offset due to reading strings to separate space before
162                        $o['pos'] -= $HposStrings;
163                        $t['pos'] -= $HposStrings;
164
165                        // extract original and translations
166                        $original    = substr( $strings, $o['pos'], $o['length'] );
167                        $translation = substr( $strings, $t['pos'], $t['length'] );
168
169
170
171                        if ( $original === '' )
172                        {
173                                // got header --> store separately
174
175                                $header = array();
176
177                                foreach ( explode( "\n", $translation ) as $line )
178                                {
179
180                                        $sep = strpos( $line, ':' );
181                                        if ( $sep !== false )
182                                                $header[trim(substr( $line, 0, $sep ))] = trim( substr( $line, $sep + 1 ));
183
184                                }
185                        }
186                        else
187                        {
188
189                                // detect context in original
190                                $sep = strpos( $original, "\04" );
191                                if ( $sep !== false )
192                                {
193                                        $context  = substr( $original, 0, $sep );
194                                        $original = substr( $original, $sep + 1 );
195                                }
196                                else
197                                        $context  = null;
198
199
200                                $original     = explode( "\00", $original );
201                                $translation  = explode( "\00", $translation );
202
203                                $singularFrom = array_shift( $original );
204                                $singularTo   = array_shift( $translation );
205
206                                if ( count( $original ) && ( count( $original ) == count( $translation ) ) )
207                                        $plurals = array_combine( $original, $translation );
208                                else
209                                        $plurals = array();
210
211
212                                $record = array(
213                                                                'context'     => $context,
214                                                                'singular'    => $singularFrom,
215                                                                'translation' => $singularTo,
216                                                                'plurals'     => $plurals,
217                                                                );
218
219                                $key = is_null( $context ) ? $singularFrom
220                                                                                   : "$context\04$singularFrom";
221
222
223                                $hash[$key] = $record;
224
225                        }
226                }
227
228
229                $this->headers = $header;
230                $this->entries = $hash;
231
232
233
234                /*
235                 * write result to cache
236                 */
237
238                // currently defunct due to internal WP_Object_Cache not saving anything
239                wp_cache_set( $filename, array( $header, $hash ), 'l10n' );
240
241
242
243                return true;
244
245        }
246
247
248        /**
249         * Retrieves translation of single entry.
250         *
251         * The provided entry is constructed manually and thus providing proper
252         * key for lookup. The method returns a matching entry from internal pool
253         * of translations.
254         *
255         * NOTE! Passing by reference is required as long as parent class is using
256         *       it (obviously due to keeping things compatible with PHP4).
257         *
258         * @param Translate_Entry $entry entry to look up
259         * @return Translate_Entry resulting entry
260         */
261
262        public function translate_entry( &$entry )
263        {
264
265                if ( !isset( $this->entries[$entry->key()] ) )
266                        return false;
267
268                if ( is_array( $this->entries[$entry->key()] ) )
269                {
270                        // convert entry to instance of Translate_Entry on demand
271
272                        // use internally managed record
273                        $args = $this->entries[$entry->key()];
274
275                        // add structures required by Translation_Entry
276                        $args['translations'] = array( $args['translation'] );
277                        if ( count( $args['plurals'] ) )
278                                $args['translations'] = array_merge( $args['translations'], array_values( $args['plurals'] ) );
279
280                        if ( count( $args['plurals'] ) )
281                                $args['plural'] = array_shift( array_keys( $args['plurals'] ) );
282
283                        // temporarily transform entry
284                        return new Translation_Entry( $args );
285
286                }
287
288                if ( $this->entries[$entry->key()] instanceof Translation_Entry )
289                        return $this->entries[$entry->key()];
290
291                return false;
292
293        }
294
295
296        public function translate( $singular, $context = null )
297        {
298
299                $key = $this->key( $singular, $context );
300
301                if ( isset( $this->entries[$key] ) )
302                        if ( is_string( $this->entries[$key]['translation'] ) )
303                                return $this->entries[$key]['translation'];
304
305                return $singular;
306
307        }
308
309
310        public function translate_plural( $singular, $plural, $count, $context = null )
311        {
312
313                $key = $this->key( $singular, $context );
314
315                $translated = $this->entries[$key];
316                if ( is_array( $translated ) )
317                {
318
319                        $index              = $this->select_plural_form( $count );
320                        $total_plural_forms = $this->get_plural_forms_count();
321
322                        if ( ( $index >= 0 ) && ( $index < $total_plural_forms ) )
323                        {
324
325                                if ( ( $index == 0 ) && is_string( $translated['translation'] ) )
326                                        return $translated['translation'];
327
328                                if ( count( $translated['plurals'] ) == $total_plural_forms - 1 )
329                                {
330                                        $plurals = array_values( $translated['plurals'] );
331                                        return $plurals[$index-1];
332                                }
333                        }
334
335
336                        return ( $count == 1 ) ? $singular : $plural;
337
338                }
339
340
341                // keep class compatible with old-style entry class
342                return parent::translate_plural( $singular, $plural, $count, $context );
343
344        }
345
346
347        public function key( $singular, $context = null )
348        {
349                return is_null( $context ) ? $singular : "$context\04$singular";
350        }
351
352
353        public function get_plural_forms_count()
354        {
355                return $this->nplurals;
356        }
357
358
359        public function select_plural_form( $count )
360        {
361                return $this->gettext_select_plural_form( $count );
362        }
363}
364
365
366?>