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