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