3 * Handle messages in the language files.
6 * @ingroup MaintenanceLanguage
10 * @ingroup MaintenanceLanguage
13 protected $mLanguages; # List of languages
14 protected $mRawMessages; # Raw list of the messages in each language
15 protected $mMessages; # Messages in each language (except for English), divided to groups
16 protected $mGeneralMessages; # General messages in English, divided to groups
17 protected $mIgnoredMessages; # All the messages which should be exist only in the English file
18 protected $mOptionalMessages; # All the messages which may be translated or not, depending on the language
21 * Load the list of languages: all the Messages*.php
22 * files in the languages directory.
24 * @param $exif Treat the EXIF messages?
26 function __construct( $exif = true ) {
27 require( dirname(__FILE__) . '/messageTypes.inc' );
28 $this->mIgnoredMessages = $wgIgnoredMessages;
30 $this->mOptionalMessages = array_merge( $wgOptionalMessages );
32 $this->mOptionalMessages = array_merge( $wgOptionalMessages, $wgEXIFMessages );
35 $this->mLanguages = array_keys( Language::getLanguageNames( true ) );
36 sort( $this->mLanguages );
40 * Get the language list.
42 * @return The language list.
44 public function getLanguages() {
45 return $this->mLanguages;
49 * Get the ignored messages list.
51 * @return The ignored messages list.
53 public function getIgnoredMessages() {
54 return $this->mIgnoredMessages;
58 * Get the optional messages list.
60 * @return The optional messages list.
62 public function getOptionalMessages() {
63 return $this->mOptionalMessages;
67 * Load the raw messages for a specific language from the messages file.
69 * @param $code The language code.
71 protected function loadRawMessages( $code ) {
72 if ( isset( $this->mRawMessages[$code] ) ) {
75 $filename = Language::getMessagesFileName( $code );
76 if ( file_exists( $filename ) ) {
78 if ( isset( $messages ) ) {
79 $this->mRawMessages[$code] = $messages;
81 $this->mRawMessages[$code] = array();
84 $this->mRawMessages[$code] = array();
89 * Load the messages for a specific language (which is not English) and divide them to groups:
90 * all - all the messages.
91 * required - messages which should be translated in order to get a complete translation.
92 * optional - messages which can be translated, the fallback translation is used if not translated.
93 * obsolete - messages which should not be translated, either because they are not exist, or they are ignored messages.
94 * translated - messages which are either required or optional, but translated from English and needed.
96 * @param $code The language code.
98 private function loadMessages( $code ) {
99 if ( isset( $this->mMessages[$code] ) ) {
102 $this->loadRawMessages( $code );
103 $this->loadGeneralMessages();
104 $this->mMessages[$code]['all'] = $this->mRawMessages[$code];
105 $this->mMessages[$code]['required'] = array();
106 $this->mMessages[$code]['optional'] = array();
107 $this->mMessages[$code]['obsolete'] = array();
108 $this->mMessages[$code]['translated'] = array();
109 foreach ( $this->mMessages[$code]['all'] as $key => $value ) {
110 if ( isset( $this->mGeneralMessages['required'][$key] ) ) {
111 $this->mMessages[$code]['required'][$key] = $value;
112 $this->mMessages[$code]['translated'][$key] = $value;
113 } else if ( isset( $this->mGeneralMessages['optional'][$key] ) ) {
114 $this->mMessages[$code]['optional'][$key] = $value;
115 $this->mMessages[$code]['translated'][$key] = $value;
117 $this->mMessages[$code]['obsolete'][$key] = $value;
123 * Load the messages for English and divide them to groups:
124 * all - all the messages.
125 * required - messages which should be translated to other languages in order to get a complete translation.
126 * optional - messages which can be translated to other languages, but it's not required for a complete translation.
127 * ignored - messages which should not be translated to other languages.
128 * translatable - messages which are either required or optional, but can be translated from English.
130 private function loadGeneralMessages() {
131 if ( isset( $this->mGeneralMessages ) ) {
134 $this->loadRawMessages( 'en' );
135 $this->mGeneralMessages['all'] = $this->mRawMessages['en'];
136 $this->mGeneralMessages['required'] = array();
137 $this->mGeneralMessages['optional'] = array();
138 $this->mGeneralMessages['ignored'] = array();
139 $this->mGeneralMessages['translatable'] = array();
140 foreach ( $this->mGeneralMessages['all'] as $key => $value ) {
141 if ( in_array( $key, $this->mIgnoredMessages ) ) {
142 $this->mGeneralMessages['ignored'][$key] = $value;
143 } else if ( in_array( $key, $this->mOptionalMessages ) ) {
144 $this->mGeneralMessages['optional'][$key] = $value;
145 $this->mGeneralMessages['translatable'][$key] = $value;
147 $this->mGeneralMessages['required'][$key] = $value;
148 $this->mGeneralMessages['translatable'][$key] = $value;
154 * Get all the messages for a specific language (not English), without the
155 * fallback language messages, divided to groups:
156 * all - all the messages.
157 * required - messages which should be translated in order to get a complete translation.
158 * optional - messages which can be translated, the fallback translation is used if not translated.
159 * obsolete - messages which should not be translated, either because they are not exist, or they are ignored messages.
160 * translated - messages which are either required or optional, but translated from English and needed.
162 * @param $code The language code.
164 * @return The messages in this language.
166 public function getMessages( $code ) {
167 $this->loadMessages( $code );
168 return $this->mMessages[$code];
172 * Get all the general English messages, divided to groups:
173 * all - all the messages.
174 * required - messages which should be translated to other languages in order to get a complete translation.
175 * optional - messages which can be translated to other languages, but it's not required for a complete translation.
176 * ignored - messages which should not be translated to other languages.
177 * translatable - messages which are either required or optional, but can be translated from English.
179 * @return The general English messages.
181 public function getGeneralMessages() {
182 $this->loadGeneralMessages();
183 return $this->mGeneralMessages;
187 * Get the untranslated messages for a specific language.
189 * @param $code The language code.
191 * @return The untranslated messages for this language.
193 public function getUntranslatedMessages( $code ) {
194 $this->loadGeneralMessages();
195 $this->loadMessages( $code );
196 $requiredGeneralMessages = array_keys( $this->mGeneralMessages['required'] );
197 $requiredMessages = array_keys( $this->mMessages[$code]['required'] );
198 $untranslatedMessages = array();
199 foreach ( array_diff( $requiredGeneralMessages, $requiredMessages ) as $key ) {
200 $untranslatedMessages[$key] = $this->mGeneralMessages['required'][$key];
202 return $untranslatedMessages;
206 * Get the duplicate messages for a specific language.
208 * @param $code The language code.
210 * @return The duplicate messages for this language.
212 public function getDuplicateMessages( $code ) {
213 $this->loadGeneralMessages();
214 $this->loadMessages( $code );
215 $duplicateMessages = array();
216 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
217 if ( $this->mGeneralMessages['translatable'][$key] == $value ) {
218 $duplicateMessages[$key] = $value;
221 return $duplicateMessages;
224 public function getObsoleteMessages( $code ) {
225 $this->loadGeneralMessages();
226 $this->loadMessages( $code );
227 return $this->mMessages[$code]['obsolete'];
231 * Get the messages which do not use some variables.
233 * @param $code The language code.
235 * @return The messages which do not use some variables in this language.
237 public function getMessagesWithoutVariables( $code ) {
238 $this->loadGeneralMessages();
239 $this->loadMessages( $code );
240 $variables = array( '\$1', '\$2', '\$3', '\$4', '\$5', '\$6', '\$7', '\$8', '\$9' );
241 $messagesWithoutVariables = array();
242 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
244 foreach ( $variables as $var ) {
245 if ( preg_match( "/$var/sU", $this->mGeneralMessages['translatable'][$key] ) &&
246 !preg_match( "/$var/sU", $value ) ) {
251 $messagesWithoutVariables[$key] = $value;
254 return $messagesWithoutVariables;
258 * Get the messages which do not use plural.
260 * @param $code The language code.
262 * @return The messages which do not use plural in this language.
264 public function getMessagesWithoutPlural( $code ) {
265 $this->loadGeneralMessages();
266 $this->loadMessages( $code );
267 $messagesWithoutPlural = array();
268 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
269 if ( stripos( $this->mGeneralMessages['translatable'][$key], '{{plural:' ) !== false && stripos( $value, '{{plural:' ) === false ) {
270 $messagesWithoutPlural[$key] = $value;
273 return $messagesWithoutPlural;
277 * Get the empty messages.
279 * @param $code The language code.
281 * @return The empty messages for this language.
283 public function getEmptyMessages( $code ) {
284 $this->loadGeneralMessages();
285 $this->loadMessages( $code );
286 $emptyMessages = array();
287 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
288 if ( $value === '' || $value === '-' ) {
289 $emptyMessages[$key] = $value;
292 return $emptyMessages;
296 * Get the messages with trailing whitespace.
298 * @param $code The language code.
300 * @return The messages with trailing whitespace in this language.
302 public function getMessagesWithWhitespace( $code ) {
303 $this->loadGeneralMessages();
304 $this->loadMessages( $code );
305 $messagesWithWhitespace = array();
306 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
307 if ( $this->mGeneralMessages['translatable'][$key] !== '' && $value !== rtrim( $value ) ) {
308 $messagesWithWhitespace[$key] = $value;
311 return $messagesWithWhitespace;
315 * Get the non-XHTML messages.
317 * @param $code The language code.
319 * @return The non-XHTML messages for this language.
321 public function getNonXHTMLMessages( $code ) {
322 $this->loadGeneralMessages();
323 $this->loadMessages( $code );
324 $wrongPhrases = array(
330 $wrongPhrases = '~(' . implode( '|', $wrongPhrases ) . ')~sDu';
331 $nonXHTMLMessages = array();
332 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
333 if ( preg_match( $wrongPhrases, $value ) ) {
334 $nonXHTMLMessages[$key] = $value;
337 return $nonXHTMLMessages;
341 * Get the messages which include wrong characters.
343 * @param $code The language code.
345 * @return The messages which include wrong characters in this language.
347 public function getMessagesWithWrongChars( $code ) {
348 $this->loadGeneralMessages();
349 $this->loadMessages( $code );
351 '[LRM]' => "\xE2\x80\x8E",
352 '[RLM]' => "\xE2\x80\x8F",
353 '[LRE]' => "\xE2\x80\xAA",
354 '[RLE]' => "\xE2\x80\xAB",
355 '[POP]' => "\xE2\x80\xAC",
356 '[LRO]' => "\xE2\x80\xAD",
357 '[RLO]' => "\xE2\x80\xAB",
358 '[ZWSP]'=> "\xE2\x80\x8B",
359 '[NBSP]'=> "\xC2\xA0",
360 '[WJ]' => "\xE2\x81\xA0",
361 '[BOM]' => "\xEF\xBB\xBF",
362 '[FFFD]'=> "\xEF\xBF\xBD",
364 $wrongRegExp = '/(' . implode( '|', array_values( $wrongChars ) ) . ')/sDu';
365 $wrongCharsMessages = array();
366 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
367 if ( preg_match( $wrongRegExp, $value ) ) {
368 foreach ( $wrongChars as $viewableChar => $hiddenChar ) {
369 $value = str_replace( $hiddenChar, $viewableChar, $value );
371 $wrongCharsMessages[$key] = $value;
374 return $wrongCharsMessages;
377 public function getMessagesWithDubiousLinks( $code ) {
378 $this->loadGeneralMessages();
379 $this->loadMessages( $code );
380 $tc = Title::legalChars() . '#%{}';
382 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
384 preg_match_all( "/\[\[([{$tc}]+)(?:\\|(.+?))?]]/sDu", $value, $matches);
385 for ($i = 0; $i < count($matches[0]); $i++ ) {
386 if ( preg_match( "/.*project.*/isDu", $matches[1][$i]) ) {
387 $messages[$key][] = $matches[0][$i];
392 if ( isset( $messages[$key] ) ) {
393 $messages[$key] = implode( $messages[$key],", " );
399 public function getMessagesWithUnbalanced( $code ) {
400 $this->loadGeneralMessages();
401 $this->loadMessages( $code );
403 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
405 $a = $b = $c = $d = 0;
406 foreach ( preg_split('//', $value) as $char ) {
408 case '[': $a++; break;
409 case ']': $b++; break;
410 case '{': $c++; break;
411 case '}': $d++; break;
415 if ( $a !== $b || $c !== $d ) {
416 $messages[$key] = "$a, $b, $c, $d";
425 class extensionLanguages extends languages {
426 private $mMessageGroup; # The message group
429 * Load the messages group.
430 * @param $group The messages group.
432 function __construct( MessageGroup $group ) {
433 $this->mMessageGroup = $group;
435 $bools = $this->mMessageGroup->getBools();
436 $this->mIgnoredMessages = $bools['ignored'];
437 $this->mOptionalMessages = $bools['optional'];
441 * Get the extension name.
443 * @return The extension name.
445 public function name() {
446 return $this->mMessageGroup->getLabel();
450 * Load the raw messages for a specific language.
452 * @param $code The language code.
454 protected function loadRawMessages( $code ) {
455 if( !isset( $this->mRawMessages[$code] ) ) {
456 $this->mRawMessages[$code] = $this->mMessageGroup->load( $code );
457 if( empty( $this->mRawMessages[$code] ) ) {
458 $this->mRawMessages[$code] = array();