3 * @defgroup Language Language
9 if( !defined( 'MEDIAWIKI' ) ) {
10 echo "This file is part of MediaWiki, it is not a valid entry point.\n";
15 global $wgLanguageNames;
16 require_once( dirname(__FILE__
) . '/Names.php' ) ;
18 global $wgInputEncoding, $wgOutputEncoding;
21 * These are always UTF-8, they exist only for backwards compatibility
23 $wgInputEncoding = "UTF-8";
24 $wgOutputEncoding = "UTF-8";
26 if( function_exists( 'mb_strtoupper' ) ) {
27 mb_internal_encoding('UTF-8');
31 * a fake language converter
37 function FakeConverter($langobj) {$this->mLang
= $langobj;}
38 function convert($t, $i) {return $t;}
39 function parserConvert($t, $p) {return $t;}
40 function getVariants() { return array( $this->mLang
->getCode() ); }
41 function getPreferredVariant() {return $this->mLang
->getCode(); }
42 function findVariantLink(&$l, &$n, $forTemplate = false) {}
43 function getExtraHashOptions() {return '';}
44 function getParsedTitle() {return '';}
45 function markNoConversion($text, $noParse=false) {return $text;}
46 function convertCategoryKey( $key ) {return $key; }
47 function convertLinkToAllVariants($text){ return array( $this->mLang
->getCode() => $text); }
48 function armourMath($text){ return $text; }
52 * Internationalisation code
56 var $mConverter, $mVariants, $mCode, $mLoaded = false;
57 var $mMagicExtensions = array(), $mMagicHookDone = false;
58 var $mLocalizedLanguagesNames = null;
60 static public $mLocalisationKeys = array(
61 'fallback', 'namespaceNames', 'mathNames', 'bookstoreList',
62 'magicWords', 'messages', 'rtl', 'digitTransformTable',
63 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
64 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases',
65 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
66 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
70 static public $mMergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames',
71 'dateFormats', 'defaultUserOptionOverrides', 'magicWords', 'imageFiles' );
73 static public $mMergeableListKeys = array( 'extraUserToggles' );
75 static public $mMergeableAliasListKeys = array( 'specialPageAliases' );
77 static public $mLocalisationCache = array();
78 static public $mLangObjCache = array();
80 static public $mWeekdayMsgs = array(
81 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
85 static public $mWeekdayAbbrevMsgs = array(
86 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
89 static public $mMonthMsgs = array(
90 'january', 'february', 'march', 'april', 'may_long', 'june',
91 'july', 'august', 'september', 'october', 'november',
94 static public $mMonthGenMsgs = array(
95 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
96 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
99 static public $mMonthAbbrevMsgs = array(
100 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
101 'sep', 'oct', 'nov', 'dec'
104 static public $mIranianCalendarMonthMsgs = array(
105 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
106 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
107 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
108 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
111 static public $mHebrewCalendarMonthMsgs = array(
112 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
113 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
114 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
115 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
116 'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
119 static public $mHebrewCalendarMonthGenMsgs = array(
120 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
121 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
122 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
123 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
124 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
127 static public $mHijriCalendarMonthMsgs = array(
128 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
129 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
130 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
131 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
135 * Get a cached language object for a given language code
137 static function factory( $code ) {
138 if ( !isset( self
::$mLangObjCache[$code] ) ) {
139 if( count( self
::$mLangObjCache ) > 10 ) {
140 // Don't keep a billion objects around, that's stupid.
141 self
::$mLangObjCache = array();
143 self
::$mLangObjCache[$code] = self
::newFromCode( $code );
145 return self
::$mLangObjCache[$code];
149 * Create a language object for a given language code
151 protected static function newFromCode( $code ) {
153 static $recursionLevel = 0;
154 if ( $code == 'en' ) {
157 $class = 'Language' . str_replace( '-', '_', ucfirst( $code ) );
158 // Preload base classes to work around APC/PHP5 bug
159 if ( file_exists( "$IP/languages/classes/$class.deps.php" ) ) {
160 include_once("$IP/languages/classes/$class.deps.php");
162 if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
163 include_once("$IP/languages/classes/$class.php");
167 if ( $recursionLevel > 5 ) {
168 throw new MWException( "Language fallback loop detected when creating class $class\n" );
171 if( ! class_exists( $class ) ) {
172 $fallback = Language
::getFallbackFor( $code );
174 $lang = Language
::newFromCode( $fallback );
176 $lang->setCode( $code );
183 function __construct() {
184 $this->mConverter
= new FakeConverter($this);
185 // Set the code to the name of the descendant
186 if ( get_class( $this ) == 'Language' ) {
189 $this->mCode
= str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
194 * Reduce memory usage
196 function __destruct() {
197 foreach ( $this as $name => $value ) {
198 unset( $this->$name );
203 * Hook which will be called if this is the content language.
204 * Descendants can use this to register hook functions or modify globals
206 function initContLang() {}
209 * @deprecated Use User::getDefaultOptions()
212 function getDefaultUserOptions() {
213 wfDeprecated( __METHOD__
);
214 return User
::getDefaultOptions();
217 function getFallbackLanguageCode() {
218 return self
::getFallbackFor( $this->mCode
);
222 * Exports $wgBookstoreListEn
225 function getBookstoreList() {
227 return $this->bookstoreList
;
233 function getNamespaces() {
235 return $this->namespaceNames
;
239 * A convenience function that returns the same thing as
240 * getNamespaces() except with the array values changed to ' '
241 * where it found '_', useful for producing output to be displayed
242 * e.g. in <select> forms.
246 function getFormattedNamespaces() {
247 $ns = $this->getNamespaces();
248 foreach($ns as $k => $v) {
249 $ns[$k] = strtr($v, '_', ' ');
255 * Get a namespace value by key
257 * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
258 * echo $mw_ns; // prints 'MediaWiki'
261 * @param $index Int: the array key of the namespace to return
262 * @return mixed, string if the namespace value exists, otherwise false
264 function getNsText( $index ) {
265 $ns = $this->getNamespaces();
266 return isset( $ns[$index] ) ?
$ns[$index] : false;
270 * A convenience function that returns the same thing as
271 * getNsText() except with '_' changed to ' ', useful for
276 function getFormattedNsText( $index ) {
277 $ns = $this->getNsText( $index );
278 return strtr($ns, '_', ' ');
282 * Get a namespace key by value, case insensitive.
283 * Only matches namespace names for the current language, not the
284 * canonical ones defined in Namespace.php.
286 * @param $text String
287 * @return mixed An integer if $text is a valid value otherwise false
289 function getLocalNsIndex( $text ) {
291 $lctext = $this->lc($text);
292 return isset( $this->mNamespaceIds
[$lctext] ) ?
$this->mNamespaceIds
[$lctext] : false;
296 * Get a namespace key by value, case insensitive. Canonical namespace
297 * names override custom ones defined for the current language.
299 * @param $text String
300 * @return mixed An integer if $text is a valid value otherwise false
302 function getNsIndex( $text ) {
304 $lctext = $this->lc($text);
305 if( ( $ns = MWNamespace
::getCanonicalIndex( $lctext ) ) !== null ) return $ns;
306 return isset( $this->mNamespaceIds
[$lctext] ) ?
$this->mNamespaceIds
[$lctext] : false;
310 * short names for language variants used for language conversion links.
312 * @param $code String
315 function getVariantname( $code ) {
316 return $this->getMessageFromDB( "variantname-$code" );
319 function specialPage( $name ) {
320 $aliases = $this->getSpecialPageAliases();
321 if ( isset( $aliases[$name][0] ) ) {
322 $name = $aliases[$name][0];
324 return $this->getNsText( NS_SPECIAL
) . ':' . $name;
327 function getQuickbarSettings() {
329 $this->getMessage( 'qbsettings-none' ),
330 $this->getMessage( 'qbsettings-fixedleft' ),
331 $this->getMessage( 'qbsettings-fixedright' ),
332 $this->getMessage( 'qbsettings-floatingleft' ),
333 $this->getMessage( 'qbsettings-floatingright' )
337 function getMathNames() {
339 return $this->mathNames
;
342 function getDatePreferences() {
344 return $this->datePreferences
;
347 function getDateFormats() {
349 return $this->dateFormats
;
352 function getDefaultDateFormat() {
354 return $this->defaultDateFormat
;
357 function getDatePreferenceMigrationMap() {
359 return $this->datePreferenceMigrationMap
;
362 function getImageFile( $image ) {
364 return $this->imageFiles
[$image];
367 function getDefaultUserOptionOverrides() {
369 # XXX - apparently some languageas get empty arrays, didn't get to it yet -- midom
370 if (is_array($this->defaultUserOptionOverrides
)) {
371 return $this->defaultUserOptionOverrides
;
377 function getExtraUserToggles() {
379 return $this->extraUserToggles
;
382 function getUserToggle( $tog ) {
383 return $this->getMessageFromDB( "tog-$tog" );
387 * Get language names, indexed by code.
388 * If $customisedOnly is true, only returns codes with a messages file
390 public static function getLanguageNames( $customisedOnly = false ) {
391 global $wgLanguageNames, $wgExtraLanguageNames;
392 $allNames = $wgExtraLanguageNames +
$wgLanguageNames;
393 if ( !$customisedOnly ) {
399 $dir = opendir( "$IP/languages/messages" );
400 while( false !== ( $file = readdir( $dir ) ) ) {
402 if( preg_match( '/Messages([A-Z][a-z_]+)\.php$/', $file, $m ) ) {
403 $code = str_replace( '_', '-', strtolower( $m[1] ) );
404 if ( isset( $allNames[$code] ) ) {
405 $names[$code] = $allNames[$code];
414 * Get localized language names
418 function getLocalizedLanguageNames() {
419 if( !is_array( $this->mLocalizedLanguagesNames
) ) {
420 $this->mLocalizedLanguagesNames
= array();
421 wfRunHooks( 'LanguageGetLocalizedLanguageNames', array( &$this->mLocalizedLanguagesNames
, $this->getCode() ) );
423 return $this->mLocalizedLanguagesNames
;
427 * Get a message from the MediaWiki namespace.
429 * @param $msg String: message name
432 function getMessageFromDB( $msg ) {
433 return wfMsgExt( $msg, array( 'parsemag', 'language' => $this ) );
437 * Get a language name
439 * @param $code String language code
440 * @return $localized boolean gets the localized language name
442 function getLanguageName( $code, $localized = false ) {
443 $names = self
::getLanguageNames();
444 if ( !array_key_exists( $code, $names ) ) {
448 $languageNames = $this->getLocalizedLanguageNames();
449 return isset( $languageNames[$code] ) ?
$languageNames[$code] : $names[$code];
451 return $names[$code];
455 function getLanguageNameLocalized( $code ) {
456 return self
::getLanguageName( $code, true );
459 function getMonthName( $key ) {
460 return $this->getMessageFromDB( self
::$mMonthMsgs[$key-1] );
463 function getMonthNameGen( $key ) {
464 return $this->getMessageFromDB( self
::$mMonthGenMsgs[$key-1] );
467 function getMonthAbbreviation( $key ) {
468 return $this->getMessageFromDB( self
::$mMonthAbbrevMsgs[$key-1] );
471 function getWeekdayName( $key ) {
472 return $this->getMessageFromDB( self
::$mWeekdayMsgs[$key-1] );
475 function getWeekdayAbbreviation( $key ) {
476 return $this->getMessageFromDB( self
::$mWeekdayAbbrevMsgs[$key-1] );
479 function getIranianCalendarMonthName( $key ) {
480 return $this->getMessageFromDB( self
::$mIranianCalendarMonthMsgs[$key-1] );
483 function getHebrewCalendarMonthName( $key ) {
484 return $this->getMessageFromDB( self
::$mHebrewCalendarMonthMsgs[$key-1] );
487 function getHebrewCalendarMonthNameGen( $key ) {
488 return $this->getMessageFromDB( self
::$mHebrewCalendarMonthGenMsgs[$key-1] );
491 function getHijriCalendarMonthName( $key ) {
492 return $this->getMessageFromDB( self
::$mHijriCalendarMonthMsgs[$key-1] );
496 * Used by date() and time() to adjust the time output.
498 * @param $ts Int the time in date('YmdHis') format
499 * @param $tz Mixed: adjust the time by this amount (default false, mean we
500 * get user timecorrection setting)
503 function userAdjust( $ts, $tz = false ) {
504 global $wgUser, $wgLocalTZoffset;
506 if ( $tz === false ) {
507 $tz = $wgUser->getOption( 'timecorrection' );
510 $data = explode( '|', $tz, 3 );
512 if ( $data[0] == 'ZoneInfo' ) {
513 if ( function_exists( 'timezone_open' ) && @timezone_open
( $data[2] ) !== false ) {
514 $date = date_create( $ts, timezone_open( 'UTC' ) );
515 date_timezone_set( $date, timezone_open( $data[2] ) );
516 $date = date_format( $date, 'YmdHis' );
519 # Unrecognized timezone, default to 'Offset' with the stored offset.
524 if ( $data[0] == 'System' ||
$tz == '' ) {
525 # Global offset in minutes.
526 if( isset($wgLocalTZoffset) ) $minDiff = $wgLocalTZoffset;
527 } else if ( $data[0] == 'Offset' ) {
528 $minDiff = intval( $data[1] );
530 $data = explode( ':', $tz );
531 if( count( $data ) == 2 ) {
532 $data[0] = intval( $data[0] );
533 $data[1] = intval( $data[1] );
534 $minDiff = abs( $data[0] ) * 60 +
$data[1];
535 if ( $data[0] < 0 ) $minDiff = -$minDiff;
537 $minDiff = intval( $data[0] ) * 60;
541 # No difference ? Return time unchanged
542 if ( 0 == $minDiff ) return $ts;
544 wfSuppressWarnings(); // E_STRICT system time bitching
545 # Generate an adjusted date; take advantage of the fact that mktime
546 # will normalize out-of-range values so we don't have to split $minDiff
547 # into hours and minutes.
549 (int)substr( $ts, 8, 2) ), # Hours
550 (int)substr( $ts, 10, 2 ) +
$minDiff, # Minutes
551 (int)substr( $ts, 12, 2 ), # Seconds
552 (int)substr( $ts, 4, 2 ), # Month
553 (int)substr( $ts, 6, 2 ), # Day
554 (int)substr( $ts, 0, 4 ) ); #Year
556 $date = date( 'YmdHis', $t );
563 * This is a workalike of PHP's date() function, but with better
564 * internationalisation, a reduced set of format characters, and a better
567 * Supported format characters are dDjlNwzWFmMntLoYyaAgGhHiscrU. See the
568 * PHP manual for definitions. "o" format character is supported since
569 * PHP 5.1.0, previous versions return literal o.
570 * There are a number of extensions, which start with "x":
572 * xn Do not translate digits of the next numeric format character
573 * xN Toggle raw digit (xn) flag, stays set until explicitly unset
574 * xr Use roman numerals for the next numeric format character
575 * xh Use hebrew numerals for the next numeric format character
577 * xg Genitive month name
579 * xij j (day number) in Iranian calendar
580 * xiF F (month name) in Iranian calendar
581 * xin n (month number) in Iranian calendar
582 * xiY Y (full year) in Iranian calendar
584 * xjj j (day number) in Hebrew calendar
585 * xjF F (month name) in Hebrew calendar
586 * xjt t (days in month) in Hebrew calendar
587 * xjx xg (genitive month name) in Hebrew calendar
588 * xjn n (month number) in Hebrew calendar
589 * xjY Y (full year) in Hebrew calendar
591 * xmj j (day number) in Hijri calendar
592 * xmF F (month name) in Hijri calendar
593 * xmn n (month number) in Hijri calendar
594 * xmY Y (full year) in Hijri calendar
596 * xkY Y (full year) in Thai solar calendar. Months and days are
597 * identical to the Gregorian calendar
599 * Characters enclosed in double quotes will be considered literal (with
600 * the quotes themselves removed). Unmatched quotes will be considered
601 * literal quotes. Example:
603 * "The month is" F => The month is January
606 * Backslash escaping is also supported.
608 * Input timestamp is assumed to be pre-normalized to the desired local
611 * @param $format String
612 * @param $ts String: 14-character timestamp
615 * @todo emulation of "o" format character for PHP pre 5.1.0
616 * @todo handling of "o" format character for Iranian, Hebrew, Hijri & Thai?
618 function sprintfDate( $format, $ts ) {
629 for ( $p = 0; $p < strlen( $format ); $p++
) {
632 if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
633 $code .= $format[++
$p];
636 if ( ( $code === 'xi' ||
$code == 'xj' ||
$code == 'xk' ||
$code == 'xm' ) && $p < strlen( $format ) - 1 ) {
637 $code .= $format[++
$p];
648 $rawToggle = !$rawToggle;
657 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
660 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
661 $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
664 $num = substr( $ts, 6, 2 );
667 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
668 $s .= $this->getWeekdayAbbreviation( gmdate( 'w', $unix ) +
1 );
671 $num = intval( substr( $ts, 6, 2 ) );
674 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
678 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
682 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
686 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
687 $s .= $this->getWeekdayName( gmdate( 'w', $unix ) +
1 );
690 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
691 $w = gmdate( 'w', $unix );
695 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
696 $num = gmdate( 'w', $unix );
699 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
700 $num = gmdate( 'z', $unix );
703 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
704 $num = gmdate( 'W', $unix );
707 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
710 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
711 $s .= $this->getIranianCalendarMonthName( $iranian[1] );
714 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
715 $s .= $this->getHijriCalendarMonthName( $hijri[1] );
718 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
719 $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
722 $num = substr( $ts, 4, 2 );
725 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
728 $num = intval( substr( $ts, 4, 2 ) );
731 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
735 if ( !$hijri ) $hijri = self
::tsToHijri ( $ts );
739 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
743 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
744 $num = gmdate( 't', $unix );
747 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
751 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
752 $num = gmdate( 'L', $unix );
754 # 'o' is supported since PHP 5.1.0
755 # return literal if not supported
756 # TODO: emulation for pre 5.1.0 versions
758 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
759 if ( version_compare(PHP_VERSION
, '5.1.0') === 1 )
760 $num = date( 'o', $unix );
765 $num = substr( $ts, 0, 4 );
768 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
772 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
776 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
780 if ( !$thai ) $thai = self
::tsToThai( $ts );
784 $num = substr( $ts, 2, 2 );
787 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'am' : 'pm';
790 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'AM' : 'PM';
793 $h = substr( $ts, 8, 2 );
794 $num = $h %
12 ?
$h %
12 : 12;
797 $num = intval( substr( $ts, 8, 2 ) );
800 $h = substr( $ts, 8, 2 );
801 $num = sprintf( '%02d', $h %
12 ?
$h %
12 : 12 );
804 $num = substr( $ts, 8, 2 );
807 $num = substr( $ts, 10, 2 );
810 $num = substr( $ts, 12, 2 );
813 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
814 $s .= gmdate( 'c', $unix );
817 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
818 $s .= gmdate( 'r', $unix );
821 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
826 if ( $p < strlen( $format ) - 1 ) {
834 if ( $p < strlen( $format ) - 1 ) {
835 $endQuote = strpos( $format, '"', $p +
1 );
836 if ( $endQuote === false ) {
837 # No terminating quote, assume literal "
840 $s .= substr( $format, $p +
1, $endQuote - $p - 1 );
844 # Quote at end of string, assume literal "
851 if ( $num !== false ) {
852 if ( $rawToggle ||
$raw ) {
855 } elseif ( $roman ) {
856 $s .= self
::romanNumeral( $num );
858 } elseif( $hebrewNum ) {
859 $s .= self
::hebrewNumeral( $num );
862 $s .= $this->formatNum( $num, true );
870 private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
871 private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 );
873 * Algorithm by Roozbeh Pournader and Mohammad Toossi to convert
874 * Gregorian dates to Iranian dates. Originally written in C, it
875 * is released under the terms of GNU Lesser General Public
876 * License. Conversion to PHP was performed by Niklas Laxström.
878 * Link: http://www.farsiweb.info/jalali/jalali.c
880 private static function tsToIranian( $ts ) {
881 $gy = substr( $ts, 0, 4 ) -1600;
882 $gm = substr( $ts, 4, 2 ) -1;
883 $gd = substr( $ts, 6, 2 ) -1;
885 # Days passed from the beginning (including leap years)
888 - floor(($gy+
99) / 100)
889 +
floor(($gy+
399) / 400);
892 // Add days of the past months of this year
893 for( $i = 0; $i < $gm; $i++
) {
894 $gDayNo +
= self
::$GREG_DAYS[$i];
898 if ( $gm > 1 && (($gy%4
===0 && $gy%100
!==0 ||
($gy%400
==0)))) {
902 // Days passed in current month
905 $jDayNo = $gDayNo - 79;
907 $jNp = floor($jDayNo / 12053);
910 $jy = 979 +
33*$jNp +
4*floor($jDayNo/1461);
913 if ( $jDayNo >= 366 ) {
914 $jy +
= floor(($jDayNo-1)/365);
915 $jDayNo = floor(($jDayNo-1)%365
);
918 for ( $i = 0; $i < 11 && $jDayNo >= self
::$IRANIAN_DAYS[$i]; $i++
) {
919 $jDayNo -= self
::$IRANIAN_DAYS[$i];
925 return array($jy, $jm, $jd);
928 * Converting Gregorian dates to Hijri dates.
930 * Based on a PHP-Nuke block by Sharjeel which is released under GNU/GPL license
932 * @link http://phpnuke.org/modules.php?name=News&file=article&sid=8234&mode=thread&order=0&thold=0
934 private static function tsToHijri ( $ts ) {
935 $year = substr( $ts, 0, 4 );
936 $month = substr( $ts, 4, 2 );
937 $day = substr( $ts, 6, 2 );
946 if (($zy>1582)||
(($zy==1582)&&($zm>10))||
(($zy==1582)&&($zm==10)&&($zd>14)))
950 $zjd=(int)((1461*($zy +
4800 +
(int)( ($zm-14) /12) ))/4) +
(int)((367*($zm-2-12*((int)(($zm-14)/12))))/12)-(int)((3*(int)(( ($zy+
4900+
(int)(($zm-14)/12))/100)))/4)+
$zd-32075;
954 $zjd = 367*$zy-(int)((7*($zy+
5001+
(int)(($zm-9)/7)))/4)+
(int)((275*$zm)/9)+
$zd+
1729777;
957 $zl=$zjd-1948440+
10632;
958 $zn=(int)(($zl-1)/10631);
959 $zl=$zl-10631*$zn+
354;
960 $zj=((int)((10985-$zl)/5316))*((int)((50*$zl)/17719))+
((int)($zl/5670))*((int)((43*$zl)/15238));
961 $zl=$zl-((int)((30-$zj)/15))*((int)((17719*$zj)/50))-((int)($zj/16))*((int)((15238*$zj)/43))+
29;
962 $zm=(int)((24*$zl)/709);
963 $zd=$zl-(int)((709*$zm)/24);
966 return array ($zy, $zm, $zd);
970 * Converting Gregorian dates to Hebrew dates.
972 * Based on a JavaScript code by Abu Mami and Yisrael Hersch
973 * (abu-mami@kaluach.net, http://www.kaluach.net), who permitted
974 * to translate the relevant functions into PHP and release them under
977 * The months are counted from Tishrei = 1. In a leap year, Adar I is 13
978 * and Adar II is 14. In a non-leap year, Adar is 6.
980 private static function tsToHebrew( $ts ) {
982 $year = substr( $ts, 0, 4 );
983 $month = substr( $ts, 4, 2 );
984 $day = substr( $ts, 6, 2 );
986 # Calculate Hebrew year
987 $hebrewYear = $year +
3760;
989 # Month number when September = 1, August = 12
998 # Calculate day of year from 1 September
1000 for( $i = 1; $i < $month; $i++
) {
1004 # Check if the year is leap
1005 if( $year %
400 == 0 ||
( $year %
4 == 0 && $year %
100 > 0 ) ) {
1008 } elseif( $i == 8 ||
$i == 10 ||
$i == 1 ||
$i == 3 ) {
1015 # Calculate the start of the Hebrew year
1016 $start = self
::hebrewYearStart( $hebrewYear );
1018 # Calculate next year's start
1019 if( $dayOfYear <= $start ) {
1020 # Day is before the start of the year - it is the previous year
1022 $nextStart = $start;
1026 # Add days since previous year's 1 September
1028 if( ( $year %
400 == 0 ) ||
( $year %
100 != 0 && $year %
4 == 0 ) ) {
1032 # Start of the new (previous) year
1033 $start = self
::hebrewYearStart( $hebrewYear );
1036 $nextStart = self
::hebrewYearStart( $hebrewYear +
1 );
1039 # Calculate Hebrew day of year
1040 $hebrewDayOfYear = $dayOfYear - $start;
1042 # Difference between year's days
1043 $diff = $nextStart - $start;
1044 # Add 12 (or 13 for leap years) days to ignore the difference between
1045 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1046 # difference is only about the year type
1047 if( ( $year %
400 == 0 ) ||
( $year %
100 != 0 && $year %
4 == 0 ) ) {
1053 # Check the year pattern, and is leap year
1054 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1055 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1056 # and non-leap years
1057 $yearPattern = $diff %
30;
1058 # Check if leap year
1059 $isLeap = $diff >= 30;
1061 # Calculate day in the month from number of day in the Hebrew year
1062 # Don't check Adar - if the day is not in Adar, we will stop before;
1063 # if it is in Adar, we will use it to check if it is Adar I or Adar II
1064 $hebrewDay = $hebrewDayOfYear;
1067 while( $hebrewMonth <= 12 ) {
1068 # Calculate days in this month
1069 if( $isLeap && $hebrewMonth == 6 ) {
1070 # Adar in a leap year
1072 # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1074 if( $hebrewDay <= $days ) {
1078 # Subtract the days of Adar I
1079 $hebrewDay -= $days;
1082 if( $hebrewDay <= $days ) {
1088 } elseif( $hebrewMonth == 2 && $yearPattern == 2 ) {
1089 # Cheshvan in a complete year (otherwise as the rule below)
1091 } elseif( $hebrewMonth == 3 && $yearPattern == 0 ) {
1092 # Kislev in an incomplete year (otherwise as the rule below)
1095 # Odd months have 30 days, even have 29
1096 $days = 30 - ( $hebrewMonth - 1 ) %
2;
1098 if( $hebrewDay <= $days ) {
1099 # In the current month
1102 # Subtract the days of the current month
1103 $hebrewDay -= $days;
1104 # Try in the next month
1109 return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days );
1113 * This calculates the Hebrew year start, as days since 1 September.
1114 * Based on Carl Friedrich Gauss algorithm for finding Easter date.
1115 * Used for Hebrew date.
1117 private static function hebrewYearStart( $year ) {
1118 $a = intval( ( 12 * ( $year - 1 ) +
17 ) %
19 );
1119 $b = intval( ( $year - 1 ) %
4 );
1120 $m = 32.044093161144 +
1.5542417966212 * $a +
$b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1124 $Mar = intval( $m );
1130 $c = intval( ( $Mar +
3 * ( $year - 1 ) +
5 * $b +
5 ) %
7);
1131 if( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1133 } else if( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1135 } else if( $c == 2 ||
$c == 4 ||
$c == 6 ) {
1139 $Mar +
= intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1144 * Algorithm to convert Gregorian dates to Thai solar dates.
1146 * Link: http://en.wikipedia.org/wiki/Thai_solar_calendar
1148 * @param $ts String: 14-character timestamp
1149 * @return array converted year, month, day
1151 private static function tsToThai( $ts ) {
1152 $gy = substr( $ts, 0, 4 );
1153 $gm = substr( $ts, 4, 2 );
1154 $gd = substr( $ts, 6, 2 );
1156 # Add 543 years to the Gregorian calendar
1157 # Months and days are identical
1158 $gy_thai = $gy +
543;
1160 return array( $gy_thai, $gm, $gd );
1165 * Roman number formatting up to 3000
1167 static function romanNumeral( $num ) {
1168 static $table = array(
1169 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
1170 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
1171 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
1172 array( '', 'M', 'MM', 'MMM' )
1175 $num = intval( $num );
1176 if ( $num > 3000 ||
$num <= 0 ) {
1181 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1182 if ( $num >= $pow10 ) {
1183 $s .= $table[$i][floor($num / $pow10)];
1185 $num = $num %
$pow10;
1191 * Hebrew Gematria number formatting up to 9999
1193 static function hebrewNumeral( $num ) {
1194 static $table = array(
1195 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
1196 array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
1197 array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
1198 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
1201 $num = intval( $num );
1202 if ( $num > 9999 ||
$num <= 0 ) {
1207 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1208 if ( $num >= $pow10 ) {
1209 if ( $num == 15 ||
$num == 16 ) {
1210 $s .= $table[0][9] . $table[0][$num - 9];
1213 $s .= $table[$i][intval( ( $num / $pow10 ) )];
1214 if( $pow10 == 1000 ) {
1219 $num = $num %
$pow10;
1221 if( strlen( $s ) == 2 ) {
1224 $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
1225 $str .= substr( $s, strlen( $s ) - 2, 2 );
1227 $start = substr( $str, 0, strlen( $str ) - 2 );
1228 $end = substr( $str, strlen( $str ) - 2 );
1231 $str = $start . 'ך';
1234 $str = $start . 'ם';
1237 $str = $start . 'ן';
1240 $str = $start . 'ף';
1243 $str = $start . 'ץ';
1250 * This is meant to be used by time(), date(), and timeanddate() to get
1251 * the date preference they're supposed to use, it should be used in
1255 * function timeanddate([...], $format = true) {
1256 * $datePreference = $this->dateFormat($format);
1261 * @param $usePrefs Mixed: if true, the user's preference is used
1262 * if false, the site/language default is used
1263 * if int/string, assumed to be a format.
1266 function dateFormat( $usePrefs = true ) {
1269 if( is_bool( $usePrefs ) ) {
1271 $datePreference = $wgUser->getDatePreference();
1273 $options = User
::getDefaultOptions();
1274 $datePreference = (string)$options['date'];
1277 $datePreference = (string)$usePrefs;
1281 if( $datePreference == '' ) {
1285 return $datePreference;
1289 * @param $ts Mixed: the time format which needs to be turned into a
1290 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1291 * @param $adj Bool: whether to adjust the time output according to the
1292 * user configured offset ($timecorrection)
1293 * @param $format Mixed: true to use user's date format preference
1294 * @param $timecorrection String: the time offset as returned by
1295 * validateTimeZone() in Special:Preferences
1298 function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
1301 $ts = $this->userAdjust( $ts, $timecorrection );
1304 $pref = $this->dateFormat( $format );
1305 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref date"] ) ) {
1306 $pref = $this->defaultDateFormat
;
1308 return $this->sprintfDate( $this->dateFormats
["$pref date"], $ts );
1312 * @param $ts Mixed: the time format which needs to be turned into a
1313 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1314 * @param $adj Bool: whether to adjust the time output according to the
1315 * user configured offset ($timecorrection)
1316 * @param $format Mixed: true to use user's date format preference
1317 * @param $timecorrection String: the time offset as returned by
1318 * validateTimeZone() in Special:Preferences
1321 function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
1324 $ts = $this->userAdjust( $ts, $timecorrection );
1327 $pref = $this->dateFormat( $format );
1328 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref time"] ) ) {
1329 $pref = $this->defaultDateFormat
;
1331 return $this->sprintfDate( $this->dateFormats
["$pref time"], $ts );
1335 * @param $ts Mixed: the time format which needs to be turned into a
1336 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1337 * @param $adj Bool: whether to adjust the time output according to the
1338 * user configured offset ($timecorrection)
1339 * @param $format Mixed: what format to return, if it's false output the
1340 * default one (default true)
1341 * @param $timecorrection String: the time offset as returned by
1342 * validateTimeZone() in Special:Preferences
1345 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) {
1348 $ts = wfTimestamp( TS_MW
, $ts );
1351 $ts = $this->userAdjust( $ts, $timecorrection );
1354 $pref = $this->dateFormat( $format );
1355 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref both"] ) ) {
1356 $pref = $this->defaultDateFormat
;
1359 return $this->sprintfDate( $this->dateFormats
["$pref both"], $ts );
1362 function getMessage( $key ) {
1364 return isset( $this->messages
[$key] ) ?
$this->messages
[$key] : null;
1367 function getAllMessages() {
1369 return $this->messages
;
1372 function iconv( $in, $out, $string ) {
1373 # For most languages, this is a wrapper for iconv
1374 return iconv( $in, $out . '//IGNORE', $string );
1377 // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
1378 function ucwordbreaksCallbackAscii($matches){
1379 return $this->ucfirst($matches[1]);
1382 function ucwordbreaksCallbackMB($matches){
1383 return mb_strtoupper($matches[0]);
1386 function ucCallback($matches){
1387 list( $wikiUpperChars ) = self
::getCaseMaps();
1388 return strtr( $matches[1], $wikiUpperChars );
1391 function lcCallback($matches){
1392 list( , $wikiLowerChars ) = self
::getCaseMaps();
1393 return strtr( $matches[1], $wikiLowerChars );
1396 function ucwordsCallbackMB($matches){
1397 return mb_strtoupper($matches[0]);
1400 function ucwordsCallbackWiki($matches){
1401 list( $wikiUpperChars ) = self
::getCaseMaps();
1402 return strtr( $matches[0], $wikiUpperChars );
1405 function ucfirst( $str ) {
1406 if ( empty($str) ) return $str;
1407 if ( ord($str[0]) < 128 ) return ucfirst($str);
1408 else return self
::uc($str,true); // fall back to more complex logic in case of multibyte strings
1411 function uc( $str, $first = false ) {
1412 if ( function_exists( 'mb_strtoupper' ) ) {
1414 if ( self
::isMultibyte( $str ) ) {
1415 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1417 return ucfirst( $str );
1420 return self
::isMultibyte( $str ) ?
mb_strtoupper( $str ) : strtoupper( $str );
1423 if ( self
::isMultibyte( $str ) ) {
1424 list( $wikiUpperChars ) = $this->getCaseMaps();
1425 $x = $first ?
'^' : '';
1426 return preg_replace_callback(
1427 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1428 array($this,"ucCallback"),
1432 return $first ?
ucfirst( $str ) : strtoupper( $str );
1437 function lcfirst( $str ) {
1438 if ( empty($str) ) return $str;
1439 if ( is_string( $str ) && ord($str[0]) < 128 ) {
1440 // editing string in place = cool
1441 $str[0]=strtolower($str[0]);
1444 else return self
::lc( $str, true );
1447 function lc( $str, $first = false ) {
1448 if ( function_exists( 'mb_strtolower' ) )
1450 if ( self
::isMultibyte( $str ) )
1451 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1453 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
1455 return self
::isMultibyte( $str ) ?
mb_strtolower( $str ) : strtolower( $str );
1457 if ( self
::isMultibyte( $str ) ) {
1458 list( , $wikiLowerChars ) = self
::getCaseMaps();
1459 $x = $first ?
'^' : '';
1460 return preg_replace_callback(
1461 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1462 array($this,"lcCallback"),
1466 return $first ?
strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
1469 function isMultibyte( $str ) {
1470 return (bool)preg_match( '/[\x80-\xff]/', $str );
1473 function ucwords($str) {
1474 if ( self
::isMultibyte( $str ) ) {
1475 $str = self
::lc($str);
1477 // regexp to find first letter in each word (i.e. after each space)
1478 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1480 // function to use to capitalize a single char
1481 if ( function_exists( 'mb_strtoupper' ) )
1482 return preg_replace_callback(
1484 array($this,"ucwordsCallbackMB"),
1488 return preg_replace_callback(
1490 array($this,"ucwordsCallbackWiki"),
1495 return ucwords( strtolower( $str ) );
1498 # capitalize words at word breaks
1499 function ucwordbreaks($str){
1500 if (self
::isMultibyte( $str ) ) {
1501 $str = self
::lc($str);
1503 // since \b doesn't work for UTF-8, we explicitely define word break chars
1504 $breaks= "[ \-\(\)\}\{\.,\?!]";
1506 // find first letter after word break
1507 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1509 if ( function_exists( 'mb_strtoupper' ) )
1510 return preg_replace_callback(
1512 array($this,"ucwordbreaksCallbackMB"),
1516 return preg_replace_callback(
1518 array($this,"ucwordsCallbackWiki"),
1523 return preg_replace_callback(
1524 '/\b([\w\x80-\xff]+)\b/',
1525 array($this,"ucwordbreaksCallbackAscii"),
1530 * Return a case-folded representation of $s
1532 * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
1533 * and $s2 are the same except for the case of their characters. It is not
1534 * necessary for the value returned to make sense when displayed.
1536 * Do *not* perform any other normalisation in this function. If a caller
1537 * uses this function when it should be using a more general normalisation
1538 * function, then fix the caller.
1540 function caseFold( $s ) {
1541 return $this->uc( $s );
1544 function checkTitleEncoding( $s ) {
1545 if( is_array( $s ) ) {
1546 wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
1548 # Check for non-UTF-8 URLs
1549 $ishigh = preg_match( '/[\x80-\xff]/', $s);
1550 if(!$ishigh) return $s;
1552 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1553 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
1554 if( $isutf8 ) return $s;
1556 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
1559 function fallback8bitEncoding() {
1561 return $this->fallback8bitEncoding
;
1565 * Some languages have special punctuation to strip out
1566 * or characters which need to be converted for MySQL's
1567 * indexing to grok it correctly. Make such changes here.
1569 * @param $string String
1572 function stripForSearch( $string ) {
1574 if ( $wgDBtype != 'mysql' ) {
1579 wfProfileIn( __METHOD__
);
1581 // MySQL fulltext index doesn't grok utf-8, so we
1582 // need to fold cases and convert to hex
1583 $out = preg_replace_callback(
1584 "/([\\xc0-\\xff][\\x80-\\xbf]*)/",
1585 array( $this, 'stripForSearchCallback' ),
1586 $this->lc( $string ) );
1588 // And to add insult to injury, the default indexing
1589 // ignores short words... Pad them so we can pass them
1590 // through without reconfiguring the server...
1591 $minLength = $this->minSearchLength();
1592 if( $minLength > 1 ) {
1594 $out = preg_replace(
1600 // Periods within things like hostnames and IP addresses
1601 // are also important -- we want a search for "example.com"
1602 // or "192.168.1.1" to work sanely.
1604 // MySQL's search seems to ignore them, so you'd match on
1605 // "example.wikipedia.com" and "192.168.83.1" as well.
1606 $out = preg_replace(
1611 wfProfileOut( __METHOD__
);
1616 * Armor a case-folded UTF-8 string to get through MySQL's
1617 * fulltext search without being mucked up by funny charset
1618 * settings or anything else of the sort.
1620 protected function stripForSearchCallback( $matches ) {
1621 return 'U8' . bin2hex( $matches[1] );
1625 * Check MySQL server's ft_min_word_len setting so we know
1626 * if we need to pad short words...
1628 protected function minSearchLength() {
1629 if( !isset( $this->minSearchLength
) ) {
1630 $sql = "show global variables like 'ft\\_min\\_word\\_len'";
1631 $dbr = wfGetDB( DB_SLAVE
);
1632 $result = $dbr->query( $sql );
1633 $row = $result->fetchObject();
1636 if( $row && $row->Variable_name
== 'ft_min_word_len' ) {
1637 $this->minSearchLength
= intval( $row->Value
);
1639 $this->minSearchLength
= 0;
1642 return $this->minSearchLength
;
1645 function convertForSearchResult( $termsArray ) {
1646 # some languages, e.g. Chinese, need to do a conversion
1647 # in order for search results to be displayed correctly
1652 * Get the first character of a string.
1657 function firstChar( $s ) {
1659 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1660 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
1662 if ( isset( $matches[1] ) ) {
1663 if ( strlen( $matches[1] ) != 3 ) {
1667 // Break down Hangul syllables to grab the first jamo
1668 $code = utf8ToCodepoint( $matches[1] );
1669 if ( $code < 0xac00 ||
0xd7a4 <= $code) {
1671 } elseif ( $code < 0xb098 ) {
1672 return "\xe3\x84\xb1";
1673 } elseif ( $code < 0xb2e4 ) {
1674 return "\xe3\x84\xb4";
1675 } elseif ( $code < 0xb77c ) {
1676 return "\xe3\x84\xb7";
1677 } elseif ( $code < 0xb9c8 ) {
1678 return "\xe3\x84\xb9";
1679 } elseif ( $code < 0xbc14 ) {
1680 return "\xe3\x85\x81";
1681 } elseif ( $code < 0xc0ac ) {
1682 return "\xe3\x85\x82";
1683 } elseif ( $code < 0xc544 ) {
1684 return "\xe3\x85\x85";
1685 } elseif ( $code < 0xc790 ) {
1686 return "\xe3\x85\x87";
1687 } elseif ( $code < 0xcc28 ) {
1688 return "\xe3\x85\x88";
1689 } elseif ( $code < 0xce74 ) {
1690 return "\xe3\x85\x8a";
1691 } elseif ( $code < 0xd0c0 ) {
1692 return "\xe3\x85\x8b";
1693 } elseif ( $code < 0xd30c ) {
1694 return "\xe3\x85\x8c";
1695 } elseif ( $code < 0xd558 ) {
1696 return "\xe3\x85\x8d";
1698 return "\xe3\x85\x8e";
1705 function initEncoding() {
1706 # Some languages may have an alternate char encoding option
1707 # (Esperanto X-coding, Japanese furigana conversion, etc)
1708 # If this language is used as the primary content language,
1709 # an override to the defaults can be set here on startup.
1712 function recodeForEdit( $s ) {
1713 # For some languages we'll want to explicitly specify
1714 # which characters make it into the edit box raw
1715 # or are converted in some way or another.
1716 # Note that if wgOutputEncoding is different from
1717 # wgInputEncoding, this text will be further converted
1718 # to wgOutputEncoding.
1719 global $wgEditEncoding;
1720 if( $wgEditEncoding == '' or
1721 $wgEditEncoding == 'UTF-8' ) {
1724 return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1728 function recodeInput( $s ) {
1729 # Take the previous into account.
1730 global $wgEditEncoding;
1731 if($wgEditEncoding != "") {
1732 $enc = $wgEditEncoding;
1736 if( $enc == 'UTF-8' ) {
1739 return $this->iconv( $enc, 'UTF-8', $s );
1744 * For right-to-left language support
1754 * A hidden direction mark (LRM or RLM), depending on the language direction
1758 function getDirMark() {
1759 return $this->isRTL() ?
"\xE2\x80\x8F" : "\xE2\x80\x8E";
1763 * An arrow, depending on the language direction
1767 function getArrow() {
1768 return $this->isRTL() ?
'←' : '→';
1772 * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1776 function linkPrefixExtension() {
1778 return $this->linkPrefixExtension
;
1781 function &getMagicWords() {
1783 return $this->magicWords
;
1786 # Fill a MagicWord object with data from here
1787 function getMagic( &$mw ) {
1788 if ( !$this->mMagicHookDone
) {
1789 $this->mMagicHookDone
= true;
1790 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions
, $this->getCode() ) );
1792 if ( isset( $this->mMagicExtensions
[$mw->mId
] ) ) {
1793 $rawEntry = $this->mMagicExtensions
[$mw->mId
];
1795 $magicWords =& $this->getMagicWords();
1796 if ( isset( $magicWords[$mw->mId
] ) ) {
1797 $rawEntry = $magicWords[$mw->mId
];
1799 # Fall back to English if local list is incomplete
1800 $magicWords =& Language
::getMagicWords();
1801 if ( !isset($magicWords[$mw->mId
]) ) {
1802 throw new MWException("Magic word '{$mw->mId}' not found" );
1804 $rawEntry = $magicWords[$mw->mId
];
1808 if( !is_array( $rawEntry ) ) {
1809 error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1811 $mw->mCaseSensitive
= $rawEntry[0];
1812 $mw->mSynonyms
= array_slice( $rawEntry, 1 );
1817 * Add magic words to the extension array
1819 function addMagicWordsByLang( $newWords ) {
1820 $code = $this->getCode();
1821 $fallbackChain = array();
1822 while ( $code && !in_array( $code, $fallbackChain ) ) {
1823 $fallbackChain[] = $code;
1824 $code = self
::getFallbackFor( $code );
1826 if ( !in_array( 'en', $fallbackChain ) ) {
1827 $fallbackChain[] = 'en';
1829 $fallbackChain = array_reverse( $fallbackChain );
1830 foreach ( $fallbackChain as $code ) {
1831 if ( isset( $newWords[$code] ) ) {
1832 $this->mMagicExtensions
= $newWords[$code] +
$this->mMagicExtensions
;
1838 * Get special page names, as an associative array
1839 * case folded alias => real name
1841 function getSpecialPageAliases() {
1844 // Cache aliases because it may be slow to load them
1845 if ( !isset( $this->mExtendedSpecialPageAliases
) ) {
1848 $this->mExtendedSpecialPageAliases
= $this->specialPageAliases
;
1850 global $wgExtensionAliasesFiles;
1851 foreach ( $wgExtensionAliasesFiles as $file ) {
1854 if ( !file_exists($file) )
1855 throw new MWException( "Aliases file does not exist: $file" );
1860 // Check the availability of aliases
1861 if ( !isset($aliases['en']) )
1862 throw new MWException( "Malformed aliases file: $file" );
1864 // Merge all aliases in fallback chain
1865 $code = $this->getCode();
1867 if ( !isset($aliases[$code]) ) continue;
1869 $aliases[$code] = $this->fixSpecialPageAliases( $aliases[$code] );
1870 /* Merge the aliases, THIS will break if there is special page name
1871 * which looks like a numerical key, thanks to PHP...
1872 * See the array_merge_recursive manual entry */
1873 $this->mExtendedSpecialPageAliases
= array_merge_recursive(
1874 $this->mExtendedSpecialPageAliases
, $aliases[$code] );
1876 } while ( $code = self
::getFallbackFor( $code ) );
1879 wfRunHooks( 'LanguageGetSpecialPageAliases',
1880 array( &$this->mExtendedSpecialPageAliases
, $this->getCode() ) );
1883 return $this->mExtendedSpecialPageAliases
;
1887 * Function to fix special page aliases. Will convert the first letter to
1888 * upper case and spaces to underscores. Can be given a full aliases array,
1889 * in which case it will recursively fix all aliases.
1891 public function fixSpecialPageAliases( $mixed ) {
1892 // Work recursively until in string level
1893 if ( is_array($mixed) ) {
1894 $callback = array( $this, 'fixSpecialPageAliases' );
1895 return array_map( $callback, $mixed );
1897 return str_replace( ' ', '_', $this->ucfirst( $mixed ) );
1901 * Italic is unsuitable for some languages
1903 * @param $text String: the text to be emphasized.
1906 function emphasize( $text ) {
1907 return "<em>$text</em>";
1911 * Normally we output all numbers in plain en_US style, that is
1912 * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
1913 * point twohundredthirtyfive. However this is not sutable for all
1914 * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
1915 * Icelandic just want to use commas instead of dots, and dots instead
1916 * of commas like "293.291,235".
1918 * An example of this function being called:
1920 * wfMsg( 'message', $wgLang->formatNum( $num ) )
1923 * See LanguageGu.php for the Gujarati implementation and
1924 * $separatorTransformTable on MessageIs.php for
1925 * the , => . and . => , implementation.
1927 * @todo check if it's viable to use localeconv() for the decimal
1929 * @param $number Mixed: the string to be formatted, should be an integer
1930 * or a floating point number.
1931 * @param $nocommafy Bool: set to true for special numbers like dates
1934 function formatNum( $number, $nocommafy = false ) {
1935 global $wgTranslateNumerals;
1937 $number = $this->commafy($number);
1938 $s = $this->separatorTransformTable();
1939 if ($s) { $number = strtr($number, $s); }
1942 if ($wgTranslateNumerals) {
1943 $s = $this->digitTransformTable();
1944 if ($s) { $number = strtr($number, $s); }
1950 function parseFormattedNumber( $number ) {
1951 $s = $this->digitTransformTable();
1952 if ($s) { $number = strtr($number, array_flip($s)); }
1954 $s = $this->separatorTransformTable();
1955 if ($s) { $number = strtr($number, array_flip($s)); }
1957 $number = strtr( $number, array (',' => '') );
1962 * Adds commas to a given number
1967 function commafy($_) {
1968 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
1971 function digitTransformTable() {
1973 return $this->digitTransformTable
;
1976 function separatorTransformTable() {
1978 return $this->separatorTransformTable
;
1983 * Take a list of strings and build a locale-friendly comma-separated
1984 * list, using the local comma-separator message.
1985 * The last two strings are chained with an "and".
1990 function listToText( $l ) {
1992 $m = count( $l ) - 1;
1994 return $l[0] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $l[1];
1997 for ( $i = $m; $i >= 0; $i-- ) {
2000 } else if( $i == $m - 1 ) {
2001 $s = $l[$i] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $s;
2003 $s = $l[$i] . $this->getMessageFromDB( 'comma-separator' ) . $s;
2011 * Take a list of strings and build a locale-friendly comma-separated
2012 * list, using the local comma-separator message.
2013 * @param $list array of strings to put in a comma list
2016 function commaList( $list ) {
2019 wfMsgExt( 'comma-separator', array( 'escapenoentities', 'language' => $this ) ) );
2023 * Same as commaList, but separate it with the pipe instead.
2024 * @param $list array of strings to put in a pipe list
2027 function pipeList( $list ) {
2030 wfMsgExt( 'pipe-separator', array( 'escapenoentities', 'language' => $this ) ) );
2034 * Truncate a string to a specified length in bytes, appending an optional
2035 * string (e.g. for ellipses)
2037 * The database offers limited byte lengths for some columns in the database;
2038 * multi-byte character sets mean we need to ensure that only whole characters
2039 * are included, otherwise broken characters can be passed to the user
2041 * If $length is negative, the string will be truncated from the beginning
2043 * @param $string String to truncate
2044 * @param $length Int: maximum length (excluding ellipses)
2045 * @param $ellipsis String to append to the truncated text
2048 function truncate( $string, $length, $ellipsis = "" ) {
2049 if( $length == 0 ) {
2052 if ( strlen( $string ) <= abs( $length ) ) {
2056 $string = substr( $string, 0, $length );
2057 $char = ord( $string[strlen( $string ) - 1] );
2059 if ($char >= 0xc0) {
2060 # We got the first byte only of a multibyte char; remove it.
2061 $string = substr( $string, 0, -1 );
2062 } elseif( $char >= 0x80 &&
2063 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
2064 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
2065 # We chopped in the middle of a character; remove it
2068 return $string . $ellipsis;
2070 $string = substr( $string, $length );
2071 $char = ord( $string[0] );
2072 if( $char >= 0x80 && $char < 0xc0 ) {
2073 # We chopped in the middle of a character; remove the whole thing
2074 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
2076 return $ellipsis . $string;
2081 * Grammatical transformations, needed for inflected languages
2082 * Invoked by putting {{grammar:case|word}} in a message
2084 * @param $word string
2085 * @param $case string
2088 function convertGrammar( $word, $case ) {
2089 global $wgGrammarForms;
2090 if ( isset($wgGrammarForms[$this->getCode()][$case][$word]) ) {
2091 return $wgGrammarForms[$this->getCode()][$case][$word];
2097 * Plural form transformations, needed for some languages.
2098 * For example, there are 3 form of plural in Russian and Polish,
2099 * depending on "count mod 10". See [[w:Plural]]
2100 * For English it is pretty simple.
2102 * Invoked by putting {{plural:count|wordform1|wordform2}}
2103 * or {{plural:count|wordform1|wordform2|wordform3}}
2105 * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
2107 * @param $count Integer: non-localized number
2108 * @param $forms Array: different plural forms
2109 * @return string Correct form of plural for $count in this language
2111 function convertPlural( $count, $forms ) {
2112 if ( !count($forms) ) { return ''; }
2113 $forms = $this->preConvertPlural( $forms, 2 );
2115 return ( $count == 1 ) ?
$forms[0] : $forms[1];
2119 * Checks that convertPlural was given an array and pads it to requested
2120 * amound of forms by copying the last one.
2122 * @param $count Integer: How many forms should there be at least
2123 * @param $forms Array of forms given to convertPlural
2124 * @return array Padded array of forms or an exception if not an array
2126 protected function preConvertPlural( /* Array */ $forms, $count ) {
2127 while ( count($forms) < $count ) {
2128 $forms[] = $forms[count($forms)-1];
2134 * For translaing of expiry times
2135 * @param $str String: the validated block time in English
2136 * @return Somehow translated block time
2137 * @see LanguageFi.php for example implementation
2139 function translateBlockExpiry( $str ) {
2141 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
2143 if ( $scBlockExpiryOptions == '-') {
2147 foreach (explode(',', $scBlockExpiryOptions) as $option) {
2148 if ( strpos($option, ":") === false )
2150 list($show, $value) = explode(":", $option);
2151 if ( strcmp ( $str, $value) == 0 ) {
2152 return htmlspecialchars( trim( $show ) );
2160 * languages like Chinese need to be segmented in order for the diff
2163 * @param $text String
2166 function segmentForDiff( $text ) {
2171 * and unsegment to show the result
2173 * @param $text String
2176 function unsegmentForDiff( $text ) {
2180 # convert text to different variants of a language.
2181 function convert( $text, $isTitle = false) {
2182 return $this->mConverter
->convert($text, $isTitle);
2185 # Convert text from within Parser
2186 function parserConvert( $text, &$parser ) {
2187 return $this->mConverter
->parserConvert( $text, $parser );
2190 # Check if this is a language with variants
2191 function hasVariants(){
2192 return sizeof($this->getVariants())>1;
2195 # Put custom tags (e.g. -{ }-) around math to prevent conversion
2196 function armourMath($text){
2197 return $this->mConverter
->armourMath($text);
2202 * Perform output conversion on a string, and encode for safe HTML output.
2203 * @param $text String
2204 * @param $isTitle Bool -- wtf?
2206 * @todo this should get integrated somewhere sane
2208 function convertHtml( $text, $isTitle = false ) {
2209 return htmlspecialchars( $this->convert( $text, $isTitle ) );
2212 function convertCategoryKey( $key ) {
2213 return $this->mConverter
->convertCategoryKey( $key );
2217 * get the list of variants supported by this langauge
2218 * see sample implementation in LanguageZh.php
2220 * @return array an array of language codes
2222 function getVariants() {
2223 return $this->mConverter
->getVariants();
2227 function getPreferredVariant( $fromUser = true ) {
2228 return $this->mConverter
->getPreferredVariant( $fromUser );
2232 * if a language supports multiple variants, it is
2233 * possible that non-existing link in one variant
2234 * actually exists in another variant. this function
2235 * tries to find it. See e.g. LanguageZh.php
2237 * @param $link String: the name of the link
2238 * @param $nt Mixed: the title object of the link
2239 * @return null the input parameters may be modified upon return
2241 function findVariantLink( &$link, &$nt, $forTemplate = false ) {
2242 $this->mConverter
->findVariantLink($link, $nt, $forTemplate );
2246 * If a language supports multiple variants, converts text
2247 * into an array of all possible variants of the text:
2248 * 'variant' => text in that variant
2251 function convertLinkToAllVariants($text){
2252 return $this->mConverter
->convertLinkToAllVariants($text);
2257 * returns language specific options used by User::getPageRenderHash()
2258 * for example, the preferred language variant
2262 function getExtraHashOptions() {
2263 return $this->mConverter
->getExtraHashOptions();
2267 * for languages that support multiple variants, the title of an
2268 * article may be displayed differently in different variants. this
2269 * function returns the apporiate title defined in the body of the article.
2273 function getParsedTitle() {
2274 return $this->mConverter
->getParsedTitle();
2278 * Enclose a string with the "no conversion" tag. This is used by
2279 * various functions in the Parser
2281 * @param $text String: text to be tagged for no conversion
2283 * @return string the tagged text
2285 function markNoConversion( $text, $noParse=false ) {
2286 return $this->mConverter
->markNoConversion( $text, $noParse );
2290 * A regular expression to match legal word-trailing characters
2291 * which should be merged onto a link of the form [[foo]]bar.
2295 function linkTrail() {
2297 return $this->linkTrail
;
2300 function getLangObj() {
2305 * Get the RFC 3066 code for this language object
2307 function getCode() {
2308 return $this->mCode
;
2311 function setCode( $code ) {
2312 $this->mCode
= $code;
2315 static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
2316 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
2319 static function getMessagesFileName( $code ) {
2321 return self
::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
2324 static function getClassFileName( $code ) {
2326 return self
::getFileName( "$IP/languages/classes/Language", $code, '.php' );
2329 static function getLocalisationArray( $code, $disableCache = false ) {
2330 self
::loadLocalisation( $code, $disableCache );
2331 return self
::$mLocalisationCache[$code];
2335 * Load localisation data for a given code into the static cache
2337 * @return array Dependencies, map of filenames to mtimes
2339 static function loadLocalisation( $code, $disableCache = false ) {
2340 static $recursionGuard = array();
2341 global $wgMemc, $wgEnableSerializedMessages, $wgCheckSerialized;
2344 throw new MWException( "Invalid language code requested" );
2347 if ( !$disableCache ) {
2348 # Try the per-process cache
2349 if ( isset( self
::$mLocalisationCache[$code] ) ) {
2350 return self
::$mLocalisationCache[$code]['deps'];
2353 wfProfileIn( __METHOD__
);
2355 # Try the serialized directory
2356 if( $wgEnableSerializedMessages ) {
2357 $cache = wfGetPrecompiledData( self
::getFileName( "Messages", $code, '.ser' ) );
2359 if ( $wgCheckSerialized && self
::isLocalisationOutOfDate( $cache ) ) {
2361 wfDebug( "Language::loadLocalisation(): precompiled data file for $code is out of date\n" );
2363 self
::$mLocalisationCache[$code] = $cache;
2364 wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" );
2365 wfProfileOut( __METHOD__
);
2366 return self
::$mLocalisationCache[$code]['deps'];
2371 # Try the global cache
2372 $memcKey = wfMemcKey('localisation', $code );
2373 $fbMemcKey = wfMemcKey('fallback', $cache['fallback'] );
2374 $cache = $wgMemc->get( $memcKey );
2376 if ( self
::isLocalisationOutOfDate( $cache ) ) {
2377 $wgMemc->delete( $memcKey );
2378 $wgMemc->delete( $fbMemcKey );
2380 wfDebug( "Language::loadLocalisation(): localisation cache for $code had expired\n" );
2382 self
::$mLocalisationCache[$code] = $cache;
2383 wfDebug( "Language::loadLocalisation(): got localisation for $code from cache\n" );
2384 wfProfileOut( __METHOD__
);
2385 return $cache['deps'];
2389 wfProfileIn( __METHOD__
);
2392 # Default fallback, may be overridden when the messages file is included
2393 if ( $code != 'en' ) {
2399 # Load the primary localisation from the source file
2400 $filename = self
::getMessagesFileName( $code );
2401 if ( !file_exists( $filename ) ) {
2402 wfDebug( "Language::loadLocalisation(): no localisation file for $code, using implicit fallback to en\n" );
2403 $cache = compact( self
::$mLocalisationKeys ); // Set correct fallback
2406 $deps = array( $filename => filemtime( $filename ) );
2407 require( $filename );
2408 $cache = compact( self
::$mLocalisationKeys );
2409 wfDebug( "Language::loadLocalisation(): got localisation for $code from source\n" );
2412 if ( !empty( $fallback ) ) {
2413 # Load the fallback localisation, with a circular reference guard
2414 if ( isset( $recursionGuard[$code] ) ) {
2415 throw new MWException( "Error: Circular fallback reference in language code $code" );
2417 $recursionGuard[$code] = true;
2418 $newDeps = self
::loadLocalisation( $fallback, $disableCache );
2419 unset( $recursionGuard[$code] );
2421 $secondary = self
::$mLocalisationCache[$fallback];
2422 $deps = array_merge( $deps, $newDeps );
2424 # Merge the fallback localisation with the current localisation
2425 foreach ( self
::$mLocalisationKeys as $key ) {
2426 if ( isset( $cache[$key] ) ) {
2427 if ( isset( $secondary[$key] ) ) {
2428 if ( in_array( $key, self
::$mMergeableMapKeys ) ) {
2429 $cache[$key] = $cache[$key] +
$secondary[$key];
2430 } elseif ( in_array( $key, self
::$mMergeableListKeys ) ) {
2431 $cache[$key] = array_merge( $secondary[$key], $cache[$key] );
2432 } elseif ( in_array( $key, self
::$mMergeableAliasListKeys ) ) {
2433 $cache[$key] = array_merge_recursive( $cache[$key], $secondary[$key] );
2437 $cache[$key] = $secondary[$key];
2441 # Merge bookstore lists if requested
2442 if ( !empty( $cache['bookstoreList']['inherit'] ) ) {
2443 $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] );
2445 if ( isset( $cache['bookstoreList']['inherit'] ) ) {
2446 unset( $cache['bookstoreList']['inherit'] );
2450 # Add dependencies to the cache entry
2451 $cache['deps'] = $deps;
2453 # Replace spaces with underscores in namespace names
2454 $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] );
2456 # And do the same for specialpage aliases. $page is an array.
2457 foreach ( $cache['specialPageAliases'] as &$page ) {
2458 $page = str_replace( ' ', '_', $page );
2460 # Decouple the reference to prevent accidental damage
2463 # Save to both caches
2464 self
::$mLocalisationCache[$code] = $cache;
2465 if ( !$disableCache ) {
2466 $wgMemc->set( $memcKey, $cache );
2467 $wgMemc->set( $fbMemcKey, (string) $cache['fallback'] );
2470 wfProfileOut( __METHOD__
);
2475 * Test if a given localisation cache is out of date with respect to the
2476 * source Messages files. This is done automatically for the global cache
2477 * in $wgMemc, but is only done on certain occasions for the serialized
2480 * @param $cache mixed Either a language code or a cache array
2482 static function isLocalisationOutOfDate( $cache ) {
2483 if ( !is_array( $cache ) ) {
2484 self
::loadLocalisation( $cache );
2485 $cache = self
::$mLocalisationCache[$cache];
2488 foreach ( $cache['deps'] as $file => $mtime ) {
2489 if ( !file_exists( $file ) ||
filemtime( $file ) > $mtime ) {
2498 * Get the fallback for a given language
2500 static function getFallbackFor( $code ) {
2502 if ( $code === 'en' ) return false;
2505 static $cache = array();
2507 if ( isset($cache[$code]) ) return $cache[$code];
2511 $memcKey = wfMemcKey( 'fallback', $code );
2512 $fbcode = $wgMemc->get( $memcKey );
2514 if ( is_string($fbcode) ) {
2515 // False is stored as a string to detect failures in memcache properly
2516 if ( $fbcode === '' ) $fbcode = false;
2518 // Update local cache and return
2519 $cache[$code] = $fbcode;
2523 // Nothing in caches, load and and update both caches
2524 self
::loadLocalisation( $code );
2525 $fbcode = self
::$mLocalisationCache[$code]['fallback'];
2527 $cache[$code] = $fbcode;
2528 $wgMemc->set( $memcKey, (string) $fbcode );
2534 * Get all messages for a given language
2536 static function getMessagesFor( $code ) {
2537 self
::loadLocalisation( $code );
2538 return self
::$mLocalisationCache[$code]['messages'];
2542 * Get a message for a given language
2544 static function getMessageFor( $key, $code ) {
2545 self
::loadLocalisation( $code );
2546 return isset( self
::$mLocalisationCache[$code]['messages'][$key] ) ? self
::$mLocalisationCache[$code]['messages'][$key] : null;
2550 * Load localisation data for this object
2553 if ( !$this->mLoaded
) {
2554 self
::loadLocalisation( $this->getCode() );
2555 $cache =& self
::$mLocalisationCache[$this->getCode()];
2556 foreach ( self
::$mLocalisationKeys as $key ) {
2557 $this->$key = $cache[$key];
2559 $this->mLoaded
= true;
2561 $this->fixUpSettings();
2566 * Do any necessary post-cache-load settings adjustment
2568 function fixUpSettings() {
2569 global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk,
2570 $wgNamespaceAliases, $wgAmericanDates;
2571 wfProfileIn( __METHOD__
);
2572 if ( $wgExtraNamespaces ) {
2573 $this->namespaceNames
= $wgExtraNamespaces +
$this->namespaceNames
;
2576 $this->namespaceNames
[NS_PROJECT
] = $wgMetaNamespace;
2577 if ( $wgMetaNamespaceTalk ) {
2578 $this->namespaceNames
[NS_PROJECT_TALK
] = $wgMetaNamespaceTalk;
2580 $talk = $this->namespaceNames
[NS_PROJECT_TALK
];
2581 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
2583 # Allow grammar transformations
2584 # Allowing full message-style parsing would make simple requests
2585 # such as action=raw much more expensive than they need to be.
2586 # This will hopefully cover most cases.
2587 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
2588 array( &$this, 'replaceGrammarInNamespace' ), $talk );
2589 $talk = str_replace( ' ', '_', $talk );
2590 $this->namespaceNames
[NS_PROJECT_TALK
] = $talk;
2593 # The above mixing may leave namespaces out of canonical order.
2594 # Re-order by namespace ID number...
2595 ksort( $this->namespaceNames
);
2597 # Put namespace names and aliases into a hashtable.
2598 # If this is too slow, then we should arrange it so that it is done
2599 # before caching. The catch is that at pre-cache time, the above
2600 # class-specific fixup hasn't been done.
2601 $this->mNamespaceIds
= array();
2602 foreach ( $this->namespaceNames
as $index => $name ) {
2603 $this->mNamespaceIds
[$this->lc($name)] = $index;
2605 if ( $this->namespaceAliases
) {
2606 foreach ( $this->namespaceAliases
as $name => $index ) {
2607 $this->mNamespaceIds
[$this->lc($name)] = $index;
2610 if ( $wgNamespaceAliases ) {
2611 foreach ( $wgNamespaceAliases as $name => $index ) {
2612 $this->mNamespaceIds
[$this->lc($name)] = $index;
2616 if ( $this->defaultDateFormat
== 'dmy or mdy' ) {
2617 $this->defaultDateFormat
= $wgAmericanDates ?
'mdy' : 'dmy';
2619 wfProfileOut( __METHOD__
);
2622 function replaceGrammarInNamespace( $m ) {
2623 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
2626 static function getCaseMaps() {
2627 static $wikiUpperChars, $wikiLowerChars;
2628 if ( isset( $wikiUpperChars ) ) {
2629 return array( $wikiUpperChars, $wikiLowerChars );
2632 wfProfileIn( __METHOD__
);
2633 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
2634 if ( $arr === false ) {
2635 throw new MWException(
2636 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
2639 wfProfileOut( __METHOD__
);
2640 return array( $wikiUpperChars, $wikiLowerChars );
2643 function formatTimePeriod( $seconds ) {
2644 if ( $seconds < 10 ) {
2645 return $this->formatNum( sprintf( "%.1f", $seconds ) ) . wfMsg( 'seconds-abbrev' );
2646 } elseif ( $seconds < 60 ) {
2647 return $this->formatNum( round( $seconds ) ) . wfMsg( 'seconds-abbrev' );
2648 } elseif ( $seconds < 3600 ) {
2649 return $this->formatNum( floor( $seconds / 60 ) ) . wfMsg( 'minutes-abbrev' ) .
2650 $this->formatNum( round( fmod( $seconds, 60 ) ) ) . wfMsg( 'seconds-abbrev' );
2652 $hours = floor( $seconds / 3600 );
2653 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
2654 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
2655 return $this->formatNum( $hours ) . wfMsg( 'hours-abbrev' ) .
2656 $this->formatNum( $minutes ) . wfMsg( 'minutes-abbrev' ) .
2657 $this->formatNum( $secondsPart ) . wfMsg( 'seconds-abbrev' );
2661 function formatBitrate( $bps ) {
2662 $units = array( 'bps', 'kbps', 'Mbps', 'Gbps' );
2664 return $this->formatNum( $bps ) . $units[0];
2666 $unitIndex = floor( log10( $bps ) / 3 );
2667 $mantissa = $bps / pow( 1000, $unitIndex );
2668 if ( $mantissa < 10 ) {
2669 $mantissa = round( $mantissa, 1 );
2671 $mantissa = round( $mantissa );
2673 return $this->formatNum( $mantissa ) . $units[$unitIndex];
2677 * Format a size in bytes for output, using an appropriate
2678 * unit (B, KB, MB or GB) according to the magnitude in question
2680 * @param $size Size to format
2681 * @return string Plain text (not HTML)
2683 function formatSize( $size ) {
2684 // For small sizes no decimal places necessary
2686 if( $size > 1024 ) {
2687 $size = $size / 1024;
2688 if( $size > 1024 ) {
2689 $size = $size / 1024;
2690 // For MB and bigger two decimal places are smarter
2692 if( $size > 1024 ) {
2693 $size = $size / 1024;
2694 $msg = 'size-gigabytes';
2696 $msg = 'size-megabytes';
2699 $msg = 'size-kilobytes';
2702 $msg = 'size-bytes';
2704 $size = round( $size, $round );
2705 $text = $this->getMessageFromDB( $msg );
2706 return str_replace( '$1', $this->formatNum( $size ), $text );