Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
JavaFFS.php
1<?php
2
12class JavaFFS extends SimpleFFS implements MetaYamlSchemaExtender {
13 public function supportsFuzzy() {
14 return 'write';
15 }
16
17 public function getFileExtensions() {
18 return [ '.properties' ];
19 }
20
21 protected $keySeparator = '=';
22
24 public function __construct( FileBasedMessageGroup $group ) {
25 parent::__construct( $group );
26
27 if ( isset( $this->extra['keySeparator'] ) ) {
28 $this->keySeparator = $this->extra['keySeparator'];
29 }
30 }
31
37 public function readFromVariable( $data ) {
38 $data = self::fixNewLines( $data );
39 $lines = array_map( 'ltrim', explode( "\n", $data ) );
40 $authors = $messages = [];
41 $linecontinuation = false;
42
43 $key = '';
44 $value = '';
45 foreach ( $lines as $line ) {
46 if ( $linecontinuation ) {
47 $linecontinuation = false;
48 $valuecont = $line;
49 $valuecont = str_replace( '\n', "\n", $valuecont );
50 $value .= $valuecont;
51 } else {
52 if ( $line === '' ) {
53 continue;
54 }
55
56 if ( $line[0] === '#' || $line[0] === '!' ) {
57 $match = [];
58 $ok = preg_match( '/#\s*Author:\s*(.*)/', $line, $match );
59
60 if ( $ok ) {
61 $authors[] = $match[1];
62 }
63
64 continue;
65 }
66
67 if ( strpos( $line, $this->keySeparator ) === false ) {
68 throw new MWException( "Line without separator '{$this->keySeparator}': $line." );
69 }
70
71 list( $key, $value ) = self::readRow( $line, $this->keySeparator );
72 if ( $key === '' ) {
73 throw new MWException( "Empty key in line $line." );
74 }
75 }
76
77 // @todo This doesn't handle the pathological case of even number of trailing \
78 if ( strlen( $value ) && $value[strlen( $value ) - 1] === "\\" ) {
79 $value = substr( $value, 0, strlen( $value ) - 1 );
80 $linecontinuation = true;
81 } else {
82 $messages[$key] = ltrim( $value );
83 }
84 }
85
86 $messages = $this->group->getMangler()->mangleArray( $messages );
87
88 return [
89 'AUTHORS' => $authors,
90 'MESSAGES' => $messages,
91 ];
92 }
93
98 protected function writeReal( MessageCollection $collection ) {
99 $header = $this->doHeader( $collection );
100 $header .= $this->doAuthors( $collection );
101 $header .= "\n";
102
103 $output = '';
104 $mangler = $this->group->getMangler();
105
107 foreach ( $collection as $key => $m ) {
108 $value = $m->translation();
109 $value = str_replace( TRANSLATE_FUZZY, '', $value );
110
111 if ( $value === '' ) {
112 continue;
113 }
114
115 // Just to give an overview of translation quality.
116 if ( $m->hasTag( 'fuzzy' ) ) {
117 $output .= "# Fuzzy\n";
118 }
119
120 $key = $mangler->unmangle( $key );
121 $output .= self::writeRow( $key, $this->keySeparator, $value );
122 }
123
124 if ( $output ) {
125 return $header . $output;
126 }
127
128 return '';
129 }
130
139 public static function writeRow( $key, $sep, $value ) {
140 /* Keys containing the separator need escaping. Also escape comment
141 * characters, though strictly they would only need escaping when
142 * they are the first character. Plus the escape character itself. */
143 $key = addcslashes( $key, "#!$sep\\" );
144 // Make sure we do not slip newlines trough... it would be fatal.
145 $value = str_replace( "\n", '\\n', $value );
146
147 return "$key$sep$value\n";
148 }
149
157 public static function readRow( $line, $sep ) {
158 if ( strpos( $line, '\\' ) === false ) {
159 /* Nothing appears to be escaped in this line.
160 * Just read the key and the value. */
161 list( $key, $value ) = explode( $sep, $line, 2 );
162 } else {
163 /* There might be escaped separators in the key.
164 * Using slower method to find the separator. */
165
166 /* Make the key default to empty instead of value, because
167 * empty key causes error on callers, while empty value
168 * wouldn't. */
169 $key = '';
170 $value = $line;
171
172 /* Find the first unescaped separator. Example:
173 * First line is the string being read, second line is the
174 * value of $escaped after having read the above character.
175 *
176 * ki\ts\\s\=a = koira
177 * 0010010010000
178 * ^ Not separator because $escaped was true
179 * ^ Split the string into key and value here
180 */
181
182 $len = strlen( $line );
183 $escaped = false;
184 for ( $i = 0; $i < $len; $i++ ) {
185 $char = $line[$i];
186 if ( $char === '\\' ) {
187 $escaped = !$escaped;
188 } elseif ( $escaped ) {
189 $escaped = false;
190 } elseif ( $char === $sep ) {
191 $key = substr( $line, 0, $i );
192 // Excluding the separator character from the value
193 $value = substr( $line, $i + 1 );
194 break;
195 }
196 }
197 }
198
199 /* We usually don't want to expand things like \t in values since
200 * translators cannot easily input those. But in keys we do.
201 * \n is exception we do handle in values. */
202 $key = trim( $key );
203 $key = stripcslashes( $key );
204 $value = ltrim( $value );
205 $value = str_replace( '\n', "\n", $value );
206
207 return [ $key, $value ];
208 }
209
214 protected function doHeader( MessageCollection $collection ) {
215 if ( isset( $this->extra['header'] ) ) {
216 $output = $this->extra['header'];
217 } else {
218 global $wgSitename;
219
220 $code = $collection->code;
221 $name = TranslateUtils::getLanguageName( $code );
222 $native = TranslateUtils::getLanguageName( $code, $code );
223 $output = "# Messages for $name ($native)\n";
224 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable
225 $output .= "# Exported from $wgSitename\n";
226 }
227
228 return $output;
229 }
230
235 protected function doAuthors( MessageCollection $collection ) {
236 $output = '';
237 $authors = $collection->getAuthors();
238 $authors = $this->filterAuthors( $authors, $collection->code );
239
240 foreach ( $authors as $author ) {
241 $output .= "# Author: $author\n";
242 }
243
244 return $output;
245 }
246
247 public static function getExtraSchema() {
248 $schema = [
249 'root' => [
250 '_type' => 'array',
251 '_children' => [
252 'FILES' => [
253 '_type' => 'array',
254 '_children' => [
255 'header' => [
256 '_type' => 'text',
257 ],
258 'keySeparator' => [
259 '_type' => 'text',
260 ],
261 ]
262 ]
263 ]
264 ]
265 ];
266
267 return $schema;
268 }
269}
This class implements default behavior for file based message groups.
JavaFFS class implements support for Java properties files.
Definition JavaFFS.php:12
__construct(FileBasedMessageGroup $group)
Definition JavaFFS.php:24
writeReal(MessageCollection $collection)
Definition JavaFFS.php:98
supportsFuzzy()
Query the capabilities of this FFS.
Definition JavaFFS.php:13
static writeRow( $key, $sep, $value)
Writes well-formed properties file row with key and value.
Definition JavaFFS.php:139
doAuthors(MessageCollection $collection)
Definition JavaFFS.php:235
static readRow( $line, $sep)
Parses non-empty properties file row to key and value.
Definition JavaFFS.php:157
getFileExtensions()
Return the commonly used file extensions for these formats.
Definition JavaFFS.php:17
static getExtraSchema()
Return a data structure that will be merged with the base schema.
Definition JavaFFS.php:247
readFromVariable( $data)
Definition JavaFFS.php:37
doHeader(MessageCollection $collection)
Definition JavaFFS.php:214
Core message collection class.
getAuthors()
Lists all translators that have contributed to the latest revisions of each translation.
static fixNewLines( $data)
Replaces all Windows and Mac line endings with Unix line endings.
Message groups are usually configured in YAML, though the actual storage format does not matter,...