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