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