3 * Handle messages in the language files.
5 * @addtogroup Maintenance
9 protected $mLanguages; # List of languages
10 protected $mRawMessages; # Raw list of the messages in each language
11 protected $mMessages; # Messages in each language (except for English), divided to groups
12 protected $mGeneralMessages; # General messages in English, divided to groups
13 protected $mIgnoredMessages; # All the messages which should be exist only in the English file
14 protected $mOptionalMessages; # All the messages which may be translated or not, depending on the language
17 * Load the list of languages: all the Messages*.php
18 * files in the languages directory.
20 * @param $exif Treat the EXIF messages?
22 function __construct( $exif = true ) {
23 require( dirname(__FILE__) . '/messageTypes.inc' );
24 $this->mIgnoredMessages = $wgIgnoredMessages;
26 $this->mOptionalMessages = array_merge( $wgOptionalMessages );
28 $this->mOptionalMessages = array_merge( $wgOptionalMessages, $wgEXIFMessages );
31 $this->mLanguages = array_keys( Language::getLanguageNames( true ) );
32 sort( $this->mLanguages );
36 * Get the language list.
38 * @return The language list.
40 public function getLanguages() {
41 return $this->mLanguages;
45 * Get the ignored messages list.
47 * @return The ignored messages list.
49 public function getIgnoredMessages() {
50 return $this->mIgnoredMessages;
54 * Get the optional messages list.
56 * @return The optional messages list.
58 public function getOptionalMessages() {
59 return $this->mOptionalMessages;
63 * Load the raw messages for a specific language from the messages file.
65 * @param $code The language code.
67 protected function loadRawMessages( $code ) {
68 if ( isset( $this->mRawMessages[$code] ) ) {
71 $filename = Language::getMessagesFileName( $code );
72 if ( file_exists( $filename ) ) {
74 if ( isset( $messages ) ) {
75 $this->mRawMessages[$code] = $messages;
77 $this->mRawMessages[$code] = array();
80 $this->mRawMessages[$code] = array();
85 * Load the messages for a specific language (which is not English) and divide them to groups:
86 * all - all the messages.
87 * required - messages which should be translated in order to get a complete translation.
88 * optional - messages which can be translated, the fallback translation is used if not translated.
89 * obsolete - messages which should not be translated, either because they are not exist, or they are ignored messages.
90 * translated - messages which are either required or optional, but translated from English and needed.
92 * @param $code The language code.
94 private function loadMessages( $code ) {
95 if ( isset( $this->mMessages[$code] ) ) {
98 $this->loadRawMessages( $code );
99 $this->loadGeneralMessages();
100 $this->mMessages[$code]['all'] = $this->mRawMessages[$code];
101 $this->mMessages[$code]['required'] = array();
102 $this->mMessages[$code]['optional'] = array();
103 $this->mMessages[$code]['obsolete'] = array();
104 $this->mMessages[$code]['translated'] = array();
105 foreach ( $this->mMessages[$code]['all'] as $key => $value ) {
106 if ( isset( $this->mGeneralMessages['required'][$key] ) ) {
107 $this->mMessages[$code]['required'][$key] = $value;
108 $this->mMessages[$code]['translated'][$key] = $value;
109 } else if ( isset( $this->mGeneralMessages['optional'][$key] ) ) {
110 $this->mMessages[$code]['optional'][$key] = $value;
111 $this->mMessages[$code]['translated'][$key] = $value;
113 $this->mMessages[$code]['obsolete'][$key] = $value;
119 * Load the messages for English and divide them to groups:
120 * all - all the messages.
121 * required - messages which should be translated to other languages in order to get a complete translation.
122 * optional - messages which can be translated to other languages, but it's not required for a complete translation.
123 * ignored - messages which should not be translated to other languages.
124 * translatable - messages which are either required or optional, but can be translated from English.
126 private function loadGeneralMessages() {
127 if ( isset( $this->mGeneralMessages ) ) {
130 $this->loadRawMessages( 'en' );
131 $this->mGeneralMessages['all'] = $this->mRawMessages['en'];
132 $this->mGeneralMessages['required'] = array();
133 $this->mGeneralMessages['optional'] = array();
134 $this->mGeneralMessages['ignored'] = array();
135 $this->mGeneralMessages['translatable'] = array();
136 foreach ( $this->mGeneralMessages['all'] as $key => $value ) {
137 if ( in_array( $key, $this->mIgnoredMessages ) ) {
138 $this->mGeneralMessages['ignored'][$key] = $value;
139 } else if ( in_array( $key, $this->mOptionalMessages ) ) {
140 $this->mGeneralMessages['optional'][$key] = $value;
141 $this->mGeneralMessages['translatable'][$key] = $value;
143 $this->mGeneralMessages['required'][$key] = $value;
144 $this->mGeneralMessages['translatable'][$key] = $value;
150 * Get all the messages for a specific language (not English), without the
151 * fallback language messages, divided to groups:
152 * all - all the messages.
153 * required - messages which should be translated in order to get a complete translation.
154 * optional - messages which can be translated, the fallback translation is used if not translated.
155 * obsolete - messages which should not be translated, either because they are not exist, or they are ignored messages.
156 * translated - messages which are either required or optional, but translated from English and needed.
158 * @param $code The language code.
160 * @return The messages in this language.
162 public function getMessages( $code ) {
163 $this->loadMessages( $code );
164 return $this->mMessages[$code];
168 * Get all the general English messages, divided to groups:
169 * all - all the messages.
170 * required - messages which should be translated to other languages in order to get a complete translation.
171 * optional - messages which can be translated to other languages, but it's not required for a complete translation.
172 * ignored - messages which should not be translated to other languages.
173 * translatable - messages which are either required or optional, but can be translated from English.
175 * @return The general English messages.
177 public function getGeneralMessages() {
178 $this->loadGeneralMessages();
179 return $this->mGeneralMessages;
183 * Get the untranslated messages for a specific language.
185 * @param $code The language code.
187 * @return The untranslated messages for this language.
189 public function getUntranslatedMessages( $code ) {
190 $this->loadGeneralMessages();
191 $this->loadMessages( $code );
192 $requiredGeneralMessages = array_keys( $this->mGeneralMessages['required'] );
193 $requiredMessages = array_keys( $this->mMessages[$code]['required'] );
194 $untranslatedMessages = array();
195 foreach ( array_diff( $requiredGeneralMessages, $requiredMessages ) as $key ) {
196 $untranslatedMessages[$key] = $this->mGeneralMessages['required'][$key];
198 return $untranslatedMessages;
202 * Get the duplicate messages for a specific language.
204 * @param $code The language code.
206 * @return The duplicate messages for this language.
208 public function getDuplicateMessages( $code ) {
209 $this->loadGeneralMessages();
210 $this->loadMessages( $code );
211 $duplicateMessages = array();
212 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
213 if ( $this->mGeneralMessages['translatable'][$key] == $value ) {
214 $duplicateMessages[$key] = $value;
217 return $duplicateMessages;
220 public function getObsoleteMessages( $code ) {
221 $this->loadGeneralMessages();
222 $this->loadMessages( $code );
223 return $this->mMessages[$code]['obsolete'];
227 * Get the messages which do not use some variables.
229 * @param $code The language code.
231 * @return The messages which do not use some variables in this language.
233 public function getMessagesWithoutVariables( $code ) {
234 $this->loadGeneralMessages();
235 $this->loadMessages( $code );
236 $variables = array( '\$1', '\$2', '\$3', '\$4', '\$5', '\$6', '\$7', '\$8', '\$9' );
237 $messagesWithoutVariables = array();
238 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
240 foreach ( $variables as $var ) {
241 if ( preg_match( "/$var/sU", $this->mGeneralMessages['translatable'][$key] ) &&
242 !preg_match( "/$var/sU", $value ) ) {
247 $messagesWithoutVariables[$key] = $value;
250 return $messagesWithoutVariables;
254 * Get the messages which do not use plural.
256 * @param $code The language code.
258 * @return The messages which do not use plural in this language.
260 public function getMessagesWithoutPlural( $code ) {
261 $this->loadGeneralMessages();
262 $this->loadMessages( $code );
263 $messagesWithoutPlural = array();
264 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
265 if ( stripos( $this->mGeneralMessages['translatable'][$key], '{{plural:' ) !== false && stripos( $value, '{{plural:' ) === false ) {
266 $messagesWithoutPlural[$key] = $value;
269 return $messagesWithoutPlural;
273 * Get the empty messages.
275 * @param $code The language code.
277 * @return The empty messages for this language.
279 public function getEmptyMessages( $code ) {
280 $this->loadGeneralMessages();
281 $this->loadMessages( $code );
282 $emptyMessages = array();
283 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
284 if ( $value === '' || $value === '-' ) {
285 $emptyMessages[$key] = $value;
288 return $emptyMessages;
292 * Get the messages with trailing whitespace.
294 * @param $code The language code.
296 * @return The messages with trailing whitespace in this language.
298 public function getMessagesWithWhitespace( $code ) {
299 $this->loadGeneralMessages();
300 $this->loadMessages( $code );
301 $messagesWithWhitespace = array();
302 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
303 if ( $this->mGeneralMessages['translatable'][$key] !== '' && $value !== rtrim( $value ) ) {
304 $messagesWithWhitespace[$key] = $value;
307 return $messagesWithWhitespace;
311 * Get the non-XHTML messages.
313 * @param $code The language code.
315 * @return The non-XHTML messages for this language.
317 public function getNonXHTMLMessages( $code ) {
318 $this->loadGeneralMessages();
319 $this->loadMessages( $code );
320 $wrongPhrases = array(
326 $wrongPhrases = '~(' . implode( '|', $wrongPhrases ) . ')~sDu';
327 $nonXHTMLMessages = array();
328 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
329 if ( preg_match( $wrongPhrases, $value ) ) {
330 $nonXHTMLMessages[$key] = $value;
333 return $nonXHTMLMessages;
337 * Get the messages which include wrong characters.
339 * @param $code The language code.
341 * @return The messages which include wrong characters in this language.
343 public function getMessagesWithWrongChars( $code ) {
344 $this->loadGeneralMessages();
345 $this->loadMessages( $code );
347 '[LRM]' => "\xE2\x80\x8E",
348 '[RLM]' => "\xE2\x80\x8F",
349 '[LRE]' => "\xE2\x80\xAA",
350 '[RLE]' => "\xE2\x80\xAB",
351 '[POP]' => "\xE2\x80\xAC",
352 '[LRO]' => "\xE2\x80\xAD",
353 '[RLO]' => "\xE2\x80\xAB",
354 '[ZWSP]'=> "\xE2\x80\x8B",
355 '[NBSP]'=> "\xC2\xA0",
356 '[WJ]' => "\xE2\x81\xA0",
357 '[BOM]' => "\xEF\xBB\xBF",
358 '[FFFD]'=> "\xEF\xBF\xBD",
360 $wrongRegExp = '/(' . implode( '|', array_values( $wrongChars ) ) . ')/sDu';
361 $wrongCharsMessages = array();
362 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
363 if ( preg_match( $wrongRegExp, $value ) ) {
364 foreach ( $wrongChars as $viewableChar => $hiddenChar ) {
365 $value = str_replace( $hiddenChar, $viewableChar, $value );
367 $wrongCharsMessages[$key] = $value;
370 return $wrongCharsMessages;
373 public function getMessagesWithDubiousLinks( $code ) {
374 $this->loadGeneralMessages();
375 $this->loadMessages( $code );
376 $tc = Title::legalChars() . '#%{}';
378 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
380 preg_match_all( "/\[\[([{$tc}]+)(?:\\|(.+?))?]]/sDu", $value, $matches);
381 for ($i = 0; $i < count($matches[0]); $i++ ) {
382 if ( preg_match( "/.*project.*/isDu", $matches[1][$i]) ) {
383 $messages[$key][] = $matches[0][$i];
388 if ( isset( $messages[$key] ) ) {
389 $messages[$key] = implode( $messages[$key],", " );
395 public function getMessagesWithUnbalanced( $code ) {
396 $this->loadGeneralMessages();
397 $this->loadMessages( $code );
399 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
401 $a = $b = $c = $d = 0;
402 foreach ( preg_split('//', $value) as $char ) {
404 case '[': $a++; break;
405 case ']': $b++; break;
406 case '{': $c++; break;
407 case '}': $d++; break;
411 if ( $a !== $b || $c !== $d ) {
412 $messages[$key] = "$a, $b, $c, $d";