6 if( !defined( 'MEDIAWIKI' ) ) {
7 echo "This file is part of MediaWiki, it is not a valid entry point.\n";
12 global $wgLanguageNames;
13 require_once( dirname(__FILE__
) . '/Names.php' ) ;
15 global $wgInputEncoding, $wgOutputEncoding;
18 * These are always UTF-8, they exist only for backwards compatibility
20 $wgInputEncoding = "UTF-8";
21 $wgOutputEncoding = "UTF-8";
23 if( function_exists( 'mb_strtoupper' ) ) {
24 mb_internal_encoding('UTF-8');
27 /* a fake language converter */
30 function FakeConverter($langobj) {$this->mLang
= $langobj;}
31 function convert($t, $i) {return $t;}
32 function parserConvert($t, $p) {return $t;}
33 function getVariants() { return array( $this->mLang
->getCode() ); }
34 function getPreferredVariant() {return $this->mLang
->getCode(); }
35 function findVariantLink(&$l, &$n) {}
36 function getExtraHashOptions() {return '';}
37 function getParsedTitle() {return '';}
38 function markNoConversion($text, $noParse=false) {return $text;}
39 function convertCategoryKey( $key ) {return $key; }
40 function convertLinkToAllVariants($text){ return array( $this->mLang
->getCode() => $text); }
41 function armourMath($text){ return $text; }
44 #--------------------------------------------------------------------------
45 # Internationalisation code
46 #--------------------------------------------------------------------------
49 var $mConverter, $mVariants, $mCode, $mLoaded = false;
50 var $mMagicExtensions = array(), $mMagicHookDone = false;
52 static public $mLocalisationKeys = array( 'fallback', 'namespaceNames',
53 'skinNames', 'mathNames',
54 'bookstoreList', 'magicWords', 'messages', 'rtl', 'digitTransformTable',
55 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
56 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases',
57 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
58 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases' );
60 static public $mMergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames',
61 'dateFormats', 'defaultUserOptionOverrides', 'magicWords' );
63 static public $mMergeableListKeys = array( 'extraUserToggles' );
65 static public $mMergeableAliasListKeys = array( 'specialPageAliases' );
67 static public $mLocalisationCache = array();
69 static public $mWeekdayMsgs = array(
70 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
74 static public $mWeekdayAbbrevMsgs = array(
75 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
78 static public $mMonthMsgs = array(
79 'january', 'february', 'march', 'april', 'may_long', 'june',
80 'july', 'august', 'september', 'october', 'november',
83 static public $mMonthGenMsgs = array(
84 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
85 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
88 static public $mMonthAbbrevMsgs = array(
89 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
90 'sep', 'oct', 'nov', 'dec'
93 static public $mIranianCalendarMonthMsgs = array(
94 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
95 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
96 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
97 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
101 * Create a language object for a given language code
103 static function factory( $code ) {
105 static $recursionLevel = 0;
107 if ( $code == 'en' ) {
110 $class = 'Language' . str_replace( '-', '_', ucfirst( $code ) );
111 // Preload base classes to work around APC/PHP5 bug
112 if ( file_exists( "$IP/languages/classes/$class.deps.php" ) ) {
113 include_once("$IP/languages/classes/$class.deps.php");
115 if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
116 include_once("$IP/languages/classes/$class.php");
120 if ( $recursionLevel > 5 ) {
121 throw new MWException( "Language fallback loop detected when creating class $class\n" );
124 if( ! class_exists( $class ) ) {
125 $fallback = Language
::getFallbackFor( $code );
127 $lang = Language
::factory( $fallback );
129 $lang->setCode( $code );
137 function __construct() {
138 $this->mConverter
= new FakeConverter($this);
139 // Set the code to the name of the descendant
140 if ( get_class( $this ) == 'Language' ) {
143 $this->mCode
= str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
148 * Hook which will be called if this is the content language.
149 * Descendants can use this to register hook functions or modify globals
151 function initContLang() {}
157 function getDefaultUserOptions() {
158 return User
::getDefaultOptions();
161 function getFallbackLanguageCode() {
163 return $this->fallback
;
167 * Exports $wgBookstoreListEn
170 function getBookstoreList() {
172 return $this->bookstoreList
;
178 function getNamespaces() {
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.
191 function getFormattedNamespaces() {
192 $ns = $this->getNamespaces();
193 foreach($ns as $k => $v) {
194 $ns[$k] = strtr($v, '_', ' ');
200 * Get a namespace value by key
202 * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
203 * echo $mw_ns; // prints 'MediaWiki'
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
221 function getFormattedNsText( $index ) {
222 $ns = $this->getNsText( $index );
223 return strtr($ns, '_', ' ');
227 * Get a namespace key by value, case insensitive.
228 * Only matches namespace names for the current language, not the
229 * canonical ones defined in Namespace.php.
231 * @param string $text
232 * @return mixed An integer if $text is a valid value otherwise false
234 function getLocalNsIndex( $text ) {
236 $lctext = $this->lc($text);
237 return isset( $this->mNamespaceIds
[$lctext] ) ?
$this->mNamespaceIds
[$lctext] : false;
241 * Get a namespace key by value, case insensitive. Canonical namespace
242 * names override custom ones defined for the current language.
244 * @param string $text
245 * @return mixed An integer if $text is a valid value otherwise false
247 function getNsIndex( $text ) {
249 $lctext = $this->lc($text);
250 if( ( $ns = Namespace::getCanonicalIndex( $lctext ) ) !== null ) return $ns;
251 return isset( $this->mNamespaceIds
[$lctext] ) ?
$this->mNamespaceIds
[$lctext] : false;
255 * short names for language variants used for language conversion links.
257 * @param string $code
260 function getVariantname( $code ) {
261 return $this->getMessageFromDB( "variantname-$code" );
264 function specialPage( $name ) {
265 $aliases = $this->getSpecialPageAliases();
266 if ( isset( $aliases[$name][0] ) ) {
267 $name = $aliases[$name][0];
269 return $this->getNsText(NS_SPECIAL
) . ':' . $name;
272 function getQuickbarSettings() {
274 $this->getMessage( 'qbsettings-none' ),
275 $this->getMessage( 'qbsettings-fixedleft' ),
276 $this->getMessage( 'qbsettings-fixedright' ),
277 $this->getMessage( 'qbsettings-floatingleft' ),
278 $this->getMessage( 'qbsettings-floatingright' )
282 function getSkinNames() {
284 return $this->skinNames
;
287 function getMathNames() {
289 return $this->mathNames
;
292 function getDatePreferences() {
294 return $this->datePreferences
;
297 function getDateFormats() {
299 return $this->dateFormats
;
302 function getDefaultDateFormat() {
304 return $this->defaultDateFormat
;
307 function getDatePreferenceMigrationMap() {
309 return $this->datePreferenceMigrationMap
;
312 function getDefaultUserOptionOverrides() {
314 # XXX - apparently some languageas get empty arrays, didn't get to it yet -- midom
315 if (is_array($this->defaultUserOptionOverrides
)) {
316 return $this->defaultUserOptionOverrides
;
322 function getExtraUserToggles() {
324 return $this->extraUserToggles
;
327 function getUserToggle( $tog ) {
328 return $this->getMessageFromDB( "tog-$tog" );
332 * Get language names, indexed by code.
333 * If $customisedOnly is true, only returns codes with a messages file
335 public static function getLanguageNames( $customisedOnly = false ) {
336 global $wgLanguageNames;
337 if ( !$customisedOnly ) {
338 return $wgLanguageNames;
343 $dir = opendir( "$IP/languages/messages" );
344 while( false !== ( $file = readdir( $dir ) ) ) {
346 if( preg_match( '/Messages([A-Z][a-z_]+)\.php$/', $file, $m ) ) {
347 $code = str_replace( '_', '-', strtolower( $m[1] ) );
348 if ( isset( $wgLanguageNames[$code] ) ) {
349 $names[$code] = $wgLanguageNames[$code];
358 * Ugly hack to get a message maybe from the MediaWiki namespace, if this
359 * language object is the content or user language.
361 function getMessageFromDB( $msg ) {
362 global $wgContLang, $wgLang;
363 if ( $wgContLang->getCode() == $this->getCode() ) {
365 return wfMsgForContent( $msg );
366 } elseif ( $wgLang->getCode() == $this->getCode() ) {
368 return wfMsg( $msg );
370 # Neither, get from localisation
371 return $this->getMessage( $msg );
375 function getLanguageName( $code ) {
376 global $wgLanguageNames;
377 if ( ! array_key_exists( $code, $wgLanguageNames ) ) {
380 return $wgLanguageNames[$code];
383 function getMonthName( $key ) {
384 return $this->getMessageFromDB( self
::$mMonthMsgs[$key-1] );
387 function getMonthNameGen( $key ) {
388 return $this->getMessageFromDB( self
::$mMonthGenMsgs[$key-1] );
391 function getMonthAbbreviation( $key ) {
392 return $this->getMessageFromDB( self
::$mMonthAbbrevMsgs[$key-1] );
395 function getWeekdayName( $key ) {
396 return $this->getMessageFromDB( self
::$mWeekdayMsgs[$key-1] );
399 function getWeekdayAbbreviation( $key ) {
400 return $this->getMessageFromDB( self
::$mWeekdayAbbrevMsgs[$key-1] );
403 function getIranianCalendarMonthName( $key ) {
404 return $this->getMessageFromDB( self
::$mIranianCalendarMonthMsgs[$key-1] );
409 * Used by date() and time() to adjust the time output.
411 * @param int $ts the time in date('YmdHis') format
412 * @param mixed $tz adjust the time by this amount (default false,
413 * mean we get user timecorrection setting)
416 function userAdjust( $ts, $tz = false ) {
417 global $wgUser, $wgLocalTZoffset;
420 $tz = $wgUser->getOption( 'timecorrection' );
423 # minutes and hours differences:
428 # Global offset in minutes.
429 if( isset($wgLocalTZoffset) ) {
430 if( $wgLocalTZoffset >= 0 ) {
431 $hrDiff = floor($wgLocalTZoffset / 60);
433 $hrDiff = ceil($wgLocalTZoffset / 60);
435 $minDiff = $wgLocalTZoffset %
60;
437 } elseif ( strpos( $tz, ':' ) !== false ) {
438 $tzArray = explode( ':', $tz );
439 $hrDiff = intval($tzArray[0]);
440 $minDiff = intval($hrDiff < 0 ?
-$tzArray[1] : $tzArray[1]);
442 $hrDiff = intval( $tz );
445 # No difference ? Return time unchanged
446 if ( 0 == $hrDiff && 0 == $minDiff ) { return $ts; }
448 wfSuppressWarnings(); // E_STRICT system time bitching
449 # Generate an adjusted date
451 (int)substr( $ts, 8, 2) ) +
$hrDiff, # Hours
452 (int)substr( $ts, 10, 2 ) +
$minDiff, # Minutes
453 (int)substr( $ts, 12, 2 ), # Seconds
454 (int)substr( $ts, 4, 2 ), # Month
455 (int)substr( $ts, 6, 2 ), # Day
456 (int)substr( $ts, 0, 4 ) ); #Year
458 $date = date( 'YmdHis', $t );
465 * This is a workalike of PHP's date() function, but with better
466 * internationalisation, a reduced set of format characters, and a better
469 * Supported format characters are dDjlNwzWFmMntLYyaAgGhHiscrU. See the
470 * PHP manual for definitions. There are a number of extensions, which
473 * xn Do not translate digits of the next numeric format character
474 * xN Toggle raw digit (xn) flag, stays set until explicitly unset
475 * xr Use roman numerals for the next numeric format character
477 * xg Genitive month name
479 * xij j (day number) in Iranian calendar
480 * xiF F (month name) in Iranian calendar
481 * xin n (month number) in Iranian calendar
482 * xiY Y (full year) in Iranian calendar
484 * Characters enclosed in double quotes will be considered literal (with
485 * the quotes themselves removed). Unmatched quotes will be considered
486 * literal quotes. Example:
488 * "The month is" F => The month is January
491 * Backslash escaping is also supported.
493 * Input timestamp is assumed to be pre-normalized to the desired local
496 * @param string $format
497 * @param string $ts 14-character timestamp
501 function sprintfDate( $format, $ts ) {
508 for ( $p = 0; $p < strlen( $format ); $p++
) {
511 if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
512 $code .= $format[++
$p];
515 if ( $code === 'xi' && $p < strlen( $format ) - 1 ) {
516 $code .= $format[++
$p];
527 $rawToggle = !$rawToggle;
533 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
536 $num = substr( $ts, 6, 2 );
539 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
540 $s .= $this->getWeekdayAbbreviation( gmdate( 'w', $unix ) +
1 );
543 $num = intval( substr( $ts, 6, 2 ) );
546 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
550 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
551 $s .= $this->getWeekdayName( gmdate( 'w', $unix ) +
1 );
554 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
555 $w = gmdate( 'w', $unix );
559 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
560 $num = gmdate( 'w', $unix );
563 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
564 $num = gmdate( 'z', $unix );
567 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
568 $num = gmdate( 'W', $unix );
571 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
574 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
575 $s .= $this->getIranianCalendarMonthName( $iranian[1] );
578 $num = substr( $ts, 4, 2 );
581 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
584 $num = intval( substr( $ts, 4, 2 ) );
587 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
591 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
592 $num = gmdate( 't', $unix );
595 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
596 $num = gmdate( 'L', $unix );
599 $num = substr( $ts, 0, 4 );
602 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
606 $num = substr( $ts, 2, 2 );
609 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'am' : 'pm';
612 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'AM' : 'PM';
615 $h = substr( $ts, 8, 2 );
616 $num = $h %
12 ?
$h %
12 : 12;
619 $num = intval( substr( $ts, 8, 2 ) );
622 $h = substr( $ts, 8, 2 );
623 $num = sprintf( '%02d', $h %
12 ?
$h %
12 : 12 );
626 $num = substr( $ts, 8, 2 );
629 $num = substr( $ts, 10, 2 );
632 $num = substr( $ts, 12, 2 );
635 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
636 $s .= gmdate( 'c', $unix );
639 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
640 $s .= gmdate( 'r', $unix );
643 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
648 if ( $p < strlen( $format ) - 1 ) {
656 if ( $p < strlen( $format ) - 1 ) {
657 $endQuote = strpos( $format, '"', $p +
1 );
658 if ( $endQuote === false ) {
659 # No terminating quote, assume literal "
662 $s .= substr( $format, $p +
1, $endQuote - $p - 1 );
666 # Quote at end of string, assume literal "
673 if ( $num !== false ) {
674 if ( $rawToggle ||
$raw ) {
677 } elseif ( $roman ) {
678 $s .= self
::romanNumeral( $num );
681 $s .= $this->formatNum( $num, true );
689 private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
690 private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 );
692 * Algorithm by Roozbeh Pournader and Mohammad Toossi to convert
693 * Gregorian dates to Iranian dates. Originally written in C, it
694 * is released under the terms of GNU Lesser General Public
695 * License. Conversion to PHP was performed by Niklas Laxström.
697 * Link: http://www.farsiweb.info/jalali/jalali.c
699 private static function tsToIranian( $ts ) {
700 $gy = substr( $ts, 0, 4 ) -1600;
701 $gm = substr( $ts, 4, 2 ) -1;
702 $gd = substr( $ts, 6, 2 ) -1;
704 # Days passed from the beginning (including leap years)
707 - floor(($gy+
99) / 100)
708 +
floor(($gy+
399) / 400);
711 // Add days of the past months of this year
712 for( $i = 0; $i < $gm; $i++
) {
713 $gDayNo +
= self
::$GREG_DAYS[$i];
717 if ( $gm > 1 && (($gy%4
===0 && $gy%100
!==0 ||
($gy%400
==0)))) {
721 // Days passed in current month
724 $jDayNo = $gDayNo - 79;
726 $jNp = floor($jDayNo / 12053);
729 $jy = 979 +
33*$jNp +
4*floor($jDayNo/1461);
732 if ( $jDayNo >= 366 ) {
733 $jy +
= floor(($jDayNo-1)/365);
734 $jDayNo = floor(($jDayNo-1)%365
);
737 for ( $i = 0; $i < 11 && $jDayNo >= self
::$IRANIAN_DAYS[$i]; $i++
) {
738 $jDayNo -= self
::$IRANIAN_DAYS[$i];
744 return array($jy, $jm, $jd);
748 * Roman number formatting up to 3000
750 static function romanNumeral( $num ) {
751 static $table = array(
752 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
753 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
754 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
755 array( '', 'M', 'MM', 'MMM' )
758 $num = intval( $num );
759 if ( $num > 3000 ||
$num <= 0 ) {
764 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
765 if ( $num >= $pow10 ) {
766 $s .= $table[$i][floor($num / $pow10)];
768 $num = $num %
$pow10;
774 * This is meant to be used by time(), date(), and timeanddate() to get
775 * the date preference they're supposed to use, it should be used in
779 * function timeanddate([...], $format = true) {
780 * $datePreference = $this->dateFormat($format);
785 * @param mixed $usePrefs: if true, the user's preference is used
786 * if false, the site/language default is used
787 * if int/string, assumed to be a format.
790 function dateFormat( $usePrefs = true ) {
793 if( is_bool( $usePrefs ) ) {
795 $datePreference = $wgUser->getDatePreference();
797 $options = User
::getDefaultOptions();
798 $datePreference = (string)$options['date'];
801 $datePreference = (string)$usePrefs;
805 if( $datePreference == '' ) {
809 return $datePreference;
814 * @param mixed $ts the time format which needs to be turned into a
815 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
816 * @param bool $adj whether to adjust the time output according to the
817 * user configured offset ($timecorrection)
818 * @param mixed $format true to use user's date format preference
819 * @param string $timecorrection the time offset as returned by
820 * validateTimeZone() in Special:Preferences
823 function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
826 $ts = $this->userAdjust( $ts, $timecorrection );
829 $pref = $this->dateFormat( $format );
830 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref date"] ) ) {
831 $pref = $this->defaultDateFormat
;
833 return $this->sprintfDate( $this->dateFormats
["$pref date"], $ts );
838 * @param mixed $ts the time format which needs to be turned into a
839 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
840 * @param bool $adj whether to adjust the time output according to the
841 * user configured offset ($timecorrection)
842 * @param mixed $format true to use user's date format preference
843 * @param string $timecorrection the time offset as returned by
844 * validateTimeZone() in Special:Preferences
847 function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
850 $ts = $this->userAdjust( $ts, $timecorrection );
853 $pref = $this->dateFormat( $format );
854 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref time"] ) ) {
855 $pref = $this->defaultDateFormat
;
857 return $this->sprintfDate( $this->dateFormats
["$pref time"], $ts );
862 * @param mixed $ts the time format which needs to be turned into a
863 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
864 * @param bool $adj whether to adjust the time output according to the
865 * user configured offset ($timecorrection)
867 * @param mixed $format what format to return, if it's false output the
868 * default one (default true)
869 * @param string $timecorrection the time offset as returned by
870 * validateTimeZone() in Special:Preferences
873 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) {
876 $ts = wfTimestamp( TS_MW
, $ts );
879 $ts = $this->userAdjust( $ts, $timecorrection );
882 $pref = $this->dateFormat( $format );
883 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref both"] ) ) {
884 $pref = $this->defaultDateFormat
;
887 return $this->sprintfDate( $this->dateFormats
["$pref both"], $ts );
890 function getMessage( $key ) {
892 return isset( $this->messages
[$key] ) ?
$this->messages
[$key] : null;
895 function getAllMessages() {
897 return $this->messages
;
900 function iconv( $in, $out, $string ) {
901 # For most languages, this is a wrapper for iconv
902 return iconv( $in, $out . '//IGNORE', $string );
905 // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
906 function ucwordbreaksCallbackAscii($matches){
907 return $this->ucfirst($matches[1]);
910 function ucwordbreaksCallbackMB($matches){
911 return mb_strtoupper($matches[0]);
914 function ucCallback($matches){
915 list( $wikiUpperChars ) = self
::getCaseMaps();
916 return strtr( $matches[1], $wikiUpperChars );
919 function lcCallback($matches){
920 list( , $wikiLowerChars ) = self
::getCaseMaps();
921 return strtr( $matches[1], $wikiLowerChars );
924 function ucwordsCallbackMB($matches){
925 return mb_strtoupper($matches[0]);
928 function ucwordsCallbackWiki($matches){
929 list( $wikiUpperChars ) = self
::getCaseMaps();
930 return strtr( $matches[0], $wikiUpperChars );
933 function ucfirst( $str ) {
934 if ( ord($str[0]) < 128 ) return ucfirst($str);
935 else return self
::uc($str,true); // fall back to more complex logic in case of multibyte strings
938 function uc( $str, $first = false ) {
939 if ( function_exists( 'mb_strtoupper' ) ) {
941 if ( self
::isMultibyte( $str ) ) {
942 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
944 return ucfirst( $str );
947 return self
::isMultibyte( $str ) ?
mb_strtoupper( $str ) : strtoupper( $str );
950 if ( self
::isMultibyte( $str ) ) {
951 list( $wikiUpperChars ) = $this->getCaseMaps();
952 $x = $first ?
'^' : '';
953 return preg_replace_callback(
954 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
955 array($this,"ucCallback"),
959 return $first ?
ucfirst( $str ) : strtoupper( $str );
964 function lcfirst( $str ) {
965 if ( ord($str[0]) < 128 ) {
966 // editing string in place = cool
967 $str[0]=strtolower($str[0]);
970 else return self
::lc( $str, true );
973 function lc( $str, $first = false ) {
974 if ( function_exists( 'mb_strtolower' ) )
976 if ( self
::isMultibyte( $str ) )
977 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
979 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
981 return self
::isMultibyte( $str ) ?
mb_strtolower( $str ) : strtolower( $str );
983 if ( self
::isMultibyte( $str ) ) {
984 list( , $wikiLowerChars ) = self
::getCaseMaps();
985 $x = $first ?
'^' : '';
986 return preg_replace_callback(
987 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
988 array($this,"lcCallback"),
992 return $first ?
strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
995 function isMultibyte( $str ) {
996 return (bool)preg_match( '/[\x80-\xff]/', $str );
999 function ucwords($str) {
1000 if ( self
::isMultibyte( $str ) ) {
1001 $str = self
::lc($str);
1003 // regexp to find first letter in each word (i.e. after each space)
1004 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1006 // function to use to capitalize a single char
1007 if ( function_exists( 'mb_strtoupper' ) )
1008 return preg_replace_callback(
1010 array($this,"ucwordsCallbackMB"),
1014 return preg_replace_callback(
1016 array($this,"ucwordsCallbackWiki"),
1021 return ucwords( strtolower( $str ) );
1024 # capitalize words at word breaks
1025 function ucwordbreaks($str){
1026 if (self
::isMultibyte( $str ) ) {
1027 $str = self
::lc($str);
1029 // since \b doesn't work for UTF-8, we explicitely define word break chars
1030 $breaks= "[ \-\(\)\}\{\.,\?!]";
1032 // find first letter after word break
1033 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1035 if ( function_exists( 'mb_strtoupper' ) )
1036 return preg_replace_callback(
1038 array($this,"ucwordbreaksCallbackMB"),
1042 return preg_replace_callback(
1044 array($this,"ucwordsCallbackWiki"),
1049 return preg_replace_callback(
1050 '/\b([\w\x80-\xff]+)\b/',
1051 array($this,"ucwordbreaksCallbackAscii"),
1056 * Return a case-folded representation of $s
1058 * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
1059 * and $s2 are the same except for the case of their characters. It is not
1060 * necessary for the value returned to make sense when displayed.
1062 * Do *not* perform any other normalisation in this function. If a caller
1063 * uses this function when it should be using a more general normalisation
1064 * function, then fix the caller.
1066 function caseFold( $s ) {
1067 return $this->uc( $s );
1070 function checkTitleEncoding( $s ) {
1071 if( is_array( $s ) ) {
1072 wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
1074 # Check for non-UTF-8 URLs
1075 $ishigh = preg_match( '/[\x80-\xff]/', $s);
1076 if(!$ishigh) return $s;
1078 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1079 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
1080 if( $isutf8 ) return $s;
1082 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
1085 function fallback8bitEncoding() {
1087 return $this->fallback8bitEncoding
;
1091 * Some languages have special punctuation to strip out
1092 * or characters which need to be converted for MySQL's
1093 * indexing to grok it correctly. Make such changes here.
1098 function stripForSearch( $string ) {
1100 if ( $wgDBtype != 'mysql' ) {
1104 # MySQL fulltext index doesn't grok utf-8, so we
1105 # need to fold cases and convert to hex
1107 wfProfileIn( __METHOD__
);
1108 if( function_exists( 'mb_strtolower' ) ) {
1109 $out = preg_replace(
1110 "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
1111 "'U8' . bin2hex( \"$1\" )",
1112 mb_strtolower( $string ) );
1114 list( , $wikiLowerChars ) = self
::getCaseMaps();
1115 $out = preg_replace(
1116 "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
1117 "'U8' . bin2hex( strtr( \"\$1\", \$wikiLowerChars ) )",
1120 wfProfileOut( __METHOD__
);
1124 function convertForSearchResult( $termsArray ) {
1125 # some languages, e.g. Chinese, need to do a conversion
1126 # in order for search results to be displayed correctly
1131 * Get the first character of a string.
1136 function firstChar( $s ) {
1138 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1139 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
1141 return isset( $matches[1] ) ?
$matches[1] : "";
1144 function initEncoding() {
1145 # Some languages may have an alternate char encoding option
1146 # (Esperanto X-coding, Japanese furigana conversion, etc)
1147 # If this language is used as the primary content language,
1148 # an override to the defaults can be set here on startup.
1151 function recodeForEdit( $s ) {
1152 # For some languages we'll want to explicitly specify
1153 # which characters make it into the edit box raw
1154 # or are converted in some way or another.
1155 # Note that if wgOutputEncoding is different from
1156 # wgInputEncoding, this text will be further converted
1157 # to wgOutputEncoding.
1158 global $wgEditEncoding;
1159 if( $wgEditEncoding == '' or
1160 $wgEditEncoding == 'UTF-8' ) {
1163 return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1167 function recodeInput( $s ) {
1168 # Take the previous into account.
1169 global $wgEditEncoding;
1170 if($wgEditEncoding != "") {
1171 $enc = $wgEditEncoding;
1175 if( $enc == 'UTF-8' ) {
1178 return $this->iconv( $enc, 'UTF-8', $s );
1183 * For right-to-left language support
1193 * A hidden direction mark (LRM or RLM), depending on the language direction
1197 function getDirMark() {
1198 return $this->isRTL() ?
"\xE2\x80\x8F" : "\xE2\x80\x8E";
1202 * An arrow, depending on the language direction
1206 function getArrow() {
1207 return $this->isRTL() ?
'←' : '→';
1211 * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1215 function linkPrefixExtension() {
1217 return $this->linkPrefixExtension
;
1220 function &getMagicWords() {
1222 return $this->magicWords
;
1225 # Fill a MagicWord object with data from here
1226 function getMagic( &$mw ) {
1227 if ( !$this->mMagicHookDone
) {
1228 $this->mMagicHookDone
= true;
1229 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions
, $this->getCode() ) );
1231 if ( isset( $this->mMagicExtensions
[$mw->mId
] ) ) {
1232 $rawEntry = $this->mMagicExtensions
[$mw->mId
];
1234 $magicWords =& $this->getMagicWords();
1235 if ( isset( $magicWords[$mw->mId
] ) ) {
1236 $rawEntry = $magicWords[$mw->mId
];
1238 # Fall back to English if local list is incomplete
1239 $magicWords =& Language
::getMagicWords();
1240 $rawEntry = $magicWords[$mw->mId
];
1244 if( !is_array( $rawEntry ) ) {
1245 error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1247 $mw->mCaseSensitive
= $rawEntry[0];
1248 $mw->mSynonyms
= array_slice( $rawEntry, 1 );
1252 * Add magic words to the extension array
1254 function addMagicWordsByLang( $newWords ) {
1255 $code = $this->getCode();
1256 $fallbackChain = array();
1257 while ( $code && !in_array( $code, $fallbackChain ) ) {
1258 $fallbackChain[] = $code;
1259 $code = self
::getFallbackFor( $code );
1261 if ( !in_array( 'en', $fallbackChain ) ) {
1262 $fallbackChain[] = 'en';
1264 $fallbackChain = array_reverse( $fallbackChain );
1265 foreach ( $fallbackChain as $code ) {
1266 if ( isset( $newWords[$code] ) ) {
1267 $this->mMagicExtensions
= $newWords[$code] +
$this->mMagicExtensions
;
1273 * Get special page names, as an associative array
1274 * case folded alias => real name
1276 function getSpecialPageAliases() {
1278 if ( !isset( $this->mExtendedSpecialPageAliases
) ) {
1279 $this->mExtendedSpecialPageAliases
= $this->specialPageAliases
;
1280 wfRunHooks( 'LanguageGetSpecialPageAliases',
1281 array( &$this->mExtendedSpecialPageAliases
, $this->getCode() ) );
1283 return $this->mExtendedSpecialPageAliases
;
1287 * Italic is unsuitable for some languages
1291 * @param string $text The text to be emphasized.
1294 function emphasize( $text ) {
1295 return "<em>$text</em>";
1299 * Normally we output all numbers in plain en_US style, that is
1300 * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
1301 * point twohundredthirtyfive. However this is not sutable for all
1302 * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
1303 * Icelandic just want to use commas instead of dots, and dots instead
1304 * of commas like "293.291,235".
1306 * An example of this function being called:
1308 * wfMsg( 'message', $wgLang->formatNum( $num ) )
1311 * See LanguageGu.php for the Gujarati implementation and
1312 * LanguageIs.php for the , => . and . => , implementation.
1314 * @todo check if it's viable to use localeconv() for the decimal
1317 * @param mixed $number the string to be formatted, should be an integer or
1318 * a floating point number.
1319 * @param bool $nocommafy Set to true for special numbers like dates
1322 function formatNum( $number, $nocommafy = false ) {
1323 global $wgTranslateNumerals;
1325 $number = $this->commafy($number);
1326 $s = $this->separatorTransformTable();
1327 if (!is_null($s)) { $number = strtr($number, $s); }
1330 if ($wgTranslateNumerals) {
1331 $s = $this->digitTransformTable();
1332 if (!is_null($s)) { $number = strtr($number, $s); }
1338 function parseFormattedNumber( $number ) {
1339 $s = $this->digitTransformTable();
1340 if (!is_null($s)) { $number = strtr($number, array_flip($s)); }
1342 $s = $this->separatorTransformTable();
1343 if (!is_null($s)) { $number = strtr($number, array_flip($s)); }
1345 $number = strtr( $number, array (',' => '') );
1350 * Adds commas to a given number
1355 function commafy($_) {
1356 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
1359 function digitTransformTable() {
1361 return $this->digitTransformTable
;
1364 function separatorTransformTable() {
1366 return $this->separatorTransformTable
;
1371 * For the credit list in includes/Credits.php (action=credits)
1376 function listToText( $l ) {
1379 for ($i = $m; $i >= 0; $i--) {
1382 } else if ($i == $m - 1) {
1383 $s = $l[$i] . ' ' . $this->getMessageFromDB( 'and' ) . ' ' . $s;
1385 $s = $l[$i] . ', ' . $s;
1392 * Truncate a string to a specified length in bytes, appending an optional
1393 * string (e.g. for ellipses)
1395 * The database offers limited byte lengths for some columns in the database;
1396 * multi-byte character sets mean we need to ensure that only whole characters
1397 * are included, otherwise broken characters can be passed to the user
1399 * If $length is negative, the string will be truncated from the beginning
1401 * @param string $string String to truncate
1402 * @param int $length Maximum length (excluding ellipses)
1403 * @param string $ellipses String to append to the truncated text
1406 function truncate( $string, $length, $ellipsis = "" ) {
1407 if( $length == 0 ) {
1410 if ( strlen( $string ) <= abs( $length ) ) {
1414 $string = substr( $string, 0, $length );
1415 $char = ord( $string[strlen( $string ) - 1] );
1417 if ($char >= 0xc0) {
1418 # We got the first byte only of a multibyte char; remove it.
1419 $string = substr( $string, 0, -1 );
1420 } elseif( $char >= 0x80 &&
1421 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
1422 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
1423 # We chopped in the middle of a character; remove it
1426 return $string . $ellipsis;
1428 $string = substr( $string, $length );
1429 $char = ord( $string[0] );
1430 if( $char >= 0x80 && $char < 0xc0 ) {
1431 # We chopped in the middle of a character; remove the whole thing
1432 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
1434 return $ellipsis . $string;
1439 * Grammatical transformations, needed for inflected languages
1440 * Invoked by putting {{grammar:case|word}} in a message
1442 * @param string $word
1443 * @param string $case
1446 function convertGrammar( $word, $case ) {
1447 global $wgGrammarForms;
1448 if ( isset($wgGrammarForms['en'][$case][$word]) ) {
1449 return $wgGrammarForms['en'][$case][$word];
1455 * Plural form transformations, needed for some languages.
1456 * For example, there are 3 form of plural in Russian and Polish,
1457 * depending on "count mod 10". See [[w:Plural]]
1458 * For English it is pretty simple.
1460 * Invoked by putting {{plural:count|wordform1|wordform2}}
1461 * or {{plural:count|wordform1|wordform2|wordform3}}
1463 * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
1465 * @param integer $count
1466 * @param string $wordform1
1467 * @param string $wordform2
1468 * @param string $wordform3 (optional)
1469 * @param string $wordform4 (optional)
1470 * @param string $wordform5 (optional)
1473 function convertPlural( $count, $w1, $w2, $w3, $w4, $w5) {
1474 return ( $count == '1' ||
$count == '-1' ) ?
$w1 : $w2;
1478 * For translaing of expiry times
1479 * @param string The validated block time in English
1480 * @return Somehow translated block time
1481 * @see LanguageFi.php for example implementation
1483 function translateBlockExpiry( $str ) {
1485 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
1487 if ( $scBlockExpiryOptions == '-') {
1491 foreach (explode(',', $scBlockExpiryOptions) as $option) {
1492 if ( strpos($option, ":") === false )
1494 list($show, $value) = explode(":", $option);
1495 if ( strcmp ( $str, $value) == 0 ) {
1496 return htmlspecialchars( trim( $show ) );
1504 * languages like Chinese need to be segmented in order for the diff
1507 * @param string $text
1510 function segmentForDiff( $text ) {
1515 * and unsegment to show the result
1517 * @param string $text
1520 function unsegmentForDiff( $text ) {
1524 # convert text to different variants of a language.
1525 function convert( $text, $isTitle = false) {
1526 return $this->mConverter
->convert($text, $isTitle);
1529 # Convert text from within Parser
1530 function parserConvert( $text, &$parser ) {
1531 return $this->mConverter
->parserConvert( $text, $parser );
1534 # Check if this is a language with variants
1535 function hasVariants(){
1536 return sizeof($this->getVariants())>1;
1539 # Put custom tags (e.g. -{ }-) around math to prevent conversion
1540 function armourMath($text){
1541 return $this->mConverter
->armourMath($text);
1546 * Perform output conversion on a string, and encode for safe HTML output.
1547 * @param string $text
1548 * @param bool $isTitle -- wtf?
1550 * @todo this should get integrated somewhere sane
1552 function convertHtml( $text, $isTitle = false ) {
1553 return htmlspecialchars( $this->convert( $text, $isTitle ) );
1556 function convertCategoryKey( $key ) {
1557 return $this->mConverter
->convertCategoryKey( $key );
1561 * get the list of variants supported by this langauge
1562 * see sample implementation in LanguageZh.php
1564 * @return array an array of language codes
1566 function getVariants() {
1567 return $this->mConverter
->getVariants();
1571 function getPreferredVariant( $fromUser = true ) {
1572 return $this->mConverter
->getPreferredVariant( $fromUser );
1576 * if a language supports multiple variants, it is
1577 * possible that non-existing link in one variant
1578 * actually exists in another variant. this function
1579 * tries to find it. See e.g. LanguageZh.php
1581 * @param string $link the name of the link
1582 * @param mixed $nt the title object of the link
1583 * @return null the input parameters may be modified upon return
1585 function findVariantLink( &$link, &$nt ) {
1586 $this->mConverter
->findVariantLink($link, $nt);
1590 * If a language supports multiple variants, converts text
1591 * into an array of all possible variants of the text:
1592 * 'variant' => text in that variant
1595 function convertLinkToAllVariants($text){
1596 return $this->mConverter
->convertLinkToAllVariants($text);
1601 * returns language specific options used by User::getPageRenderHash()
1602 * for example, the preferred language variant
1607 function getExtraHashOptions() {
1608 return $this->mConverter
->getExtraHashOptions();
1612 * for languages that support multiple variants, the title of an
1613 * article may be displayed differently in different variants. this
1614 * function returns the apporiate title defined in the body of the article.
1618 function getParsedTitle() {
1619 return $this->mConverter
->getParsedTitle();
1623 * Enclose a string with the "no conversion" tag. This is used by
1624 * various functions in the Parser
1626 * @param string $text text to be tagged for no conversion
1627 * @return string the tagged text
1629 function markNoConversion( $text, $noParse=false ) {
1630 return $this->mConverter
->markNoConversion( $text, $noParse );
1634 * A regular expression to match legal word-trailing characters
1635 * which should be merged onto a link of the form [[foo]]bar.
1640 function linkTrail() {
1642 return $this->linkTrail
;
1645 function getLangObj() {
1650 * Get the RFC 3066 code for this language object
1652 function getCode() {
1653 return $this->mCode
;
1656 function setCode( $code ) {
1657 $this->mCode
= $code;
1660 static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
1661 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
1664 static function getMessagesFileName( $code ) {
1666 return self
::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
1669 static function getClassFileName( $code ) {
1671 return self
::getFileName( "$IP/languages/classes/Language", $code, '.php' );
1674 static function getLocalisationArray( $code, $disableCache = false ) {
1675 self
::loadLocalisation( $code, $disableCache );
1676 return self
::$mLocalisationCache[$code];
1680 * Load localisation data for a given code into the static cache
1682 * @return array Dependencies, map of filenames to mtimes
1684 static function loadLocalisation( $code, $disableCache = false ) {
1685 static $recursionGuard = array();
1689 throw new MWException( "Invalid language code requested" );
1692 if ( !$disableCache ) {
1693 # Try the per-process cache
1694 if ( isset( self
::$mLocalisationCache[$code] ) ) {
1695 return self
::$mLocalisationCache[$code]['deps'];
1698 wfProfileIn( __METHOD__
);
1700 # Try the serialized directory
1701 $cache = wfGetPrecompiledData( self
::getFileName( "Messages", $code, '.ser' ) );
1703 self
::$mLocalisationCache[$code] = $cache;
1704 wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" );
1705 wfProfileOut( __METHOD__
);
1706 return self
::$mLocalisationCache[$code]['deps'];
1709 # Try the global cache
1710 $memcKey = wfMemcKey('localisation', $code );
1711 $cache = $wgMemc->get( $memcKey );
1713 # Check file modification times
1714 foreach ( $cache['deps'] as $file => $mtime ) {
1715 if ( !file_exists( $file ) ||
filemtime( $file ) > $mtime ) {
1719 if ( self
::isLocalisationOutOfDate( $cache ) ) {
1720 $wgMemc->delete( $memcKey );
1722 wfDebug( "Language::loadLocalisation(): localisation cache for $code had expired due to update of $file\n" );
1724 self
::$mLocalisationCache[$code] = $cache;
1725 wfDebug( "Language::loadLocalisation(): got localisation for $code from cache\n" );
1726 wfProfileOut( __METHOD__
);
1727 return $cache['deps'];
1731 wfProfileIn( __METHOD__
);
1734 # Default fallback, may be overridden when the messages file is included
1735 if ( $code != 'en' ) {
1741 # Load the primary localisation from the source file
1742 $filename = self
::getMessagesFileName( $code );
1743 if ( !file_exists( $filename ) ) {
1744 wfDebug( "Language::loadLocalisation(): no localisation file for $code, using implicit fallback to en\n" );
1748 $deps = array( $filename => filemtime( $filename ) );
1749 require( $filename );
1750 $cache = compact( self
::$mLocalisationKeys );
1751 wfDebug( "Language::loadLocalisation(): got localisation for $code from source\n" );
1754 if ( !empty( $fallback ) ) {
1755 # Load the fallback localisation, with a circular reference guard
1756 if ( isset( $recursionGuard[$code] ) ) {
1757 throw new MWException( "Error: Circular fallback reference in language code $code" );
1759 $recursionGuard[$code] = true;
1760 $newDeps = self
::loadLocalisation( $fallback, $disableCache );
1761 unset( $recursionGuard[$code] );
1763 $secondary = self
::$mLocalisationCache[$fallback];
1764 $deps = array_merge( $deps, $newDeps );
1766 # Merge the fallback localisation with the current localisation
1767 foreach ( self
::$mLocalisationKeys as $key ) {
1768 if ( isset( $cache[$key] ) ) {
1769 if ( isset( $secondary[$key] ) ) {
1770 if ( in_array( $key, self
::$mMergeableMapKeys ) ) {
1771 $cache[$key] = $cache[$key] +
$secondary[$key];
1772 } elseif ( in_array( $key, self
::$mMergeableListKeys ) ) {
1773 $cache[$key] = array_merge( $secondary[$key], $cache[$key] );
1774 } elseif ( in_array( $key, self
::$mMergeableAliasListKeys ) ) {
1775 $cache[$key] = array_merge_recursive( $cache[$key], $secondary[$key] );
1779 $cache[$key] = $secondary[$key];
1783 # Merge bookstore lists if requested
1784 if ( !empty( $cache['bookstoreList']['inherit'] ) ) {
1785 $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] );
1787 if ( isset( $cache['bookstoreList']['inherit'] ) ) {
1788 unset( $cache['bookstoreList']['inherit'] );
1792 # Add dependencies to the cache entry
1793 $cache['deps'] = $deps;
1795 # Replace spaces with underscores in namespace names
1796 $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] );
1798 # Save to both caches
1799 self
::$mLocalisationCache[$code] = $cache;
1800 if ( !$disableCache ) {
1801 $wgMemc->set( $memcKey, $cache );
1804 wfProfileOut( __METHOD__
);
1809 * Test if a given localisation cache is out of date with respect to the
1810 * source Messages files. This is done automatically for the global cache
1811 * in $wgMemc, but is only done on certain occasions for the serialized
1814 * @param $cache mixed Either a language code or a cache array
1816 static function isLocalisationOutOfDate( $cache ) {
1817 if ( !is_array( $cache ) ) {
1818 self
::loadLocalisation( $cache );
1819 $cache = self
::$mLocalisationCache[$cache];
1822 foreach ( $cache['deps'] as $file => $mtime ) {
1823 if ( !file_exists( $file ) ||
filemtime( $file ) > $mtime ) {
1832 * Get the fallback for a given language
1834 static function getFallbackFor( $code ) {
1835 self
::loadLocalisation( $code );
1836 return self
::$mLocalisationCache[$code]['fallback'];
1840 * Get all messages for a given language
1842 static function getMessagesFor( $code ) {
1843 self
::loadLocalisation( $code );
1844 return self
::$mLocalisationCache[$code]['messages'];
1848 * Get a message for a given language
1850 static function getMessageFor( $key, $code ) {
1851 self
::loadLocalisation( $code );
1852 return isset( self
::$mLocalisationCache[$code]['messages'][$key] ) ? self
::$mLocalisationCache[$code]['messages'][$key] : null;
1856 * Load localisation data for this object
1859 if ( !$this->mLoaded
) {
1860 self
::loadLocalisation( $this->getCode() );
1861 $cache =& self
::$mLocalisationCache[$this->getCode()];
1862 foreach ( self
::$mLocalisationKeys as $key ) {
1863 $this->$key = $cache[$key];
1865 $this->mLoaded
= true;
1867 $this->fixUpSettings();
1872 * Do any necessary post-cache-load settings adjustment
1874 function fixUpSettings() {
1875 global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk,
1876 $wgNamespaceAliases, $wgAmericanDates;
1877 wfProfileIn( __METHOD__
);
1878 if ( $wgExtraNamespaces ) {
1879 $this->namespaceNames
= $wgExtraNamespaces +
$this->namespaceNames
;
1882 $this->namespaceNames
[NS_PROJECT
] = $wgMetaNamespace;
1883 if ( $wgMetaNamespaceTalk ) {
1884 $this->namespaceNames
[NS_PROJECT_TALK
] = $wgMetaNamespaceTalk;
1886 $talk = $this->namespaceNames
[NS_PROJECT_TALK
];
1887 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
1889 # Allow grammar transformations
1890 # Allowing full message-style parsing would make simple requests
1891 # such as action=raw much more expensive than they need to be.
1892 # This will hopefully cover most cases.
1893 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
1894 array( &$this, 'replaceGrammarInNamespace' ), $talk );
1895 $talk = str_replace( ' ', '_', $talk );
1896 $this->namespaceNames
[NS_PROJECT_TALK
] = $talk;
1899 # The above mixing may leave namespaces out of canonical order.
1900 # Re-order by namespace ID number...
1901 ksort( $this->namespaceNames
);
1903 # Put namespace names and aliases into a hashtable.
1904 # If this is too slow, then we should arrange it so that it is done
1905 # before caching. The catch is that at pre-cache time, the above
1906 # class-specific fixup hasn't been done.
1907 $this->mNamespaceIds
= array();
1908 foreach ( $this->namespaceNames
as $index => $name ) {
1909 $this->mNamespaceIds
[$this->lc($name)] = $index;
1911 if ( $this->namespaceAliases
) {
1912 foreach ( $this->namespaceAliases
as $name => $index ) {
1913 $this->mNamespaceIds
[$this->lc($name)] = $index;
1916 if ( $wgNamespaceAliases ) {
1917 foreach ( $wgNamespaceAliases as $name => $index ) {
1918 $this->mNamespaceIds
[$this->lc($name)] = $index;
1922 if ( $this->defaultDateFormat
== 'dmy or mdy' ) {
1923 $this->defaultDateFormat
= $wgAmericanDates ?
'mdy' : 'dmy';
1925 wfProfileOut( __METHOD__
);
1928 function replaceGrammarInNamespace( $m ) {
1929 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
1932 static function getCaseMaps() {
1933 static $wikiUpperChars, $wikiLowerChars;
1934 if ( isset( $wikiUpperChars ) ) {
1935 return array( $wikiUpperChars, $wikiLowerChars );
1938 wfProfileIn( __METHOD__
);
1939 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
1940 if ( $arr === false ) {
1941 throw new MWException(
1942 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
1945 wfProfileOut( __METHOD__
);
1946 return array( $wikiUpperChars, $wikiLowerChars );
1949 function formatTimePeriod( $seconds ) {
1950 if ( $seconds < 10 ) {
1951 return $this->formatNum( sprintf( "%.1f", $seconds ) ) . wfMsg( 'seconds-abbrev' );
1952 } elseif ( $seconds < 60 ) {
1953 return $this->formatNum( round( $seconds ) ) . wfMsg( 'seconds-abbrev' );
1954 } elseif ( $seconds < 3600 ) {
1955 return $this->formatNum( floor( $seconds / 60 ) ) . wfMsg( 'minutes-abbrev' ) .
1956 $this->formatNum( round( fmod( $seconds, 60 ) ) ) . wfMsg( 'seconds-abbrev' );
1958 $hours = floor( $seconds / 3600 );
1959 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
1960 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
1961 return $this->formatNum( $hours ) . wfMsg( 'hours-abbrev' ) .
1962 $this->formatNum( $minutes ) . wfMsg( 'minutes-abbrev' ) .
1963 $this->formatNum( $secondsPart ) . wfMsg( 'seconds-abbrev' );
1967 function formatBitrate( $bps ) {
1968 $units = array( 'bps', 'kbps', 'Mbps', 'Gbps' );
1970 return $this->formatNum( $bps ) . $units[0];
1972 $unitIndex = floor( log10( $bps ) / 3 );
1973 $mantissa = $bps / pow( 1000, $unitIndex );
1974 if ( $mantissa < 10 ) {
1975 $mantissa = round( $mantissa, 1 );
1977 $mantissa = round( $mantissa );
1979 return $this->formatNum( $mantissa ) . $units[$unitIndex];
1983 * Format a size in bytes for output, using an appropriate
1984 * unit (B, KB, MB or GB) according to the magnitude in question
1986 * @param $size Size to format
1987 * @return string Plain text (not HTML)
1989 function formatSize( $size ) {
1990 // For small sizes no decimal places necessary
1992 if( $size > 1024 ) {
1993 $size = $size / 1024;
1994 if( $size > 1024 ) {
1995 $size = $size / 1024;
1996 // For MB and bigger two decimal places are smarter
1998 if( $size > 1024 ) {
1999 $size = $size / 1024;
2000 $msg = 'size-gigabytes';
2002 $msg = 'size-megabytes';
2005 $msg = 'size-kilobytes';
2008 $msg = 'size-bytes';
2010 $size = round( $size, $round );
2011 $text = $this->getMessageFromDB( $msg );
2012 return str_replace( '$1', $this->formatNum( $size ), $text );