]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - languages/Language.php
MediaWiki 1.11.0
[autoinstallsdev/mediawiki.git] / languages / Language.php
1 <?php
2 /**
3  * @addtogroup Language
4  */
5
6 if( !defined( 'MEDIAWIKI' ) ) {
7         echo "This file is part of MediaWiki, it is not a valid entry point.\n";
8         exit( 1 );
9 }
10
11 #
12 # In general you should not make customizations in these language files
13 # directly, but should use the MediaWiki: special namespace to customize
14 # user interface messages through the wiki.
15 # See http://meta.wikipedia.org/wiki/MediaWiki_namespace
16 #
17 # NOTE TO TRANSLATORS: Do not copy this whole file when making translations!
18 # A lot of common constants and a base class with inheritable methods are
19 # defined here, which should not be redefined. See the other LanguageXx.php
20 # files for examples.
21 #
22
23 # Read language names
24 global $wgLanguageNames;
25 require_once( dirname(__FILE__) . '/Names.php' ) ;
26
27 global $wgInputEncoding, $wgOutputEncoding;
28
29 /**
30  * These are always UTF-8, they exist only for backwards compatibility
31  */
32 $wgInputEncoding    = "UTF-8";
33 $wgOutputEncoding       = "UTF-8";
34
35 if( function_exists( 'mb_strtoupper' ) ) {
36         mb_internal_encoding('UTF-8');
37 }
38
39 /* a fake language converter */
40 class FakeConverter {
41         var $mLang;
42         function FakeConverter($langobj) {$this->mLang = $langobj;}
43         function convert($t, $i) {return $t;}
44         function parserConvert($t, $p) {return $t;}
45         function getVariants() { return array( $this->mLang->getCode() ); }
46         function getPreferredVariant() {return $this->mLang->getCode(); }
47         function findVariantLink(&$l, &$n) {}
48         function getExtraHashOptions() {return '';}
49         function getParsedTitle() {return '';}
50         function markNoConversion($text, $noParse=false) {return $text;}
51         function convertCategoryKey( $key ) {return $key; }
52         function convertLinkToAllVariants($text){ return array( $this->mLang->getCode() => $text); }
53         function armourMath($text){ return $text; }
54 }
55
56 #--------------------------------------------------------------------------
57 # Internationalisation code
58 #--------------------------------------------------------------------------
59
60 class Language {
61         var $mConverter, $mVariants, $mCode, $mLoaded = false;
62         var $mMagicExtensions = array(), $mMagicHookDone = false;
63
64         static public $mLocalisationKeys = array( 'fallback', 'namespaceNames',
65                 'skinNames', 'mathNames', 
66                 'bookstoreList', 'magicWords', 'messages', 'rtl', 'digitTransformTable', 
67                 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
68                 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases', 
69                 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap', 
70                 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases' );
71
72         static public $mMergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames', 
73                 'dateFormats', 'defaultUserOptionOverrides', 'magicWords' );
74
75         static public $mMergeableListKeys = array( 'extraUserToggles' );
76
77         static public $mMergeableAliasListKeys = array( 'specialPageAliases' );
78
79         static public $mLocalisationCache = array();
80
81         static public $mWeekdayMsgs = array(
82                 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
83                 'friday', 'saturday'
84         );
85
86         static public $mWeekdayAbbrevMsgs = array(
87                 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
88         );
89
90         static public $mMonthMsgs = array(
91                 'january', 'february', 'march', 'april', 'may_long', 'june',
92                 'july', 'august', 'september', 'october', 'november',
93                 'december'
94         );
95         static public $mMonthGenMsgs = array(
96                 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
97                 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
98                 'december-gen'
99         );
100         static public $mMonthAbbrevMsgs = array(
101                 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
102                 'sep', 'oct', 'nov', 'dec'
103         );
104
105         /**
106          * Create a language object for a given language code
107          */
108         static function factory( $code ) {
109                 global $IP;
110                 static $recursionLevel = 0;
111
112                 if ( $code == 'en' ) {
113                         $class = 'Language';
114                 } else {
115                         $class = 'Language' . str_replace( '-', '_', ucfirst( $code ) );
116                         // Preload base classes to work around APC/PHP5 bug
117                         if ( file_exists( "$IP/languages/classes/$class.deps.php" ) ) {
118                                 include_once("$IP/languages/classes/$class.deps.php");
119                         }
120                         if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
121                                 include_once("$IP/languages/classes/$class.php");
122                         }
123                 }
124
125                 if ( $recursionLevel > 5 ) {
126                         throw new MWException( "Language fallback loop detected when creating class $class\n" );
127                 }       
128
129                 if( ! class_exists( $class ) ) {
130                         $fallback = Language::getFallbackFor( $code );
131                         ++$recursionLevel;
132                         $lang = Language::factory( $fallback );
133                         --$recursionLevel;
134                         $lang->setCode( $code );
135                 } else {
136                         $lang = new $class;
137                 }
138
139                 return $lang;
140         }
141
142         function __construct() {
143                 $this->mConverter = new FakeConverter($this);
144                 // Set the code to the name of the descendant
145                 if ( get_class( $this ) == 'Language' ) {
146                         $this->mCode = 'en';
147                 } else {
148                         $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
149                 }
150         }
151
152         /**
153          * Hook which will be called if this is the content language.
154          * Descendants can use this to register hook functions or modify globals
155          */
156         function initContLang() {}
157
158         /**
159          * @deprecated
160          * @return array
161          */
162         function getDefaultUserOptions() {
163                 return User::getDefaultOptions();
164         }
165
166         function getFallbackLanguageCode() {
167                 $this->load();
168                 return $this->fallback;
169         }
170
171         /**
172          * Exports $wgBookstoreListEn
173          * @return array
174          */
175         function getBookstoreList() {
176                 $this->load();
177                 return $this->bookstoreList;
178         }
179
180         /**
181          * @return array
182          */
183         function getNamespaces() {
184                 $this->load();
185                 return $this->namespaceNames;
186         }
187
188         /**
189          * A convenience function that returns the same thing as
190          * getNamespaces() except with the array values changed to ' '
191          * where it found '_', useful for producing output to be displayed
192          * e.g. in <select> forms.
193          *
194          * @return array
195          */
196         function getFormattedNamespaces() {
197                 $ns = $this->getNamespaces();
198                 foreach($ns as $k => $v) {
199                         $ns[$k] = strtr($v, '_', ' ');
200                 }
201                 return $ns;
202         }
203
204         /**
205          * Get a namespace value by key
206          * <code>
207          * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
208          * echo $mw_ns; // prints 'MediaWiki'
209          * </code>
210          *
211          * @param int $index the array key of the namespace to return
212          * @return mixed, string if the namespace value exists, otherwise false
213          */
214         function getNsText( $index ) {
215                 $ns = $this->getNamespaces();
216                 return isset( $ns[$index] ) ? $ns[$index] : false;
217         }
218
219         /**
220          * A convenience function that returns the same thing as
221          * getNsText() except with '_' changed to ' ', useful for
222          * producing output.
223          *
224          * @return array
225          */
226         function getFormattedNsText( $index ) {
227                 $ns = $this->getNsText( $index );
228                 return strtr($ns, '_', ' ');
229         }
230
231         /**
232          * Get a namespace key by value, case insensitive.
233          * Only matches namespace names for the current language, not the
234          * canonical ones defined in Namespace.php.
235          *
236          * @param string $text
237          * @return mixed An integer if $text is a valid value otherwise false
238          */
239         function getLocalNsIndex( $text ) {
240                 $this->load();
241                 $lctext = $this->lc($text);
242                 return isset( $this->mNamespaceIds[$lctext] ) ? $this->mNamespaceIds[$lctext] : false;
243         }
244
245         /**
246          * Get a namespace key by value, case insensitive.  Canonical namespace
247          * names override custom ones defined for the current language.
248          *
249          * @param string $text
250          * @return mixed An integer if $text is a valid value otherwise false
251          */
252         function getNsIndex( $text ) {
253                 $this->load();
254                 $lctext = $this->lc($text);
255                 if( ( $ns = Namespace::getCanonicalIndex( $lctext ) ) !== null ) return $ns;
256                 return isset( $this->mNamespaceIds[$lctext] ) ? $this->mNamespaceIds[$lctext] : false;
257         }
258
259         /**
260          * short names for language variants used for language conversion links.
261          *
262          * @param string $code
263          * @return string
264          */
265         function getVariantname( $code ) {
266                 return $this->getMessageFromDB( "variantname-$code" );
267         }
268
269         function specialPage( $name ) {
270                 $aliases = $this->getSpecialPageAliases();
271                 if ( isset( $aliases[$name][0] ) ) {
272                         $name = $aliases[$name][0];
273                 }
274                 return $this->getNsText(NS_SPECIAL) . ':' . $name;
275         }
276
277         function getQuickbarSettings() {
278                 return array(
279                         $this->getMessage( 'qbsettings-none' ),
280                         $this->getMessage( 'qbsettings-fixedleft' ),
281                         $this->getMessage( 'qbsettings-fixedright' ),
282                         $this->getMessage( 'qbsettings-floatingleft' ),
283                         $this->getMessage( 'qbsettings-floatingright' )
284                 );
285         }
286
287         function getSkinNames() {
288                 $this->load();
289                 return $this->skinNames;
290         }
291
292         function getMathNames() {
293                 $this->load();
294                 return $this->mathNames;
295         }
296
297         function getDatePreferences() {
298                 $this->load();
299                 return $this->datePreferences;
300         }
301         
302         function getDateFormats() {
303                 $this->load();
304                 return $this->dateFormats;
305         }
306
307         function getDefaultDateFormat() {
308                 $this->load();
309                 return $this->defaultDateFormat;
310         }
311
312         function getDatePreferenceMigrationMap() {
313                 $this->load();
314                 return $this->datePreferenceMigrationMap;
315         }
316
317         function getDefaultUserOptionOverrides() {
318                 $this->load();
319                 return $this->defaultUserOptionOverrides;
320         }
321
322         function getExtraUserToggles() {
323                 $this->load();
324                 return $this->extraUserToggles;
325         }
326
327         function getUserToggle( $tog ) {
328                 return $this->getMessageFromDB( "tog-$tog" );
329         }
330
331         /**
332          * Get language names, indexed by code.
333          * If $customisedOnly is true, only returns codes with a messages file
334          */
335         public static function getLanguageNames( $customisedOnly = false ) {
336                 global $wgLanguageNames;
337                 if ( !$customisedOnly ) {
338                         return $wgLanguageNames;
339                 }
340                 
341                 global $IP;
342                 $names = array();
343                 $dir = opendir( "$IP/languages/messages" );
344                 while( false !== ( $file = readdir( $dir ) ) ) {
345                         $m = array();
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];
350                                 }
351                         }
352                 }
353                 closedir( $dir );
354                 return $names;
355         }
356
357         /**
358          * Ugly hack to get a message maybe from the MediaWiki namespace, if this
359          * language object is the content or user language.
360          */
361         function getMessageFromDB( $msg ) {
362                 global $wgContLang, $wgLang;
363                 if ( $wgContLang->getCode() == $this->getCode() ) {
364                         # Content language
365                         return wfMsgForContent( $msg );
366                 } elseif ( $wgLang->getCode() == $this->getCode() ) {
367                         # User language
368                         return wfMsg( $msg );
369                 } else {
370                         # Neither, get from localisation
371                         return $this->getMessage( $msg );
372                 }
373         }
374
375         function getLanguageName( $code ) {
376                 global $wgLanguageNames;
377                 if ( ! array_key_exists( $code, $wgLanguageNames ) ) {
378                         return '';
379                 }
380                 return $wgLanguageNames[$code];
381         }
382
383         function getMonthName( $key ) {
384                 return $this->getMessageFromDB( self::$mMonthMsgs[$key-1] );
385         }
386
387         function getMonthNameGen( $key ) {
388                 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key-1] );
389         }
390
391         function getMonthAbbreviation( $key ) {
392                 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key-1] );
393         }
394
395         function getWeekdayName( $key ) {
396                 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key-1] );
397         }
398
399         function getWeekdayAbbreviation( $key ) {
400                 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key-1] );
401         }
402
403         /**
404          * Used by date() and time() to adjust the time output.
405          * @public
406          * @param int   $ts the time in date('YmdHis') format
407          * @param mixed $tz adjust the time by this amount (default false,
408          *                  mean we get user timecorrection setting)
409          * @return int
410          */
411         function userAdjust( $ts, $tz = false ) {
412                 global $wgUser, $wgLocalTZoffset;
413
414                 if (!$tz) {
415                         $tz = $wgUser->getOption( 'timecorrection' );
416                 }
417
418                 # minutes and hours differences:
419                 $minDiff = 0;
420                 $hrDiff  = 0;
421
422                 if ( $tz === '' ) {
423                         # Global offset in minutes.
424                         if( isset($wgLocalTZoffset) ) {
425                                 if( $wgLocalTZoffset >= 0 ) {
426                                         $hrDiff = floor($wgLocalTZoffset / 60);
427                                 } else {
428                                         $hrDiff = ceil($wgLocalTZoffset / 60);
429                                 }
430                                 $minDiff = $wgLocalTZoffset % 60;
431                         }
432                 } elseif ( strpos( $tz, ':' ) !== false ) {
433                         $tzArray = explode( ':', $tz );
434                         $hrDiff = intval($tzArray[0]);
435                         $minDiff = intval($hrDiff < 0 ? -$tzArray[1] : $tzArray[1]);
436                 } else {
437                         $hrDiff = intval( $tz );
438                 }
439
440                 # No difference ? Return time unchanged
441                 if ( 0 == $hrDiff && 0 == $minDiff ) { return $ts; }
442
443                 wfSuppressWarnings(); // E_STRICT system time bitching
444                 # Generate an adjusted date
445                 $t = mktime( (
446                   (int)substr( $ts, 8, 2) ) + $hrDiff, # Hours
447                   (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
448                   (int)substr( $ts, 12, 2 ), # Seconds
449                   (int)substr( $ts, 4, 2 ), # Month
450                   (int)substr( $ts, 6, 2 ), # Day
451                   (int)substr( $ts, 0, 4 ) ); #Year
452                 
453                 $date = date( 'YmdHis', $t );
454                 wfRestoreWarnings();
455                 
456                 return $date;
457         }
458
459         /**
460          * This is a workalike of PHP's date() function, but with better
461          * internationalisation, a reduced set of format characters, and a better 
462          * escaping format.
463          *
464          * Supported format characters are dDjlNwzWFmMntLYyaAgGhHiscrU. See the 
465          * PHP manual for definitions. There are a number of extensions, which 
466          * start with "x":
467          *
468          *    xn   Do not translate digits of the next numeric format character
469          *    xN   Toggle raw digit (xn) flag, stays set until explicitly unset
470          *    xr   Use roman numerals for the next numeric format character
471          *    xx   Literal x
472          *    xg   Genitive month name
473          *
474          * Characters enclosed in double quotes will be considered literal (with
475          * the quotes themselves removed). Unmatched quotes will be considered
476          * literal quotes. Example:
477          *
478          * "The month is" F       => The month is January
479          * i's"                   => 20'11"
480          *
481          * Backslash escaping is also supported.
482          *
483          * Input timestamp is assumed to be pre-normalized to the desired local
484          * time zone, if any.
485          * 
486          * @param string $format
487          * @param string $ts 14-character timestamp
488          *      YYYYMMDDHHMMSS
489          *      01234567890123
490          */
491         function sprintfDate( $format, $ts ) {
492                 $s = '';
493                 $raw = false;
494                 $roman = false;
495                 $unix = false;
496                 $rawToggle = false;
497                 for ( $p = 0; $p < strlen( $format ); $p++ ) {
498                         $num = false;
499                         $code = $format[$p];
500                         if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
501                                 $code .= $format[++$p];
502                         }
503                         
504                         switch ( $code ) {
505                                 case 'xx':
506                                         $s .= 'x';
507                                         break;
508                                 case 'xn':
509                                         $raw = true;
510                                         break;
511                                 case 'xN':
512                                         $rawToggle = !$rawToggle;
513                                         break;
514                                 case 'xr':
515                                         $roman = true;
516                                         break;
517                                 case 'xg':
518                                         $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
519                                         break;
520                                 case 'd':
521                                         $num = substr( $ts, 6, 2 );
522                                         break;
523                                 case 'D':
524                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
525                                         $s .= $this->getWeekdayAbbreviation( gmdate( 'w', $unix ) + 1 );
526                                         break;
527                                 case 'j':
528                                         $num = intval( substr( $ts, 6, 2 ) );
529                                         break;
530                                 case 'l':
531                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
532                                         $s .= $this->getWeekdayName( gmdate( 'w', $unix ) + 1 );
533                                         break;
534                                 case 'N':
535                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
536                                         $w = gmdate( 'w', $unix );
537                                         $num = $w ? $w : 7;
538                                         break;
539                                 case 'w':
540                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
541                                         $num = gmdate( 'w', $unix );
542                                         break;
543                                 case 'z':
544                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
545                                         $num = gmdate( 'z', $unix );
546                                         break;
547                                 case 'W':
548                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
549                                         $num = gmdate( 'W', $unix );
550                                         break;                                  
551                                 case 'F':
552                                         $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
553                                         break;
554                                 case 'm':
555                                         $num = substr( $ts, 4, 2 );
556                                         break;
557                                 case 'M':
558                                         $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
559                                         break;
560                                 case 'n':
561                                         $num = intval( substr( $ts, 4, 2 ) );
562                                         break;
563                                 case 't':
564                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
565                                         $num = gmdate( 't', $unix );
566                                         break;
567                                 case 'L':
568                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
569                                         $num = gmdate( 'L', $unix );
570                                         break;                                  
571                                 case 'Y':
572                                         $num = substr( $ts, 0, 4 );
573                                         break;
574                                 case 'y':
575                                         $num = substr( $ts, 2, 2 );
576                                         break;
577                                 case 'a':
578                                         $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
579                                         break;
580                                 case 'A':
581                                         $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
582                                         break;
583                                 case 'g':
584                                         $h = substr( $ts, 8, 2 );
585                                         $num = $h % 12 ? $h % 12 : 12;
586                                         break;
587                                 case 'G':
588                                         $num = intval( substr( $ts, 8, 2 ) );
589                                         break;
590                                 case 'h':
591                                         $h = substr( $ts, 8, 2 );
592                                         $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
593                                         break;                                  
594                                 case 'H':
595                                         $num = substr( $ts, 8, 2 );
596                                         break;
597                                 case 'i':
598                                         $num = substr( $ts, 10, 2 );
599                                         break;
600                                 case 's':
601                                         $num = substr( $ts, 12, 2 );
602                                         break;
603                                 case 'c':
604                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
605                                         $s .= gmdate( 'c', $unix );
606                                         break;
607                                 case 'r':
608                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
609                                         $s .= gmdate( 'r', $unix );
610                                         break;
611                                 case 'U':
612                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
613                                         $num = $unix;
614                                         break;
615                                 case '\\':
616                                         # Backslash escaping
617                                         if ( $p < strlen( $format ) - 1 ) {
618                                                 $s .= $format[++$p];
619                                         } else {
620                                                 $s .= '\\';
621                                         }
622                                         break;
623                                 case '"':
624                                         # Quoted literal
625                                         if ( $p < strlen( $format ) - 1 ) {
626                                                 $endQuote = strpos( $format, '"', $p + 1 );
627                                                 if ( $endQuote === false ) {
628                                                         # No terminating quote, assume literal "
629                                                         $s .= '"';
630                                                 } else {
631                                                         $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
632                                                         $p = $endQuote;
633                                                 }
634                                         } else {
635                                                 # Quote at end of string, assume literal "
636                                                 $s .= '"';
637                                         }
638                                         break;
639                                 default:
640                                         $s .= $format[$p];
641                         }
642                         if ( $num !== false ) {
643                                 if ( $rawToggle || $raw ) {
644                                         $s .= $num;
645                                         $raw = false;
646                                 } elseif ( $roman ) {
647                                         $s .= self::romanNumeral( $num );
648                                         $roman = false;
649                                 } else {
650                                         $s .= $this->formatNum( $num, true );
651                                 }
652                                 $num = false;
653                         }
654                 }
655                 return $s;
656         }
657
658         /**
659          * Roman number formatting up to 3000
660          */
661         static function romanNumeral( $num ) {
662                 static $table = array(
663                         array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
664                         array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
665                         array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
666                         array( '', 'M', 'MM', 'MMM' )
667                 );
668                         
669                 $num = intval( $num );
670                 if ( $num > 3000 || $num <= 0 ) {
671                         return $num;
672                 }
673
674                 $s = '';
675                 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
676                         if ( $num >= $pow10 ) {
677                                 $s .= $table[$i][floor($num / $pow10)];
678                         }
679                         $num = $num % $pow10;
680                 }
681                 return $s;
682         }
683
684         /**
685          * This is meant to be used by time(), date(), and timeanddate() to get
686          * the date preference they're supposed to use, it should be used in
687          * all children.
688          *
689          *<code>
690          * function timeanddate([...], $format = true) {
691          *      $datePreference = $this->dateFormat($format);
692          * [...]
693          * }
694          *</code>
695          *
696          * @param mixed $usePrefs: if true, the user's preference is used
697          *                         if false, the site/language default is used
698          *                         if int/string, assumed to be a format.
699          * @return string
700          */
701         function dateFormat( $usePrefs = true ) {
702                 global $wgUser;
703
704                 if( is_bool( $usePrefs ) ) {
705                         if( $usePrefs ) {
706                                 $datePreference = $wgUser->getDatePreference();
707                         } else {
708                                 $options = User::getDefaultOptions();
709                                 $datePreference = (string)$options['date'];
710                         }
711                 } else {
712                         $datePreference = (string)$usePrefs;
713                 }
714
715                 // return int
716                 if( $datePreference == '' ) {
717                         return 'default';
718                 }
719                 
720                 return $datePreference;
721         }
722
723         /**
724          * @public
725          * @param mixed  $ts the time format which needs to be turned into a
726          *               date('YmdHis') format with wfTimestamp(TS_MW,$ts)
727          * @param bool   $adj whether to adjust the time output according to the
728          *               user configured offset ($timecorrection)
729          * @param mixed  $format true to use user's date format preference
730          * @param string $timecorrection the time offset as returned by
731          *               validateTimeZone() in Special:Preferences
732          * @return string
733          */
734         function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
735                 $this->load();
736                 if ( $adj ) { 
737                         $ts = $this->userAdjust( $ts, $timecorrection ); 
738                 }
739
740                 $pref = $this->dateFormat( $format );
741                 if( $pref == 'default' || !isset( $this->dateFormats["$pref date"] ) ) {
742                         $pref = $this->defaultDateFormat;
743                 }
744                 return $this->sprintfDate( $this->dateFormats["$pref date"], $ts );
745         }
746
747         /**
748         * @public
749         * @param mixed  $ts the time format which needs to be turned into a
750         *               date('YmdHis') format with wfTimestamp(TS_MW,$ts)
751         * @param bool   $adj whether to adjust the time output according to the
752         *               user configured offset ($timecorrection)
753         * @param mixed  $format true to use user's date format preference
754         * @param string $timecorrection the time offset as returned by
755         *               validateTimeZone() in Special:Preferences
756         * @return string
757         */
758         function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
759                 $this->load();
760                 if ( $adj ) { 
761                         $ts = $this->userAdjust( $ts, $timecorrection ); 
762                 }
763
764                 $pref = $this->dateFormat( $format );
765                 if( $pref == 'default' || !isset( $this->dateFormats["$pref time"] ) ) {
766                         $pref = $this->defaultDateFormat;
767                 }
768                 return $this->sprintfDate( $this->dateFormats["$pref time"], $ts );
769         }
770
771         /**
772         * @public
773         * @param mixed  $ts the time format which needs to be turned into a
774         *               date('YmdHis') format with wfTimestamp(TS_MW,$ts)
775         * @param bool   $adj whether to adjust the time output according to the
776         *               user configured offset ($timecorrection)
777
778         * @param mixed  $format what format to return, if it's false output the
779         *               default one (default true)
780         * @param string $timecorrection the time offset as returned by
781         *               validateTimeZone() in Special:Preferences
782         * @return string
783         */
784         function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) {
785                 $this->load();
786
787                 $ts = wfTimestamp( TS_MW, $ts );
788
789                 if ( $adj ) { 
790                         $ts = $this->userAdjust( $ts, $timecorrection ); 
791                 }
792
793                 $pref = $this->dateFormat( $format );
794                 if( $pref == 'default' || !isset( $this->dateFormats["$pref both"] ) ) {
795                         $pref = $this->defaultDateFormat;
796                 }
797
798                 return $this->sprintfDate( $this->dateFormats["$pref both"], $ts );
799         }
800
801         function getMessage( $key ) {
802                 $this->load();
803                 return isset( $this->messages[$key] ) ? $this->messages[$key] : null;
804         }
805
806         function getAllMessages() {
807                 $this->load();
808                 return $this->messages;
809         }
810
811         function iconv( $in, $out, $string ) {
812                 # For most languages, this is a wrapper for iconv
813                 return iconv( $in, $out . '//IGNORE', $string );
814         }
815
816         // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
817         function ucwordbreaksCallbackAscii($matches){
818                 return $this->ucfirst($matches[1]);
819         }
820         
821         function ucwordbreaksCallbackMB($matches){
822                 return mb_strtoupper($matches[0]);
823         }
824         
825         function ucCallback($matches){
826                 list( $wikiUpperChars ) = self::getCaseMaps();
827                 return strtr( $matches[1], $wikiUpperChars );
828         }
829         
830         function lcCallback($matches){
831                 list( , $wikiLowerChars ) = self::getCaseMaps();
832                 return strtr( $matches[1], $wikiLowerChars );
833         }
834         
835         function ucwordsCallbackMB($matches){
836                 return mb_strtoupper($matches[0]);
837         }
838         
839         function ucwordsCallbackWiki($matches){
840                 list( $wikiUpperChars ) = self::getCaseMaps();
841                 return strtr( $matches[0], $wikiUpperChars );
842         }
843
844         function ucfirst( $str ) {
845                 return self::uc( $str, true );
846         }
847
848         function uc( $str, $first = false ) {
849                 if ( function_exists( 'mb_strtoupper' ) ) {
850                         if ( $first ) {
851                                 if ( self::isMultibyte( $str ) ) {
852                                         return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
853                                 } else {
854                                         return ucfirst( $str );
855                                 }
856                         } else {
857                                 return self::isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
858                         }
859                 } else {
860                         if ( self::isMultibyte( $str ) ) {
861                                 list( $wikiUpperChars ) = $this->getCaseMaps();
862                                 $x = $first ? '^' : '';
863                                 return preg_replace_callback(
864                                         "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
865                                         array($this,"ucCallback"),
866                                         $str
867                                 );
868                         } else {
869                                 return $first ? ucfirst( $str ) : strtoupper( $str );
870                         }
871                 }
872         }
873         
874         function lcfirst( $str ) {
875                 return self::lc( $str, true );
876         }
877
878         function lc( $str, $first = false ) {
879                 if ( function_exists( 'mb_strtolower' ) )
880                         if ( $first )
881                                 if ( self::isMultibyte( $str ) )
882                                         return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
883                                 else
884                                         return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
885                         else
886                                 return self::isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
887                 else
888                         if ( self::isMultibyte( $str ) ) {
889                                 list( , $wikiLowerChars ) = self::getCaseMaps();
890                                 $x = $first ? '^' : '';
891                                 return preg_replace_callback(
892                                         "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
893                                         array($this,"lcCallback"),
894                                         $str
895                                 );
896                         } else
897                                 return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
898         }
899
900         function isMultibyte( $str ) {
901                 return (bool)preg_match( '/[\x80-\xff]/', $str );
902         }
903
904         function ucwords($str) {
905                 if ( self::isMultibyte( $str ) ) {
906                         $str = self::lc($str);
907
908                         // regexp to find first letter in each word (i.e. after each space)
909                         $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
910
911                         // function to use to capitalize a single char
912                         if ( function_exists( 'mb_strtoupper' ) )
913                                 return preg_replace_callback(
914                                         $replaceRegexp,
915                                         array($this,"ucwordsCallbackMB"),
916                                         $str
917                                 );
918                         else 
919                                 return preg_replace_callback(
920                                         $replaceRegexp,
921                                         array($this,"ucwordsCallbackWiki"),
922                                         $str
923                                 );
924                 }
925                 else
926                         return ucwords( strtolower( $str ) );
927         }
928
929   # capitalize words at word breaks
930         function ucwordbreaks($str){
931                 if (self::isMultibyte( $str ) ) {
932                         $str = self::lc($str);
933
934                         // since \b doesn't work for UTF-8, we explicitely define word break chars
935                         $breaks= "[ \-\(\)\}\{\.,\?!]";
936
937                         // find first letter after word break
938                         $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
939
940                         if ( function_exists( 'mb_strtoupper' ) )
941                                 return preg_replace_callback(
942                                         $replaceRegexp,
943                                         array($this,"ucwordbreaksCallbackMB"),
944                                         $str
945                                 );
946                         else 
947                                 return preg_replace_callback(
948                                         $replaceRegexp,
949                                         array($this,"ucwordsCallbackWiki"),
950                                         $str
951                                 );
952                 }
953                 else
954                         return preg_replace_callback(
955                         '/\b([\w\x80-\xff]+)\b/',
956                         array($this,"ucwordbreaksCallbackAscii"),
957                         $str );
958         }
959
960         /**
961          * Return a case-folded representation of $s
962          *
963          * This is a representation such that caseFold($s1)==caseFold($s2) if $s1 
964          * and $s2 are the same except for the case of their characters. It is not
965          * necessary for the value returned to make sense when displayed.
966          *
967          * Do *not* perform any other normalisation in this function. If a caller
968          * uses this function when it should be using a more general normalisation
969          * function, then fix the caller.
970          */
971         function caseFold( $s ) {
972                 return $this->uc( $s );
973         }
974
975         function checkTitleEncoding( $s ) {
976                 if( is_array( $s ) ) {
977                         wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
978                 }
979                 # Check for non-UTF-8 URLs
980                 $ishigh = preg_match( '/[\x80-\xff]/', $s);
981                 if(!$ishigh) return $s;
982
983                 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
984                 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
985                 if( $isutf8 ) return $s;
986
987                 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
988         }
989
990         function fallback8bitEncoding() {
991                 $this->load();
992                 return $this->fallback8bitEncoding;
993         }
994         
995         /**
996          * Some languages have special punctuation to strip out
997          * or characters which need to be converted for MySQL's
998          * indexing to grok it correctly. Make such changes here.
999          *
1000          * @param string $in
1001          * @return string
1002          */
1003         function stripForSearch( $string ) {
1004                 global $wgDBtype;
1005                 if ( $wgDBtype != 'mysql' ) {
1006                         return $string;
1007                 }
1008
1009                 # MySQL fulltext index doesn't grok utf-8, so we
1010                 # need to fold cases and convert to hex
1011
1012                 wfProfileIn( __METHOD__ );
1013                 if( function_exists( 'mb_strtolower' ) ) {
1014                         $out = preg_replace(
1015                                 "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
1016                                 "'U8' . bin2hex( \"$1\" )",
1017                                 mb_strtolower( $string ) );
1018                 } else {
1019                         list( , $wikiLowerChars ) = self::getCaseMaps();
1020                         $out = preg_replace(
1021                                 "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
1022                                 "'U8' . bin2hex( strtr( \"\$1\", \$wikiLowerChars ) )",
1023                                 $string );
1024                 }
1025                 wfProfileOut( __METHOD__ );
1026                 return $out;
1027         }
1028
1029         function convertForSearchResult( $termsArray ) {
1030                 # some languages, e.g. Chinese, need to do a conversion
1031                 # in order for search results to be displayed correctly
1032                 return $termsArray;
1033         }
1034
1035         /**
1036          * Get the first character of a string. 
1037          *
1038          * @param string $s
1039          * @return string
1040          */
1041         function firstChar( $s ) {
1042                 $matches = array();
1043                 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1044                 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
1045
1046                 return isset( $matches[1] ) ? $matches[1] : "";
1047         }
1048
1049         function initEncoding() {
1050                 # Some languages may have an alternate char encoding option
1051                 # (Esperanto X-coding, Japanese furigana conversion, etc)
1052                 # If this language is used as the primary content language,
1053                 # an override to the defaults can be set here on startup.
1054         }
1055
1056         function recodeForEdit( $s ) {
1057                 # For some languages we'll want to explicitly specify
1058                 # which characters make it into the edit box raw
1059                 # or are converted in some way or another.
1060                 # Note that if wgOutputEncoding is different from
1061                 # wgInputEncoding, this text will be further converted
1062                 # to wgOutputEncoding.
1063                 global $wgEditEncoding;
1064                 if( $wgEditEncoding == '' or
1065                   $wgEditEncoding == 'UTF-8' ) {
1066                         return $s;
1067                 } else {
1068                         return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1069                 }
1070         }
1071
1072         function recodeInput( $s ) {
1073                 # Take the previous into account.
1074                 global $wgEditEncoding;
1075                 if($wgEditEncoding != "") {
1076                         $enc = $wgEditEncoding;
1077                 } else {
1078                         $enc = 'UTF-8';
1079                 }
1080                 if( $enc == 'UTF-8' ) {
1081                         return $s;
1082                 } else {
1083                         return $this->iconv( $enc, 'UTF-8', $s );
1084                 }
1085         }
1086
1087         /**
1088          * For right-to-left language support
1089          *
1090          * @return bool
1091          */
1092         function isRTL() { 
1093                 $this->load();
1094                 return $this->rtl;
1095         }
1096
1097         /**
1098          * A hidden direction mark (LRM or RLM), depending on the language direction
1099          *
1100          * @return string
1101          */
1102         function getDirMark() {
1103                 return $this->isRTL() ? "\xE2\x80\x8F" : "\xE2\x80\x8E";
1104         }
1105
1106         /**
1107          * An arrow, depending on the language direction
1108          *
1109          * @return string
1110          */
1111         function getArrow() {
1112                 return $this->isRTL() ? '←' : '→';
1113         }
1114
1115         /**
1116          * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1117          *
1118          * @return bool
1119          */
1120         function linkPrefixExtension() {
1121                 $this->load();
1122                 return $this->linkPrefixExtension;
1123         }
1124
1125         function &getMagicWords() {
1126                 $this->load();
1127                 return $this->magicWords;
1128         }
1129
1130         # Fill a MagicWord object with data from here
1131         function getMagic( &$mw ) {
1132                 if ( !$this->mMagicHookDone ) {
1133                         $this->mMagicHookDone = true;
1134                         wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) );
1135                 }
1136                 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
1137                         $rawEntry = $this->mMagicExtensions[$mw->mId];
1138                 } else {
1139                         $magicWords =& $this->getMagicWords();
1140                         if ( isset( $magicWords[$mw->mId] ) ) {
1141                                 $rawEntry = $magicWords[$mw->mId];
1142                         } else {
1143                                 # Fall back to English if local list is incomplete
1144                                 $magicWords =& Language::getMagicWords();
1145                                 $rawEntry = $magicWords[$mw->mId];
1146                         }
1147                 }
1148
1149                 if( !is_array( $rawEntry ) ) {
1150                         error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1151                 }
1152                 $mw->mCaseSensitive = $rawEntry[0];
1153                 $mw->mSynonyms = array_slice( $rawEntry, 1 );
1154         }
1155
1156         /**
1157          * Add magic words to the extension array
1158          */
1159         function addMagicWordsByLang( $newWords ) {
1160                 $code = $this->getCode();
1161                 $fallbackChain = array();
1162                 while ( $code && !in_array( $code, $fallbackChain ) ) {
1163                         $fallbackChain[] = $code;
1164                         $code = self::getFallbackFor( $code );
1165                 }
1166                 $fallbackChain = array_reverse( $fallbackChain );
1167                 foreach ( $fallbackChain as $code ) {
1168                         if ( isset( $newWords[$code] ) ) {
1169                                 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
1170                         }
1171                 }
1172         }
1173
1174         /**
1175          * Get special page names, as an associative array
1176          *   case folded alias => real name
1177          */
1178         function getSpecialPageAliases() {
1179                 $this->load();
1180                 if ( !isset( $this->mExtendedSpecialPageAliases ) ) {
1181                         $this->mExtendedSpecialPageAliases = $this->specialPageAliases;
1182                         wfRunHooks( 'LangugeGetSpecialPageAliases', 
1183                                 array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) );
1184                 }
1185                 return $this->mExtendedSpecialPageAliases;
1186         }
1187
1188         /**
1189          * Italic is unsuitable for some languages
1190          *
1191          * @public
1192          *
1193          * @param string $text The text to be emphasized.
1194          * @return string
1195          */
1196         function emphasize( $text ) {
1197                 return "<em>$text</em>";
1198         }
1199
1200          /**
1201          * Normally we output all numbers in plain en_US style, that is
1202          * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
1203          * point twohundredthirtyfive. However this is not sutable for all
1204          * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
1205          * Icelandic just want to use commas instead of dots, and dots instead
1206          * of commas like "293.291,235".
1207          *
1208          * An example of this function being called:
1209          * <code>
1210          * wfMsg( 'message', $wgLang->formatNum( $num ) )
1211          * </code>
1212          *
1213          * See LanguageGu.php for the Gujarati implementation and
1214          * LanguageIs.php for the , => . and . => , implementation.
1215          *
1216          * @todo check if it's viable to use localeconv() for the decimal
1217          *       seperator thing.
1218          * @public
1219          * @param mixed $number the string to be formatted, should be an integer or
1220          *        a floating point number.
1221          * @param bool $nocommafy Set to true for special numbers like dates
1222          * @return string
1223          */
1224         function formatNum( $number, $nocommafy = false ) {
1225                 global $wgTranslateNumerals;
1226                 if (!$nocommafy) {
1227                         $number = $this->commafy($number);
1228                         $s = $this->separatorTransformTable();
1229                         if (!is_null($s)) { $number = strtr($number, $s); }
1230                 }
1231
1232                 if ($wgTranslateNumerals) {
1233                         $s = $this->digitTransformTable();
1234                         if (!is_null($s)) { $number = strtr($number, $s); }
1235                 }
1236
1237                 return $number;
1238         }
1239
1240         function parseFormattedNumber( $number ) {
1241                 $s = $this->digitTransformTable();
1242                 if (!is_null($s)) { $number = strtr($number, array_flip($s)); }
1243
1244                 $s = $this->separatorTransformTable();
1245                 if (!is_null($s)) { $number = strtr($number, array_flip($s)); }
1246
1247                 $number = strtr( $number, array (',' => '') );
1248                 return $number;
1249         }
1250
1251         /**
1252          * Adds commas to a given number
1253          *
1254          * @param mixed $_
1255          * @return string
1256          */
1257         function commafy($_) {
1258                 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
1259         }
1260
1261         function digitTransformTable() {
1262                 $this->load();
1263                 return $this->digitTransformTable;
1264         }
1265
1266         function separatorTransformTable() {
1267                 $this->load();
1268                 return $this->separatorTransformTable;
1269         }
1270
1271
1272         /**
1273          * For the credit list in includes/Credits.php (action=credits)
1274          *
1275          * @param array $l
1276          * @return string
1277          */
1278         function listToText( $l ) {
1279                 $s = '';
1280                 $m = count($l) - 1;
1281                 for ($i = $m; $i >= 0; $i--) {
1282                         if ($i == $m) {
1283                                 $s = $l[$i];
1284                         } else if ($i == $m - 1) {
1285                                 $s = $l[$i] . ' ' . $this->getMessageFromDB( 'and' ) . ' ' . $s;
1286                         } else {
1287                                 $s = $l[$i] . ', ' . $s;
1288                         }
1289                 }
1290                 return $s;
1291         }
1292
1293         /**
1294          * Truncate a string to a specified length in bytes, appending an optional
1295          * string (e.g. for ellipses)
1296          *
1297          * The database offers limited byte lengths for some columns in the database;
1298          * multi-byte character sets mean we need to ensure that only whole characters
1299          * are included, otherwise broken characters can be passed to the user
1300          *
1301          * If $length is negative, the string will be truncated from the beginning
1302          *       
1303          * @param string $string String to truncate
1304          * @param int $length Maximum length (excluding ellipses)
1305          * @param string $ellipses String to append to the truncated text
1306          * @return string
1307          */
1308         function truncate( $string, $length, $ellipsis = "" ) {
1309                 if( $length == 0 ) {
1310                         return $ellipsis;
1311                 }
1312                 if ( strlen( $string ) <= abs( $length ) ) {
1313                         return $string;
1314                 }
1315                 if( $length > 0 ) {
1316                         $string = substr( $string, 0, $length );
1317                         $char = ord( $string[strlen( $string ) - 1] );
1318                         $m = array();
1319                         if ($char >= 0xc0) {
1320                                 # We got the first byte only of a multibyte char; remove it.
1321                                 $string = substr( $string, 0, -1 );
1322                         } elseif( $char >= 0x80 &&
1323                                   preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
1324                                               '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
1325                             # We chopped in the middle of a character; remove it
1326                                 $string = $m[1];
1327                         }
1328                         return $string . $ellipsis;
1329                 } else {
1330                         $string = substr( $string, $length );
1331                         $char = ord( $string[0] );
1332                         if( $char >= 0x80 && $char < 0xc0 ) {
1333                                 # We chopped in the middle of a character; remove the whole thing
1334                                 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
1335                         }
1336                         return $ellipsis . $string;
1337                 }
1338         }
1339
1340         /**
1341          * Grammatical transformations, needed for inflected languages
1342          * Invoked by putting {{grammar:case|word}} in a message
1343          *
1344          * @param string $word
1345          * @param string $case
1346          * @return string
1347          */
1348         function convertGrammar( $word, $case ) {
1349                 global $wgGrammarForms;
1350                 if ( isset($wgGrammarForms['en'][$case][$word]) ) {
1351                         return $wgGrammarForms['en'][$case][$word];
1352                 }
1353                 return $word;
1354         }
1355
1356         /**
1357          * Plural form transformations, needed for some languages.
1358          * For example, where are 3 form of plural in Russian and Polish,
1359          * depending on "count mod 10". See [[w:Plural]]
1360          * For English it is pretty simple.
1361          *
1362          * Invoked by putting {{plural:count|wordform1|wordform2}}
1363          * or {{plural:count|wordform1|wordform2|wordform3}}
1364          *
1365          * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
1366          *
1367          * @param integer $count
1368          * @param string $wordform1
1369          * @param string $wordform2
1370          * @param string $wordform3 (optional)
1371          * @param string $wordform4 (optional)
1372          * @param string $wordform5 (optional)
1373          * @return string
1374          */
1375         function convertPlural( $count, $w1, $w2, $w3, $w4, $w5) {
1376                 return ( $count == '1' || $count == '-1' ) ? $w1 : $w2;
1377         }
1378
1379         /**
1380          * For translaing of expiry times
1381          * @param string The validated block time in English
1382          * @param $forContent, avoid html?
1383          * @return Somehow translated block time
1384          * @see LanguageFi.php for example implementation
1385          */
1386         function translateBlockExpiry( $str, $forContent=false ) {
1387
1388                 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
1389
1390                 if ( $scBlockExpiryOptions == '-') {
1391                         return $str;
1392                 }
1393
1394                 foreach (explode(',', $scBlockExpiryOptions) as $option) {
1395                         if ( strpos($option, ":") === false )
1396                                 continue;
1397                         list($show, $value) = explode(":", $option);
1398                         if ( strcmp ( $str, $value) == 0 ) {
1399                                 if ( $forContent )
1400                                         return htmlspecialchars($str) . htmlspecialchars( trim( $show ) );
1401                                 else
1402                                         return '<span title="' . htmlspecialchars($str). '">' . htmlspecialchars( trim( $show ) ) . '</span>';
1403                         }
1404                 }
1405
1406                 return $str;
1407         }
1408
1409         /**
1410          * languages like Chinese need to be segmented in order for the diff
1411          * to be of any use
1412          *
1413          * @param string $text
1414          * @return string
1415          */
1416         function segmentForDiff( $text ) {
1417                 return $text;
1418         }
1419
1420         /**
1421          * and unsegment to show the result
1422          *
1423          * @param string $text
1424          * @return string
1425          */
1426         function unsegmentForDiff( $text ) {
1427                 return $text;
1428         }
1429
1430         # convert text to different variants of a language.
1431         function convert( $text, $isTitle = false) {
1432                 return $this->mConverter->convert($text, $isTitle);
1433         }
1434
1435         # Convert text from within Parser
1436         function parserConvert( $text, &$parser ) {
1437                 return $this->mConverter->parserConvert( $text, $parser );
1438         }
1439
1440         # Check if this is a language with variants
1441         function hasVariants(){
1442                 return sizeof($this->getVariants())>1;
1443         }
1444
1445         # Put custom tags (e.g. -{ }-) around math to prevent conversion
1446         function armourMath($text){ 
1447                 return $this->mConverter->armourMath($text);
1448         }
1449
1450
1451         /**
1452          * Perform output conversion on a string, and encode for safe HTML output.
1453          * @param string $text
1454          * @param bool $isTitle -- wtf?
1455          * @return string
1456          * @todo this should get integrated somewhere sane
1457          */
1458         function convertHtml( $text, $isTitle = false ) {
1459                 return htmlspecialchars( $this->convert( $text, $isTitle ) );
1460         }
1461
1462         function convertCategoryKey( $key ) {
1463                 return $this->mConverter->convertCategoryKey( $key );
1464         }
1465
1466         /**
1467          * get the list of variants supported by this langauge
1468          * see sample implementation in LanguageZh.php
1469          *
1470          * @return array an array of language codes
1471          */
1472         function getVariants() {
1473                 return $this->mConverter->getVariants();
1474         }
1475
1476
1477         function getPreferredVariant( $fromUser = true ) {
1478                 return $this->mConverter->getPreferredVariant( $fromUser );
1479         }
1480
1481         /**
1482          * if a language supports multiple variants, it is
1483          * possible that non-existing link in one variant
1484          * actually exists in another variant. this function
1485          * tries to find it. See e.g. LanguageZh.php
1486          *
1487          * @param string $link the name of the link
1488          * @param mixed $nt the title object of the link
1489          * @return null the input parameters may be modified upon return
1490          */
1491         function findVariantLink( &$link, &$nt ) {
1492                 $this->mConverter->findVariantLink($link, $nt);
1493         }
1494
1495         /**
1496          * If a language supports multiple variants, converts text
1497          * into an array of all possible variants of the text:
1498          *  'variant' => text in that variant
1499          */
1500
1501         function convertLinkToAllVariants($text){
1502                 return $this->mConverter->convertLinkToAllVariants($text);
1503         }
1504
1505
1506         /**
1507          * returns language specific options used by User::getPageRenderHash()
1508          * for example, the preferred language variant
1509          *
1510          * @return string
1511          * @public
1512          */
1513         function getExtraHashOptions() {
1514                 return $this->mConverter->getExtraHashOptions();
1515         }
1516
1517         /**
1518          * for languages that support multiple variants, the title of an
1519          * article may be displayed differently in different variants. this
1520          * function returns the apporiate title defined in the body of the article.
1521          *
1522          * @return string
1523          */
1524         function getParsedTitle() {
1525                 return $this->mConverter->getParsedTitle();
1526         }
1527
1528         /**
1529          * Enclose a string with the "no conversion" tag. This is used by
1530          * various functions in the Parser
1531          *
1532          * @param string $text text to be tagged for no conversion
1533          * @return string the tagged text
1534         */
1535         function markNoConversion( $text, $noParse=false ) {
1536                 return $this->mConverter->markNoConversion( $text, $noParse );
1537         }
1538
1539         /**
1540          * A regular expression to match legal word-trailing characters
1541          * which should be merged onto a link of the form [[foo]]bar.
1542          *
1543          * @return string
1544          * @public
1545          */
1546         function linkTrail() {
1547                 $this->load();
1548                 return $this->linkTrail;
1549         }
1550
1551         function getLangObj() {
1552                 return $this;
1553         }
1554
1555         /**
1556          * Get the RFC 3066 code for this language object
1557          */
1558         function getCode() {
1559                 return $this->mCode;
1560         }
1561
1562         function setCode( $code ) {
1563                 $this->mCode = $code;
1564         }
1565
1566         static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
1567                 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
1568         }
1569
1570         static function getMessagesFileName( $code ) {
1571                 global $IP;
1572                 return self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
1573         }
1574
1575         static function getClassFileName( $code ) {
1576                 global $IP;
1577                 return self::getFileName( "$IP/languages/classes/Language", $code, '.php' );
1578         }
1579         
1580         static function getLocalisationArray( $code, $disableCache = false ) {
1581                 self::loadLocalisation( $code, $disableCache );
1582                 return self::$mLocalisationCache[$code];
1583         }
1584
1585         /**
1586          * Load localisation data for a given code into the static cache
1587          *
1588          * @return array Dependencies, map of filenames to mtimes
1589          */
1590         static function loadLocalisation( $code, $disableCache = false ) {
1591                 static $recursionGuard = array();
1592                 global $wgMemc;
1593
1594                 if ( !$code ) {
1595                         throw new MWException( "Invalid language code requested" );
1596                 }
1597
1598                 if ( !$disableCache ) {
1599                         # Try the per-process cache
1600                         if ( isset( self::$mLocalisationCache[$code] ) ) {
1601                                 return self::$mLocalisationCache[$code]['deps'];
1602                         }
1603
1604                         wfProfileIn( __METHOD__ );
1605
1606                         # Try the serialized directory
1607                         $cache = wfGetPrecompiledData( self::getFileName( "Messages", $code, '.ser' ) );
1608                         if ( $cache ) {
1609                                 self::$mLocalisationCache[$code] = $cache;
1610                                 wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" );
1611                                 wfProfileOut( __METHOD__ );
1612                                 return self::$mLocalisationCache[$code]['deps'];
1613                         }
1614
1615                         # Try the global cache
1616                         $memcKey = wfMemcKey('localisation', $code );
1617                         $cache = $wgMemc->get( $memcKey );
1618                         if ( $cache ) {
1619                                 # Check file modification times
1620                                 foreach ( $cache['deps'] as $file => $mtime ) {
1621                                         if ( !file_exists( $file ) || filemtime( $file ) > $mtime ) {
1622                                                 break;
1623                                         }
1624                                 }
1625                                 if ( self::isLocalisationOutOfDate( $cache ) ) {
1626                                         $wgMemc->delete( $memcKey );
1627                                         $cache = false;
1628                                         wfDebug( "Language::loadLocalisation(): localisation cache for $code had expired due to update of $file\n" );
1629                                 } else {
1630                                         self::$mLocalisationCache[$code] = $cache;
1631                                         wfDebug( "Language::loadLocalisation(): got localisation for $code from cache\n" );
1632                                         wfProfileOut( __METHOD__ );
1633                                         return $cache['deps'];
1634                                 }
1635                         }
1636                 } else {
1637                         wfProfileIn( __METHOD__ );
1638                 }
1639
1640                 # Default fallback, may be overridden when the messages file is included
1641                 if ( $code != 'en' ) {
1642                         $fallback = 'en';
1643                 } else {
1644                         $fallback = false;
1645                 }
1646
1647                 # Load the primary localisation from the source file
1648                 $filename = self::getMessagesFileName( $code );
1649                 if ( !file_exists( $filename ) ) {
1650                         wfDebug( "Language::loadLocalisation(): no localisation file for $code, using implicit fallback to en\n" );
1651                         $cache = array();
1652                         $deps = array();
1653                 } else {
1654                         $deps = array( $filename => filemtime( $filename ) );
1655                         require( $filename );
1656                         $cache = compact( self::$mLocalisationKeys );   
1657                         wfDebug( "Language::loadLocalisation(): got localisation for $code from source\n" );
1658                 }
1659
1660                 if ( !empty( $fallback ) ) {
1661                         # Load the fallback localisation, with a circular reference guard
1662                         if ( isset( $recursionGuard[$code] ) ) {
1663                                 throw new MWException( "Error: Circular fallback reference in language code $code" );
1664                         }
1665                         $recursionGuard[$code] = true;
1666                         $newDeps = self::loadLocalisation( $fallback, $disableCache );
1667                         unset( $recursionGuard[$code] );
1668
1669                         $secondary = self::$mLocalisationCache[$fallback];
1670                         $deps = array_merge( $deps, $newDeps );
1671
1672                         # Merge the fallback localisation with the current localisation
1673                         foreach ( self::$mLocalisationKeys as $key ) {
1674                                 if ( isset( $cache[$key] ) ) {
1675                                         if ( isset( $secondary[$key] ) ) {
1676                                                 if ( in_array( $key, self::$mMergeableMapKeys ) ) {
1677                                                         $cache[$key] = $cache[$key] + $secondary[$key];
1678                                                 } elseif ( in_array( $key, self::$mMergeableListKeys ) ) {
1679                                                         $cache[$key] = array_merge( $secondary[$key], $cache[$key] );
1680                                                 } elseif ( in_array( $key, self::$mMergeableAliasListKeys ) ) {
1681                                                         $cache[$key] = array_merge_recursive( $cache[$key], $secondary[$key] );
1682                                                 }
1683                                         }
1684                                 } else {
1685                                         $cache[$key] = $secondary[$key];
1686                                 }
1687                         }
1688
1689                         # Merge bookstore lists if requested
1690                         if ( !empty( $cache['bookstoreList']['inherit'] ) ) {
1691                                 $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] );
1692                         }
1693                         if ( isset( $cache['bookstoreList']['inherit'] ) ) {
1694                                 unset( $cache['bookstoreList']['inherit'] );
1695                         }
1696                 }
1697                 
1698                 # Add dependencies to the cache entry
1699                 $cache['deps'] = $deps;
1700
1701                 # Replace spaces with underscores in namespace names
1702                 $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] );
1703                 
1704                 # Save to both caches
1705                 self::$mLocalisationCache[$code] = $cache;
1706                 if ( !$disableCache ) {
1707                         $wgMemc->set( $memcKey, $cache );
1708                 }
1709
1710                 wfProfileOut( __METHOD__ );
1711                 return $deps;
1712         }
1713
1714         /**
1715          * Test if a given localisation cache is out of date with respect to the 
1716          * source Messages files. This is done automatically for the global cache
1717          * in $wgMemc, but is only done on certain occasions for the serialized 
1718          * data file.
1719          *
1720          * @param $cache mixed Either a language code or a cache array
1721          */
1722         static function isLocalisationOutOfDate( $cache ) {
1723                 if ( !is_array( $cache ) ) {
1724                         self::loadLocalisation( $cache );
1725                         $cache = self::$mLocalisationCache[$cache];
1726                 }
1727                 $expired = false;
1728                 foreach ( $cache['deps'] as $file => $mtime ) {
1729                         if ( !file_exists( $file ) || filemtime( $file ) > $mtime ) {
1730                                 $expired = true;
1731                                 break;
1732                         }
1733                 }
1734                 return $expired;
1735         }
1736         
1737         /**
1738          * Get the fallback for a given language
1739          */
1740         static function getFallbackFor( $code ) {
1741                 self::loadLocalisation( $code );
1742                 return self::$mLocalisationCache[$code]['fallback'];
1743         }
1744
1745         /** 
1746          * Get all messages for a given language
1747          */
1748         static function getMessagesFor( $code ) {
1749                 self::loadLocalisation( $code );
1750                 return self::$mLocalisationCache[$code]['messages'];
1751         }
1752
1753         /** 
1754          * Get a message for a given language
1755          */
1756         static function getMessageFor( $key, $code ) {
1757                 self::loadLocalisation( $code );
1758                 return isset( self::$mLocalisationCache[$code]['messages'][$key] ) ? self::$mLocalisationCache[$code]['messages'][$key] : null;
1759         }
1760
1761         /**
1762          * Load localisation data for this object
1763          */
1764         function load() {
1765                 if ( !$this->mLoaded ) {
1766                         self::loadLocalisation( $this->getCode() );
1767                         $cache =& self::$mLocalisationCache[$this->getCode()];
1768                         foreach ( self::$mLocalisationKeys as $key ) {
1769                                 $this->$key = $cache[$key];
1770                         }
1771                         $this->mLoaded = true;
1772
1773                         $this->fixUpSettings();
1774                 }
1775         }
1776
1777         /**
1778          * Do any necessary post-cache-load settings adjustment
1779          */
1780         function fixUpSettings() {
1781                 global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk,
1782                         $wgNamespaceAliases, $wgAmericanDates;
1783                 wfProfileIn( __METHOD__ );
1784                 if ( $wgExtraNamespaces ) {
1785                         $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames;
1786                 }
1787
1788                 $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
1789                 if ( $wgMetaNamespaceTalk ) {
1790                         $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
1791                 } else {
1792                         $talk = $this->namespaceNames[NS_PROJECT_TALK];
1793                         $talk = str_replace( '$1', $wgMetaNamespace, $talk );
1794
1795                         # Allow grammar transformations
1796                         # Allowing full message-style parsing would make simple requests 
1797                         # such as action=raw much more expensive than they need to be. 
1798                         # This will hopefully cover most cases.
1799                         $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i', 
1800                                 array( &$this, 'replaceGrammarInNamespace' ), $talk );
1801                         $talk = str_replace( ' ', '_', $talk );
1802                         $this->namespaceNames[NS_PROJECT_TALK] = $talk;
1803                 }
1804                 
1805                 # The above mixing may leave namespaces out of canonical order.
1806                 # Re-order by namespace ID number...
1807                 ksort( $this->namespaceNames );
1808
1809                 # Put namespace names and aliases into a hashtable.
1810                 # If this is too slow, then we should arrange it so that it is done 
1811                 # before caching. The catch is that at pre-cache time, the above
1812                 # class-specific fixup hasn't been done.
1813                 $this->mNamespaceIds = array();
1814                 foreach ( $this->namespaceNames as $index => $name ) {
1815                         $this->mNamespaceIds[$this->lc($name)] = $index;
1816                 }
1817                 if ( $this->namespaceAliases ) {
1818                         foreach ( $this->namespaceAliases as $name => $index ) {
1819                                 $this->mNamespaceIds[$this->lc($name)] = $index;
1820                         }
1821                 }
1822                 if ( $wgNamespaceAliases ) {
1823                         foreach ( $wgNamespaceAliases as $name => $index ) {
1824                                 $this->mNamespaceIds[$this->lc($name)] = $index;
1825                         }
1826                 }
1827
1828                 if ( $this->defaultDateFormat == 'dmy or mdy' ) {
1829                         $this->defaultDateFormat = $wgAmericanDates ? 'mdy' : 'dmy';
1830                 }
1831                 wfProfileOut( __METHOD__ );
1832         }
1833
1834         function replaceGrammarInNamespace( $m ) {
1835                 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
1836         }
1837
1838         static function getCaseMaps() {
1839                 static $wikiUpperChars, $wikiLowerChars;
1840                 if ( isset( $wikiUpperChars ) ) {
1841                         return array( $wikiUpperChars, $wikiLowerChars );
1842                 }
1843
1844                 wfProfileIn( __METHOD__ );
1845                 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
1846                 if ( $arr === false ) {
1847                         throw new MWException( 
1848                                 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
1849                 }
1850                 extract( $arr );
1851                 wfProfileOut( __METHOD__ );
1852                 return array( $wikiUpperChars, $wikiLowerChars );
1853         }
1854
1855         function formatTimePeriod( $seconds ) {
1856                 if ( $seconds < 10 ) {
1857                         return $this->formatNum( sprintf( "%.1f", $seconds ) ) . wfMsg( 'seconds-abbrev' );
1858                 } elseif ( $seconds < 60 ) {
1859                         return $this->formatNum( round( $seconds ) ) . wfMsg( 'seconds-abbrev' );
1860                 } elseif ( $seconds < 3600 ) {
1861                         return $this->formatNum( floor( $seconds / 60 ) ) . wfMsg( 'minutes-abbrev' ) . 
1862                                 $this->formatNum( round( fmod( $seconds, 60 ) ) ) . wfMsg( 'seconds-abbrev' );
1863                 } else {
1864                         $hours = floor( $seconds / 3600 );
1865                         $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
1866                         $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
1867                         return $this->formatNum( $hours ) . wfMsg( 'hours-abbrev' ) . 
1868                                 $this->formatNum( $minutes ) . wfMsg( 'minutes-abbrev' ) .
1869                                 $this->formatNum( $secondsPart ) . wfMsg( 'seconds-abbrev' );
1870                 }
1871         }
1872
1873         function formatBitrate( $bps ) {
1874                 $units = array( 'bps', 'kbps', 'Mbps', 'Gbps' );
1875                 if ( $bps <= 0 ) {
1876                         return $this->formatNum( $bps ) . $units[0];
1877                 }
1878                 $unitIndex = floor( log10( $bps ) / 3 );
1879                 $mantissa = $bps / pow( 1000, $unitIndex );
1880                 if ( $mantissa < 10 ) {
1881                         $mantissa = round( $mantissa, 1 );
1882                 } else {
1883                         $mantissa = round( $mantissa );
1884                 }
1885                 return $this->formatNum( $mantissa ) . $units[$unitIndex];
1886         }
1887
1888         /**
1889          * Format a size in bytes for output, using an appropriate
1890          * unit (B, KB, MB or GB) according to the magnitude in question
1891          *
1892          * @param $size Size to format
1893          * @return string Plain text (not HTML)
1894          */
1895         function formatSize( $size ) {
1896                 // For small sizes no decimal places necessary
1897                 $round = 0;
1898                 if( $size > 1024 ) {
1899                         $size = $size / 1024;
1900                         if( $size > 1024 ) {
1901                                 $size = $size / 1024;
1902                                 // For MB and bigger two decimal places are smarter
1903                                 $round = 2;
1904                                 if( $size > 1024 ) {
1905                                         $size = $size / 1024;
1906                                         $msg = 'size-gigabytes';
1907                                 } else {
1908                                         $msg = 'size-megabytes';
1909                                 }
1910                         } else {
1911                                 $msg = 'size-kilobytes';
1912                         }
1913                 } else {
1914                         $msg = 'size-bytes';
1915                 }
1916                 $size = round( $size, $round );
1917                 $text = $this->getMessageFromDB( $msg );
1918                 return str_replace( '$1', $this->formatNum( $size ), $text );
1919         }
1920 }
1921
1922
1923