Add convenience links for bug 18068
[lhc/web/wiklou.git] / maintenance / language / languages.inc
1 <?php
2 /**
3 * Handle messages in the language files.
4 *
5 * @file
6 * @ingroup MaintenanceLanguage
7 */
8
9 /**
10 * @ingroup MaintenanceLanguage
11 */
12 class languages {
13 protected $mLanguages; # List of languages
14
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
20
21 protected $mNamespaceNames; # Namespace names
22 protected $mNamespaceAliases; # Namespace aliases
23 protected $mMagicWords; # Magic words
24 protected $mSpecialPageAliases; # Special page aliases
25
26 /**
27 * Load the list of languages: all the Messages*.php
28 * files in the languages directory.
29 *
30 * @param $exif Treat the EXIF messages?
31 */
32 function __construct( $exif = true ) {
33 require( dirname(__FILE__) . '/messageTypes.inc' );
34 $this->mIgnoredMessages = $wgIgnoredMessages;
35 if ( $exif ) {
36 $this->mOptionalMessages = array_merge( $wgOptionalMessages );
37 } else {
38 $this->mOptionalMessages = array_merge( $wgOptionalMessages, $wgEXIFMessages );
39 }
40
41 $this->mLanguages = array_keys( Language::getLanguageNames( true ) );
42 sort( $this->mLanguages );
43 }
44
45 /**
46 * Get the language list.
47 *
48 * @return The language list.
49 */
50 public function getLanguages() {
51 return $this->mLanguages;
52 }
53
54 /**
55 * Get the ignored messages list.
56 *
57 * @return The ignored messages list.
58 */
59 public function getIgnoredMessages() {
60 return $this->mIgnoredMessages;
61 }
62
63 /**
64 * Get the optional messages list.
65 *
66 * @return The optional messages list.
67 */
68 public function getOptionalMessages() {
69 return $this->mOptionalMessages;
70 }
71
72 /**
73 * Load the language file.
74 *
75 * @param $code The language code.
76 */
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] ) ) {
83 return;
84 }
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 ) ) {
92 require( $filename );
93 if ( isset( $messages ) ) {
94 $this->mRawMessages[$code] = $messages;
95 }
96 if ( isset( $namespaceNames ) ) {
97 $this->mNamespaceNames[$code] = $namespaceNames;
98 }
99 if ( isset( $namespaceAliases ) ) {
100 $this->mNamespaceAliases[$code] = $namespaceAliases;
101 }
102 if ( isset( $magicWords ) ) {
103 $this->mMagicWords[$code] = $magicWords;
104 }
105 if ( isset( $specialPageAliases ) ) {
106 $this->mSpecialPageAliases[$code] = $specialPageAliases;
107 }
108 }
109 }
110
111 /**
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.
118 *
119 * @param $code The language code.
120 */
121 private function loadMessages( $code ) {
122 if ( isset( $this->mMessages[$code] ) ) {
123 return;
124 }
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;
139 } else {
140 $this->mMessages[$code]['obsolete'][$key] = $value;
141 }
142 }
143 }
144
145 /**
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.
152 */
153 private function loadGeneralMessages() {
154 if ( isset( $this->mGeneralMessages ) ) {
155 return;
156 }
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;
169 } else {
170 $this->mGeneralMessages['required'][$key] = $value;
171 $this->mGeneralMessages['translatable'][$key] = $value;
172 }
173 }
174 }
175
176 /**
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.
184 *
185 * @param $code The language code.
186 *
187 * @return The messages in this language.
188 */
189 public function getMessages( $code ) {
190 $this->loadMessages( $code );
191 return $this->mMessages[$code];
192 }
193
194 /**
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.
201 *
202 * @return The general English messages.
203 */
204 public function getGeneralMessages() {
205 $this->loadGeneralMessages();
206 return $this->mGeneralMessages;
207 }
208
209 /**
210 * Get namespace names for a specific language.
211 *
212 * @param $code The language code.
213 *
214 * @return Namespace names.
215 */
216 public function getNamespaceNames( $code ) {
217 $this->loadFile( $code );
218 return $this->mNamespaceNames[$code];
219 }
220
221 /**
222 * Get namespace aliases for a specific language.
223 *
224 * @param $code The language code.
225 *
226 * @return Namespace aliases.
227 */
228 public function getNamespaceAliases( $code ) {
229 $this->loadFile( $code );
230 return $this->mNamespaceAliases[$code];
231 }
232
233 /**
234 * Get magic words for a specific language.
235 *
236 * @param $code The language code.
237 *
238 * @return Magic words.
239 */
240 public function getMagicWords( $code ) {
241 $this->loadFile( $code );
242 return $this->mMagicWords[$code];
243 }
244
245 /**
246 * Get special page aliases for a specific language.
247 *
248 * @param $code The language code.
249 *
250 * @return Special page aliases.
251 */
252 public function getSpecialPageAliases( $code ) {
253 $this->loadFile( $code );
254 return $this->mSpecialPageAliases[$code];
255 }
256
257 /**
258 * Get the untranslated messages for a specific language.
259 *
260 * @param $code The language code.
261 *
262 * @return The untranslated messages for this language.
263 */
264 public function getUntranslatedMessages( $code ) {
265 $this->loadGeneralMessages();
266 $this->loadMessages( $code );
267 return array_diff_key( $this->mGeneralMessages['required'], $this->mMessages[$code]['required'] );
268 }
269
270 /**
271 * Get the duplicate messages for a specific language.
272 *
273 * @param $code The language code.
274 *
275 * @return The duplicate messages for this language.
276 */
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;
284 }
285 }
286 return $duplicateMessages;
287 }
288
289 /**
290 * Get the obsolete messages for a specific language.
291 *
292 * @param $code The language code.
293 *
294 * @return The obsolete messages for this language.
295 */
296 public function getObsoleteMessages( $code ) {
297 $this->loadGeneralMessages();
298 $this->loadMessages( $code );
299 return $this->mMessages[$code]['obsolete'];
300 }
301
302 /**
303 * Get the messages whose variables do not match the original ones.
304 *
305 * @param $code The language code.
306 *
307 * @return The messages whose variables do not match the original ones.
308 */
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 ) {
315 $missing = false;
316 foreach ( $variables as $var ) {
317 if ( preg_match( "/$var/sU", $this->mGeneralMessages['translatable'][$key] ) &&
318 !preg_match( "/$var/sU", $value ) ) {
319 $missing = true;
320 }
321 if ( !preg_match( "/$var/sU", $this->mGeneralMessages['translatable'][$key] ) &&
322 preg_match( "/$var/sU", $value ) ) {
323 $missing = true;
324 }
325 }
326 if ( $missing ) {
327 $mismatchMessages[$key] = $value;
328 }
329 }
330 return $mismatchMessages;
331 }
332
333 /**
334 * Get the messages which do not use plural.
335 *
336 * @param $code The language code.
337 *
338 * @return The messages which do not use plural in this language.
339 */
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;
347 }
348 }
349 return $messagesWithoutPlural;
350 }
351
352 /**
353 * Get the empty messages.
354 *
355 * @param $code The language code.
356 *
357 * @return The empty messages for this language.
358 */
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;
366 }
367 }
368 return $emptyMessages;
369 }
370
371 /**
372 * Get the messages with trailing whitespace.
373 *
374 * @param $code The language code.
375 *
376 * @return The messages with trailing whitespace in this language.
377 */
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;
385 }
386 }
387 return $messagesWithWhitespace;
388 }
389
390 /**
391 * Get the non-XHTML messages.
392 *
393 * @param $code The language code.
394 *
395 * @return The non-XHTML messages for this language.
396 */
397 public function getNonXHTMLMessages( $code ) {
398 $this->loadGeneralMessages();
399 $this->loadMessages( $code );
400 $wrongPhrases = array(
401 '<hr *\\?>',
402 '<br *\\?>',
403 '<hr/>',
404 '<br/>',
405 '<hr>',
406 '<br>',
407 );
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;
413 }
414 }
415 return $nonXHTMLMessages;
416 }
417
418 /**
419 * Get the messages which include wrong characters.
420 *
421 * @param $code The language code.
422 *
423 * @return The messages which include wrong characters in this language.
424 */
425 public function getMessagesWithWrongChars( $code ) {
426 $this->loadGeneralMessages();
427 $this->loadMessages( $code );
428 $wrongChars = array(
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",
441 );
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 );
448 }
449 $wrongCharsMessages[$key] = $value;
450 }
451 }
452 return $wrongCharsMessages;
453 }
454
455 /**
456 * Get the messages which include dubious links.
457 *
458 * @param $code The language code.
459 *
460 * @return The messages which include dubious links in this language.
461 */
462 public function getMessagesWithDubiousLinks( $code ) {
463 $this->loadGeneralMessages();
464 $this->loadMessages( $code );
465 $tc = Title::legalChars() . '#%{}';
466 $messages = array();
467 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
468 $matches = array();
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];
473 }
474 }
475
476
477 if ( isset( $messages[$key] ) ) {
478 $messages[$key] = implode( $messages[$key],", " );
479 }
480 }
481 return $messages;
482 }
483
484 /**
485 * Get the messages which include unbalanced brackets.
486 *
487 * @param $code The language code.
488 *
489 * @return The messages which include unbalanced brackets in this language.
490 */
491 public function getMessagesWithUnbalanced( $code ) {
492 $this->loadGeneralMessages();
493 $this->loadMessages( $code );
494 $messages = array();
495 foreach ( $this->mMessages[$code]['translated'] as $key => $value ) {
496 $a = $b = $c = $d = 0;
497 foreach ( preg_split( '//', $value ) as $char ) {
498 switch ( $char ) {
499 case '[':
500 $a++;
501 break;
502 case ']':
503 $b++;
504 break;
505 case '{':
506 $c++;
507 break;
508 case '}':
509 $d++;
510 break;
511 }
512 }
513
514 if ( $a !== $b || $c !== $d ) {
515 $messages[$key] = "$a, $b, $c, $d";
516 }
517
518 }
519 return $messages;
520 }
521
522 /**
523 * Get the untranslated namespace names.
524 *
525 * @param $code The language code.
526 *
527 * @return The untranslated namespace names in this language.
528 */
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] ) );
533 }
534
535 /**
536 * Get the project talk namespace names with no $1.
537 *
538 * @param $code The language code.
539 *
540 * @return The problematic project talk namespaces in this language.
541 */
542 public function getProblematicProjectTalks( $code ) {
543 $this->loadFile( $code );
544 $namespaces = array();
545
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';
551 }
552 }
553
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] = '';
558 }
559 }
560
561 return $namespaces;
562 }
563
564 /**
565 * Get the untranslated magic words.
566 *
567 * @param $code The language code.
568 *
569 * @return The untranslated magic words in this language.
570 */
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];
578 }
579 }
580 return $magicWords;
581 }
582
583 /**
584 * Get the obsolete magic words.
585 *
586 * @param $code The language code.
587 *
588 * @return The obsolete magic words in this language.
589 */
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];
597 }
598 }
599 return $magicWords;
600 }
601
602 /**
603 * Get the magic words that override the original English magic word.
604 *
605 * @param $code The language code.
606 *
607 * @return The overriding magic words in this language.
608 */
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
616 continue;
617 }
618 $en = $this->mMagicWords['en'][$key];
619 array_shift( $local );
620 array_shift( $en );
621 foreach ( $en as $word ) {
622 if ( !in_array( $word, $local ) ) {
623 $magicWords[$key] = $word;
624 break;
625 }
626 }
627 }
628 return $magicWords;
629 }
630
631 /**
632 * Get the magic words which do not match the case-sensitivity of the original words.
633 *
634 * @param $code The language code.
635 *
636 * @return The magic words whose case does not match in this language.
637 */
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
645 continue;
646 }
647 if ( $local[0] != $this->mMagicWords['en'][$key][0] ) {
648 $magicWords[$key] = $local[0];
649 }
650 }
651 return $magicWords;
652 }
653
654 /**
655 * Get the untranslated special page names.
656 *
657 * @param $code The language code.
658 *
659 * @return The untranslated special page names in this language.
660 */
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];
668 }
669 }
670 return $specialPageAliases;
671 }
672
673 /**
674 * Get the obsolete special page names.
675 *
676 * @param $code The language code.
677 *
678 * @return The obsolete special page names in this language.
679 */
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];
687 }
688 }
689 return $specialPageAliases;
690 }
691 }
692
693 class extensionLanguages extends languages {
694 private $mMessageGroup; # The message group
695
696 /**
697 * Load the messages group.
698 * @param $group The messages group.
699 */
700 function __construct( MessageGroup $group ) {
701 $this->mMessageGroup = $group;
702
703 $bools = $this->mMessageGroup->getBools();
704 $this->mIgnoredMessages = $bools['ignored'];
705 $this->mOptionalMessages = $bools['optional'];
706 }
707
708 /**
709 * Get the extension name.
710 *
711 * @return The extension name.
712 */
713 public function name() {
714 return $this->mMessageGroup->getLabel();
715 }
716
717 /**
718 * Load the language file.
719 *
720 * @param $code The language code.
721 */
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();
727 }
728 }
729 }
730 }