Old typo.
[mediawiki.git] / languages / Language.php
blob877ebd681d8cbd96f0deef5848681ffe8d171cfd
1 <?php
2 /**
3 * @package MediaWiki
4 * @subpackage Language
5 */
7 if( !defined( 'MEDIAWIKI' ) ) {
8 echo "This file is part of MediaWiki, it is not a valid entry point.\n";
9 exit( 1 );
13 # In general you should not make customizations in these language files
14 # directly, but should use the MediaWiki: special namespace to customize
15 # user interface messages through the wiki.
16 # See http://meta.wikipedia.org/wiki/MediaWiki_namespace
18 # NOTE TO TRANSLATORS: Do not copy this whole file when making translations!
19 # A lot of common constants and a base class with inheritable methods are
20 # defined here, which should not be redefined. See the other LanguageXx.php
21 # files for examples.
24 # Read language names
25 global $wgLanguageNames;
26 require_once( 'Names.php' );
28 global $wgInputEncoding, $wgOutputEncoding;
30 /**
31 * These are always UTF-8, they exist only for backwards compatibility
33 $wgInputEncoding = "UTF-8";
34 $wgOutputEncoding = "UTF-8";
36 if( function_exists( 'mb_strtoupper' ) ) {
37 mb_internal_encoding('UTF-8');
40 /* a fake language converter */
41 class FakeConverter {
42 var $mLang;
43 function FakeConverter($langobj) {$this->mLang = $langobj;}
44 function convert($t, $i) {return $t;}
45 function parserConvert($t, $p) {return $t;}
46 function getVariants() { return array( $this->mLang->getCode() ); }
47 function getPreferredVariant() {return $this->mLang->getCode(); }
48 function findVariantLink(&$l, &$n) {}
49 function getExtraHashOptions() {return '';}
50 function getParsedTitle() {return '';}
51 function markNoConversion($text, $noParse=false) {return $text;}
52 function convertCategoryKey( $key ) {return $key; }
53 function convertLinkToAllVariants($text){ return array( $this->mLang->getCode() => $text); }
54 function setNoTitleConvert(){}
57 #--------------------------------------------------------------------------
58 # Internationalisation code
59 #--------------------------------------------------------------------------
61 class Language {
62 var $mConverter, $mVariants, $mCode, $mLoaded = false;
64 static public $mLocalisationKeys = array( 'fallback', 'namespaceNames',
65 'quickbarSettings', 'skinNames', 'mathNames',
66 'bookstoreList', 'magicWords', 'messages', 'rtl', 'digitTransformTable',
67 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
68 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases',
69 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
70 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases' );
72 static public $mMergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames',
73 'dateFormats', 'defaultUserOptionOverrides', 'magicWords' );
75 static public $mMergeableListKeys = array( 'extraUserToggles' );
77 static public $mMergeableAliasListKeys = array( 'specialPageAliases' );
79 static public $mLocalisationCache = array();
81 static public $mWeekdayMsgs = array(
82 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
83 'friday', 'saturday'
86 static public $mWeekdayAbbrevMsgs = array(
87 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
90 static public $mMonthMsgs = array(
91 'january', 'february', 'march', 'april', 'may_long', 'june',
92 'july', 'august', 'september', 'october', 'november',
93 'december'
95 static public $mMonthGenMsgs = array(
96 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
97 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
98 'december-gen'
100 static public $mMonthAbbrevMsgs = array(
101 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
102 'sep', 'oct', 'nov', 'dec'
106 * Create a language object for a given language code
108 static function factory( $code ) {
109 global $IP;
110 static $recursionLevel = 0;
112 if ( $code == 'en' ) {
113 $class = 'Language';
114 } else {
115 $class = 'Language' . str_replace( '-', '_', ucfirst( $code ) );
116 // Preload base classes to work around APC/PHP5 bug
117 if ( file_exists( "$IP/languages/classes/$class.deps.php" ) ) {
118 include_once("$IP/languages/classes/$class.deps.php");
120 if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
121 include_once("$IP/languages/classes/$class.php");
125 if ( $recursionLevel > 5 ) {
126 throw new MWException( "Language fallback loop detected when creating class $class\n" );
129 if( ! class_exists( $class ) ) {
130 $fallback = Language::getFallbackFor( $code );
131 ++$recursionLevel;
132 $lang = Language::factory( $fallback );
133 --$recursionLevel;
134 $lang->setCode( $code );
135 } else {
136 $lang = new $class;
139 return $lang;
142 function __construct() {
143 $this->mConverter = new FakeConverter($this);
144 // Set the code to the name of the descendant
145 if ( get_class( $this ) == 'Language' ) {
146 $this->mCode = 'en';
147 } else {
148 $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
153 * Hook which will be called if this is the content language.
154 * Descendants can use this to register hook functions or modify globals
156 function initContLang() {}
159 * @deprecated
160 * @return array
162 function getDefaultUserOptions() {
163 return User::getDefaultOptions();
167 * Exports $wgBookstoreListEn
168 * @return array
170 function getBookstoreList() {
171 $this->load();
172 return $this->bookstoreList;
176 * @return array
178 function getNamespaces() {
179 $this->load();
180 return $this->namespaceNames;
184 * A convenience function that returns the same thing as
185 * getNamespaces() except with the array values changed to ' '
186 * where it found '_', useful for producing output to be displayed
187 * e.g. in <select> forms.
189 * @return array
191 function getFormattedNamespaces() {
192 $ns = $this->getNamespaces();
193 foreach($ns as $k => $v) {
194 $ns[$k] = strtr($v, '_', ' ');
196 return $ns;
200 * Get a namespace value by key
201 * <code>
202 * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
203 * echo $mw_ns; // prints 'MediaWiki'
204 * </code>
206 * @param int $index the array key of the namespace to return
207 * @return mixed, string if the namespace value exists, otherwise false
209 function getNsText( $index ) {
210 $ns = $this->getNamespaces();
211 return isset( $ns[$index] ) ? $ns[$index] : false;
215 * A convenience function that returns the same thing as
216 * getNsText() except with '_' changed to ' ', useful for
217 * producing output.
219 * @return array
221 function getFormattedNsText( $index ) {
222 $ns = $this->getNsText( $index );
223 return strtr($ns, '_', ' ');
227 * Get a namespace key by value, case insensetive.
229 * @param string $text
230 * @return mixed An integer if $text is a valid value otherwise false
232 function getNsIndex( $text ) {
233 $this->load();
234 $index = @$this->mNamespaceIds[$this->lc($text)];
235 if ( is_null( $index ) ) {
236 return false;
237 } else {
238 return $index;
243 * short names for language variants used for language conversion links.
245 * @param string $code
246 * @return string
248 function getVariantname( $code ) {
249 return $this->getMessageFromDB( "variantname-$code" );
252 function specialPage( $name ) {
253 $aliases = $this->getSpecialPageAliases();
254 if ( isset( $aliases[$name][0] ) ) {
255 $name = $aliases[$name][0];
257 return $this->getNsText(NS_SPECIAL) . ':' . $name;
260 function getQuickbarSettings() {
261 $this->load();
262 return $this->quickbarSettings;
265 function getSkinNames() {
266 $this->load();
267 return $this->skinNames;
270 function getMathNames() {
271 $this->load();
272 return $this->mathNames;
275 function getDatePreferences() {
276 $this->load();
277 return $this->datePreferences;
280 function getDateFormats() {
281 $this->load();
282 return $this->dateFormats;
285 function getDefaultDateFormat() {
286 $this->load();
287 return $this->defaultDateFormat;
290 function getDatePreferenceMigrationMap() {
291 $this->load();
292 return $this->datePreferenceMigrationMap;
295 function getDefaultUserOptionOverrides() {
296 $this->load();
297 return $this->defaultUserOptionOverrides;
300 function getExtraUserToggles() {
301 $this->load();
302 return $this->extraUserToggles;
305 function getUserToggle( $tog ) {
306 return $this->getMessageFromDB( "tog-$tog" );
310 * Get language names, indexed by code.
311 * If $customisedOnly is true, only returns codes with a messages file
313 function getLanguageNames( $customisedOnly = false ) {
314 global $wgLanguageNames;
315 if ( !$customisedOnly ) {
316 return $wgLanguageNames;
319 global $IP;
320 $messageFiles = glob( "$IP/languages/messages/Messages*.php" );
321 $names = array();
322 foreach ( $messageFiles as $file ) {
323 if( preg_match( '/Messages([A-Z][a-z_]+)\.php$/', $file, $m ) ) {
324 $code = str_replace( '_', '-', strtolower( $m[1] ) );
325 if ( isset( $wgLanguageNames[$code] ) ) {
326 $names[$code] = $wgLanguageNames[$code];
330 return $names;
334 * Ugly hack to get a message maybe from the MediaWiki namespace, if this
335 * language object is the content or user language.
337 function getMessageFromDB( $msg ) {
338 global $wgContLang, $wgLang;
339 if ( $wgContLang->getCode() == $this->getCode() ) {
340 # Content language
341 return wfMsgForContent( $msg );
342 } elseif ( $wgLang->getCode() == $this->getCode() ) {
343 # User language
344 return wfMsg( $msg );
345 } else {
346 # Neither, get from localisation
347 return $this->getMessage( $msg );
351 function getLanguageName( $code ) {
352 global $wgLanguageNames;
353 if ( ! array_key_exists( $code, $wgLanguageNames ) ) {
354 return '';
356 return $wgLanguageNames[$code];
359 function getMonthName( $key ) {
360 return $this->getMessageFromDB( self::$mMonthMsgs[$key-1] );
363 function getMonthNameGen( $key ) {
364 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key-1] );
367 function getMonthAbbreviation( $key ) {
368 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key-1] );
371 function getWeekdayName( $key ) {
372 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key-1] );
375 function getWeekdayAbbreviation( $key ) {
376 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key-1] );
380 * Used by date() and time() to adjust the time output.
381 * @public
382 * @param int $ts the time in date('YmdHis') format
383 * @param mixed $tz adjust the time by this amount (default false,
384 * mean we get user timecorrection setting)
385 * @return int
387 function userAdjust( $ts, $tz = false ) {
388 global $wgUser, $wgLocalTZoffset;
390 if (!$tz) {
391 $tz = $wgUser->getOption( 'timecorrection' );
394 # minutes and hours differences:
395 $minDiff = 0;
396 $hrDiff = 0;
398 if ( $tz === '' ) {
399 # Global offset in minutes.
400 if( isset($wgLocalTZoffset) ) {
401 $hrDiff = $wgLocalTZoffset % 60;
402 $minDiff = $wgLocalTZoffset - ($hrDiff * 60);
404 } elseif ( strpos( $tz, ':' ) !== false ) {
405 $tzArray = explode( ':', $tz );
406 $hrDiff = intval($tzArray[0]);
407 $minDiff = intval($hrDiff < 0 ? -$tzArray[1] : $tzArray[1]);
408 } else {
409 $hrDiff = intval( $tz );
412 # No difference ? Return time unchanged
413 if ( 0 == $hrDiff && 0 == $minDiff ) { return $ts; }
415 # Generate an adjusted date
416 $t = mktime( (
417 (int)substr( $ts, 8, 2) ) + $hrDiff, # Hours
418 (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
419 (int)substr( $ts, 12, 2 ), # Seconds
420 (int)substr( $ts, 4, 2 ), # Month
421 (int)substr( $ts, 6, 2 ), # Day
422 (int)substr( $ts, 0, 4 ) ); #Year
423 return date( 'YmdHis', $t );
427 * This is a workalike of PHP's date() function, but with better
428 * internationalisation, a reduced set of format characters, and a better
429 * escaping format.
431 * Supported format characters are dDjlNwzWFmMntLYyaAgGhHiscrU. See the
432 * PHP manual for definitions. There are a number of extensions, which
433 * start with "x":
435 * xn Do not translate digits of the next numeric format character
436 * xN Toggle raw digit (xn) flag, stays set until explicitly unset
437 * xr Use roman numerals for the next numeric format character
438 * xx Literal x
439 * xg Genitive month name
441 * Characters enclosed in double quotes will be considered literal (with
442 * the quotes themselves removed). Unmatched quotes will be considered
443 * literal quotes. Example:
445 * "The month is" F => The month is January
446 * i's" => 20'11"
448 * Backslash escaping is also supported.
450 * @param string $format
451 * @param string $ts 14-character timestamp
452 * YYYYMMDDHHMMSS
453 * 01234567890123
455 function sprintfDate( $format, $ts ) {
456 $s = '';
457 $raw = false;
458 $roman = false;
459 $unix = false;
460 $rawToggle = false;
461 for ( $p = 0; $p < strlen( $format ); $p++ ) {
462 $num = false;
463 $code = $format[$p];
464 if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
465 $code .= $format[++$p];
468 switch ( $code ) {
469 case 'xx':
470 $s .= 'x';
471 break;
472 case 'xn':
473 $raw = true;
474 break;
475 case 'xN':
476 $rawToggle = !$rawToggle;
477 break;
478 case 'xr':
479 $roman = true;
480 break;
481 case 'xg':
482 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
483 break;
484 case 'd':
485 $num = substr( $ts, 6, 2 );
486 break;
487 case 'D':
488 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
489 $s .= $this->getWeekdayAbbreviation( date( 'w', $unix ) + 1 );
490 break;
491 case 'j':
492 $num = intval( substr( $ts, 6, 2 ) );
493 break;
494 case 'l':
495 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
496 $s .= $this->getWeekdayName( date( 'w', $unix ) + 1 );
497 break;
498 case 'N':
499 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
500 $w = date( 'w', $unix );
501 $num = $w ? $w : 7;
502 break;
503 case 'w':
504 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
505 $num = date( 'w', $unix );
506 break;
507 case 'z':
508 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
509 $num = date( 'z', $unix );
510 break;
511 case 'W':
512 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
513 $num = date( 'W', $unix );
514 break;
515 case 'F':
516 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
517 break;
518 case 'm':
519 $num = substr( $ts, 4, 2 );
520 break;
521 case 'M':
522 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
523 break;
524 case 'n':
525 $num = intval( substr( $ts, 4, 2 ) );
526 break;
527 case 't':
528 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
529 $num = date( 't', $unix );
530 break;
531 case 'L':
532 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
533 $num = date( 'L', $unix );
534 break;
535 case 'Y':
536 $num = substr( $ts, 0, 4 );
537 break;
538 case 'y':
539 $num = substr( $ts, 2, 2 );
540 break;
541 case 'a':
542 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
543 break;
544 case 'A':
545 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
546 break;
547 case 'g':
548 $h = substr( $ts, 8, 2 );
549 $num = $h % 12 ? $h % 12 : 12;
550 break;
551 case 'G':
552 $num = intval( substr( $ts, 8, 2 ) );
553 break;
554 case 'h':
555 $h = substr( $ts, 8, 2 );
556 $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
557 break;
558 case 'H':
559 $num = substr( $ts, 8, 2 );
560 break;
561 case 'i':
562 $num = substr( $ts, 10, 2 );
563 break;
564 case 's':
565 $num = substr( $ts, 12, 2 );
566 break;
567 case 'c':
568 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
569 $s .= date( 'c', $unix );
570 break;
571 case 'r':
572 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
573 $s .= date( 'r', $unix );
574 break;
575 case 'U':
576 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
577 $num = $unix;
578 break;
579 case '\\':
580 # Backslash escaping
581 if ( $p < strlen( $format ) - 1 ) {
582 $s .= $format[++$p];
583 } else {
584 $s .= '\\';
586 break;
587 case '"':
588 # Quoted literal
589 if ( $p < strlen( $format ) - 1 ) {
590 $endQuote = strpos( $format, '"', $p + 1 );
591 if ( $endQuote === false ) {
592 # No terminating quote, assume literal "
593 $s .= '"';
594 } else {
595 $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
596 $p = $endQuote;
598 } else {
599 # Quote at end of string, assume literal "
600 $s .= '"';
602 break;
603 default:
604 $s .= $format[$p];
606 if ( $num !== false ) {
607 if ( $rawToggle || $raw ) {
608 $s .= $num;
609 $raw = false;
610 } elseif ( $roman ) {
611 $s .= self::romanNumeral( $num );
612 $roman = false;
613 } else {
614 $s .= $this->formatNum( $num, true );
616 $num = false;
619 return $s;
623 * Roman number formatting up to 3000
625 static function romanNumeral( $num ) {
626 static $table = array(
627 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
628 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
629 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
630 array( '', 'M', 'MM', 'MMM' )
633 $num = intval( $num );
634 if ( $num > 3000 || $num <= 0 ) {
635 return $num;
638 $s = '';
639 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
640 if ( $num >= $pow10 ) {
641 $s .= $table[$i][floor($num / $pow10)];
643 $num = $num % $pow10;
645 return $s;
649 * This is meant to be used by time(), date(), and timeanddate() to get
650 * the date preference they're supposed to use, it should be used in
651 * all children.
653 *<code>
654 * function timeanddate([...], $format = true) {
655 * $datePreference = $this->dateFormat($format);
656 * [...]
658 *</code>
660 * @param mixed $usePrefs: if true, the user's preference is used
661 * if false, the site/language default is used
662 * if int/string, assumed to be a format.
663 * @return string
665 function dateFormat( $usePrefs = true ) {
666 global $wgUser;
668 if( is_bool( $usePrefs ) ) {
669 if( $usePrefs ) {
670 $datePreference = $wgUser->getDatePreference();
671 } else {
672 $options = User::getDefaultOptions();
673 $datePreference = (string)$options['date'];
675 } else {
676 $datePreference = (string)$usePrefs;
679 // return int
680 if( $datePreference == '' ) {
681 return 'default';
684 return $datePreference;
688 * @public
689 * @param mixed $ts the time format which needs to be turned into a
690 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
691 * @param bool $adj whether to adjust the time output according to the
692 * user configured offset ($timecorrection)
693 * @param mixed $format true to use user's date format preference
694 * @param string $timecorrection the time offset as returned by
695 * validateTimeZone() in Special:Preferences
696 * @return string
698 function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
699 $this->load();
700 if ( $adj ) {
701 $ts = $this->userAdjust( $ts, $timecorrection );
704 $pref = $this->dateFormat( $format );
705 if( $pref == 'default' || !isset( $this->dateFormats["$pref date"] ) ) {
706 $pref = $this->defaultDateFormat;
708 return $this->sprintfDate( $this->dateFormats["$pref date"], $ts );
712 * @public
713 * @param mixed $ts the time format which needs to be turned into a
714 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
715 * @param bool $adj whether to adjust the time output according to the
716 * user configured offset ($timecorrection)
717 * @param mixed $format true to use user's date format preference
718 * @param string $timecorrection the time offset as returned by
719 * validateTimeZone() in Special:Preferences
720 * @return string
722 function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
723 $this->load();
724 if ( $adj ) {
725 $ts = $this->userAdjust( $ts, $timecorrection );
728 $pref = $this->dateFormat( $format );
729 if( $pref == 'default' || !isset( $this->dateFormats["$pref time"] ) ) {
730 $pref = $this->defaultDateFormat;
732 return $this->sprintfDate( $this->dateFormats["$pref time"], $ts );
736 * @public
737 * @param mixed $ts the time format which needs to be turned into a
738 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
739 * @param bool $adj whether to adjust the time output according to the
740 * user configured offset ($timecorrection)
742 * @param mixed $format what format to return, if it's false output the
743 * default one (default true)
744 * @param string $timecorrection the time offset as returned by
745 * validateTimeZone() in Special:Preferences
746 * @return string
748 function timeanddate( $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 both"] ) ) {
756 $pref = $this->defaultDateFormat;
759 return $this->sprintfDate( $this->dateFormats["$pref both"], $ts );
762 function getMessage( $key ) {
763 $this->load();
764 return @$this->messages[$key];
767 function getAllMessages() {
768 $this->load();
769 return $this->messages;
772 function iconv( $in, $out, $string ) {
773 # For most languages, this is a wrapper for iconv
774 return iconv( $in, $out, $string );
777 // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
778 function ucwordbreaksCallbackAscii($matches){
779 return $this->ucfirst($matches[1]);
782 function ucwordbreaksCallbackMB($matches){
783 return mb_strtoupper($matches[0]);
786 function ucCallback($matches){
787 list( $wikiUpperChars ) = self::getCaseMaps();
788 return strtr( $matches[1], $wikiUpperChars );
791 function lcCallback($matches){
792 list( , $wikiLowerChars ) = self::getCaseMaps();
793 return strtr( $matches[1], $wikiLowerChars );
796 function ucwordsCallbackMB($matches){
797 return mb_strtoupper($matches[0]);
800 function ucwordsCallbackWiki($matches){
801 list( $wikiUpperChars ) = self::getCaseMaps();
802 return strtr( $matches[0], $wikiUpperChars );
805 function ucfirst( $str ) {
806 return self::uc( $str, true );
809 function uc( $str, $first = false ) {
810 if ( function_exists( 'mb_strtoupper' ) )
811 if ( $first )
812 if ( self::isMultibyte( $str ) )
813 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
814 else
815 return ucfirst( $str );
816 else
817 return self::isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
818 else
819 if ( self::isMultibyte( $str ) ) {
820 list( $wikiUpperChars ) = $this->getCaseMaps();
821 $x = $first ? '^' : '';
822 return preg_replace_callback(
823 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
824 array($this,"ucCallback"),
825 $str
827 } else
828 return $first ? ucfirst( $str ) : strtoupper( $str );
831 function lcfirst( $str ) {
832 return self::lc( $str, true );
835 function lc( $str, $first = false ) {
836 if ( function_exists( 'mb_strtolower' ) )
837 if ( $first )
838 if ( self::isMultibyte( $str ) )
839 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
840 else
841 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
842 else
843 return self::isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
844 else
845 if ( self::isMultibyte( $str ) ) {
846 list( , $wikiLowerChars ) = self::getCaseMaps();
847 $x = $first ? '^' : '';
848 return preg_replace_callback(
849 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
850 array($this,"lcCallback"),
851 $str
853 } else
854 return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
857 function isMultibyte( $str ) {
858 return (bool)preg_match( '/[\x80-\xff]/', $str );
861 function ucwords($str) {
862 if ( self::isMultibyte( $str ) ) {
863 $str = self::lc($str);
865 // regexp to find first letter in each word (i.e. after each space)
866 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
868 // function to use to capitalize a single char
869 if ( function_exists( 'mb_strtoupper' ) )
870 return preg_replace_callback(
871 $replaceRegexp,
872 array($this,"ucwordsCallbackMB"),
873 $str
875 else
876 return preg_replace_callback(
877 $replaceRegexp,
878 array($this,"ucwordsCallbackWiki"),
879 $str
882 else
883 return ucwords( strtolower( $str ) );
886 # capitalize words at word breaks
887 function ucwordbreaks($str){
888 if (self::isMultibyte( $str ) ) {
889 $str = self::lc($str);
891 // since \b doesn't work for UTF-8, we explicitely define word break chars
892 $breaks= "[ \-\(\)\}\{\.,\?!]";
894 // find first letter after word break
895 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
897 if ( function_exists( 'mb_strtoupper' ) )
898 return preg_replace_callback(
899 $replaceRegexp,
900 array($this,"ucwordbreaksCallbackMB"),
901 $str
903 else
904 return preg_replace_callback(
905 $replaceRegexp,
906 array($this,"ucwordsCallbackWiki"),
907 $str
910 else
911 return preg_replace_callback(
912 '/\b([\w\x80-\xff]+)\b/',
913 array($this,"ucwordbreaksCallbackAscii"),
914 $str );
918 * Return a case-folded representation of $s
920 * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
921 * and $s2 are the same except for the case of their characters. It is not
922 * necessary for the value returned to make sense when displayed.
924 * Do *not* perform any other normalisation in this function. If a caller
925 * uses this function when it should be using a more general normalisation
926 * function, then fix the caller.
928 function caseFold( $s ) {
929 return $this->uc( $s );
932 function checkTitleEncoding( $s ) {
933 if( is_array( $s ) ) {
934 wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
936 # Check for non-UTF-8 URLs
937 $ishigh = preg_match( '/[\x80-\xff]/', $s);
938 if(!$ishigh) return $s;
940 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
941 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
942 if( $isutf8 ) return $s;
944 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
947 function fallback8bitEncoding() {
948 $this->load();
949 return $this->fallback8bitEncoding;
953 * Some languages have special punctuation to strip out
954 * or characters which need to be converted for MySQL's
955 * indexing to grok it correctly. Make such changes here.
957 * @param string $in
958 * @return string
960 function stripForSearch( $string ) {
961 # MySQL fulltext index doesn't grok utf-8, so we
962 # need to fold cases and convert to hex
964 wfProfileIn( __METHOD__ );
965 if( function_exists( 'mb_strtolower' ) ) {
966 $out = preg_replace(
967 "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
968 "'U8' . bin2hex( \"$1\" )",
969 mb_strtolower( $string ) );
970 } else {
971 list( , $wikiLowerChars ) = self::getCaseMaps();
972 $out = preg_replace(
973 "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
974 "'U8' . bin2hex( strtr( \"\$1\", \$wikiLowerChars ) )",
975 $string );
977 wfProfileOut( __METHOD__ );
978 return $out;
981 function convertForSearchResult( $termsArray ) {
982 # some languages, e.g. Chinese, need to do a conversion
983 # in order for search results to be displayed correctly
984 return $termsArray;
988 * Get the first character of a string.
990 * @param string $s
991 * @return string
993 function firstChar( $s ) {
994 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
995 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
997 return isset( $matches[1] ) ? $matches[1] : "";
1000 function initEncoding() {
1001 # Some languages may have an alternate char encoding option
1002 # (Esperanto X-coding, Japanese furigana conversion, etc)
1003 # If this language is used as the primary content language,
1004 # an override to the defaults can be set here on startup.
1007 function recodeForEdit( $s ) {
1008 # For some languages we'll want to explicitly specify
1009 # which characters make it into the edit box raw
1010 # or are converted in some way or another.
1011 # Note that if wgOutputEncoding is different from
1012 # wgInputEncoding, this text will be further converted
1013 # to wgOutputEncoding.
1014 global $wgEditEncoding;
1015 if( $wgEditEncoding == '' or
1016 $wgEditEncoding == 'UTF-8' ) {
1017 return $s;
1018 } else {
1019 return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1023 function recodeInput( $s ) {
1024 # Take the previous into account.
1025 global $wgEditEncoding;
1026 if($wgEditEncoding != "") {
1027 $enc = $wgEditEncoding;
1028 } else {
1029 $enc = 'UTF-8';
1031 if( $enc == 'UTF-8' ) {
1032 return $s;
1033 } else {
1034 return $this->iconv( $enc, 'UTF-8', $s );
1039 * For right-to-left language support
1041 * @return bool
1043 function isRTL() {
1044 $this->load();
1045 return $this->rtl;
1049 * A hidden direction mark (LRM or RLM), depending on the language direction
1051 * @return string
1053 function getDirMark() {
1054 return $this->isRTL() ? "\xE2\x80\x8F" : "\xE2\x80\x8E";
1058 * An arrow, depending on the language direction
1060 * @return string
1062 function getArrow() {
1063 return $this->isRTL() ? '←' : '→';
1067 * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1069 * @return bool
1071 function linkPrefixExtension() {
1072 $this->load();
1073 return $this->linkPrefixExtension;
1076 function &getMagicWords() {
1077 $this->load();
1078 return $this->magicWords;
1081 # Fill a MagicWord object with data from here
1082 function getMagic( &$mw ) {
1083 if ( !isset( $this->mMagicExtensions ) ) {
1084 $this->mMagicExtensions = array();
1085 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) );
1087 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
1088 $rawEntry = $this->mMagicExtensions[$mw->mId];
1089 } else {
1090 $magicWords =& $this->getMagicWords();
1091 if ( isset( $magicWords[$mw->mId] ) ) {
1092 $rawEntry = $magicWords[$mw->mId];
1093 } else {
1094 # Fall back to English if local list is incomplete
1095 $magicWords =& Language::getMagicWords();
1096 $rawEntry = $magicWords[$mw->mId];
1100 if( !is_array( $rawEntry ) ) {
1101 error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1103 $mw->mCaseSensitive = $rawEntry[0];
1104 $mw->mSynonyms = array_slice( $rawEntry, 1 );
1108 * Get special page names, as an associative array
1109 * case folded alias => real name
1111 function getSpecialPageAliases() {
1112 $this->load();
1113 if ( !isset( $this->mExtendedSpecialPageAliases ) ) {
1114 $this->mExtendedSpecialPageAliases = $this->specialPageAliases;
1115 wfRunHooks( 'LangugeGetSpecialPageAliases',
1116 array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) );
1118 return $this->mExtendedSpecialPageAliases;
1122 * Italic is unsuitable for some languages
1124 * @public
1126 * @param string $text The text to be emphasized.
1127 * @return string
1129 function emphasize( $text ) {
1130 return "<em>$text</em>";
1134 * Normally we output all numbers in plain en_US style, that is
1135 * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
1136 * point twohundredthirtyfive. However this is not sutable for all
1137 * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
1138 * Icelandic just want to use commas instead of dots, and dots instead
1139 * of commas like "293.291,235".
1141 * An example of this function being called:
1142 * <code>
1143 * wfMsg( 'message', $wgLang->formatNum( $num ) )
1144 * </code>
1146 * See LanguageGu.php for the Gujarati implementation and
1147 * LanguageIs.php for the , => . and . => , implementation.
1149 * @todo check if it's viable to use localeconv() for the decimal
1150 * seperator thing.
1151 * @public
1152 * @param mixed $number the string to be formatted, should be an integer or
1153 * a floating point number.
1154 * @param bool $nocommafy Set to true for special numbers like dates
1155 * @return string
1157 function formatNum( $number, $nocommafy = false ) {
1158 global $wgTranslateNumerals;
1159 if (!$nocommafy) {
1160 $number = $this->commafy($number);
1161 $s = $this->separatorTransformTable();
1162 if (!is_null($s)) { $number = strtr($number, $s); }
1165 if ($wgTranslateNumerals) {
1166 $s = $this->digitTransformTable();
1167 if (!is_null($s)) { $number = strtr($number, $s); }
1170 return $number;
1174 * Adds commas to a given number
1176 * @param mixed $_
1177 * @return string
1179 function commafy($_) {
1180 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
1183 function digitTransformTable() {
1184 $this->load();
1185 return $this->digitTransformTable;
1188 function separatorTransformTable() {
1189 $this->load();
1190 return $this->separatorTransformTable;
1195 * For the credit list in includes/Credits.php (action=credits)
1197 * @param array $l
1198 * @return string
1200 function listToText( $l ) {
1201 $s = '';
1202 $m = count($l) - 1;
1203 for ($i = $m; $i >= 0; $i--) {
1204 if ($i == $m) {
1205 $s = $l[$i];
1206 } else if ($i == $m - 1) {
1207 $s = $l[$i] . ' ' . $this->getMessageFromDB( 'and' ) . ' ' . $s;
1208 } else {
1209 $s = $l[$i] . ', ' . $s;
1212 return $s;
1215 # Crop a string from the beginning or end to a certain number of bytes.
1216 # (Bytes are used because our storage has limited byte lengths for some
1217 # columns in the database.) Multibyte charsets will need to make sure that
1218 # only whole characters are included!
1220 # $length does not include the optional ellipsis.
1221 # If $length is negative, snip from the beginning
1222 function truncate( $string, $length, $ellipsis = "" ) {
1223 if( $length == 0 ) {
1224 return $ellipsis;
1226 if ( strlen( $string ) <= abs( $length ) ) {
1227 return $string;
1229 if( $length > 0 ) {
1230 $string = substr( $string, 0, $length );
1231 $char = ord( $string[strlen( $string ) - 1] );
1232 if ($char >= 0xc0) {
1233 # We got the first byte only of a multibyte char; remove it.
1234 $string = substr( $string, 0, -1 );
1235 } elseif( $char >= 0x80 &&
1236 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
1237 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
1238 # We chopped in the middle of a character; remove it
1239 $string = $m[1];
1241 return $string . $ellipsis;
1242 } else {
1243 $string = substr( $string, $length );
1244 $char = ord( $string[0] );
1245 if( $char >= 0x80 && $char < 0xc0 ) {
1246 # We chopped in the middle of a character; remove the whole thing
1247 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
1249 return $ellipsis . $string;
1254 * Grammatical transformations, needed for inflected languages
1255 * Invoked by putting {{grammar:case|word}} in a message
1257 * @param string $word
1258 * @param string $case
1259 * @return string
1261 function convertGrammar( $word, $case ) {
1262 global $wgGrammarForms;
1263 if ( isset($wgGrammarForms['en'][$case][$word]) ) {
1264 return $wgGrammarForms['en'][$case][$word];
1266 return $word;
1270 * Plural form transformations, needed for some languages.
1271 * For example, where are 3 form of plural in Russian and Polish,
1272 * depending on "count mod 10". See [[w:Plural]]
1273 * For English it is pretty simple.
1275 * Invoked by putting {{plural:count|wordform1|wordform2}}
1276 * or {{plural:count|wordform1|wordform2|wordform3}}
1278 * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
1280 * @param integer $count
1281 * @param string $wordform1
1282 * @param string $wordform2
1283 * @param string $wordform3 (optional)
1284 * @return string
1286 function convertPlural( $count, $w1, $w2, $w3) {
1287 return $count == '1' ? $w1 : $w2;
1291 * For translaing of expiry times
1292 * @param string The validated block time in English
1293 * @return Somehow translated block time
1294 * @see LanguageFi.php for example implementation
1296 function translateBlockExpiry( $str ) {
1298 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
1300 if ( $scBlockExpiryOptions == '-') {
1301 return $str;
1304 foreach (explode(',', $scBlockExpiryOptions) as $option) {
1305 if ( strpos($option, ":") === false )
1306 continue;
1307 list($show, $value) = explode(":", $option);
1308 if ( strcmp ( $str, $value) == 0 )
1309 return '<span title="' . htmlspecialchars($str). '">' .
1310 htmlspecialchars( trim( $show ) ) . '</span>';
1313 return $str;
1317 * languages like Chinese need to be segmented in order for the diff
1318 * to be of any use
1320 * @param string $text
1321 * @return string
1323 function segmentForDiff( $text ) {
1324 return $text;
1328 * and unsegment to show the result
1330 * @param string $text
1331 * @return string
1333 function unsegmentForDiff( $text ) {
1334 return $text;
1337 # convert text to different variants of a language.
1338 function convert( $text, $isTitle = false) {
1339 return $this->mConverter->convert($text, $isTitle);
1342 # Convert text from within Parser
1343 function parserConvert( $text, &$parser ) {
1344 return $this->mConverter->parserConvert( $text, $parser );
1347 # Tell the converter that it shouldn't convert titles
1348 function setNoTitleConvert(){
1349 $this->mConverter->setNotitleConvert();
1352 # Check if this is a language with variants
1353 function hasVariants(){
1354 return sizeof($this->getVariants())>1;
1359 * Perform output conversion on a string, and encode for safe HTML output.
1360 * @param string $text
1361 * @param bool $isTitle -- wtf?
1362 * @return string
1363 * @todo this should get integrated somewhere sane
1365 function convertHtml( $text, $isTitle = false ) {
1366 return htmlspecialchars( $this->convert( $text, $isTitle ) );
1369 function convertCategoryKey( $key ) {
1370 return $this->mConverter->convertCategoryKey( $key );
1374 * get the list of variants supported by this langauge
1375 * see sample implementation in LanguageZh.php
1377 * @return array an array of language codes
1379 function getVariants() {
1380 return $this->mConverter->getVariants();
1384 function getPreferredVariant( $fromUser = true ) {
1385 return $this->mConverter->getPreferredVariant( $fromUser );
1389 * if a language supports multiple variants, it is
1390 * possible that non-existing link in one variant
1391 * actually exists in another variant. this function
1392 * tries to find it. See e.g. LanguageZh.php
1394 * @param string $link the name of the link
1395 * @param mixed $nt the title object of the link
1396 * @return null the input parameters may be modified upon return
1398 function findVariantLink( &$link, &$nt ) {
1399 $this->mConverter->findVariantLink($link, $nt);
1403 * If a language supports multiple variants, converts text
1404 * into an array of all possible variants of the text:
1405 * 'variant' => text in that variant
1408 function convertLinkToAllVariants($text){
1409 return $this->mConverter->convertLinkToAllVariants($text);
1414 * returns language specific options used by User::getPageRenderHash()
1415 * for example, the preferred language variant
1417 * @return string
1418 * @public
1420 function getExtraHashOptions() {
1421 return $this->mConverter->getExtraHashOptions();
1425 * for languages that support multiple variants, the title of an
1426 * article may be displayed differently in different variants. this
1427 * function returns the apporiate title defined in the body of the article.
1429 * @return string
1431 function getParsedTitle() {
1432 return $this->mConverter->getParsedTitle();
1436 * Enclose a string with the "no conversion" tag. This is used by
1437 * various functions in the Parser
1439 * @param string $text text to be tagged for no conversion
1440 * @return string the tagged text
1442 function markNoConversion( $text, $noParse=false ) {
1443 return $this->mConverter->markNoConversion( $text, $noParse );
1447 * A regular expression to match legal word-trailing characters
1448 * which should be merged onto a link of the form [[foo]]bar.
1450 * @return string
1451 * @public
1453 function linkTrail() {
1454 $this->load();
1455 return $this->linkTrail;
1458 function getLangObj() {
1459 return $this;
1463 * Get the RFC 3066 code for this language object
1465 function getCode() {
1466 return $this->mCode;
1469 function setCode( $code ) {
1470 $this->mCode = $code;
1473 static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
1474 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
1477 static function getMessagesFileName( $code ) {
1478 global $IP;
1479 return self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
1482 static function getClassFileName( $code ) {
1483 global $IP;
1484 return self::getFileName( "$IP/languages/classes/Language", $code, '.php' );
1487 static function getLocalisationArray( $code, $disableCache = false ) {
1488 self::loadLocalisation( $code, $disableCache );
1489 return self::$mLocalisationCache[$code];
1493 * Load localisation data for a given code into the static cache
1495 * @return array Dependencies, map of filenames to mtimes
1497 static function loadLocalisation( $code, $disableCache = false ) {
1498 static $recursionGuard = array();
1499 global $wgMemc;
1501 if ( !$code ) {
1502 throw new MWException( "Invalid language code requested" );
1505 if ( !$disableCache ) {
1506 # Try the per-process cache
1507 if ( isset( self::$mLocalisationCache[$code] ) ) {
1508 return self::$mLocalisationCache[$code]['deps'];
1511 wfProfileIn( __METHOD__ );
1513 # Try the serialized directory
1514 $cache = wfGetPrecompiledData( self::getFileName( "Messages", $code, '.ser' ) );
1515 if ( $cache ) {
1516 self::$mLocalisationCache[$code] = $cache;
1517 wfDebug( "Got localisation for $code from precompiled data file\n" );
1518 wfProfileOut( __METHOD__ );
1519 return self::$mLocalisationCache[$code]['deps'];
1522 # Try the global cache
1523 $memcKey = wfMemcKey('localisation', $code );
1524 $cache = $wgMemc->get( $memcKey );
1525 if ( $cache ) {
1526 $expired = false;
1527 # Check file modification times
1528 foreach ( $cache['deps'] as $file => $mtime ) {
1529 if ( !file_exists( $file ) || filemtime( $file ) > $mtime ) {
1530 $expired = true;
1531 break;
1534 if ( self::isLocalisationOutOfDate( $cache ) ) {
1535 $wgMemc->delete( $memcKey );
1536 $cache = false;
1537 wfDebug( "Localisation cache for $code had expired due to update of $file\n" );
1538 } else {
1539 self::$mLocalisationCache[$code] = $cache;
1540 wfDebug( "Got localisation for $code from cache\n" );
1541 wfProfileOut( __METHOD__ );
1542 return $cache['deps'];
1545 } else {
1546 wfProfileIn( __METHOD__ );
1549 if ( $code != 'en' ) {
1550 $fallback = 'en';
1551 } else {
1552 $fallback = false;
1555 # Load the primary localisation from the source file
1556 $filename = self::getMessagesFileName( $code );
1557 if ( !file_exists( $filename ) ) {
1558 wfDebug( "No localisation file for $code, using implicit fallback to en\n" );
1559 $cache = array();
1560 $deps = array();
1561 } else {
1562 $deps = array( $filename => filemtime( $filename ) );
1563 require( $filename );
1564 $cache = compact( self::$mLocalisationKeys );
1565 wfDebug( "Got localisation for $code from source\n" );
1568 if ( !empty( $fallback ) ) {
1569 # Load the fallback localisation, with a circular reference guard
1570 if ( isset( $recursionGuard[$code] ) ) {
1571 throw new MWException( "Error: Circular fallback reference in language code $code" );
1573 $recursionGuard[$code] = true;
1574 $newDeps = self::loadLocalisation( $fallback, $disableCache );
1575 unset( $recursionGuard[$code] );
1577 $secondary = self::$mLocalisationCache[$fallback];
1578 $deps = array_merge( $deps, $newDeps );
1580 # Merge the fallback localisation with the current localisation
1581 foreach ( self::$mLocalisationKeys as $key ) {
1582 if ( isset( $cache[$key] ) ) {
1583 if ( isset( $secondary[$key] ) ) {
1584 if ( in_array( $key, self::$mMergeableMapKeys ) ) {
1585 $cache[$key] = $cache[$key] + $secondary[$key];
1586 } elseif ( in_array( $key, self::$mMergeableListKeys ) ) {
1587 $cache[$key] = array_merge( $secondary[$key], $cache[$key] );
1588 } elseif ( in_array( $key, self::$mMergeableAliasListKeys ) ) {
1589 $cache[$key] = array_merge_recursive( $cache[$key], $secondary[$key] );
1592 } else {
1593 $cache[$key] = $secondary[$key];
1597 # Merge bookstore lists if requested
1598 if ( !empty( $cache['bookstoreList']['inherit'] ) ) {
1599 $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] );
1601 if ( isset( $cache['bookstoreList']['inherit'] ) ) {
1602 unset( $cache['bookstoreList']['inherit'] );
1606 # Add dependencies to the cache entry
1607 $cache['deps'] = $deps;
1609 # Replace spaces with underscores in namespace names
1610 $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] );
1612 # Save to both caches
1613 self::$mLocalisationCache[$code] = $cache;
1614 if ( !$disableCache ) {
1615 $wgMemc->set( $memcKey, $cache );
1618 wfProfileOut( __METHOD__ );
1619 return $deps;
1623 * Test if a given localisation cache is out of date with respect to the
1624 * source Messages files. This is done automatically for the global cache
1625 * in $wgMemc, but is only done on certain occasions for the serialized
1626 * data file.
1628 * @param $cache mixed Either a language code or a cache array
1630 static function isLocalisationOutOfDate( $cache ) {
1631 if ( !is_array( $cache ) ) {
1632 self::loadLocalisation( $cache );
1633 $cache = self::$mLocalisationCache[$cache];
1635 $expired = false;
1636 foreach ( $cache['deps'] as $file => $mtime ) {
1637 if ( !file_exists( $file ) || filemtime( $file ) > $mtime ) {
1638 $expired = true;
1639 break;
1642 return $expired;
1646 * Get the fallback for a given language
1648 static function getFallbackFor( $code ) {
1649 self::loadLocalisation( $code );
1650 return self::$mLocalisationCache[$code]['fallback'];
1653 /**
1654 * Get all messages for a given language
1656 static function getMessagesFor( $code ) {
1657 self::loadLocalisation( $code );
1658 return self::$mLocalisationCache[$code]['messages'];
1661 /**
1662 * Get a message for a given language
1664 static function getMessageFor( $key, $code ) {
1665 self::loadLocalisation( $code );
1666 return @self::$mLocalisationCache[$code]['messages'][$key];
1670 * Load localisation data for this object
1672 function load() {
1673 if ( !$this->mLoaded ) {
1674 self::loadLocalisation( $this->getCode() );
1675 $cache =& self::$mLocalisationCache[$this->getCode()];
1676 foreach ( self::$mLocalisationKeys as $key ) {
1677 $this->$key = $cache[$key];
1679 $this->mLoaded = true;
1681 $this->fixUpSettings();
1686 * Do any necessary post-cache-load settings adjustment
1688 function fixUpSettings() {
1689 global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk, $wgMessageCache,
1690 $wgNamespaceAliases, $wgAmericanDates;
1691 wfProfileIn( __METHOD__ );
1692 if ( $wgExtraNamespaces ) {
1693 $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames;
1696 $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
1697 if ( $wgMetaNamespaceTalk ) {
1698 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
1699 } else {
1700 $talk = $this->namespaceNames[NS_PROJECT_TALK];
1701 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
1703 # Allow grammar transformations
1704 # Allowing full message-style parsing would make simple requests
1705 # such as action=raw much more expensive than they need to be.
1706 # This will hopefully cover most cases.
1707 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
1708 array( &$this, 'replaceGrammarInNamespace' ), $talk );
1709 $talk = str_replace( ' ', '_', $talk );
1710 $this->namespaceNames[NS_PROJECT_TALK] = $talk;
1713 # The above mixing may leave namespaces out of canonical order.
1714 # Re-order by namespace ID number...
1715 ksort( $this->namespaceNames );
1717 # Put namespace names and aliases into a hashtable.
1718 # If this is too slow, then we should arrange it so that it is done
1719 # before caching. The catch is that at pre-cache time, the above
1720 # class-specific fixup hasn't been done.
1721 $this->mNamespaceIds = array();
1722 foreach ( $this->namespaceNames as $index => $name ) {
1723 $this->mNamespaceIds[$this->lc($name)] = $index;
1725 if ( $this->namespaceAliases ) {
1726 foreach ( $this->namespaceAliases as $name => $index ) {
1727 $this->mNamespaceIds[$this->lc($name)] = $index;
1730 if ( $wgNamespaceAliases ) {
1731 foreach ( $wgNamespaceAliases as $name => $index ) {
1732 $this->mNamespaceIds[$this->lc($name)] = $index;
1736 if ( $this->defaultDateFormat == 'dmy or mdy' ) {
1737 $this->defaultDateFormat = $wgAmericanDates ? 'mdy' : 'dmy';
1739 wfProfileOut( __METHOD__ );
1742 function replaceGrammarInNamespace( $m ) {
1743 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
1746 static function getCaseMaps() {
1747 static $wikiUpperChars, $wikiLowerChars;
1748 if ( isset( $wikiUpperChars ) ) {
1749 return array( $wikiUpperChars, $wikiLowerChars );
1752 wfProfileIn( __METHOD__ );
1753 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
1754 if ( $arr === false ) {
1755 throw new MWException(
1756 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
1758 extract( $arr );
1759 wfProfileOut( __METHOD__ );
1760 return array( $wikiUpperChars, $wikiLowerChars );