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] ) ) {
102 $this->mRawMessages[$code] = array();
103 $this->mFallback[$code] = '';
104 $this->mNamespaceNames[$code] = array();
105 $this->mNamespaceAliases[$code] = array();
106 $this->mMagicWords[$code] = array();
107 $this->mSpecialPageAliases[$code] = array();
108 $filename = Language::getMessagesFileName( $code );
109 if ( file_exists( $filename ) ) {
111 if ( isset( $messages ) ) {
112 $this->mRawMessages[$code] = $messages;
114 if ( isset( $fallback ) ) {
115 $this->mFallback[$code] = $fallback;
117 if ( isset( $namespaceNames ) ) {
118 $this->mNamespaceNames[$code] = $namespaceNames;
120 if ( isset( $namespaceAliases ) ) {
121 $this->mNamespaceAliases[$code] = $namespaceAliases;
123 if ( isset( $magicWords ) ) {
124 $this->mMagicWords[$code] = $magicWords;
126 if ( isset( $specialPageAliases ) ) {
127 $this->mSpecialPageAliases[$code] = $specialPageAliases;
133 * Load the messages for a specific language (which is not English) and divide them to groups:
134 * all - all the messages.
135 * required - messages which should be translated in order to get a complete translation.
136 * optional - messages which can be translated, the fallback translation is used if not translated.
137 * obsolete - messages which should not be translated, either because they do not exist, or they are ignored messages.
138 * translated - messages which are either required or optional, but translated from English and needed.
140 * @param $code string The language code.
142 private function loadMessages( $code ) {
143 if ( isset( $this->mMessages[$code] ) ) {
146 $this->loadFile( $code );
147 $this->loadGeneralMessages();
148 $this->mMessages[$code]['all'] = $this->mRawMessages[$code];
149 $this->mMessages[$code]['required'] = array();
150 $this->mMessages[$code]['optional'] = array();
151 $this->mMessages[$code]['obsolete'] = array();
152 $this->mMessages[$code]['translated'] = array();
153 foreach ( $this->mMessages[$code]['all'] as $key => $value ) {
154 if ( isset( $this->mGeneralMessages['required'][$key] ) ) {
155 $this->mMessages[$code]['required'][$key] = $value;
156 $this->mMessages[$code]['translated'][$key] = $value;
157 } elseif ( isset( $this->mGeneralMessages['optional'][$key] ) ) {
158 $this->mMessages[$code]['optional'][$key] = $value;
159 $this->mMessages[$code]['translated'][$key] = $value;
161 $this->mMessages[$code]['obsolete'][$key] = $value;
167 * Load the messages for English and divide them to groups:
168 * all - all the messages.
169 * required - messages which should be translated to other languages in order to get a complete translation.
170 * optional - messages which can be translated to other languages, but it's not required for a complete translation.
171 * ignored - messages which should not be translated to other languages.
172 * translatable - messages which are either required or optional, but can be translated from English.
174 private function loadGeneralMessages() {
175 if ( isset( $this->mGeneralMessages ) ) {
178 $this->loadFile( 'en' );
179 $this->mGeneralMessages['all'] = $this->mRawMessages['en'];
180 $this->mGeneralMessages['required'] = array();
181 $this->mGeneralMessages['optional'] = array();
182 $this->mGeneralMessages['ignored'] = array();
183 $this->mGeneralMessages['translatable'] = array();
184 foreach ( $this->mGeneralMessages['all'] as $key => $value ) {
185 if ( in_array( $key, $this->mIgnoredMessages ) ) {
186 $this->mGeneralMessages['ignored'][$key] = $value;
187 } elseif ( in_array( $key, $this->mOptionalMessages ) ) {
188 $this->mGeneralMessages['optional'][$key] = $value;
189 $this->mGeneralMessages['translatable'][$key] = $value;
191 $this->mGeneralMessages['required'][$key] = $value;
192 $this->mGeneralMessages['translatable'][$key] = $value;
198 * Get all the messages for a specific language (not English), without the
199 * fallback language messages, divided to groups:
200 * all - all the messages.
201 * required - messages which should be translated in order to get a complete translation.
202 * optional - messages which can be translated, the fallback translation is used if not translated.
203 * obsolete - messages which should not be translated, either because they do not exist, or they are ignored messages.
204 * translated - messages which are either required or optional, but translated from English and needed.
206 * @param $code string The language code.
208 * @return string The messages in this language.
210 public function getMessages( $code ) {
211 $this->loadMessages( $code );
212 return $this->mMessages[$code];
216 * Get all the general English messages, divided to groups:
217 * all - all the messages.
218 * required - messages which should be translated to other languages in order to get a complete translation.
219 * optional - messages which can be translated to other languages, but it's not required for a complete translation.
220 * ignored - messages which should not be translated to other languages.
221 * translatable - messages which are either required or optional, but can be translated from English.
223 * @return array The general English messages.
225 public function getGeneralMessages() {
226 $this->loadGeneralMessages();
227 return $this->mGeneralMessages;
231 * Get fallback language code for a specific language.
233 * @param $code string The language code.
235 * @return string Fallback code.
237 public function getFallback( $code ) {
238 $this->loadFile( $code );
239 return $this->mFallback[$code];
243 * Get namespace names for a specific language.
245 * @param $code string The language code.
247 * @return array Namespace names.
249 public function getNamespaceNames( $code ) {
250 $this->loadFile( $code );
251 return $this->mNamespaceNames[$code];
255 * Get namespace aliases for a specific language.
257 * @param $code string The language code.
259 * @return array Namespace aliases.
261 public function getNamespaceAliases( $code ) {
262 $this->loadFile( $code );
263 return $this->mNamespaceAliases[$code];
267 * Get magic words for a specific language.
269 * @param $code string The language code.
271 * @return array Magic words.
273 public function getMagicWords( $code ) {
274 $this->loadFile( $code );
275 return $this->mMagicWords[$code];
279 * Get special page aliases for a specific language.
281 * @param $code string The language code.
283 * @return array Special page aliases.
285 public function getSpecialPageAliases( $code ) {
286 $this->loadFile( $code );
287 return $this->mSpecialPageAliases[$code];
291 * Get the untranslated messages for a specific language.
293 * @param $code string The language code.
295 * @return array The untranslated messages for this language.
297 public function getUntranslatedMessages( $code ) {
298 $this->loadGeneralMessages();
299 $this->loadMessages( $code );
300 return array_diff_key( $this->mGeneralMessages['required'], $this->mMessages[$code]['required'] );
304 * Get the duplicate messages for a specific language.
306 * @param $code string The language code.
308 * @return array The duplicate messages for this language.
310 public function getDuplicateMessages( $code ) {
311 $this->loadGeneralMessages();
312 $this->loadMessages( $code );
313 $duplicateMessages = array();
314 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
315 if ( $this->mGeneralMessages['translatable'][$key] == $value ) {
316 $duplicateMessages[$key] = $value;
319 return $duplicateMessages;
323 * Get the obsolete messages for a specific language.
325 * @param $code string The language code.
327 * @return array The obsolete messages for this language.
329 public function getObsoleteMessages( $code ) {
330 $this->loadGeneralMessages();
331 $this->loadMessages( $code );
332 return $this->mMessages[$code]['obsolete'];
336 * Get the messages whose variables do not match the original ones.
338 * @param $code string The language code.
340 * @return array The messages whose variables do not match the original ones.
342 public function getMessagesWithMismatchVariables( $code ) {
343 $this->loadGeneralMessages();
344 $this->loadMessages( $code );
345 $variables = array( '\$1', '\$2', '\$3', '\$4', '\$5', '\$6', '\$7', '\$8', '\$9' );
346 $mismatchMessages = array();
347 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
349 foreach ( $variables as $var ) {
350 if ( preg_match( "/$var/sU", $this->mGeneralMessages['translatable'][$key] ) &&
351 !preg_match( "/$var/sU", $value ) ) {
354 if ( !preg_match( "/$var/sU", $this->mGeneralMessages['translatable'][$key] ) &&
355 preg_match( "/$var/sU", $value ) ) {
360 $mismatchMessages[$key] = $value;
363 return $mismatchMessages;
367 * Get the messages which do not use plural.
369 * @param $code string The language code.
371 * @return array The messages which do not use plural in this language.
373 public function getMessagesWithoutPlural( $code ) {
374 $this->loadGeneralMessages();
375 $this->loadMessages( $code );
376 $messagesWithoutPlural = array();
377 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
378 if ( stripos( $this->mGeneralMessages['translatable'][$key], '{{plural:' ) !== false && stripos( $value, '{{plural:' ) === false ) {
379 $messagesWithoutPlural[$key] = $value;
382 return $messagesWithoutPlural;
386 * Get the empty messages.
388 * @param $code string The language code.
390 * @return array The empty messages for this language.
392 public function getEmptyMessages( $code ) {
393 $this->loadGeneralMessages();
394 $this->loadMessages( $code );
395 $emptyMessages = array();
396 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
397 if ( $value === '' || $value === '-' ) {
398 $emptyMessages[$key] = $value;
401 return $emptyMessages;
405 * Get the messages with trailing whitespace.
407 * @param $code string The language code.
409 * @return array The messages with trailing whitespace in this language.
411 public function getMessagesWithWhitespace( $code ) {
412 $this->loadGeneralMessages();
413 $this->loadMessages( $code );
414 $messagesWithWhitespace = array();
415 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
416 if ( $this->mGeneralMessages['translatable'][$key] !== '' && $value !== rtrim( $value ) ) {
417 $messagesWithWhitespace[$key] = $value;
420 return $messagesWithWhitespace;
424 * Get the non-XHTML messages.
426 * @param $code string The language code.
428 * @return array The non-XHTML messages for this language.
430 public function getNonXHTMLMessages( $code ) {
431 $this->loadGeneralMessages();
432 $this->loadMessages( $code );
433 $wrongPhrases = array(
441 $wrongPhrases = '~(' . implode( '|', $wrongPhrases ) . ')~sDu';
442 $nonXHTMLMessages = array();
443 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
444 if ( preg_match( $wrongPhrases, $value ) ) {
445 $nonXHTMLMessages[$key] = $value;
448 return $nonXHTMLMessages;
452 * Get the messages which include wrong characters.
454 * @param $code string The language code.
456 * @return array The messages which include wrong characters in this language.
458 public function getMessagesWithWrongChars( $code ) {
459 $this->loadGeneralMessages();
460 $this->loadMessages( $code );
462 '[LRM]' => "\xE2\x80\x8E",
463 '[RLM]' => "\xE2\x80\x8F",
464 '[LRE]' => "\xE2\x80\xAA",
465 '[RLE]' => "\xE2\x80\xAB",
466 '[POP]' => "\xE2\x80\xAC",
467 '[LRO]' => "\xE2\x80\xAD",
468 '[RLO]' => "\xE2\x80\xAB",
469 '[ZWSP]' => "\xE2\x80\x8B",
470 '[NBSP]' => "\xC2\xA0",
471 '[WJ]' => "\xE2\x81\xA0",
472 '[BOM]' => "\xEF\xBB\xBF",
473 '[FFFD]' => "\xEF\xBF\xBD",
475 $wrongRegExp = '/(' . implode( '|', array_values( $wrongChars ) ) . ')/sDu';
476 $wrongCharsMessages = array();
477 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
478 if ( preg_match( $wrongRegExp, $value ) ) {
479 foreach ( $wrongChars as $viewableChar => $hiddenChar ) {
480 $value = str_replace( $hiddenChar, $viewableChar, $value );
482 $wrongCharsMessages[$key] = $value;
485 return $wrongCharsMessages;
489 * Get the messages which include dubious links.
491 * @param $code string The language code.
493 * @return array The messages which include dubious links in this language.
495 public function getMessagesWithDubiousLinks( $code ) {
496 $this->loadGeneralMessages();
497 $this->loadMessages( $code );
498 $tc = Title::legalChars() . '#%{}';
500 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
502 preg_match_all( "/\[\[([{$tc}]+)(?:\\|(.+?))?]]/sDu", $value, $matches );
503 for ( $i = 0; $i < count( $matches[0] ); $i++ ) {
504 if ( preg_match( "/.*project.*/isDu", $matches[1][$i] ) ) {
505 $messages[$key][] = $matches[0][$i];
510 if ( isset( $messages[$key] ) ) {
511 $messages[$key] = implode( $messages[$key], ", " );
518 * Get the messages which include unbalanced brackets.
520 * @param $code string The language code.
522 * @return array The messages which include unbalanced brackets in this language.
524 public function getMessagesWithUnbalanced( $code ) {
525 $this->loadGeneralMessages();
526 $this->loadMessages( $code );
528 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
529 $a = $b = $c = $d = 0;
530 foreach ( preg_split( '//', $value ) as $char ) {
547 if ( $a !== $b || $c !== $d ) {
548 $messages[$key] = "$a, $b, $c, $d";
556 * Get the untranslated namespace names.
558 * @param $code string The language code.
560 * @return array The untranslated namespace names in this language.
562 public function getUntranslatedNamespaces( $code ) {
563 $this->loadFile( 'en' );
564 $this->loadFile( $code );
565 $namespacesDiff = array_diff_key( $this->mNamespaceNames['en'], $this->mNamespaceNames[$code] );
566 if ( isset( $namespacesDiff[NS_MAIN] ) ) {
567 unset( $namespacesDiff[NS_MAIN] );
569 return $namespacesDiff;
573 * Get the project talk namespace names with no $1.
575 * @param $code string The language code.
577 * @return array The problematic project talk namespaces in this language.
579 public function getProblematicProjectTalks( $code ) {
580 $this->loadFile( $code );
581 $namespaces = array();
583 # Check default namespace name
584 if ( isset( $this->mNamespaceNames[$code][NS_PROJECT_TALK] ) ) {
585 $default = $this->mNamespaceNames[$code][NS_PROJECT_TALK];
586 if ( strpos( $default, '$1' ) === false ) {
587 $namespaces[$default] = 'default';
591 # Check namespace aliases
592 foreach ( $this->mNamespaceAliases[$code] as $key => $value ) {
593 if ( $value == NS_PROJECT_TALK && strpos( $key, '$1' ) === false ) {
594 $namespaces[$key] = '';
602 * Get the untranslated magic words.
604 * @param $code string The language code.
606 * @return array The untranslated magic words in this language.
608 public function getUntranslatedMagicWords( $code ) {
609 $this->loadFile( 'en' );
610 $this->loadFile( $code );
611 $magicWords = array();
612 foreach ( $this->mMagicWords['en'] as $key => $value ) {
613 if ( !isset( $this->mMagicWords[$code][$key] ) ) {
614 $magicWords[$key] = $value[1];
621 * Get the obsolete magic words.
623 * @param $code string The language code.
625 * @return array The obsolete magic words in this language.
627 public function getObsoleteMagicWords( $code ) {
628 $this->loadFile( 'en' );
629 $this->loadFile( $code );
630 $magicWords = array();
631 foreach ( $this->mMagicWords[$code] as $key => $value ) {
632 if ( !isset( $this->mMagicWords['en'][$key] ) ) {
633 $magicWords[$key] = $value[1];
640 * Get the magic words that override the original English magic word.
642 * @param $code string The language code.
644 * @return array The overriding magic words in this language.
646 public function getOverridingMagicWords( $code ) {
647 $this->loadFile( 'en' );
648 $this->loadFile( $code );
649 $magicWords = array();
650 foreach ( $this->mMagicWords[$code] as $key => $local ) {
651 if ( !isset( $this->mMagicWords['en'][$key] ) ) {
652 # Unrecognized magic word
655 $en = $this->mMagicWords['en'][$key];
656 array_shift( $local );
658 foreach ( $en as $word ) {
659 if ( !in_array( $word, $local ) ) {
660 $magicWords[$key] = $word;
669 * Get the magic words which do not match the case-sensitivity of the original words.
671 * @param $code string The language code.
673 * @return array The magic words whose case does not match in this language.
675 public function getCaseMismatchMagicWords( $code ) {
676 $this->loadFile( 'en' );
677 $this->loadFile( $code );
678 $magicWords = array();
679 foreach ( $this->mMagicWords[$code] as $key => $local ) {
680 if ( !isset( $this->mMagicWords['en'][$key] ) ) {
681 # Unrecognized magic word
684 if ( $local[0] != $this->mMagicWords['en'][$key][0] ) {
685 $magicWords[$key] = $local[0];
692 * Get the untranslated special page names.
694 * @param $code string The language code.
696 * @return array The untranslated special page names in this language.
698 public function getUntraslatedSpecialPages( $code ) {
699 $this->loadFile( 'en' );
700 $this->loadFile( $code );
701 $specialPageAliases = array();
702 foreach ( $this->mSpecialPageAliases['en'] as $key => $value ) {
703 if ( !isset( $this->mSpecialPageAliases[$code][$key] ) ) {
704 $specialPageAliases[$key] = $value[0];
707 return $specialPageAliases;
711 * Get the obsolete special page names.
713 * @param $code string The language code.
715 * @return array The obsolete special page names in this language.
717 public function getObsoleteSpecialPages( $code ) {
718 $this->loadFile( 'en' );
719 $this->loadFile( $code );
720 $specialPageAliases = array();
721 foreach ( $this->mSpecialPageAliases[$code] as $key => $value ) {
722 if ( !isset( $this->mSpecialPageAliases['en'][$key] ) ) {
723 $specialPageAliases[$key] = $value[0];
726 return $specialPageAliases;
730 class extensionLanguages extends languages {
735 private $mMessageGroup;
738 * Load the messages group.
739 * @param $group MessageGroup The messages group.
741 function __construct( MessageGroup $group ) {
742 $this->mMessageGroup = $group;
744 $this->mIgnoredMessages = $this->mMessageGroup->getIgnored();
745 $this->mOptionalMessages = $this->mMessageGroup->getOptional();
749 * Get the extension name.
751 * @return string The extension name.
753 public function name() {
754 return $this->mMessageGroup->getLabel();
758 * Load the language file.
760 * @param $code string The language code.
762 protected function loadFile( $code ) {
763 if ( !isset( $this->mRawMessages[$code] ) ) {
764 $this->mRawMessages[$code] = $this->mMessageGroup->load( $code );
765 if ( empty( $this->mRawMessages[$code] ) ) {
766 $this->mRawMessages[$code] = array();