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