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