* (bug 8734) Different log message when article protection level is changed
[mediawiki.git] / languages / Language.php
blobd36b5490d58bdba212933e091bc9ad61ed47a1cf
1 <?php
2 /**
3 * @addtogroup Language
4 */
6 if( !defined( 'MEDIAWIKI' ) ) {
7 echo "This file is part of MediaWiki, it is not a valid entry point.\n";
8 exit( 1 );
12 # In general you should not make customizations in these language files
13 # directly, but should use the MediaWiki: special namespace to customize
14 # user interface messages through the wiki.
15 # See http://meta.wikipedia.org/wiki/MediaWiki_namespace
17 # NOTE TO TRANSLATORS: Do not copy this whole file when making translations!
18 # A lot of common constants and a base class with inheritable methods are
19 # defined here, which should not be redefined. See the other LanguageXx.php
20 # files for examples.
23 # Read language names
24 global $wgLanguageNames;
25 require_once( dirname(__FILE__) . '/Names.php' ) ;
27 global $wgInputEncoding, $wgOutputEncoding;
29 /**
30 * These are always UTF-8, they exist only for backwards compatibility
32 $wgInputEncoding = "UTF-8";
33 $wgOutputEncoding = "UTF-8";
35 if( function_exists( 'mb_strtoupper' ) ) {
36 mb_internal_encoding('UTF-8');
39 /* a fake language converter */
40 class FakeConverter {
41 var $mLang;
42 function FakeConverter($langobj) {$this->mLang = $langobj;}
43 function convert($t, $i) {return $t;}
44 function parserConvert($t, $p) {return $t;}
45 function getVariants() { return array( $this->mLang->getCode() ); }
46 function getPreferredVariant() {return $this->mLang->getCode(); }
47 function findVariantLink(&$l, &$n) {}
48 function getExtraHashOptions() {return '';}
49 function getParsedTitle() {return '';}
50 function markNoConversion($text, $noParse=false) {return $text;}
51 function convertCategoryKey( $key ) {return $key; }
52 function convertLinkToAllVariants($text){ return array( $this->mLang->getCode() => $text); }
53 function armourMath($text){ return $text; }
56 #--------------------------------------------------------------------------
57 # Internationalisation code
58 #--------------------------------------------------------------------------
60 class Language {
61 var $mConverter, $mVariants, $mCode, $mLoaded = false;
63 static public $mLocalisationKeys = array( 'fallback', 'namespaceNames',
64 'skinNames', 'mathNames',
65 'bookstoreList', 'magicWords', 'messages', 'rtl', 'digitTransformTable',
66 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
67 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases',
68 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
69 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases' );
71 static public $mMergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames',
72 'dateFormats', 'defaultUserOptionOverrides', 'magicWords' );
74 static public $mMergeableListKeys = array( 'extraUserToggles' );
76 static public $mMergeableAliasListKeys = array( 'specialPageAliases' );
78 static public $mLocalisationCache = array();
80 static public $mWeekdayMsgs = array(
81 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
82 'friday', 'saturday'
85 static public $mWeekdayAbbrevMsgs = array(
86 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
89 static public $mMonthMsgs = array(
90 'january', 'february', 'march', 'april', 'may_long', 'june',
91 'july', 'august', 'september', 'october', 'november',
92 'december'
94 static public $mMonthGenMsgs = array(
95 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
96 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
97 'december-gen'
99 static public $mMonthAbbrevMsgs = array(
100 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
101 'sep', 'oct', 'nov', 'dec'
105 * Create a language object for a given language code
107 static function factory( $code ) {
108 global $IP;
109 static $recursionLevel = 0;
111 if ( $code == 'en' ) {
112 $class = 'Language';
113 } else {
114 $class = 'Language' . str_replace( '-', '_', ucfirst( $code ) );
115 // Preload base classes to work around APC/PHP5 bug
116 if ( file_exists( "$IP/languages/classes/$class.deps.php" ) ) {
117 include_once("$IP/languages/classes/$class.deps.php");
119 if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
120 include_once("$IP/languages/classes/$class.php");
124 if ( $recursionLevel > 5 ) {
125 throw new MWException( "Language fallback loop detected when creating class $class\n" );
128 if( ! class_exists( $class ) ) {
129 $fallback = Language::getFallbackFor( $code );
130 ++$recursionLevel;
131 $lang = Language::factory( $fallback );
132 --$recursionLevel;
133 $lang->setCode( $code );
134 } else {
135 $lang = new $class;
138 return $lang;
141 function __construct() {
142 $this->mConverter = new FakeConverter($this);
143 // Set the code to the name of the descendant
144 if ( get_class( $this ) == 'Language' ) {
145 $this->mCode = 'en';
146 } else {
147 $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
152 * Hook which will be called if this is the content language.
153 * Descendants can use this to register hook functions or modify globals
155 function initContLang() {}
158 * @deprecated
159 * @return array
161 function getDefaultUserOptions() {
162 return User::getDefaultOptions();
165 function getFallbackLanguageCode() {
166 $this->load();
167 return $this->fallback;
171 * Exports $wgBookstoreListEn
172 * @return array
174 function getBookstoreList() {
175 $this->load();
176 return $this->bookstoreList;
180 * @return array
182 function getNamespaces() {
183 $this->load();
184 return $this->namespaceNames;
188 * A convenience function that returns the same thing as
189 * getNamespaces() except with the array values changed to ' '
190 * where it found '_', useful for producing output to be displayed
191 * e.g. in <select> forms.
193 * @return array
195 function getFormattedNamespaces() {
196 $ns = $this->getNamespaces();
197 foreach($ns as $k => $v) {
198 $ns[$k] = strtr($v, '_', ' ');
200 return $ns;
204 * Get a namespace value by key
205 * <code>
206 * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
207 * echo $mw_ns; // prints 'MediaWiki'
208 * </code>
210 * @param int $index the array key of the namespace to return
211 * @return mixed, string if the namespace value exists, otherwise false
213 function getNsText( $index ) {
214 $ns = $this->getNamespaces();
215 return isset( $ns[$index] ) ? $ns[$index] : false;
219 * A convenience function that returns the same thing as
220 * getNsText() except with '_' changed to ' ', useful for
221 * producing output.
223 * @return array
225 function getFormattedNsText( $index ) {
226 $ns = $this->getNsText( $index );
227 return strtr($ns, '_', ' ');
231 * Get a namespace key by value, case insensitive.
232 * Only matches namespace names for the current language, not the
233 * canonical ones defined in Namespace.php.
235 * @param string $text
236 * @return mixed An integer if $text is a valid value otherwise false
238 function getLocalNsIndex( $text ) {
239 $this->load();
240 $lctext = $this->lc($text);
241 return isset( $this->mNamespaceIds[$lctext] ) ? $this->mNamespaceIds[$lctext] : false;
245 * Get a namespace key by value, case insensitive. Canonical namespace
246 * names override custom ones defined for the current language.
248 * @param string $text
249 * @return mixed An integer if $text is a valid value otherwise false
251 function getNsIndex( $text ) {
252 $this->load();
253 $lctext = $this->lc($text);
254 if( ( $ns = Namespace::getCanonicalIndex( $lctext ) ) !== null ) return $ns;
255 return isset( $this->mNamespaceIds[$lctext] ) ? $this->mNamespaceIds[$lctext] : false;
259 * short names for language variants used for language conversion links.
261 * @param string $code
262 * @return string
264 function getVariantname( $code ) {
265 return $this->getMessageFromDB( "variantname-$code" );
268 function specialPage( $name ) {
269 $aliases = $this->getSpecialPageAliases();
270 if ( isset( $aliases[$name][0] ) ) {
271 $name = $aliases[$name][0];
273 return $this->getNsText(NS_SPECIAL) . ':' . $name;
276 function getQuickbarSettings() {
277 return array(
278 $this->getMessage( 'qbsettings-none' ),
279 $this->getMessage( 'qbsettings-fixedleft' ),
280 $this->getMessage( 'qbsettings-fixedright' ),
281 $this->getMessage( 'qbsettings-floatingleft' ),
282 $this->getMessage( 'qbsettings-floatingright' )
286 function getSkinNames() {
287 $this->load();
288 return $this->skinNames;
291 function getMathNames() {
292 $this->load();
293 return $this->mathNames;
296 function getDatePreferences() {
297 $this->load();
298 return $this->datePreferences;
301 function getDateFormats() {
302 $this->load();
303 return $this->dateFormats;
306 function getDefaultDateFormat() {
307 $this->load();
308 return $this->defaultDateFormat;
311 function getDatePreferenceMigrationMap() {
312 $this->load();
313 return $this->datePreferenceMigrationMap;
316 function getDefaultUserOptionOverrides() {
317 $this->load();
318 return $this->defaultUserOptionOverrides;
321 function getExtraUserToggles() {
322 $this->load();
323 return $this->extraUserToggles;
326 function getUserToggle( $tog ) {
327 return $this->getMessageFromDB( "tog-$tog" );
331 * Get language names, indexed by code.
332 * If $customisedOnly is true, only returns codes with a messages file
334 public static function getLanguageNames( $customisedOnly = false ) {
335 global $wgLanguageNames;
336 if ( !$customisedOnly ) {
337 return $wgLanguageNames;
340 global $IP;
341 $messageFiles = glob( "$IP/languages/messages/Messages*.php" );
342 $names = array();
343 foreach ( $messageFiles as $file ) {
344 $m = array();
345 if( preg_match( '/Messages([A-Z][a-z_]+)\.php$/', $file, $m ) ) {
346 $code = str_replace( '_', '-', strtolower( $m[1] ) );
347 if ( isset( $wgLanguageNames[$code] ) ) {
348 $names[$code] = $wgLanguageNames[$code];
352 return $names;
356 * Ugly hack to get a message maybe from the MediaWiki namespace, if this
357 * language object is the content or user language.
359 function getMessageFromDB( $msg ) {
360 global $wgContLang, $wgLang;
361 if ( $wgContLang->getCode() == $this->getCode() ) {
362 # Content language
363 return wfMsgForContent( $msg );
364 } elseif ( $wgLang->getCode() == $this->getCode() ) {
365 # User language
366 return wfMsg( $msg );
367 } else {
368 # Neither, get from localisation
369 return $this->getMessage( $msg );
373 function getLanguageName( $code ) {
374 global $wgLanguageNames;
375 if ( ! array_key_exists( $code, $wgLanguageNames ) ) {
376 return '';
378 return $wgLanguageNames[$code];
381 function getMonthName( $key ) {
382 return $this->getMessageFromDB( self::$mMonthMsgs[$key-1] );
385 function getMonthNameGen( $key ) {
386 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key-1] );
389 function getMonthAbbreviation( $key ) {
390 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key-1] );
393 function getWeekdayName( $key ) {
394 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key-1] );
397 function getWeekdayAbbreviation( $key ) {
398 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key-1] );
402 * Used by date() and time() to adjust the time output.
403 * @public
404 * @param int $ts the time in date('YmdHis') format
405 * @param mixed $tz adjust the time by this amount (default false,
406 * mean we get user timecorrection setting)
407 * @return int
409 function userAdjust( $ts, $tz = false ) {
410 global $wgUser, $wgLocalTZoffset;
412 if (!$tz) {
413 $tz = $wgUser->getOption( 'timecorrection' );
416 # minutes and hours differences:
417 $minDiff = 0;
418 $hrDiff = 0;
420 if ( $tz === '' ) {
421 # Global offset in minutes.
422 if( isset($wgLocalTZoffset) ) {
423 if( $wgLocalTZoffset >= 0 ) {
424 $hrDiff = floor($wgLocalTZoffset / 60);
425 } else {
426 $hrDiff = ceil($wgLocalTZoffset / 60);
428 $minDiff = $wgLocalTZoffset % 60;
430 } elseif ( strpos( $tz, ':' ) !== false ) {
431 $tzArray = explode( ':', $tz );
432 $hrDiff = intval($tzArray[0]);
433 $minDiff = intval($hrDiff < 0 ? -$tzArray[1] : $tzArray[1]);
434 } else {
435 $hrDiff = intval( $tz );
438 # No difference ? Return time unchanged
439 if ( 0 == $hrDiff && 0 == $minDiff ) { return $ts; }
441 # Generate an adjusted date
442 $t = mktime( (
443 (int)substr( $ts, 8, 2) ) + $hrDiff, # Hours
444 (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
445 (int)substr( $ts, 12, 2 ), # Seconds
446 (int)substr( $ts, 4, 2 ), # Month
447 (int)substr( $ts, 6, 2 ), # Day
448 (int)substr( $ts, 0, 4 ) ); #Year
449 return date( 'YmdHis', $t );
453 * This is a workalike of PHP's date() function, but with better
454 * internationalisation, a reduced set of format characters, and a better
455 * escaping format.
457 * Supported format characters are dDjlNwzWFmMntLYyaAgGhHiscrU. See the
458 * PHP manual for definitions. There are a number of extensions, which
459 * start with "x":
461 * xn Do not translate digits of the next numeric format character
462 * xN Toggle raw digit (xn) flag, stays set until explicitly unset
463 * xr Use roman numerals for the next numeric format character
464 * xx Literal x
465 * xg Genitive month name
467 * Characters enclosed in double quotes will be considered literal (with
468 * the quotes themselves removed). Unmatched quotes will be considered
469 * literal quotes. Example:
471 * "The month is" F => The month is January
472 * i's" => 20'11"
474 * Backslash escaping is also supported.
476 * @param string $format
477 * @param string $ts 14-character timestamp
478 * YYYYMMDDHHMMSS
479 * 01234567890123
481 function sprintfDate( $format, $ts ) {
482 $s = '';
483 $raw = false;
484 $roman = false;
485 $unix = false;
486 $rawToggle = false;
487 for ( $p = 0; $p < strlen( $format ); $p++ ) {
488 $num = false;
489 $code = $format[$p];
490 if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
491 $code .= $format[++$p];
494 switch ( $code ) {
495 case 'xx':
496 $s .= 'x';
497 break;
498 case 'xn':
499 $raw = true;
500 break;
501 case 'xN':
502 $rawToggle = !$rawToggle;
503 break;
504 case 'xr':
505 $roman = true;
506 break;
507 case 'xg':
508 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
509 break;
510 case 'd':
511 $num = substr( $ts, 6, 2 );
512 break;
513 case 'D':
514 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
515 $s .= $this->getWeekdayAbbreviation( date( 'w', $unix ) + 1 );
516 break;
517 case 'j':
518 $num = intval( substr( $ts, 6, 2 ) );
519 break;
520 case 'l':
521 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
522 $s .= $this->getWeekdayName( date( 'w', $unix ) + 1 );
523 break;
524 case 'N':
525 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
526 $w = date( 'w', $unix );
527 $num = $w ? $w : 7;
528 break;
529 case 'w':
530 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
531 $num = date( 'w', $unix );
532 break;
533 case 'z':
534 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
535 $num = date( 'z', $unix );
536 break;
537 case 'W':
538 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
539 $num = date( 'W', $unix );
540 break;
541 case 'F':
542 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
543 break;
544 case 'm':
545 $num = substr( $ts, 4, 2 );
546 break;
547 case 'M':
548 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
549 break;
550 case 'n':
551 $num = intval( substr( $ts, 4, 2 ) );
552 break;
553 case 't':
554 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
555 $num = date( 't', $unix );
556 break;
557 case 'L':
558 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
559 $num = date( 'L', $unix );
560 break;
561 case 'Y':
562 $num = substr( $ts, 0, 4 );
563 break;
564 case 'y':
565 $num = substr( $ts, 2, 2 );
566 break;
567 case 'a':
568 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
569 break;
570 case 'A':
571 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
572 break;
573 case 'g':
574 $h = substr( $ts, 8, 2 );
575 $num = $h % 12 ? $h % 12 : 12;
576 break;
577 case 'G':
578 $num = intval( substr( $ts, 8, 2 ) );
579 break;
580 case 'h':
581 $h = substr( $ts, 8, 2 );
582 $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
583 break;
584 case 'H':
585 $num = substr( $ts, 8, 2 );
586 break;
587 case 'i':
588 $num = substr( $ts, 10, 2 );
589 break;
590 case 's':
591 $num = substr( $ts, 12, 2 );
592 break;
593 case 'c':
594 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
595 $s .= date( 'c', $unix );
596 break;
597 case 'r':
598 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
599 $s .= date( 'r', $unix );
600 break;
601 case 'U':
602 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
603 $num = $unix;
604 break;
605 case '\\':
606 # Backslash escaping
607 if ( $p < strlen( $format ) - 1 ) {
608 $s .= $format[++$p];
609 } else {
610 $s .= '\\';
612 break;
613 case '"':
614 # Quoted literal
615 if ( $p < strlen( $format ) - 1 ) {
616 $endQuote = strpos( $format, '"', $p + 1 );
617 if ( $endQuote === false ) {
618 # No terminating quote, assume literal "
619 $s .= '"';
620 } else {
621 $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
622 $p = $endQuote;
624 } else {
625 # Quote at end of string, assume literal "
626 $s .= '"';
628 break;
629 default:
630 $s .= $format[$p];
632 if ( $num !== false ) {
633 if ( $rawToggle || $raw ) {
634 $s .= $num;
635 $raw = false;
636 } elseif ( $roman ) {
637 $s .= self::romanNumeral( $num );
638 $roman = false;
639 } else {
640 $s .= $this->formatNum( $num, true );
642 $num = false;
645 return $s;
649 * Roman number formatting up to 3000
651 static function romanNumeral( $num ) {
652 static $table = array(
653 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
654 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
655 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
656 array( '', 'M', 'MM', 'MMM' )
659 $num = intval( $num );
660 if ( $num > 3000 || $num <= 0 ) {
661 return $num;
664 $s = '';
665 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
666 if ( $num >= $pow10 ) {
667 $s .= $table[$i][floor($num / $pow10)];
669 $num = $num % $pow10;
671 return $s;
675 * This is meant to be used by time(), date(), and timeanddate() to get
676 * the date preference they're supposed to use, it should be used in
677 * all children.
679 *<code>
680 * function timeanddate([...], $format = true) {
681 * $datePreference = $this->dateFormat($format);
682 * [...]
684 *</code>
686 * @param mixed $usePrefs: if true, the user's preference is used
687 * if false, the site/language default is used
688 * if int/string, assumed to be a format.
689 * @return string
691 function dateFormat( $usePrefs = true ) {
692 global $wgUser;
694 if( is_bool( $usePrefs ) ) {
695 if( $usePrefs ) {
696 $datePreference = $wgUser->getDatePreference();
697 } else {
698 $options = User::getDefaultOptions();
699 $datePreference = (string)$options['date'];
701 } else {
702 $datePreference = (string)$usePrefs;
705 // return int
706 if( $datePreference == '' ) {
707 return 'default';
710 return $datePreference;
714 * @public
715 * @param mixed $ts the time format which needs to be turned into a
716 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
717 * @param bool $adj whether to adjust the time output according to the
718 * user configured offset ($timecorrection)
719 * @param mixed $format true to use user's date format preference
720 * @param string $timecorrection the time offset as returned by
721 * validateTimeZone() in Special:Preferences
722 * @return string
724 function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
725 $this->load();
726 if ( $adj ) {
727 $ts = $this->userAdjust( $ts, $timecorrection );
730 $pref = $this->dateFormat( $format );
731 if( $pref == 'default' || !isset( $this->dateFormats["$pref date"] ) ) {
732 $pref = $this->defaultDateFormat;
734 return $this->sprintfDate( $this->dateFormats["$pref date"], $ts );
738 * @public
739 * @param mixed $ts the time format which needs to be turned into a
740 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
741 * @param bool $adj whether to adjust the time output according to the
742 * user configured offset ($timecorrection)
743 * @param mixed $format true to use user's date format preference
744 * @param string $timecorrection the time offset as returned by
745 * validateTimeZone() in Special:Preferences
746 * @return string
748 function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
749 $this->load();
750 if ( $adj ) {
751 $ts = $this->userAdjust( $ts, $timecorrection );
754 $pref = $this->dateFormat( $format );
755 if( $pref == 'default' || !isset( $this->dateFormats["$pref time"] ) ) {
756 $pref = $this->defaultDateFormat;
758 return $this->sprintfDate( $this->dateFormats["$pref time"], $ts );
762 * @public
763 * @param mixed $ts the time format which needs to be turned into a
764 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
765 * @param bool $adj whether to adjust the time output according to the
766 * user configured offset ($timecorrection)
768 * @param mixed $format what format to return, if it's false output the
769 * default one (default true)
770 * @param string $timecorrection the time offset as returned by
771 * validateTimeZone() in Special:Preferences
772 * @return string
774 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) {
775 $this->load();
777 $ts = wfTimestamp( TS_MW, $ts );
779 if ( $adj ) {
780 $ts = $this->userAdjust( $ts, $timecorrection );
783 $pref = $this->dateFormat( $format );
784 if( $pref == 'default' || !isset( $this->dateFormats["$pref both"] ) ) {
785 $pref = $this->defaultDateFormat;
788 return $this->sprintfDate( $this->dateFormats["$pref both"], $ts );
791 function getMessage( $key ) {
792 $this->load();
793 return isset( $this->messages[$key] ) ? $this->messages[$key] : null;
796 function getAllMessages() {
797 $this->load();
798 return $this->messages;
801 function iconv( $in, $out, $string ) {
802 # For most languages, this is a wrapper for iconv
803 return iconv( $in, $out . '//IGNORE', $string );
806 // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
807 function ucwordbreaksCallbackAscii($matches){
808 return $this->ucfirst($matches[1]);
811 function ucwordbreaksCallbackMB($matches){
812 return mb_strtoupper($matches[0]);
815 function ucCallback($matches){
816 list( $wikiUpperChars ) = self::getCaseMaps();
817 return strtr( $matches[1], $wikiUpperChars );
820 function lcCallback($matches){
821 list( , $wikiLowerChars ) = self::getCaseMaps();
822 return strtr( $matches[1], $wikiLowerChars );
825 function ucwordsCallbackMB($matches){
826 return mb_strtoupper($matches[0]);
829 function ucwordsCallbackWiki($matches){
830 list( $wikiUpperChars ) = self::getCaseMaps();
831 return strtr( $matches[0], $wikiUpperChars );
834 function ucfirst( $str ) {
835 return self::uc( $str, true );
838 function uc( $str, $first = false ) {
839 if ( function_exists( 'mb_strtoupper' ) ) {
840 if ( $first ) {
841 if ( self::isMultibyte( $str ) ) {
842 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
843 } else {
844 return ucfirst( $str );
846 } else {
847 return self::isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
849 } else {
850 if ( self::isMultibyte( $str ) ) {
851 list( $wikiUpperChars ) = $this->getCaseMaps();
852 $x = $first ? '^' : '';
853 return preg_replace_callback(
854 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
855 array($this,"ucCallback"),
856 $str
858 } else {
859 return $first ? ucfirst( $str ) : strtoupper( $str );
864 function lcfirst( $str ) {
865 return self::lc( $str, true );
868 function lc( $str, $first = false ) {
869 if ( function_exists( 'mb_strtolower' ) )
870 if ( $first )
871 if ( self::isMultibyte( $str ) )
872 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
873 else
874 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
875 else
876 return self::isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
877 else
878 if ( self::isMultibyte( $str ) ) {
879 list( , $wikiLowerChars ) = self::getCaseMaps();
880 $x = $first ? '^' : '';
881 return preg_replace_callback(
882 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
883 array($this,"lcCallback"),
884 $str
886 } else
887 return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
890 function isMultibyte( $str ) {
891 return (bool)preg_match( '/[\x80-\xff]/', $str );
894 function ucwords($str) {
895 if ( self::isMultibyte( $str ) ) {
896 $str = self::lc($str);
898 // regexp to find first letter in each word (i.e. after each space)
899 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
901 // function to use to capitalize a single char
902 if ( function_exists( 'mb_strtoupper' ) )
903 return preg_replace_callback(
904 $replaceRegexp,
905 array($this,"ucwordsCallbackMB"),
906 $str
908 else
909 return preg_replace_callback(
910 $replaceRegexp,
911 array($this,"ucwordsCallbackWiki"),
912 $str
915 else
916 return ucwords( strtolower( $str ) );
919 # capitalize words at word breaks
920 function ucwordbreaks($str){
921 if (self::isMultibyte( $str ) ) {
922 $str = self::lc($str);
924 // since \b doesn't work for UTF-8, we explicitely define word break chars
925 $breaks= "[ \-\(\)\}\{\.,\?!]";
927 // find first letter after word break
928 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
930 if ( function_exists( 'mb_strtoupper' ) )
931 return preg_replace_callback(
932 $replaceRegexp,
933 array($this,"ucwordbreaksCallbackMB"),
934 $str
936 else
937 return preg_replace_callback(
938 $replaceRegexp,
939 array($this,"ucwordsCallbackWiki"),
940 $str
943 else
944 return preg_replace_callback(
945 '/\b([\w\x80-\xff]+)\b/',
946 array($this,"ucwordbreaksCallbackAscii"),
947 $str );
951 * Return a case-folded representation of $s
953 * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
954 * and $s2 are the same except for the case of their characters. It is not
955 * necessary for the value returned to make sense when displayed.
957 * Do *not* perform any other normalisation in this function. If a caller
958 * uses this function when it should be using a more general normalisation
959 * function, then fix the caller.
961 function caseFold( $s ) {
962 return $this->uc( $s );
965 function checkTitleEncoding( $s ) {
966 if( is_array( $s ) ) {
967 wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
969 # Check for non-UTF-8 URLs
970 $ishigh = preg_match( '/[\x80-\xff]/', $s);
971 if(!$ishigh) return $s;
973 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
974 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
975 if( $isutf8 ) return $s;
977 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
980 function fallback8bitEncoding() {
981 $this->load();
982 return $this->fallback8bitEncoding;
986 * Some languages have special punctuation to strip out
987 * or characters which need to be converted for MySQL's
988 * indexing to grok it correctly. Make such changes here.
990 * @param string $in
991 * @return string
993 function stripForSearch( $string ) {
994 global $wgDBtype;
995 if ( $wgDBtype != 'mysql' ) {
996 return $string;
999 # MySQL fulltext index doesn't grok utf-8, so we
1000 # need to fold cases and convert to hex
1002 wfProfileIn( __METHOD__ );
1003 if( function_exists( 'mb_strtolower' ) ) {
1004 $out = preg_replace(
1005 "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
1006 "'U8' . bin2hex( \"$1\" )",
1007 mb_strtolower( $string ) );
1008 } else {
1009 list( , $wikiLowerChars ) = self::getCaseMaps();
1010 $out = preg_replace(
1011 "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
1012 "'U8' . bin2hex( strtr( \"\$1\", \$wikiLowerChars ) )",
1013 $string );
1015 wfProfileOut( __METHOD__ );
1016 return $out;
1019 function convertForSearchResult( $termsArray ) {
1020 # some languages, e.g. Chinese, need to do a conversion
1021 # in order for search results to be displayed correctly
1022 return $termsArray;
1026 * Get the first character of a string.
1028 * @param string $s
1029 * @return string
1031 function firstChar( $s ) {
1032 $matches = array();
1033 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1034 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
1036 return isset( $matches[1] ) ? $matches[1] : "";
1039 function initEncoding() {
1040 # Some languages may have an alternate char encoding option
1041 # (Esperanto X-coding, Japanese furigana conversion, etc)
1042 # If this language is used as the primary content language,
1043 # an override to the defaults can be set here on startup.
1046 function recodeForEdit( $s ) {
1047 # For some languages we'll want to explicitly specify
1048 # which characters make it into the edit box raw
1049 # or are converted in some way or another.
1050 # Note that if wgOutputEncoding is different from
1051 # wgInputEncoding, this text will be further converted
1052 # to wgOutputEncoding.
1053 global $wgEditEncoding;
1054 if( $wgEditEncoding == '' or
1055 $wgEditEncoding == 'UTF-8' ) {
1056 return $s;
1057 } else {
1058 return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1062 function recodeInput( $s ) {
1063 # Take the previous into account.
1064 global $wgEditEncoding;
1065 if($wgEditEncoding != "") {
1066 $enc = $wgEditEncoding;
1067 } else {
1068 $enc = 'UTF-8';
1070 if( $enc == 'UTF-8' ) {
1071 return $s;
1072 } else {
1073 return $this->iconv( $enc, 'UTF-8', $s );
1078 * For right-to-left language support
1080 * @return bool
1082 function isRTL() {
1083 $this->load();
1084 return $this->rtl;
1088 * A hidden direction mark (LRM or RLM), depending on the language direction
1090 * @return string
1092 function getDirMark() {
1093 return $this->isRTL() ? "\xE2\x80\x8F" : "\xE2\x80\x8E";
1097 * An arrow, depending on the language direction
1099 * @return string
1101 function getArrow() {
1102 return $this->isRTL() ? '←' : '→';
1106 * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1108 * @return bool
1110 function linkPrefixExtension() {
1111 $this->load();
1112 return $this->linkPrefixExtension;
1115 function &getMagicWords() {
1116 $this->load();
1117 return $this->magicWords;
1120 # Fill a MagicWord object with data from here
1121 function getMagic( &$mw ) {
1122 if ( !isset( $this->mMagicExtensions ) ) {
1123 $this->mMagicExtensions = array();
1124 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) );
1126 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
1127 $rawEntry = $this->mMagicExtensions[$mw->mId];
1128 } else {
1129 $magicWords =& $this->getMagicWords();
1130 if ( isset( $magicWords[$mw->mId] ) ) {
1131 $rawEntry = $magicWords[$mw->mId];
1132 } else {
1133 # Fall back to English if local list is incomplete
1134 $magicWords =& Language::getMagicWords();
1135 $rawEntry = $magicWords[$mw->mId];
1139 if( !is_array( $rawEntry ) ) {
1140 error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1142 $mw->mCaseSensitive = $rawEntry[0];
1143 $mw->mSynonyms = array_slice( $rawEntry, 1 );
1147 * Get special page names, as an associative array
1148 * case folded alias => real name
1150 function getSpecialPageAliases() {
1151 $this->load();
1152 if ( !isset( $this->mExtendedSpecialPageAliases ) ) {
1153 $this->mExtendedSpecialPageAliases = $this->specialPageAliases;
1154 wfRunHooks( 'LangugeGetSpecialPageAliases',
1155 array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) );
1157 return $this->mExtendedSpecialPageAliases;
1161 * Italic is unsuitable for some languages
1163 * @public
1165 * @param string $text The text to be emphasized.
1166 * @return string
1168 function emphasize( $text ) {
1169 return "<em>$text</em>";
1173 * Normally we output all numbers in plain en_US style, that is
1174 * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
1175 * point twohundredthirtyfive. However this is not sutable for all
1176 * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
1177 * Icelandic just want to use commas instead of dots, and dots instead
1178 * of commas like "293.291,235".
1180 * An example of this function being called:
1181 * <code>
1182 * wfMsg( 'message', $wgLang->formatNum( $num ) )
1183 * </code>
1185 * See LanguageGu.php for the Gujarati implementation and
1186 * LanguageIs.php for the , => . and . => , implementation.
1188 * @todo check if it's viable to use localeconv() for the decimal
1189 * seperator thing.
1190 * @public
1191 * @param mixed $number the string to be formatted, should be an integer or
1192 * a floating point number.
1193 * @param bool $nocommafy Set to true for special numbers like dates
1194 * @return string
1196 function formatNum( $number, $nocommafy = false ) {
1197 global $wgTranslateNumerals;
1198 if (!$nocommafy) {
1199 $number = $this->commafy($number);
1200 $s = $this->separatorTransformTable();
1201 if (!is_null($s)) { $number = strtr($number, $s); }
1204 if ($wgTranslateNumerals) {
1205 $s = $this->digitTransformTable();
1206 if (!is_null($s)) { $number = strtr($number, $s); }
1209 return $number;
1212 function parseFormattedNumber( $number ) {
1213 $s = $this->digitTransformTable();
1214 if (!is_null($s)) { $number = strtr($number, array_flip($s)); }
1216 $s = $this->separatorTransformTable();
1217 if (!is_null($s)) { $number = strtr($number, array_flip($s)); }
1219 $number = strtr( $number, array (',' => '') );
1220 return $number;
1224 * Adds commas to a given number
1226 * @param mixed $_
1227 * @return string
1229 function commafy($_) {
1230 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
1233 function digitTransformTable() {
1234 $this->load();
1235 return $this->digitTransformTable;
1238 function separatorTransformTable() {
1239 $this->load();
1240 return $this->separatorTransformTable;
1245 * For the credit list in includes/Credits.php (action=credits)
1247 * @param array $l
1248 * @return string
1250 function listToText( $l ) {
1251 $s = '';
1252 $m = count($l) - 1;
1253 for ($i = $m; $i >= 0; $i--) {
1254 if ($i == $m) {
1255 $s = $l[$i];
1256 } else if ($i == $m - 1) {
1257 $s = $l[$i] . ' ' . $this->getMessageFromDB( 'and' ) . ' ' . $s;
1258 } else {
1259 $s = $l[$i] . ', ' . $s;
1262 return $s;
1265 # Crop a string from the beginning or end to a certain number of bytes.
1266 # (Bytes are used because our storage has limited byte lengths for some
1267 # columns in the database.) Multibyte charsets will need to make sure that
1268 # only whole characters are included!
1270 # $length does not include the optional ellipsis.
1271 # If $length is negative, snip from the beginning
1272 function truncate( $string, $length, $ellipsis = "" ) {
1273 if( $length == 0 ) {
1274 return $ellipsis;
1276 if ( strlen( $string ) <= abs( $length ) ) {
1277 return $string;
1279 if( $length > 0 ) {
1280 $string = substr( $string, 0, $length );
1281 $char = ord( $string[strlen( $string ) - 1] );
1282 $m = array();
1283 if ($char >= 0xc0) {
1284 # We got the first byte only of a multibyte char; remove it.
1285 $string = substr( $string, 0, -1 );
1286 } elseif( $char >= 0x80 &&
1287 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
1288 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
1289 # We chopped in the middle of a character; remove it
1290 $string = $m[1];
1292 return $string . $ellipsis;
1293 } else {
1294 $string = substr( $string, $length );
1295 $char = ord( $string[0] );
1296 if( $char >= 0x80 && $char < 0xc0 ) {
1297 # We chopped in the middle of a character; remove the whole thing
1298 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
1300 return $ellipsis . $string;
1305 * Grammatical transformations, needed for inflected languages
1306 * Invoked by putting {{grammar:case|word}} in a message
1308 * @param string $word
1309 * @param string $case
1310 * @return string
1312 function convertGrammar( $word, $case ) {
1313 global $wgGrammarForms;
1314 if ( isset($wgGrammarForms['en'][$case][$word]) ) {
1315 return $wgGrammarForms['en'][$case][$word];
1317 return $word;
1321 * Plural form transformations, needed for some languages.
1322 * For example, where are 3 form of plural in Russian and Polish,
1323 * depending on "count mod 10". See [[w:Plural]]
1324 * For English it is pretty simple.
1326 * Invoked by putting {{plural:count|wordform1|wordform2}}
1327 * or {{plural:count|wordform1|wordform2|wordform3}}
1329 * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
1331 * @param integer $count
1332 * @param string $wordform1
1333 * @param string $wordform2
1334 * @param string $wordform3 (optional)
1335 * @param string $wordform4 (optional)
1336 * @param string $wordform5 (optional)
1337 * @return string
1339 function convertPlural( $count, $w1, $w2, $w3, $w4, $w5) {
1340 return ( $count == '1' || $count == '-1' ) ? $w1 : $w2;
1344 * For translaing of expiry times
1345 * @param string The validated block time in English
1346 * @param $forContent, avoid html?
1347 * @return Somehow translated block time
1348 * @see LanguageFi.php for example implementation
1350 function translateBlockExpiry( $str, $forContent=false ) {
1352 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
1354 if ( $scBlockExpiryOptions == '-') {
1355 return $str;
1358 foreach (explode(',', $scBlockExpiryOptions) as $option) {
1359 if ( strpos($option, ":") === false )
1360 continue;
1361 list($show, $value) = explode(":", $option);
1362 if ( strcmp ( $str, $value) == 0 ) {
1363 if ( $forContent )
1364 return htmlspecialchars($str) . htmlspecialchars( trim( $show ) );
1365 else
1366 return '<span title="' . htmlspecialchars($str). '">' . htmlspecialchars( trim( $show ) ) . '</span>';
1370 return $str;
1374 * languages like Chinese need to be segmented in order for the diff
1375 * to be of any use
1377 * @param string $text
1378 * @return string
1380 function segmentForDiff( $text ) {
1381 return $text;
1385 * and unsegment to show the result
1387 * @param string $text
1388 * @return string
1390 function unsegmentForDiff( $text ) {
1391 return $text;
1394 # convert text to different variants of a language.
1395 function convert( $text, $isTitle = false) {
1396 return $this->mConverter->convert($text, $isTitle);
1399 # Convert text from within Parser
1400 function parserConvert( $text, &$parser ) {
1401 return $this->mConverter->parserConvert( $text, $parser );
1404 # Check if this is a language with variants
1405 function hasVariants(){
1406 return sizeof($this->getVariants())>1;
1409 # Put custom tags (e.g. -{ }-) around math to prevent conversion
1410 function armourMath($text){
1411 return $this->mConverter->armourMath($text);
1416 * Perform output conversion on a string, and encode for safe HTML output.
1417 * @param string $text
1418 * @param bool $isTitle -- wtf?
1419 * @return string
1420 * @todo this should get integrated somewhere sane
1422 function convertHtml( $text, $isTitle = false ) {
1423 return htmlspecialchars( $this->convert( $text, $isTitle ) );
1426 function convertCategoryKey( $key ) {
1427 return $this->mConverter->convertCategoryKey( $key );
1431 * get the list of variants supported by this langauge
1432 * see sample implementation in LanguageZh.php
1434 * @return array an array of language codes
1436 function getVariants() {
1437 return $this->mConverter->getVariants();
1441 function getPreferredVariant( $fromUser = true ) {
1442 return $this->mConverter->getPreferredVariant( $fromUser );
1446 * if a language supports multiple variants, it is
1447 * possible that non-existing link in one variant
1448 * actually exists in another variant. this function
1449 * tries to find it. See e.g. LanguageZh.php
1451 * @param string $link the name of the link
1452 * @param mixed $nt the title object of the link
1453 * @return null the input parameters may be modified upon return
1455 function findVariantLink( &$link, &$nt ) {
1456 $this->mConverter->findVariantLink($link, $nt);
1460 * If a language supports multiple variants, converts text
1461 * into an array of all possible variants of the text:
1462 * 'variant' => text in that variant
1465 function convertLinkToAllVariants($text){
1466 return $this->mConverter->convertLinkToAllVariants($text);
1471 * returns language specific options used by User::getPageRenderHash()
1472 * for example, the preferred language variant
1474 * @return string
1475 * @public
1477 function getExtraHashOptions() {
1478 return $this->mConverter->getExtraHashOptions();
1482 * for languages that support multiple variants, the title of an
1483 * article may be displayed differently in different variants. this
1484 * function returns the apporiate title defined in the body of the article.
1486 * @return string
1488 function getParsedTitle() {
1489 return $this->mConverter->getParsedTitle();
1493 * Enclose a string with the "no conversion" tag. This is used by
1494 * various functions in the Parser
1496 * @param string $text text to be tagged for no conversion
1497 * @return string the tagged text
1499 function markNoConversion( $text, $noParse=false ) {
1500 return $this->mConverter->markNoConversion( $text, $noParse );
1504 * A regular expression to match legal word-trailing characters
1505 * which should be merged onto a link of the form [[foo]]bar.
1507 * @return string
1508 * @public
1510 function linkTrail() {
1511 $this->load();
1512 return $this->linkTrail;
1515 function getLangObj() {
1516 return $this;
1520 * Get the RFC 3066 code for this language object
1522 function getCode() {
1523 return $this->mCode;
1526 function setCode( $code ) {
1527 $this->mCode = $code;
1530 static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
1531 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
1534 static function getMessagesFileName( $code ) {
1535 global $IP;
1536 return self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
1539 static function getClassFileName( $code ) {
1540 global $IP;
1541 return self::getFileName( "$IP/languages/classes/Language", $code, '.php' );
1544 static function getLocalisationArray( $code, $disableCache = false ) {
1545 self::loadLocalisation( $code, $disableCache );
1546 return self::$mLocalisationCache[$code];
1550 * Load localisation data for a given code into the static cache
1552 * @return array Dependencies, map of filenames to mtimes
1554 static function loadLocalisation( $code, $disableCache = false ) {
1555 static $recursionGuard = array();
1556 global $wgMemc;
1558 if ( !$code ) {
1559 throw new MWException( "Invalid language code requested" );
1562 if ( !$disableCache ) {
1563 # Try the per-process cache
1564 if ( isset( self::$mLocalisationCache[$code] ) ) {
1565 return self::$mLocalisationCache[$code]['deps'];
1568 wfProfileIn( __METHOD__ );
1570 # Try the serialized directory
1571 $cache = wfGetPrecompiledData( self::getFileName( "Messages", $code, '.ser' ) );
1572 if ( $cache ) {
1573 self::$mLocalisationCache[$code] = $cache;
1574 wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" );
1575 wfProfileOut( __METHOD__ );
1576 return self::$mLocalisationCache[$code]['deps'];
1579 # Try the global cache
1580 $memcKey = wfMemcKey('localisation', $code );
1581 $cache = $wgMemc->get( $memcKey );
1582 if ( $cache ) {
1583 # Check file modification times
1584 foreach ( $cache['deps'] as $file => $mtime ) {
1585 if ( !file_exists( $file ) || filemtime( $file ) > $mtime ) {
1586 break;
1589 if ( self::isLocalisationOutOfDate( $cache ) ) {
1590 $wgMemc->delete( $memcKey );
1591 $cache = false;
1592 wfDebug( "Language::loadLocalisation(): localisation cache for $code had expired due to update of $file\n" );
1593 } else {
1594 self::$mLocalisationCache[$code] = $cache;
1595 wfDebug( "Language::loadLocalisation(): got localisation for $code from cache\n" );
1596 wfProfileOut( __METHOD__ );
1597 return $cache['deps'];
1600 } else {
1601 wfProfileIn( __METHOD__ );
1604 # Default fallback, may be overridden when the messages file is included
1605 if ( $code != 'en' ) {
1606 $fallback = 'en';
1607 } else {
1608 $fallback = false;
1611 # Load the primary localisation from the source file
1612 $filename = self::getMessagesFileName( $code );
1613 if ( !file_exists( $filename ) ) {
1614 wfDebug( "Language::loadLocalisation(): no localisation file for $code, using implicit fallback to en\n" );
1615 $cache = array();
1616 $deps = array();
1617 } else {
1618 $deps = array( $filename => filemtime( $filename ) );
1619 require( $filename );
1620 $cache = compact( self::$mLocalisationKeys );
1621 wfDebug( "Language::loadLocalisation(): got localisation for $code from source\n" );
1624 if ( !empty( $fallback ) ) {
1625 # Load the fallback localisation, with a circular reference guard
1626 if ( isset( $recursionGuard[$code] ) ) {
1627 throw new MWException( "Error: Circular fallback reference in language code $code" );
1629 $recursionGuard[$code] = true;
1630 $newDeps = self::loadLocalisation( $fallback, $disableCache );
1631 unset( $recursionGuard[$code] );
1633 $secondary = self::$mLocalisationCache[$fallback];
1634 $deps = array_merge( $deps, $newDeps );
1636 # Merge the fallback localisation with the current localisation
1637 foreach ( self::$mLocalisationKeys as $key ) {
1638 if ( isset( $cache[$key] ) ) {
1639 if ( isset( $secondary[$key] ) ) {
1640 if ( in_array( $key, self::$mMergeableMapKeys ) ) {
1641 $cache[$key] = $cache[$key] + $secondary[$key];
1642 } elseif ( in_array( $key, self::$mMergeableListKeys ) ) {
1643 $cache[$key] = array_merge( $secondary[$key], $cache[$key] );
1644 } elseif ( in_array( $key, self::$mMergeableAliasListKeys ) ) {
1645 $cache[$key] = array_merge_recursive( $cache[$key], $secondary[$key] );
1648 } else {
1649 $cache[$key] = $secondary[$key];
1653 # Merge bookstore lists if requested
1654 if ( !empty( $cache['bookstoreList']['inherit'] ) ) {
1655 $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] );
1657 if ( isset( $cache['bookstoreList']['inherit'] ) ) {
1658 unset( $cache['bookstoreList']['inherit'] );
1662 # Add dependencies to the cache entry
1663 $cache['deps'] = $deps;
1665 # Replace spaces with underscores in namespace names
1666 $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] );
1668 # Save to both caches
1669 self::$mLocalisationCache[$code] = $cache;
1670 if ( !$disableCache ) {
1671 $wgMemc->set( $memcKey, $cache );
1674 wfProfileOut( __METHOD__ );
1675 return $deps;
1679 * Test if a given localisation cache is out of date with respect to the
1680 * source Messages files. This is done automatically for the global cache
1681 * in $wgMemc, but is only done on certain occasions for the serialized
1682 * data file.
1684 * @param $cache mixed Either a language code or a cache array
1686 static function isLocalisationOutOfDate( $cache ) {
1687 if ( !is_array( $cache ) ) {
1688 self::loadLocalisation( $cache );
1689 $cache = self::$mLocalisationCache[$cache];
1691 $expired = false;
1692 foreach ( $cache['deps'] as $file => $mtime ) {
1693 if ( !file_exists( $file ) || filemtime( $file ) > $mtime ) {
1694 $expired = true;
1695 break;
1698 return $expired;
1702 * Get the fallback for a given language
1704 static function getFallbackFor( $code ) {
1705 self::loadLocalisation( $code );
1706 return self::$mLocalisationCache[$code]['fallback'];
1709 /**
1710 * Get all messages for a given language
1712 static function getMessagesFor( $code ) {
1713 self::loadLocalisation( $code );
1714 return self::$mLocalisationCache[$code]['messages'];
1717 /**
1718 * Get a message for a given language
1720 static function getMessageFor( $key, $code ) {
1721 self::loadLocalisation( $code );
1722 return isset( self::$mLocalisationCache[$code]['messages'][$key] ) ? self::$mLocalisationCache[$code]['messages'][$key] : null;
1726 * Load localisation data for this object
1728 function load() {
1729 if ( !$this->mLoaded ) {
1730 self::loadLocalisation( $this->getCode() );
1731 $cache =& self::$mLocalisationCache[$this->getCode()];
1732 foreach ( self::$mLocalisationKeys as $key ) {
1733 $this->$key = $cache[$key];
1735 $this->mLoaded = true;
1737 $this->fixUpSettings();
1742 * Do any necessary post-cache-load settings adjustment
1744 function fixUpSettings() {
1745 global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk,
1746 $wgNamespaceAliases, $wgAmericanDates;
1747 wfProfileIn( __METHOD__ );
1748 if ( $wgExtraNamespaces ) {
1749 $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames;
1752 $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
1753 if ( $wgMetaNamespaceTalk ) {
1754 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
1755 } else {
1756 $talk = $this->namespaceNames[NS_PROJECT_TALK];
1757 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
1759 # Allow grammar transformations
1760 # Allowing full message-style parsing would make simple requests
1761 # such as action=raw much more expensive than they need to be.
1762 # This will hopefully cover most cases.
1763 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
1764 array( &$this, 'replaceGrammarInNamespace' ), $talk );
1765 $talk = str_replace( ' ', '_', $talk );
1766 $this->namespaceNames[NS_PROJECT_TALK] = $talk;
1769 # The above mixing may leave namespaces out of canonical order.
1770 # Re-order by namespace ID number...
1771 ksort( $this->namespaceNames );
1773 # Put namespace names and aliases into a hashtable.
1774 # If this is too slow, then we should arrange it so that it is done
1775 # before caching. The catch is that at pre-cache time, the above
1776 # class-specific fixup hasn't been done.
1777 $this->mNamespaceIds = array();
1778 foreach ( $this->namespaceNames as $index => $name ) {
1779 $this->mNamespaceIds[$this->lc($name)] = $index;
1781 if ( $this->namespaceAliases ) {
1782 foreach ( $this->namespaceAliases as $name => $index ) {
1783 $this->mNamespaceIds[$this->lc($name)] = $index;
1786 if ( $wgNamespaceAliases ) {
1787 foreach ( $wgNamespaceAliases as $name => $index ) {
1788 $this->mNamespaceIds[$this->lc($name)] = $index;
1792 if ( $this->defaultDateFormat == 'dmy or mdy' ) {
1793 $this->defaultDateFormat = $wgAmericanDates ? 'mdy' : 'dmy';
1795 wfProfileOut( __METHOD__ );
1798 function replaceGrammarInNamespace( $m ) {
1799 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
1802 static function getCaseMaps() {
1803 static $wikiUpperChars, $wikiLowerChars;
1804 if ( isset( $wikiUpperChars ) ) {
1805 return array( $wikiUpperChars, $wikiLowerChars );
1808 wfProfileIn( __METHOD__ );
1809 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
1810 if ( $arr === false ) {
1811 throw new MWException(
1812 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
1814 extract( $arr );
1815 wfProfileOut( __METHOD__ );
1816 return array( $wikiUpperChars, $wikiLowerChars );