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