3 * Handle messages in the language files.
6 * @ingroup MaintenanceLanguage
10 * @ingroup MaintenanceLanguage
13 protected $mLanguages; # List of languages
15 protected $mRawMessages; # Raw list of the messages in each language
16 protected $mMessages; # Messages in each language (except for English), divided to groups
17 protected $mGeneralMessages; # General messages in English, divided to groups
18 protected $mIgnoredMessages; # All the messages which should be exist only in the English file
19 protected $mOptionalMessages; # All the messages which may be translated or not, depending on the language
21 protected $mNamespaceNames; # Namespace names
22 protected $mNamespaceAliases; # Namespace aliases
23 protected $mMagicWords; # Magic words
24 protected $mSpecialPageAliases; # Special page aliases
27 * Load the list of languages: all the Messages*.php
28 * files in the languages directory.
30 * @param $exif Treat the EXIF messages?
32 function __construct( $exif = true ) {
33 require( dirname(__FILE__) . '/messageTypes.inc' );
34 $this->mIgnoredMessages = $wgIgnoredMessages;
36 $this->mOptionalMessages = array_merge( $wgOptionalMessages );
38 $this->mOptionalMessages = array_merge( $wgOptionalMessages, $wgEXIFMessages );
41 $this->mLanguages = array_keys( Language::getLanguageNames( true ) );
42 sort( $this->mLanguages );
46 * Get the language list.
48 * @return The language list.
50 public function getLanguages() {
51 return $this->mLanguages;
55 * Get the ignored messages list.
57 * @return The ignored messages list.
59 public function getIgnoredMessages() {
60 return $this->mIgnoredMessages;
64 * Get the optional messages list.
66 * @return The optional messages list.
68 public function getOptionalMessages() {
69 return $this->mOptionalMessages;
73 * Load the language file.
75 * @param $code The language code.
77 protected function loadFile( $code ) {
78 if ( isset( $this->mRawMessages[$code] ) &&
79 isset( $this->mNamespaceNames[$code] ) &&
80 isset( $this->mNamespaceAliases[$code] ) &&
81 isset( $this->mMagicWords[$code] ) &&
82 isset( $this->mSpecialPageAliases[$code] ) ) {
85 $this->mRawMessages[$code] = array();
86 $this->mNamespaceNames[$code] = array();
87 $this->mNamespaceAliases[$code] = array();
88 $this->mMagicWords[$code] = array();
89 $this->mSpecialPageAliases[$code] = array();
90 $filename = Language::getMessagesFileName( $code );
91 if ( file_exists( $filename ) ) {
93 if ( isset( $messages ) ) {
94 $this->mRawMessages[$code] = $messages;
96 if ( isset( $namespaceNames ) ) {
97 $this->mNamespaceNames[$code] = $namespaceNames;
99 if ( isset( $namespaceAliases ) ) {
100 $this->mNamespaceAliases[$code] = $namespaceAliases;
102 if ( isset( $magicWords ) ) {
103 $this->mMagicWords[$code] = $magicWords;
105 if ( isset( $specialPageAliases ) ) {
106 $this->mSpecialPageAliases[$code] = $specialPageAliases;
112 * Load the messages for a specific language (which is not English) and divide them to groups:
113 * all - all the messages.
114 * required - messages which should be translated in order to get a complete translation.
115 * optional - messages which can be translated, the fallback translation is used if not translated.
116 * obsolete - messages which should not be translated, either because they do not exist, or they are ignored messages.
117 * translated - messages which are either required or optional, but translated from English and needed.
119 * @param $code The language code.
121 private function loadMessages( $code ) {
122 if ( isset( $this->mMessages[$code] ) ) {
125 $this->loadFile( $code );
126 $this->loadGeneralMessages();
127 $this->mMessages[$code]['all'] = $this->mRawMessages[$code];
128 $this->mMessages[$code]['required'] = array();
129 $this->mMessages[$code]['optional'] = array();
130 $this->mMessages[$code]['obsolete'] = array();
131 $this->mMessages[$code]['translated'] = array();
132 foreach ( $this->mMessages[$code]['all'] as $key => $value ) {
133 if ( isset( $this->mGeneralMessages['required'][$key] ) ) {
134 $this->mMessages[$code]['required'][$key] = $value;
135 $this->mMessages[$code]['translated'][$key] = $value;
136 } else if ( isset( $this->mGeneralMessages['optional'][$key] ) ) {
137 $this->mMessages[$code]['optional'][$key] = $value;
138 $this->mMessages[$code]['translated'][$key] = $value;
140 $this->mMessages[$code]['obsolete'][$key] = $value;
146 * Load the messages for English and divide them to groups:
147 * all - all the messages.
148 * required - messages which should be translated to other languages in order to get a complete translation.
149 * optional - messages which can be translated to other languages, but it's not required for a complete translation.
150 * ignored - messages which should not be translated to other languages.
151 * translatable - messages which are either required or optional, but can be translated from English.
153 private function loadGeneralMessages() {
154 if ( isset( $this->mGeneralMessages ) ) {
157 $this->loadFile( 'en' );
158 $this->mGeneralMessages['all'] = $this->mRawMessages['en'];
159 $this->mGeneralMessages['required'] = array();
160 $this->mGeneralMessages['optional'] = array();
161 $this->mGeneralMessages['ignored'] = array();
162 $this->mGeneralMessages['translatable'] = array();
163 foreach ( $this->mGeneralMessages['all'] as $key => $value ) {
164 if ( in_array( $key, $this->mIgnoredMessages ) ) {
165 $this->mGeneralMessages['ignored'][$key] = $value;
166 } else if ( in_array( $key, $this->mOptionalMessages ) ) {
167 $this->mGeneralMessages['optional'][$key] = $value;
168 $this->mGeneralMessages['translatable'][$key] = $value;
170 $this->mGeneralMessages['required'][$key] = $value;
171 $this->mGeneralMessages['translatable'][$key] = $value;
177 * Get all the messages for a specific language (not English), without the
178 * fallback language messages, divided to groups:
179 * all - all the messages.
180 * required - messages which should be translated in order to get a complete translation.
181 * optional - messages which can be translated, the fallback translation is used if not translated.
182 * obsolete - messages which should not be translated, either because they do not exist, or they are ignored messages.
183 * translated - messages which are either required or optional, but translated from English and needed.
185 * @param $code The language code.
187 * @return The messages in this language.
189 public function getMessages( $code ) {
190 $this->loadMessages( $code );
191 return $this->mMessages[$code];
195 * Get all the general English messages, divided to groups:
196 * all - all the messages.
197 * required - messages which should be translated to other languages in order to get a complete translation.
198 * optional - messages which can be translated to other languages, but it's not required for a complete translation.
199 * ignored - messages which should not be translated to other languages.
200 * translatable - messages which are either required or optional, but can be translated from English.
202 * @return The general English messages.
204 public function getGeneralMessages() {
205 $this->loadGeneralMessages();
206 return $this->mGeneralMessages;
210 * Get namespace names for a specific language.
212 * @param $code The language code.
214 * @return Namespace names.
216 public function getNamespaceNames( $code ) {
217 $this->loadFile( $code );
218 return $this->mNamespaceNames[$code];
222 * Get namespace aliases for a specific language.
224 * @param $code The language code.
226 * @return Namespace aliases.
228 public function getNamespaceAliases( $code ) {
229 $this->loadFile( $code );
230 return $this->mNamespaceAliases[$code];
234 * Get magic words for a specific language.
236 * @param $code The language code.
238 * @return Magic words.
240 public function getMagicWords( $code ) {
241 $this->loadFile( $code );
242 return $this->mMagicWords[$code];
246 * Get special page aliases for a specific language.
248 * @param $code The language code.
250 * @return Special page aliases.
252 public function getSpecialPageAliases( $code ) {
253 $this->loadFile( $code );
254 return $this->mSpecialPageAliases[$code];
258 * Get the untranslated messages for a specific language.
260 * @param $code The language code.
262 * @return The untranslated messages for this language.
264 public function getUntranslatedMessages( $code ) {
265 $this->loadGeneralMessages();
266 $this->loadMessages( $code );
267 return array_diff_key( $this->mGeneralMessages['required'], $this->mMessages[$code]['required'] );
271 * Get the duplicate messages for a specific language.
273 * @param $code The language code.
275 * @return The duplicate messages for this language.
277 public function getDuplicateMessages( $code ) {
278 $this->loadGeneralMessages();
279 $this->loadMessages( $code );
280 $duplicateMessages = array();
281 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
282 if ( $this->mGeneralMessages['translatable'][$key] == $value ) {
283 $duplicateMessages[$key] = $value;
286 return $duplicateMessages;
290 * Get the obsolete messages for a specific language.
292 * @param $code The language code.
294 * @return The obsolete messages for this language.
296 public function getObsoleteMessages( $code ) {
297 $this->loadGeneralMessages();
298 $this->loadMessages( $code );
299 return $this->mMessages[$code]['obsolete'];
303 * Get the messages whose variables do not match the original ones.
305 * @param $code The language code.
307 * @return The messages whose variables do not match the original ones.
309 public function getMessagesWithMismatchVariables( $code ) {
310 $this->loadGeneralMessages();
311 $this->loadMessages( $code );
312 $variables = array( '\$1', '\$2', '\$3', '\$4', '\$5', '\$6', '\$7', '\$8', '\$9' );
313 $mismatchMessages = array();
314 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
316 foreach ( $variables as $var ) {
317 if ( preg_match( "/$var/sU", $this->mGeneralMessages['translatable'][$key] ) &&
318 !preg_match( "/$var/sU", $value ) ) {
321 if ( !preg_match( "/$var/sU", $this->mGeneralMessages['translatable'][$key] ) &&
322 preg_match( "/$var/sU", $value ) ) {
327 $mismatchMessages[$key] = $value;
330 return $mismatchMessages;
334 * Get the messages which do not use plural.
336 * @param $code The language code.
338 * @return The messages which do not use plural in this language.
340 public function getMessagesWithoutPlural( $code ) {
341 $this->loadGeneralMessages();
342 $this->loadMessages( $code );
343 $messagesWithoutPlural = array();
344 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
345 if ( stripos( $this->mGeneralMessages['translatable'][$key], '{{plural:' ) !== false && stripos( $value, '{{plural:' ) === false ) {
346 $messagesWithoutPlural[$key] = $value;
349 return $messagesWithoutPlural;
353 * Get the empty messages.
355 * @param $code The language code.
357 * @return The empty messages for this language.
359 public function getEmptyMessages( $code ) {
360 $this->loadGeneralMessages();
361 $this->loadMessages( $code );
362 $emptyMessages = array();
363 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
364 if ( $value === '' || $value === '-' ) {
365 $emptyMessages[$key] = $value;
368 return $emptyMessages;
372 * Get the messages with trailing whitespace.
374 * @param $code The language code.
376 * @return The messages with trailing whitespace in this language.
378 public function getMessagesWithWhitespace( $code ) {
379 $this->loadGeneralMessages();
380 $this->loadMessages( $code );
381 $messagesWithWhitespace = array();
382 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
383 if ( $this->mGeneralMessages['translatable'][$key] !== '' && $value !== rtrim( $value ) ) {
384 $messagesWithWhitespace[$key] = $value;
387 return $messagesWithWhitespace;
391 * Get the non-XHTML messages.
393 * @param $code The language code.
395 * @return The non-XHTML messages for this language.
397 public function getNonXHTMLMessages( $code ) {
398 $this->loadGeneralMessages();
399 $this->loadMessages( $code );
400 $wrongPhrases = array(
408 $wrongPhrases = '~(' . implode( '|', $wrongPhrases ) . ')~sDu';
409 $nonXHTMLMessages = array();
410 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
411 if ( preg_match( $wrongPhrases, $value ) ) {
412 $nonXHTMLMessages[$key] = $value;
415 return $nonXHTMLMessages;
419 * Get the messages which include wrong characters.
421 * @param $code The language code.
423 * @return The messages which include wrong characters in this language.
425 public function getMessagesWithWrongChars( $code ) {
426 $this->loadGeneralMessages();
427 $this->loadMessages( $code );
429 '[LRM]' => "\xE2\x80\x8E",
430 '[RLM]' => "\xE2\x80\x8F",
431 '[LRE]' => "\xE2\x80\xAA",
432 '[RLE]' => "\xE2\x80\xAB",
433 '[POP]' => "\xE2\x80\xAC",
434 '[LRO]' => "\xE2\x80\xAD",
435 '[RLO]' => "\xE2\x80\xAB",
436 '[ZWSP]'=> "\xE2\x80\x8B",
437 '[NBSP]'=> "\xC2\xA0",
438 '[WJ]' => "\xE2\x81\xA0",
439 '[BOM]' => "\xEF\xBB\xBF",
440 '[FFFD]'=> "\xEF\xBF\xBD",
442 $wrongRegExp = '/(' . implode( '|', array_values( $wrongChars ) ) . ')/sDu';
443 $wrongCharsMessages = array();
444 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
445 if ( preg_match( $wrongRegExp, $value ) ) {
446 foreach ( $wrongChars as $viewableChar => $hiddenChar ) {
447 $value = str_replace( $hiddenChar, $viewableChar, $value );
449 $wrongCharsMessages[$key] = $value;
452 return $wrongCharsMessages;
456 * Get the messages which include dubious links.
458 * @param $code The language code.
460 * @return The messages which include dubious links in this language.
462 public function getMessagesWithDubiousLinks( $code ) {
463 $this->loadGeneralMessages();
464 $this->loadMessages( $code );
465 $tc = Title::legalChars() . '#%{}';
467 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
469 preg_match_all( "/\[\[([{$tc}]+)(?:\\|(.+?))?]]/sDu", $value, $matches );
470 for ($i = 0; $i < count($matches[0]); $i++ ) {
471 if ( preg_match( "/.*project.*/isDu", $matches[1][$i] ) ) {
472 $messages[$key][] = $matches[0][$i];
477 if ( isset( $messages[$key] ) ) {
478 $messages[$key] = implode( $messages[$key],", " );
485 * Get the messages which include unbalanced brackets.
487 * @param $code The language code.
489 * @return The messages which include unbalanced brackets in this language.
491 public function getMessagesWithUnbalanced( $code ) {
492 $this->loadGeneralMessages();
493 $this->loadMessages( $code );
495 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
496 $a = $b = $c = $d = 0;
497 foreach ( preg_split( '//', $value ) as $char ) {
514 if ( $a !== $b || $c !== $d ) {
515 $messages[$key] = "$a, $b, $c, $d";
523 * Get the untranslated namespace names.
525 * @param $code The language code.
527 * @return The untranslated namespace names in this language.
529 public function getUntranslatedNamespaces( $code ) {
530 $this->loadFile( 'en' );
531 $this->loadFile( $code );
532 return array_flip( array_diff_key( $this->mNamespaceNames['en'], $this->mNamespaceNames[$code] ) );
536 * Get the project talk namespace names with no $1.
538 * @param $code The language code.
540 * @return The problematic project talk namespaces in this language.
542 public function getProblematicProjectTalks( $code ) {
543 $this->loadFile( $code );
544 $namespaces = array();
546 # Check default namespace name
547 if( isset( $this->mNamespaceNames[$code][NS_PROJECT_TALK] ) ) {
548 $default = $this->mNamespaceNames[$code][NS_PROJECT_TALK];
549 if ( strpos( $default, '$1' ) === FALSE ) {
550 $namespaces[$default] = 'default';
554 # Check namespace aliases
555 foreach( $this->mNamespaceAliases[$code] as $key => $value ) {
556 if ( $value == NS_PROJECT_TALK && strpos( $key, '$1' ) === FALSE ) {
557 $namespaces[$key] = '';
565 * Get the untranslated magic words.
567 * @param $code The language code.
569 * @return The untranslated magic words in this language.
571 public function getUntranslatedMagicWords( $code ) {
572 $this->loadFile( 'en' );
573 $this->loadFile( $code );
574 $magicWords = array();
575 foreach ( $this->mMagicWords['en'] as $key => $value ) {
576 if ( !isset( $this->mMagicWords[$code][$key] ) ) {
577 $magicWords[$key] = $value[1];
584 * Get the obsolete magic words.
586 * @param $code The language code.
588 * @return The obsolete magic words in this language.
590 public function getObsoleteMagicWords( $code ) {
591 $this->loadFile( 'en' );
592 $this->loadFile( $code );
593 $magicWords = array();
594 foreach ( $this->mMagicWords[$code] as $key => $value ) {
595 if ( !isset( $this->mMagicWords['en'][$key] ) ) {
596 $magicWords[$key] = $value[1];
603 * Get the magic words that override the original English magic word.
605 * @param $code The language code.
607 * @return The overriding magic words in this language.
609 public function getOverridingMagicWords( $code ) {
610 $this->loadFile( 'en' );
611 $this->loadFile( $code );
612 $magicWords = array();
613 foreach ( $this->mMagicWords[$code] as $key => $local ) {
614 if ( !isset( $this->mMagicWords['en'][$key] ) ) {
615 # Unrecognized magic word
618 $en = $this->mMagicWords['en'][$key];
619 array_shift( $local );
621 foreach ( $en as $word ) {
622 if ( !in_array( $word, $local ) ) {
623 $magicWords[$key] = $word;
632 * Get the magic words which do not match the case-sensitivity of the original words.
634 * @param $code The language code.
636 * @return The magic words whose case does not match in this language.
638 public function getCaseMismatchMagicWords( $code ) {
639 $this->loadFile( 'en' );
640 $this->loadFile( $code );
641 $magicWords = array();
642 foreach ( $this->mMagicWords[$code] as $key => $local ) {
643 if ( !isset( $this->mMagicWords['en'][$key] ) ) {
644 # Unrecognized magic word
647 if ( $local[0] != $this->mMagicWords['en'][$key][0] ) {
648 $magicWords[$key] = $local[0];
655 * Get the untranslated special page names.
657 * @param $code The language code.
659 * @return The untranslated special page names in this language.
661 public function getUntraslatedSpecialPages( $code ) {
662 $this->loadFile( 'en' );
663 $this->loadFile( $code );
664 $specialPageAliases = array();
665 foreach ( $this->mSpecialPageAliases['en'] as $key => $value ) {
666 if ( !isset( $this->mSpecialPageAliases[$code][$key] ) ) {
667 $specialPageAliases[$key] = $value[0];
670 return $specialPageAliases;
674 * Get the obsolete special page names.
676 * @param $code The language code.
678 * @return The obsolete special page names in this language.
680 public function getObsoleteSpecialPages( $code ) {
681 $this->loadFile( 'en' );
682 $this->loadFile( $code );
683 $specialPageAliases = array();
684 foreach ( $this->mSpecialPageAliases[$code] as $key => $value ) {
685 if ( !isset( $this->mSpecialPageAliases['en'][$key] ) ) {
686 $specialPageAliases[$key] = $value[0];
689 return $specialPageAliases;
693 class extensionLanguages extends languages {
694 private $mMessageGroup; # The message group
697 * Load the messages group.
698 * @param $group The messages group.
700 function __construct( MessageGroup $group ) {
701 $this->mMessageGroup = $group;
703 $bools = $this->mMessageGroup->getBools();
704 $this->mIgnoredMessages = $bools['ignored'];
705 $this->mOptionalMessages = $bools['optional'];
709 * Get the extension name.
711 * @return The extension name.
713 public function name() {
714 return $this->mMessageGroup->getLabel();
718 * Load the language file.
720 * @param $code The language code.
722 protected function loadFile( $code ) {
723 if( !isset( $this->mRawMessages[$code] ) ) {
724 $this->mRawMessages[$code] = $this->mMessageGroup->load( $code );
725 if( empty( $this->mRawMessages[$code] ) ) {
726 $this->mRawMessages[$code] = array();