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