3 * Handle messages in the language files.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
21 * @ingroup MaintenanceLanguage
25 * @ingroup MaintenanceLanguage
28 protected $mLanguages; # List of languages
30 protected $mRawMessages; # Raw list of the messages in each language
31 protected $mMessages; # Messages in each language (except for English), divided to groups
32 protected $mFallback; # Fallback language in each language
33 protected $mGeneralMessages; # General messages in English, divided to groups
34 protected $mIgnoredMessages; # All the messages which should be exist only in the English file
35 protected $mOptionalMessages; # All the messages which may be translated or not, depending on the language
37 protected $mNamespaceNames; # Namespace names
38 protected $mNamespaceAliases; # Namespace aliases
39 protected $mMagicWords; # Magic words
40 protected $mSpecialPageAliases; # Special page aliases
43 * Load the list of languages: all the Messages*.php
44 * files in the languages directory.
46 * @param $exif bool Treat the Exif messages?
48 function __construct( $exif = true ) {
49 require __DIR__ . '/messageTypes.inc';
50 $this->mIgnoredMessages = $wgIgnoredMessages;
52 $this->mOptionalMessages = array_merge( $wgOptionalMessages );
54 $this->mOptionalMessages = array_merge( $wgOptionalMessages, $wgEXIFMessages );
57 $this->mLanguages = array_keys( Language::fetchLanguageNames( null, 'mwfile' ) );
58 sort( $this->mLanguages );
62 * Get the language list.
64 * @return array The language list.
66 public function getLanguages() {
67 return $this->mLanguages;
71 * Get the ignored messages list.
73 * @return array The ignored messages list.
75 public function getIgnoredMessages() {
76 return $this->mIgnoredMessages;
80 * Get the optional messages list.
82 * @return array The optional messages list.
84 public function getOptionalMessages() {
85 return $this->mOptionalMessages;
89 * Load the language file.
91 * @param $code string The language code.
93 protected function loadFile( $code ) {
94 if ( isset( $this->mRawMessages[$code] ) &&
95 isset( $this->mFallback[$code] ) &&
96 isset( $this->mNamespaceNames[$code] ) &&
97 isset( $this->mNamespaceAliases[$code] ) &&
98 isset( $this->mMagicWords[$code] ) &&
99 isset( $this->mSpecialPageAliases[$code] )
103 $this->mRawMessages[$code] = array();
104 $this->mFallback[$code] = '';
105 $this->mNamespaceNames[$code] = array();
106 $this->mNamespaceAliases[$code] = array();
107 $this->mMagicWords[$code] = array();
108 $this->mSpecialPageAliases[$code] = array();
109 $filename = Language::getMessagesFileName( $code );
110 if ( file_exists( $filename ) ) {
112 if ( isset( $messages ) ) {
113 $this->mRawMessages[$code] = $messages;
115 if ( isset( $fallback ) ) {
116 $this->mFallback[$code] = $fallback;
118 if ( isset( $namespaceNames ) ) {
119 $this->mNamespaceNames[$code] = $namespaceNames;
121 if ( isset( $namespaceAliases ) ) {
122 $this->mNamespaceAliases[$code] = $namespaceAliases;
124 if ( isset( $magicWords ) ) {
125 $this->mMagicWords[$code] = $magicWords;
127 if ( isset( $specialPageAliases ) ) {
128 $this->mSpecialPageAliases[$code] = $specialPageAliases;
134 * Load the messages for a specific language (which is not English) and divide them to groups:
135 * all - all the messages.
136 * required - messages which should be translated in order to get a complete translation.
137 * optional - messages which can be translated, the fallback translation is used if not translated.
138 * obsolete - messages which should not be translated, either because they do not exist, or they are ignored messages.
139 * translated - messages which are either required or optional, but translated from English and needed.
141 * @param $code string The language code.
143 private function loadMessages( $code ) {
144 if ( isset( $this->mMessages[$code] ) ) {
147 $this->loadFile( $code );
148 $this->loadGeneralMessages();
149 $this->mMessages[$code]['all'] = $this->mRawMessages[$code];
150 $this->mMessages[$code]['required'] = array();
151 $this->mMessages[$code]['optional'] = array();
152 $this->mMessages[$code]['obsolete'] = array();
153 $this->mMessages[$code]['translated'] = array();
154 foreach ( $this->mMessages[$code]['all'] as $key => $value ) {
155 if ( isset( $this->mGeneralMessages['required'][$key] ) ) {
156 $this->mMessages[$code]['required'][$key] = $value;
157 $this->mMessages[$code]['translated'][$key] = $value;
158 } elseif ( isset( $this->mGeneralMessages['optional'][$key] ) ) {
159 $this->mMessages[$code]['optional'][$key] = $value;
160 $this->mMessages[$code]['translated'][$key] = $value;
162 $this->mMessages[$code]['obsolete'][$key] = $value;
168 * Load the messages for English and divide them 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 private function loadGeneralMessages() {
176 if ( isset( $this->mGeneralMessages ) ) {
179 $this->loadFile( 'en' );
180 $this->mGeneralMessages['all'] = $this->mRawMessages['en'];
181 $this->mGeneralMessages['required'] = array();
182 $this->mGeneralMessages['optional'] = array();
183 $this->mGeneralMessages['ignored'] = array();
184 $this->mGeneralMessages['translatable'] = array();
185 foreach ( $this->mGeneralMessages['all'] as $key => $value ) {
186 if ( in_array( $key, $this->mIgnoredMessages ) ) {
187 $this->mGeneralMessages['ignored'][$key] = $value;
188 } elseif ( in_array( $key, $this->mOptionalMessages ) ) {
189 $this->mGeneralMessages['optional'][$key] = $value;
190 $this->mGeneralMessages['translatable'][$key] = $value;
192 $this->mGeneralMessages['required'][$key] = $value;
193 $this->mGeneralMessages['translatable'][$key] = $value;
199 * Get all the messages for a specific language (not English), without the
200 * fallback language messages, divided to groups:
201 * all - all the messages.
202 * required - messages which should be translated in order to get a complete translation.
203 * optional - messages which can be translated, the fallback translation is used if not translated.
204 * obsolete - messages which should not be translated, either because they do not exist, or they are ignored messages.
205 * translated - messages which are either required or optional, but translated from English and needed.
207 * @param $code string The language code.
209 * @return string The messages in this language.
211 public function getMessages( $code ) {
212 $this->loadMessages( $code );
214 return $this->mMessages[$code];
218 * Get all the general English messages, divided to groups:
219 * all - all the messages.
220 * required - messages which should be translated to other languages in order to get a complete translation.
221 * optional - messages which can be translated to other languages, but it's not required for a complete translation.
222 * ignored - messages which should not be translated to other languages.
223 * translatable - messages which are either required or optional, but can be translated from English.
225 * @return array The general English messages.
227 public function getGeneralMessages() {
228 $this->loadGeneralMessages();
230 return $this->mGeneralMessages;
234 * Get fallback language code for a specific language.
236 * @param $code string The language code.
238 * @return string Fallback code.
240 public function getFallback( $code ) {
241 $this->loadFile( $code );
243 return $this->mFallback[$code];
247 * Get namespace names for a specific language.
249 * @param $code string The language code.
251 * @return array Namespace names.
253 public function getNamespaceNames( $code ) {
254 $this->loadFile( $code );
256 return $this->mNamespaceNames[$code];
260 * Get namespace aliases for a specific language.
262 * @param $code string The language code.
264 * @return array Namespace aliases.
266 public function getNamespaceAliases( $code ) {
267 $this->loadFile( $code );
269 return $this->mNamespaceAliases[$code];
273 * Get magic words for a specific language.
275 * @param $code string The language code.
277 * @return array Magic words.
279 public function getMagicWords( $code ) {
280 $this->loadFile( $code );
282 return $this->mMagicWords[$code];
286 * Get special page aliases for a specific language.
288 * @param $code string The language code.
290 * @return array Special page aliases.
292 public function getSpecialPageAliases( $code ) {
293 $this->loadFile( $code );
295 return $this->mSpecialPageAliases[$code];
299 * Get the untranslated messages for a specific language.
301 * @param $code string The language code.
303 * @return array The untranslated messages for this language.
305 public function getUntranslatedMessages( $code ) {
306 $this->loadGeneralMessages();
307 $this->loadMessages( $code );
309 return array_diff_key( $this->mGeneralMessages['required'], $this->mMessages[$code]['required'] );
313 * Get the duplicate messages for a specific language.
315 * @param $code string The language code.
317 * @return array The duplicate messages for this language.
319 public function getDuplicateMessages( $code ) {
320 $this->loadGeneralMessages();
321 $this->loadMessages( $code );
322 $duplicateMessages = array();
323 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
324 if ( $this->mGeneralMessages['translatable'][$key] == $value ) {
325 $duplicateMessages[$key] = $value;
329 return $duplicateMessages;
333 * Get the obsolete messages for a specific language.
335 * @param $code string The language code.
337 * @return array The obsolete messages for this language.
339 public function getObsoleteMessages( $code ) {
340 $this->loadGeneralMessages();
341 $this->loadMessages( $code );
343 return $this->mMessages[$code]['obsolete'];
347 * Get the messages whose variables do not match the original ones.
349 * @param $code string The language code.
351 * @return array The messages whose variables do not match the original ones.
353 public function getMessagesWithMismatchVariables( $code ) {
354 $this->loadGeneralMessages();
355 $this->loadMessages( $code );
356 $variables = array( '\$1', '\$2', '\$3', '\$4', '\$5', '\$6', '\$7', '\$8', '\$9' );
357 $mismatchMessages = array();
358 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
360 foreach ( $variables as $var ) {
361 if ( preg_match( "/$var/sU", $this->mGeneralMessages['translatable'][$key] ) &&
362 !preg_match( "/$var/sU", $value )
366 if ( !preg_match( "/$var/sU", $this->mGeneralMessages['translatable'][$key] ) &&
367 preg_match( "/$var/sU", $value )
373 $mismatchMessages[$key] = $value;
377 return $mismatchMessages;
381 * Get the messages which do not use plural.
383 * @param $code string The language code.
385 * @return array The messages which do not use plural in this language.
387 public function getMessagesWithoutPlural( $code ) {
388 $this->loadGeneralMessages();
389 $this->loadMessages( $code );
390 $messagesWithoutPlural = array();
391 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
392 if ( stripos( $this->mGeneralMessages['translatable'][$key], '{{plural:' ) !== false && stripos( $value, '{{plural:' ) === false ) {
393 $messagesWithoutPlural[$key] = $value;
397 return $messagesWithoutPlural;
401 * Get the empty messages.
403 * @param $code string The language code.
405 * @return array The empty messages for this language.
407 public function getEmptyMessages( $code ) {
408 $this->loadGeneralMessages();
409 $this->loadMessages( $code );
410 $emptyMessages = array();
411 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
412 if ( $value === '' || $value === '-' ) {
413 $emptyMessages[$key] = $value;
417 return $emptyMessages;
421 * Get the messages with trailing whitespace.
423 * @param $code string The language code.
425 * @return array The messages with trailing whitespace in this language.
427 public function getMessagesWithWhitespace( $code ) {
428 $this->loadGeneralMessages();
429 $this->loadMessages( $code );
430 $messagesWithWhitespace = array();
431 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
432 if ( $this->mGeneralMessages['translatable'][$key] !== '' && $value !== rtrim( $value ) ) {
433 $messagesWithWhitespace[$key] = $value;
437 return $messagesWithWhitespace;
441 * Get the non-XHTML messages.
443 * @param $code string The language code.
445 * @return array The non-XHTML messages for this language.
447 public function getNonXHTMLMessages( $code ) {
448 $this->loadGeneralMessages();
449 $this->loadMessages( $code );
450 $wrongPhrases = array(
458 $wrongPhrases = '~(' . implode( '|', $wrongPhrases ) . ')~sDu';
459 $nonXHTMLMessages = array();
460 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
461 if ( preg_match( $wrongPhrases, $value ) ) {
462 $nonXHTMLMessages[$key] = $value;
466 return $nonXHTMLMessages;
470 * Get the messages which include wrong characters.
472 * @param $code string The language code.
474 * @return array The messages which include wrong characters in this language.
476 public function getMessagesWithWrongChars( $code ) {
477 $this->loadGeneralMessages();
478 $this->loadMessages( $code );
480 '[LRM]' => "\xE2\x80\x8E",
481 '[RLM]' => "\xE2\x80\x8F",
482 '[LRE]' => "\xE2\x80\xAA",
483 '[RLE]' => "\xE2\x80\xAB",
484 '[POP]' => "\xE2\x80\xAC",
485 '[LRO]' => "\xE2\x80\xAD",
486 '[RLO]' => "\xE2\x80\xAB",
487 '[ZWSP]' => "\xE2\x80\x8B",
488 '[NBSP]' => "\xC2\xA0",
489 '[WJ]' => "\xE2\x81\xA0",
490 '[BOM]' => "\xEF\xBB\xBF",
491 '[FFFD]' => "\xEF\xBF\xBD",
493 $wrongRegExp = '/(' . implode( '|', array_values( $wrongChars ) ) . ')/sDu';
494 $wrongCharsMessages = array();
495 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
496 if ( preg_match( $wrongRegExp, $value ) ) {
497 foreach ( $wrongChars as $viewableChar => $hiddenChar ) {
498 $value = str_replace( $hiddenChar, $viewableChar, $value );
500 $wrongCharsMessages[$key] = $value;
504 return $wrongCharsMessages;
508 * Get the messages which include dubious links.
510 * @param $code string The language code.
512 * @return array The messages which include dubious links in this language.
514 public function getMessagesWithDubiousLinks( $code ) {
515 $this->loadGeneralMessages();
516 $this->loadMessages( $code );
517 $tc = Title::legalChars() . '#%{}';
519 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
521 preg_match_all( "/\[\[([{$tc}]+)(?:\\|(.+?))?]]/sDu", $value, $matches );
522 for ( $i = 0; $i < count( $matches[0] ); $i++ ) {
523 if ( preg_match( "/.*project.*/isDu", $matches[1][$i] ) ) {
524 $messages[$key][] = $matches[0][$i];
528 if ( isset( $messages[$key] ) ) {
529 $messages[$key] = implode( $messages[$key], ", " );
537 * Get the messages which include unbalanced brackets.
539 * @param $code string The language code.
541 * @return array The messages which include unbalanced brackets in this language.
543 public function getMessagesWithUnbalanced( $code ) {
544 $this->loadGeneralMessages();
545 $this->loadMessages( $code );
547 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
548 $a = $b = $c = $d = 0;
549 foreach ( preg_split( '//', $value ) as $char ) {
566 if ( $a !== $b || $c !== $d ) {
567 $messages[$key] = "$a, $b, $c, $d";
575 * Get the untranslated namespace names.
577 * @param $code string The language code.
579 * @return array The untranslated namespace names in this language.
581 public function getUntranslatedNamespaces( $code ) {
582 $this->loadFile( 'en' );
583 $this->loadFile( $code );
584 $namespacesDiff = array_diff_key( $this->mNamespaceNames['en'], $this->mNamespaceNames[$code] );
585 if ( isset( $namespacesDiff[NS_MAIN] ) ) {
586 unset( $namespacesDiff[NS_MAIN] );
589 return $namespacesDiff;
593 * Get the project talk namespace names with no $1.
595 * @param $code string The language code.
597 * @return array The problematic project talk namespaces in this language.
599 public function getProblematicProjectTalks( $code ) {
600 $this->loadFile( $code );
601 $namespaces = array();
603 # Check default namespace name
604 if ( isset( $this->mNamespaceNames[$code][NS_PROJECT_TALK] ) ) {
605 $default = $this->mNamespaceNames[$code][NS_PROJECT_TALK];
606 if ( strpos( $default, '$1' ) === false ) {
607 $namespaces[$default] = 'default';
611 # Check namespace aliases
612 foreach ( $this->mNamespaceAliases[$code] as $key => $value ) {
613 if ( $value == NS_PROJECT_TALK && strpos( $key, '$1' ) === false ) {
614 $namespaces[$key] = '';
622 * Get the untranslated magic words.
624 * @param $code string The language code.
626 * @return array The untranslated magic words in this language.
628 public function getUntranslatedMagicWords( $code ) {
629 $this->loadFile( 'en' );
630 $this->loadFile( $code );
631 $magicWords = array();
632 foreach ( $this->mMagicWords['en'] as $key => $value ) {
633 if ( !isset( $this->mMagicWords[$code][$key] ) ) {
634 $magicWords[$key] = $value[1];
642 * Get the obsolete magic words.
644 * @param $code string The language code.
646 * @return array The obsolete magic words in this language.
648 public function getObsoleteMagicWords( $code ) {
649 $this->loadFile( 'en' );
650 $this->loadFile( $code );
651 $magicWords = array();
652 foreach ( $this->mMagicWords[$code] as $key => $value ) {
653 if ( !isset( $this->mMagicWords['en'][$key] ) ) {
654 $magicWords[$key] = $value[1];
662 * Get the magic words that override the original English magic word.
664 * @param $code string The language code.
666 * @return array The overriding magic words in this language.
668 public function getOverridingMagicWords( $code ) {
669 $this->loadFile( 'en' );
670 $this->loadFile( $code );
671 $magicWords = array();
672 foreach ( $this->mMagicWords[$code] as $key => $local ) {
673 if ( !isset( $this->mMagicWords['en'][$key] ) ) {
674 # Unrecognized magic word
677 $en = $this->mMagicWords['en'][$key];
678 array_shift( $local );
680 foreach ( $en as $word ) {
681 if ( !in_array( $word, $local ) ) {
682 $magicWords[$key] = $word;
692 * Get the magic words which do not match the case-sensitivity of the original words.
694 * @param $code string The language code.
696 * @return array The magic words whose case does not match in this language.
698 public function getCaseMismatchMagicWords( $code ) {
699 $this->loadFile( 'en' );
700 $this->loadFile( $code );
701 $magicWords = array();
702 foreach ( $this->mMagicWords[$code] as $key => $local ) {
703 if ( !isset( $this->mMagicWords['en'][$key] ) ) {
704 # Unrecognized magic word
707 if ( $local[0] != $this->mMagicWords['en'][$key][0] ) {
708 $magicWords[$key] = $local[0];
716 * Get the untranslated special page names.
718 * @param $code string The language code.
720 * @return array The untranslated special page names in this language.
722 public function getUntraslatedSpecialPages( $code ) {
723 $this->loadFile( 'en' );
724 $this->loadFile( $code );
725 $specialPageAliases = array();
726 foreach ( $this->mSpecialPageAliases['en'] as $key => $value ) {
727 if ( !isset( $this->mSpecialPageAliases[$code][$key] ) ) {
728 $specialPageAliases[$key] = $value[0];
732 return $specialPageAliases;
736 * Get the obsolete special page names.
738 * @param $code string The language code.
740 * @return array The obsolete special page names in this language.
742 public function getObsoleteSpecialPages( $code ) {
743 $this->loadFile( 'en' );
744 $this->loadFile( $code );
745 $specialPageAliases = array();
746 foreach ( $this->mSpecialPageAliases[$code] as $key => $value ) {
747 if ( !isset( $this->mSpecialPageAliases['en'][$key] ) ) {
748 $specialPageAliases[$key] = $value[0];
752 return $specialPageAliases;
756 class extensionLanguages extends languages {
761 private $mMessageGroup;
764 * Load the messages group.
765 * @param $group MessageGroup The messages group.
767 function __construct( MessageGroup $group ) {
768 $this->mMessageGroup = $group;
770 $this->mIgnoredMessages = $this->mMessageGroup->getIgnored();
771 $this->mOptionalMessages = $this->mMessageGroup->getOptional();
775 * Get the extension name.
777 * @return string The extension name.
779 public function name() {
780 return $this->mMessageGroup->getLabel();
784 * Load the language file.
786 * @param $code string The language code.
788 protected function loadFile( $code ) {
789 if ( !isset( $this->mRawMessages[$code] ) ) {
790 $this->mRawMessages[$code] = $this->mMessageGroup->load( $code );
791 if ( empty( $this->mRawMessages[$code] ) ) {
792 $this->mRawMessages[$code] = array();