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