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