7 if( !defined( 'MEDIAWIKI' ) ) {
8 echo "This file is part of MediaWiki, it is not a valid entry point.\n";
13 # In general you should not make customizations in these language files
14 # directly, but should use the MediaWiki: special namespace to customize
15 # user interface messages through the wiki.
16 # See http://meta.wikipedia.org/wiki/MediaWiki_namespace
18 # NOTE TO TRANSLATORS: Do not copy this whole file when making translations!
19 # A lot of common constants and a base class with inheritable methods are
20 # defined here, which should not be redefined. See the other LanguageXx.php
25 global $wgLanguageNames;
26 require_once( 'Names.php' );
28 global $wgInputEncoding, $wgOutputEncoding;
31 * These are always UTF-8, they exist only for backwards compatibility
33 $wgInputEncoding = "UTF-8";
34 $wgOutputEncoding = "UTF-8";
36 if( function_exists( 'mb_strtoupper' ) ) {
37 mb_internal_encoding('UTF-8');
40 /* a fake language converter */
43 function FakeConverter($langobj) {$this->mLang
= $langobj;}
44 function convert($t, $i) {return $t;}
45 function parserConvert($t, $p) {return $t;}
46 function getVariants() { return array( $this->mLang
->getCode() ); }
47 function getPreferredVariant() {return $this->mLang
->getCode(); }
48 function findVariantLink(&$l, &$n) {}
49 function getExtraHashOptions() {return '';}
50 function getParsedTitle() {return '';}
51 function markNoConversion($text, $noParse=false) {return $text;}
52 function convertCategoryKey( $key ) {return $key; }
53 function convertLinkToAllVariants($text){ return array( $this->mLang
->getCode() => $text); }
54 function setNoTitleConvert(){}
57 #--------------------------------------------------------------------------
58 # Internationalisation code
59 #--------------------------------------------------------------------------
62 var $mConverter, $mVariants, $mCode, $mLoaded = false;
64 static public $mLocalisationKeys = array( 'fallback', 'namespaceNames',
65 'quickbarSettings', 'skinNames', 'mathNames',
66 'bookstoreList', 'magicWords', 'messages', 'rtl', 'digitTransformTable',
67 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
68 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases',
69 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
70 'defaultDateFormat', 'extraUserToggles' );
72 static public $mMergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames',
73 'dateFormats', 'defaultUserOptionOverrides', 'magicWords' );
75 static public $mMergeableListKeys = array( 'extraUserToggles' );
77 static public $mLocalisationCache = array();
79 static public $mWeekdayMsgs = array(
80 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
84 static public $mWeekdayAbbrevMsgs = array(
85 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
88 static public $mMonthMsgs = array(
89 'january', 'february', 'march', 'april', 'may_long', 'june',
90 'july', 'august', 'september', 'october', 'november',
93 static public $mMonthGenMsgs = array(
94 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
95 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
98 static public $mMonthAbbrevMsgs = array(
99 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
100 'sep', 'oct', 'nov', 'dec'
104 * Create a language object for a given language code
106 static function factory( $code ) {
108 static $recursionLevel = 0;
110 if ( $code == 'en' ) {
113 $class = 'Language' . str_replace( '-', '_', ucfirst( $code ) );
114 // Preload base classes to work around APC/PHP5 bug
115 if ( file_exists( "$IP/languages/classes/$class.deps.php" ) ) {
116 include_once("$IP/languages/classes/$class.deps.php");
118 if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
119 include_once("$IP/languages/classes/$class.php");
123 if ( $recursionLevel > 5 ) {
124 throw new MWException( "Language fallback loop detected when creating class $class\n" );
127 if( ! class_exists( $class ) ) {
128 $fallback = Language
::getFallbackFor( $code );
130 $lang = Language
::factory( $fallback );
132 $lang->setCode( $code );
140 function __construct() {
141 $this->mConverter
= new FakeConverter($this);
142 // Set the code to the name of the descendant
143 if ( get_class( $this ) == 'Language' ) {
146 $this->mCode
= str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
151 * Hook which will be called if this is the content language.
152 * Descendants can use this to register hook functions or modify globals
154 function initContLang() {}
160 function getDefaultUserOptions() {
161 return User
::getDefaultOptions();
165 * Exports $wgBookstoreListEn
168 function getBookstoreList() {
170 return $this->bookstoreList
;
176 function getNamespaces() {
178 return $this->namespaceNames
;
182 * A convenience function that returns the same thing as
183 * getNamespaces() except with the array values changed to ' '
184 * where it found '_', useful for producing output to be displayed
185 * e.g. in <select> forms.
189 function getFormattedNamespaces() {
190 $ns = $this->getNamespaces();
191 foreach($ns as $k => $v) {
192 $ns[$k] = strtr($v, '_', ' ');
198 * Get a namespace value by key
200 * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
201 * echo $mw_ns; // prints 'MediaWiki'
204 * @param int $index the array key of the namespace to return
205 * @return mixed, string if the namespace value exists, otherwise false
207 function getNsText( $index ) {
208 $ns = $this->getNamespaces();
209 return isset( $ns[$index] ) ?
$ns[$index] : false;
213 * A convenience function that returns the same thing as
214 * getNsText() except with '_' changed to ' ', useful for
219 function getFormattedNsText( $index ) {
220 $ns = $this->getNsText( $index );
221 return strtr($ns, '_', ' ');
225 * Get a namespace key by value, case insensetive.
227 * @param string $text
228 * @return mixed An integer if $text is a valid value otherwise false
230 function getNsIndex( $text ) {
232 $index = @$this->mNamespaceIds
[$this->lc($text)];
233 if ( is_null( $index ) ) {
241 * short names for language variants used for language conversion links.
243 * @param string $code
246 function getVariantname( $code ) {
247 return $this->getMessageFromDB( "variantname-$code" );
250 function specialPage( $name ) {
251 return $this->getNsText(NS_SPECIAL
) . ':' . $name;
254 function getQuickbarSettings() {
256 return $this->quickbarSettings
;
259 function getSkinNames() {
261 return $this->skinNames
;
264 function getMathNames() {
266 return $this->mathNames
;
269 function getDatePreferences() {
271 return $this->datePreferences
;
274 function getDateFormats() {
276 return $this->dateFormats
;
279 function getDefaultDateFormat() {
281 return $this->defaultDateFormat
;
284 function getDatePreferenceMigrationMap() {
286 return $this->datePreferenceMigrationMap
;
289 function getDefaultUserOptionOverrides() {
291 return $this->defaultUserOptionOverrides
;
294 function getExtraUserToggles() {
296 return $this->extraUserToggles
;
299 function getUserToggle( $tog ) {
300 return $this->getMessageFromDB( "tog-$tog" );
304 * Get language names, indexed by code.
305 * If $customisedOnly is true, only returns codes with a messages file
307 function getLanguageNames( $customisedOnly = false ) {
308 global $wgLanguageNames;
309 if ( !$customisedOnly ) {
310 return $wgLanguageNames;
314 $messageFiles = glob( "$IP/languages/messages/Messages*.php" );
316 foreach ( $messageFiles as $file ) {
317 if( preg_match( '/Messages([A-Z][a-z_]+)\.php$/', $file, $m ) ) {
318 $code = str_replace( '_', '-', strtolower( $m[1] ) );
319 if ( isset( $wgLanguageNames[$code] ) ) {
320 $names[$code] = $wgLanguageNames[$code];
328 * Ugly hack to get a message maybe from the MediaWiki namespace, if this
329 * language object is the content or user language.
331 function getMessageFromDB( $msg ) {
332 global $wgContLang, $wgLang;
333 if ( $wgContLang->getCode() == $this->getCode() ) {
335 return wfMsgForContent( $msg );
336 } elseif ( $wgLang->getCode() == $this->getCode() ) {
338 return wfMsg( $msg );
340 # Neither, get from localisation
341 return $this->getMessage( $msg );
345 function getLanguageName( $code ) {
346 global $wgLanguageNames;
347 if ( ! array_key_exists( $code, $wgLanguageNames ) ) {
350 return $wgLanguageNames[$code];
353 function getMonthName( $key ) {
354 return $this->getMessageFromDB( self
::$mMonthMsgs[$key-1] );
357 function getMonthNameGen( $key ) {
358 return $this->getMessageFromDB( self
::$mMonthGenMsgs[$key-1] );
361 function getMonthAbbreviation( $key ) {
362 return $this->getMessageFromDB( self
::$mMonthAbbrevMsgs[$key-1] );
365 function getWeekdayName( $key ) {
366 return $this->getMessageFromDB( self
::$mWeekdayMsgs[$key-1] );
369 function getWeekdayAbbreviation( $key ) {
370 return $this->getMessageFromDB( self
::$mWeekdayAbbrevMsgs[$key-1] );
374 * Used by date() and time() to adjust the time output.
376 * @param int $ts the time in date('YmdHis') format
377 * @param mixed $tz adjust the time by this amount (default false,
378 * mean we get user timecorrection setting)
381 function userAdjust( $ts, $tz = false ) {
382 global $wgUser, $wgLocalTZoffset;
385 $tz = $wgUser->getOption( 'timecorrection' );
388 # minutes and hours differences:
393 # Global offset in minutes.
394 if( isset($wgLocalTZoffset) ) {
395 $hrDiff = $wgLocalTZoffset %
60;
396 $minDiff = $wgLocalTZoffset - ($hrDiff * 60);
398 } elseif ( strpos( $tz, ':' ) !== false ) {
399 $tzArray = explode( ':', $tz );
400 $hrDiff = intval($tzArray[0]);
401 $minDiff = intval($hrDiff < 0 ?
-$tzArray[1] : $tzArray[1]);
403 $hrDiff = intval( $tz );
406 # No difference ? Return time unchanged
407 if ( 0 == $hrDiff && 0 == $minDiff ) { return $ts; }
409 # Generate an adjusted date
411 (int)substr( $ts, 8, 2) ) +
$hrDiff, # Hours
412 (int)substr( $ts, 10, 2 ) +
$minDiff, # Minutes
413 (int)substr( $ts, 12, 2 ), # Seconds
414 (int)substr( $ts, 4, 2 ), # Month
415 (int)substr( $ts, 6, 2 ), # Day
416 (int)substr( $ts, 0, 4 ) ); #Year
417 return date( 'YmdHis', $t );
421 * This is a workalike of PHP's date() function, but with better
422 * internationalisation, a reduced set of format characters, and a better
425 * Supported format characters are dDjlNwzWFmMntLYyaAgGhHiscrU. See the
426 * PHP manual for definitions. There are a number of extensions, which
429 * xn Do not translate digits of the next numeric format character
430 * xN Toggle raw digit (xn) flag, stays set until explicitly unset
431 * xr Use roman numerals for the next numeric format character
433 * xg Genitive month name
435 * Characters enclosed in double quotes will be considered literal (with
436 * the quotes themselves removed). Unmatched quotes will be considered
437 * literal quotes. Example:
439 * "The month is" F => The month is January
442 * Backslash escaping is also supported.
444 * @param string $format
445 * @param string $ts 14-character timestamp
449 function sprintfDate( $format, $ts ) {
455 for ( $p = 0; $p < strlen( $format ); $p++
) {
458 if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
459 $code .= $format[++
$p];
470 $rawToggle = !$rawToggle;
476 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
479 $num = substr( $ts, 6, 2 );
482 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
483 $s .= $this->getWeekdayAbbreviation( date( 'w', $unix ) +
1 );
486 $num = intval( substr( $ts, 6, 2 ) );
489 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
490 $s .= $this->getWeekdayName( date( 'w', $unix ) +
1 );
493 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
494 $w = date( 'w', $unix );
498 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
499 $num = date( 'w', $unix );
502 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
503 $num = date( 'z', $unix );
506 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
507 $num = date( 'W', $unix );
510 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
513 $num = substr( $ts, 4, 2 );
516 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
519 $num = intval( substr( $ts, 4, 2 ) );
522 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
523 $num = date( 't', $unix );
526 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
527 $num = date( 'L', $unix );
530 $num = substr( $ts, 0, 4 );
533 $num = substr( $ts, 2, 2 );
536 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'am' : 'pm';
539 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'AM' : 'PM';
542 $h = substr( $ts, 8, 2 );
543 $num = $h %
12 ?
$h %
12 : 12;
546 $num = intval( substr( $ts, 8, 2 ) );
549 $h = substr( $ts, 8, 2 );
550 $num = sprintf( '%02d', $h %
12 ?
$h %
12 : 12 );
553 $num = substr( $ts, 8, 2 );
556 $num = substr( $ts, 10, 2 );
559 $num = substr( $ts, 12, 2 );
562 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
563 $s .= date( 'c', $unix );
566 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
567 $s .= date( 'r', $unix );
570 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
575 if ( $p < strlen( $format ) - 1 ) {
583 if ( $p < strlen( $format ) - 1 ) {
584 $endQuote = strpos( $format, '"', $p +
1 );
585 if ( $endQuote === false ) {
586 # No terminating quote, assume literal "
589 $s .= substr( $format, $p +
1, $endQuote - $p - 1 );
593 # Quote at end of string, assume literal "
600 if ( $num !== false ) {
601 if ( $rawToggle ||
$raw ) {
604 } elseif ( $roman ) {
605 $s .= self
::romanNumeral( $num );
608 $s .= $this->formatNum( $num, true );
617 * Roman number formatting up to 3000
619 static function romanNumeral( $num ) {
620 static $table = array(
621 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
622 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
623 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
624 array( '', 'M', 'MM', 'MMM' )
627 $num = intval( $num );
628 if ( $num > 3000 ||
$num <= 0 ) {
633 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
634 if ( $num >= $pow10 ) {
635 $s .= $table[$i][floor($num / $pow10)];
637 $num = $num %
$pow10;
643 * This is meant to be used by time(), date(), and timeanddate() to get
644 * the date preference they're supposed to use, it should be used in
648 * function timeanddate([...], $format = true) {
649 * $datePreference = $this->dateFormat($format);
654 * @param mixed $usePrefs: if true, the user's preference is used
655 * if false, the site/language default is used
656 * if int/string, assumed to be a format.
659 function dateFormat( $usePrefs = true ) {
662 if( is_bool( $usePrefs ) ) {
664 $datePreference = $wgUser->getDatePreference();
666 $options = User
::getDefaultOptions();
667 $datePreference = (string)$options['date'];
670 $datePreference = (string)$usePrefs;
674 if( $datePreference == '' ) {
678 return $datePreference;
683 * @param mixed $ts the time format which needs to be turned into a
684 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
685 * @param bool $adj whether to adjust the time output according to the
686 * user configured offset ($timecorrection)
687 * @param mixed $format true to use user's date format preference
688 * @param string $timecorrection the time offset as returned by
689 * validateTimeZone() in Special:Preferences
692 function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
695 $ts = $this->userAdjust( $ts, $timecorrection );
698 $pref = $this->dateFormat( $format );
699 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref date"] ) ) {
700 $pref = $this->defaultDateFormat
;
702 return $this->sprintfDate( $this->dateFormats
["$pref date"], $ts );
707 * @param mixed $ts the time format which needs to be turned into a
708 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
709 * @param bool $adj whether to adjust the time output according to the
710 * user configured offset ($timecorrection)
711 * @param mixed $format true to use user's date format preference
712 * @param string $timecorrection the time offset as returned by
713 * validateTimeZone() in Special:Preferences
716 function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
719 $ts = $this->userAdjust( $ts, $timecorrection );
722 $pref = $this->dateFormat( $format );
723 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref time"] ) ) {
724 $pref = $this->defaultDateFormat
;
726 return $this->sprintfDate( $this->dateFormats
["$pref time"], $ts );
731 * @param mixed $ts the time format which needs to be turned into a
732 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
733 * @param bool $adj whether to adjust the time output according to the
734 * user configured offset ($timecorrection)
736 * @param mixed $format what format to return, if it's false output the
737 * default one (default true)
738 * @param string $timecorrection the time offset as returned by
739 * validateTimeZone() in Special:Preferences
742 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) {
745 $ts = $this->userAdjust( $ts, $timecorrection );
748 $pref = $this->dateFormat( $format );
749 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref both"] ) ) {
750 $pref = $this->defaultDateFormat
;
753 return $this->sprintfDate( $this->dateFormats
["$pref both"], $ts );
756 function getMessage( $key ) {
758 return @$this->messages
[$key];
761 function getAllMessages() {
763 return $this->messages
;
766 function iconv( $in, $out, $string ) {
767 # For most languages, this is a wrapper for iconv
768 return iconv( $in, $out, $string );
771 // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
772 function ucwordbreaksCallbackAscii($matches){
773 return $this->ucfirst($matches[1]);
776 function ucwordbreaksCallbackMB($matches){
777 return mb_strtoupper($matches[0]);
780 function ucCallback($matches){
781 list( $wikiUpperChars ) = self
::getCaseMaps();
782 return strtr( $matches[1], $wikiUpperChars );
785 function lcCallback($matches){
786 list( , $wikiLowerChars ) = self
::getCaseMaps();
787 return strtr( $matches[1], $wikiLowerChars );
790 function ucwordsCallbackMB($matches){
791 return mb_strtoupper($matches[0]);
794 function ucwordsCallbackWiki($matches){
795 list( $wikiUpperChars ) = self
::getCaseMaps();
796 return strtr( $matches[0], $wikiUpperChars );
799 function ucfirst( $str ) {
800 return self
::uc( $str, true );
803 function uc( $str, $first = false ) {
804 if ( function_exists( 'mb_strtoupper' ) )
806 if ( self
::isMultibyte( $str ) )
807 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
809 return ucfirst( $str );
811 return self
::isMultibyte( $str ) ?
mb_strtoupper( $str ) : strtoupper( $str );
813 if ( self
::isMultibyte( $str ) ) {
814 list( $wikiUpperChars ) = $this->getCaseMaps();
815 $x = $first ?
'^' : '';
816 return preg_replace_callback(
817 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
818 array($this,"ucCallback"),
822 return $first ?
ucfirst( $str ) : strtoupper( $str );
825 function lcfirst( $str ) {
826 return self
::lc( $str, true );
829 function lc( $str, $first = false ) {
830 if ( function_exists( 'mb_strtolower' ) )
832 if ( self
::isMultibyte( $str ) )
833 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
835 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
837 return self
::isMultibyte( $str ) ?
mb_strtolower( $str ) : strtolower( $str );
839 if ( self
::isMultibyte( $str ) ) {
840 list( , $wikiLowerChars ) = self
::getCaseMaps();
841 $x = $first ?
'^' : '';
842 return preg_replace_callback(
843 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
844 array($this,"lcCallback"),
848 return $first ?
strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
851 function isMultibyte( $str ) {
852 return (bool)preg_match( '/[\x80-\xff]/', $str );
855 function ucwords($str) {
856 if ( self
::isMultibyte( $str ) ) {
857 $str = self
::lc($str);
859 // regexp to find first letter in each word (i.e. after each space)
860 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
862 // function to use to capitalize a single char
863 if ( function_exists( 'mb_strtoupper' ) )
864 return preg_replace_callback(
866 array($this,"ucwordsCallbackMB"),
870 return preg_replace_callback(
872 array($this,"ucwordsCallbackWiki"),
877 return ucwords( strtolower( $str ) );
880 # capitalize words at word breaks
881 function ucwordbreaks($str){
882 if (self
::isMultibyte( $str ) ) {
883 $str = self
::lc($str);
885 // since \b doesn't work for UTF-8, we explicitely define word break chars
886 $breaks= "[ \-\(\)\}\{\.,\?!]";
888 // find first letter after word break
889 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
891 if ( function_exists( 'mb_strtoupper' ) )
892 return preg_replace_callback(
894 array($this,"ucwordbreaksCallbackMB"),
898 return preg_replace_callback(
900 array($this,"ucwordsCallbackWiki"),
905 return preg_replace_callback(
906 '/\b([\w\x80-\xff]+)\b/',
907 array($this,"ucwordbreaksCallbackAscii"),
911 function checkTitleEncoding( $s ) {
912 if( is_array( $s ) ) {
913 wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
915 # Check for non-UTF-8 URLs
916 $ishigh = preg_match( '/[\x80-\xff]/', $s);
917 if(!$ishigh) return $s;
919 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
920 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
921 if( $isutf8 ) return $s;
923 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
926 function fallback8bitEncoding() {
928 return $this->fallback8bitEncoding
;
932 * Some languages have special punctuation to strip out
933 * or characters which need to be converted for MySQL's
934 * indexing to grok it correctly. Make such changes here.
939 function stripForSearch( $string ) {
940 # MySQL fulltext index doesn't grok utf-8, so we
941 # need to fold cases and convert to hex
943 wfProfileIn( __METHOD__
);
944 if( function_exists( 'mb_strtolower' ) ) {
946 "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
947 "'U8' . bin2hex( \"$1\" )",
948 mb_strtolower( $string ) );
950 list( , $wikiLowerChars ) = self
::getCaseMaps();
952 "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
953 "'U8' . bin2hex( strtr( \"\$1\", \$wikiLowerChars ) )",
956 wfProfileOut( __METHOD__
);
960 function convertForSearchResult( $termsArray ) {
961 # some languages, e.g. Chinese, need to do a conversion
962 # in order for search results to be displayed correctly
967 * Get the first character of a string.
972 function firstChar( $s ) {
973 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
974 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
976 return isset( $matches[1] ) ?
$matches[1] : "";
979 function initEncoding() {
980 # Some languages may have an alternate char encoding option
981 # (Esperanto X-coding, Japanese furigana conversion, etc)
982 # If this language is used as the primary content language,
983 # an override to the defaults can be set here on startup.
986 function recodeForEdit( $s ) {
987 # For some languages we'll want to explicitly specify
988 # which characters make it into the edit box raw
989 # or are converted in some way or another.
990 # Note that if wgOutputEncoding is different from
991 # wgInputEncoding, this text will be further converted
992 # to wgOutputEncoding.
993 global $wgEditEncoding;
994 if( $wgEditEncoding == '' or
995 $wgEditEncoding == 'UTF-8' ) {
998 return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1002 function recodeInput( $s ) {
1003 # Take the previous into account.
1004 global $wgEditEncoding;
1005 if($wgEditEncoding != "") {
1006 $enc = $wgEditEncoding;
1010 if( $enc == 'UTF-8' ) {
1013 return $this->iconv( $enc, 'UTF-8', $s );
1018 * For right-to-left language support
1028 * A hidden direction mark (LRM or RLM), depending on the language direction
1032 function getDirMark() {
1033 return $this->isRTL() ?
"\xE2\x80\x8F" : "\xE2\x80\x8E";
1037 * An arrow, depending on the language direction
1041 function getArrow() {
1042 return $this->isRTL() ?
'←' : '→';
1046 * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1050 function linkPrefixExtension() {
1052 return $this->linkPrefixExtension
;
1055 function &getMagicWords() {
1057 return $this->magicWords
;
1060 # Fill a MagicWord object with data from here
1061 function getMagic( &$mw ) {
1062 if ( !isset( $this->mMagicExtensions
) ) {
1063 $this->mMagicExtensions
= array();
1064 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions
, $this->getCode() ) );
1066 if ( isset( $this->mMagicExtensions
[$mw->mId
] ) ) {
1067 $rawEntry = $this->mMagicExtensions
[$mw->mId
];
1069 $magicWords =& $this->getMagicWords();
1070 if ( isset( $magicWords[$mw->mId
] ) ) {
1071 $rawEntry = $magicWords[$mw->mId
];
1073 # Fall back to English if local list is incomplete
1074 $magicWords =& Language
::getMagicWords();
1075 $rawEntry = $magicWords[$mw->mId
];
1079 if( !is_array( $rawEntry ) ) {
1080 error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1082 $mw->mCaseSensitive
= $rawEntry[0];
1083 $mw->mSynonyms
= array_slice( $rawEntry, 1 );
1087 * Italic is unsuitable for some languages
1091 * @param string $text The text to be emphasized.
1094 function emphasize( $text ) {
1095 return "<em>$text</em>";
1099 * Normally we output all numbers in plain en_US style, that is
1100 * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
1101 * point twohundredthirtyfive. However this is not sutable for all
1102 * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
1103 * Icelandic just want to use commas instead of dots, and dots instead
1104 * of commas like "293.291,235".
1106 * An example of this function being called:
1108 * wfMsg( 'message', $wgLang->formatNum( $num ) )
1111 * See LanguageGu.php for the Gujarati implementation and
1112 * LanguageIs.php for the , => . and . => , implementation.
1114 * @todo check if it's viable to use localeconv() for the decimal
1117 * @param mixed $number the string to be formatted, should be an integer or
1118 * a floating point number.
1119 * @param bool $nocommafy Set to true for special numbers like dates
1122 function formatNum( $number, $nocommafy = false ) {
1123 global $wgTranslateNumerals;
1125 $number = $this->commafy($number);
1126 $s = $this->separatorTransformTable();
1127 if (!is_null($s)) { $number = strtr($number, $s); }
1130 if ($wgTranslateNumerals) {
1131 $s = $this->digitTransformTable();
1132 if (!is_null($s)) { $number = strtr($number, $s); }
1139 * Adds commas to a given number
1144 function commafy($_) {
1145 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
1148 function digitTransformTable() {
1150 return $this->digitTransformTable
;
1153 function separatorTransformTable() {
1155 return $this->separatorTransformTable
;
1160 * For the credit list in includes/Credits.php (action=credits)
1165 function listToText( $l ) {
1168 for ($i = $m; $i >= 0; $i--) {
1171 } else if ($i == $m - 1) {
1172 $s = $l[$i] . ' ' . $this->getMessageFromDB( 'and' ) . ' ' . $s;
1174 $s = $l[$i] . ', ' . $s;
1180 # Crop a string from the beginning or end to a certain number of bytes.
1181 # (Bytes are used because our storage has limited byte lengths for some
1182 # columns in the database.) Multibyte charsets will need to make sure that
1183 # only whole characters are included!
1185 # $length does not include the optional ellipsis.
1186 # If $length is negative, snip from the beginning
1187 function truncate( $string, $length, $ellipsis = "" ) {
1188 if( $length == 0 ) {
1191 if ( strlen( $string ) <= abs( $length ) ) {
1195 $string = substr( $string, 0, $length );
1196 $char = ord( $string[strlen( $string ) - 1] );
1197 if ($char >= 0xc0) {
1198 # We got the first byte only of a multibyte char; remove it.
1199 $string = substr( $string, 0, -1 );
1200 } elseif( $char >= 0x80 &&
1201 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
1202 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
1203 # We chopped in the middle of a character; remove it
1206 return $string . $ellipsis;
1208 $string = substr( $string, $length );
1209 $char = ord( $string[0] );
1210 if( $char >= 0x80 && $char < 0xc0 ) {
1211 # We chopped in the middle of a character; remove the whole thing
1212 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
1214 return $ellipsis . $string;
1219 * Grammatical transformations, needed for inflected languages
1220 * Invoked by putting {{grammar:case|word}} in a message
1222 * @param string $word
1223 * @param string $case
1226 function convertGrammar( $word, $case ) {
1227 global $wgGrammarForms;
1228 if ( isset($wgGrammarForms['en'][$case][$word]) ) {
1229 return $wgGrammarForms['en'][$case][$word];
1235 * Plural form transformations, needed for some languages.
1236 * For example, where are 3 form of plural in Russian and Polish,
1237 * depending on "count mod 10". See [[w:Plural]]
1238 * For English it is pretty simple.
1240 * Invoked by putting {{plural:count|wordform1|wordform2}}
1241 * or {{plural:count|wordform1|wordform2|wordform3}}
1243 * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
1245 * @param integer $count
1246 * @param string $wordform1
1247 * @param string $wordform2
1248 * @param string $wordform3 (optional)
1251 function convertPlural( $count, $w1, $w2, $w3) {
1252 return $count == '1' ?
$w1 : $w2;
1256 * For translaing of expiry times
1257 * @param string The validated block time in English
1258 * @return Somehow translated block time
1259 * @see LanguageFi.php for example implementation
1261 function translateBlockExpiry( $str ) {
1263 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
1265 if ( $scBlockExpiryOptions == '-') {
1269 foreach (explode(',', $scBlockExpiryOptions) as $option) {
1270 if ( strpos($option, ":") === false )
1272 list($show, $value) = explode(":", $option);
1273 if ( strcmp ( $str, $value) == 0 )
1274 return '<span title="' . htmlspecialchars($str). '">' .
1275 htmlspecialchars( trim( $show ) ) . '</span>';
1282 * languages like Chinese need to be segmented in order for the diff
1285 * @param string $text
1288 function segmentForDiff( $text ) {
1293 * and unsegment to show the result
1295 * @param string $text
1298 function unsegmentForDiff( $text ) {
1302 # convert text to different variants of a language.
1303 function convert( $text, $isTitle = false) {
1304 return $this->mConverter
->convert($text, $isTitle);
1307 # Convert text from within Parser
1308 function parserConvert( $text, &$parser ) {
1309 return $this->mConverter
->parserConvert( $text, $parser );
1312 # Tell the converter that it shouldn't convert titles
1313 function setNoTitleConvert(){
1314 $this->mConverter
->setNotitleConvert();
1317 # Check if this is a language with variants
1318 function hasVariants(){
1319 return sizeof($this->getVariants())>1;
1324 * Perform output conversion on a string, and encode for safe HTML output.
1325 * @param string $text
1326 * @param bool $isTitle -- wtf?
1328 * @todo this should get integrated somewhere sane
1330 function convertHtml( $text, $isTitle = false ) {
1331 return htmlspecialchars( $this->convert( $text, $isTitle ) );
1334 function convertCategoryKey( $key ) {
1335 return $this->mConverter
->convertCategoryKey( $key );
1339 * get the list of variants supported by this langauge
1340 * see sample implementation in LanguageZh.php
1342 * @return array an array of language codes
1344 function getVariants() {
1345 return $this->mConverter
->getVariants();
1349 function getPreferredVariant( $fromUser = true ) {
1350 return $this->mConverter
->getPreferredVariant( $fromUser );
1354 * if a language supports multiple variants, it is
1355 * possible that non-existing link in one variant
1356 * actually exists in another variant. this function
1357 * tries to find it. See e.g. LanguageZh.php
1359 * @param string $link the name of the link
1360 * @param mixed $nt the title object of the link
1361 * @return null the input parameters may be modified upon return
1363 function findVariantLink( &$link, &$nt ) {
1364 $this->mConverter
->findVariantLink($link, $nt);
1368 * If a language supports multiple variants, converts text
1369 * into an array of all possible variants of the text:
1370 * 'variant' => text in that variant
1373 function convertLinkToAllVariants($text){
1374 return $this->mConverter
->convertLinkToAllVariants($text);
1379 * returns language specific options used by User::getPageRenderHash()
1380 * for example, the preferred language variant
1385 function getExtraHashOptions() {
1386 return $this->mConverter
->getExtraHashOptions();
1390 * for languages that support multiple variants, the title of an
1391 * article may be displayed differently in different variants. this
1392 * function returns the apporiate title defined in the body of the article.
1396 function getParsedTitle() {
1397 return $this->mConverter
->getParsedTitle();
1401 * Enclose a string with the "no conversion" tag. This is used by
1402 * various functions in the Parser
1404 * @param string $text text to be tagged for no conversion
1405 * @return string the tagged text
1407 function markNoConversion( $text, $noParse=false ) {
1408 return $this->mConverter
->markNoConversion( $text, $noParse );
1412 * A regular expression to match legal word-trailing characters
1413 * which should be merged onto a link of the form [[foo]]bar.
1418 function linkTrail() {
1420 return $this->linkTrail
;
1423 function getLangObj() {
1428 * Get the RFC 3066 code for this language object
1430 function getCode() {
1431 return $this->mCode
;
1434 function setCode( $code ) {
1435 $this->mCode
= $code;
1438 static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
1439 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
1442 static function getMessagesFileName( $code ) {
1444 return self
::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
1447 static function getClassFileName( $code ) {
1449 return self
::getFileName( "$IP/languages/classes/Language", $code, '.php' );
1452 static function getLocalisationArray( $code, $disableCache = false ) {
1453 self
::loadLocalisation( $code, $disableCache );
1454 return self
::$mLocalisationCache[$code];
1458 * Load localisation data for a given code into the static cache
1460 * @return array Dependencies, map of filenames to mtimes
1462 static function loadLocalisation( $code, $disableCache = false ) {
1463 static $recursionGuard = array();
1467 throw new MWException( "Invalid language code requested" );
1470 if ( !$disableCache ) {
1471 # Try the per-process cache
1472 if ( isset( self
::$mLocalisationCache[$code] ) ) {
1473 return self
::$mLocalisationCache[$code]['deps'];
1476 wfProfileIn( __METHOD__
);
1478 # Try the serialized directory
1479 $cache = wfGetPrecompiledData( self
::getFileName( "Messages", $code, '.ser' ) );
1481 self
::$mLocalisationCache[$code] = $cache;
1482 wfDebug( "Got localisation for $code from precompiled data file\n" );
1483 wfProfileOut( __METHOD__
);
1484 return self
::$mLocalisationCache[$code]['deps'];
1487 # Try the global cache
1488 $memcKey = wfMemcKey('localisation', $code );
1489 $cache = $wgMemc->get( $memcKey );
1492 # Check file modification times
1493 foreach ( $cache['deps'] as $file => $mtime ) {
1494 if ( !file_exists( $file ) ||
filemtime( $file ) > $mtime ) {
1499 if ( self
::isLocalisationOutOfDate( $cache ) ) {
1500 $wgMemc->delete( $memcKey );
1502 wfDebug( "Localisation cache for $code had expired due to update of $file\n" );
1504 self
::$mLocalisationCache[$code] = $cache;
1505 wfDebug( "Got localisation for $code from cache\n" );
1506 wfProfileOut( __METHOD__
);
1507 return $cache['deps'];
1511 wfProfileIn( __METHOD__
);
1514 if ( $code != 'en' ) {
1520 # Load the primary localisation from the source file
1521 $filename = self
::getMessagesFileName( $code );
1522 if ( !file_exists( $filename ) ) {
1523 wfDebug( "No localisation file for $code, using implicit fallback to en\n" );
1527 $deps = array( $filename => filemtime( $filename ) );
1528 require( $filename );
1529 $cache = compact( self
::$mLocalisationKeys );
1530 wfDebug( "Got localisation for $code from source\n" );
1533 if ( !empty( $fallback ) ) {
1534 # Load the fallback localisation, with a circular reference guard
1535 if ( isset( $recursionGuard[$code] ) ) {
1536 throw new MWException( "Error: Circular fallback reference in language code $code" );
1538 $recursionGuard[$code] = true;
1539 $newDeps = self
::loadLocalisation( $fallback, $disableCache );
1540 unset( $recursionGuard[$code] );
1542 $secondary = self
::$mLocalisationCache[$fallback];
1543 $deps = array_merge( $deps, $newDeps );
1545 # Merge the fallback localisation with the current localisation
1546 foreach ( self
::$mLocalisationKeys as $key ) {
1547 if ( isset( $cache[$key] ) ) {
1548 if ( isset( $secondary[$key] ) ) {
1549 if ( in_array( $key, self
::$mMergeableMapKeys ) ) {
1550 $cache[$key] = $cache[$key] +
$secondary[$key];
1551 } elseif ( in_array( $key, self
::$mMergeableListKeys ) ) {
1552 $cache[$key] = array_merge( $secondary[$key], $cache[$key] );
1556 $cache[$key] = $secondary[$key];
1560 # Merge bookstore lists if requested
1561 if ( !empty( $cache['bookstoreList']['inherit'] ) ) {
1562 $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] );
1564 if ( isset( $cache['bookstoreList']['inherit'] ) ) {
1565 unset( $cache['bookstoreList']['inherit'] );
1569 # Add dependencies to the cache entry
1570 $cache['deps'] = $deps;
1572 # Replace spaces with underscores in namespace names
1573 $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] );
1575 # Save to both caches
1576 self
::$mLocalisationCache[$code] = $cache;
1577 if ( !$disableCache ) {
1578 $wgMemc->set( $memcKey, $cache );
1581 wfProfileOut( __METHOD__
);
1586 * Test if a given localisation cache is out of date with respect to the
1587 * source Messages files. This is done automatically for the global cache
1588 * in $wgMemc, but is only done on certain occasions for the serialized
1591 * @param $cache mixed Either a language code or a cache array
1593 static function isLocalisationOutOfDate( $cache ) {
1594 if ( !is_array( $cache ) ) {
1595 self
::loadLocalisation( $cache );
1596 $cache = self
::$mLocalisationCache[$cache];
1599 foreach ( $cache['deps'] as $file => $mtime ) {
1600 if ( !file_exists( $file ) ||
filemtime( $file ) > $mtime ) {
1609 * Get the fallback for a given language
1611 static function getFallbackFor( $code ) {
1612 self
::loadLocalisation( $code );
1613 return self
::$mLocalisationCache[$code]['fallback'];
1617 * Get all messages for a given language
1619 static function getMessagesFor( $code ) {
1620 self
::loadLocalisation( $code );
1621 return self
::$mLocalisationCache[$code]['messages'];
1625 * Get a message for a given language
1627 static function getMessageFor( $key, $code ) {
1628 self
::loadLocalisation( $code );
1629 return @self
::$mLocalisationCache[$code]['messages'][$key];
1633 * Load localisation data for this object
1636 if ( !$this->mLoaded
) {
1637 self
::loadLocalisation( $this->getCode() );
1638 $cache =& self
::$mLocalisationCache[$this->getCode()];
1639 foreach ( self
::$mLocalisationKeys as $key ) {
1640 $this->$key = $cache[$key];
1642 $this->mLoaded
= true;
1644 $this->fixUpSettings();
1649 * Do any necessary post-cache-load settings adjustment
1651 function fixUpSettings() {
1652 global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk, $wgMessageCache,
1653 $wgNamespaceAliases, $wgAmericanDates;
1654 wfProfileIn( __METHOD__
);
1655 if ( $wgExtraNamespaces ) {
1656 $this->namespaceNames
= $wgExtraNamespaces +
$this->namespaceNames
;
1659 $this->namespaceNames
[NS_PROJECT
] = $wgMetaNamespace;
1660 if ( $wgMetaNamespaceTalk ) {
1661 $this->namespaceNames
[NS_PROJECT_TALK
] = $wgMetaNamespaceTalk;
1663 $talk = $this->namespaceNames
[NS_PROJECT_TALK
];
1664 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
1666 # Allow grammar transformations
1667 # Allowing full message-style parsing would make simple requests
1668 # such as action=raw much more expensive than they need to be.
1669 # This will hopefully cover most cases.
1670 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
1671 array( &$this, 'replaceGrammarInNamespace' ), $talk );
1672 $talk = str_replace( ' ', '_', $talk );
1673 $this->namespaceNames
[NS_PROJECT_TALK
] = $talk;
1676 # The above mixing may leave namespaces out of canonical order.
1677 # Re-order by namespace ID number...
1678 ksort( $this->namespaceNames
);
1680 # Put namespace names and aliases into a hashtable.
1681 # If this is too slow, then we should arrange it so that it is done
1682 # before caching. The catch is that at pre-cache time, the above
1683 # class-specific fixup hasn't been done.
1684 $this->mNamespaceIds
= array();
1685 foreach ( $this->namespaceNames
as $index => $name ) {
1686 $this->mNamespaceIds
[$this->lc($name)] = $index;
1688 if ( $this->namespaceAliases
) {
1689 foreach ( $this->namespaceAliases
as $name => $index ) {
1690 $this->mNamespaceIds
[$this->lc($name)] = $index;
1693 if ( $wgNamespaceAliases ) {
1694 foreach ( $wgNamespaceAliases as $name => $index ) {
1695 $this->mNamespaceIds
[$this->lc($name)] = $index;
1699 if ( $this->defaultDateFormat
== 'dmy or mdy' ) {
1700 $this->defaultDateFormat
= $wgAmericanDates ?
'mdy' : 'dmy';
1702 wfProfileOut( __METHOD__
);
1705 function replaceGrammarInNamespace( $m ) {
1706 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
1709 static function getCaseMaps() {
1710 static $wikiUpperChars, $wikiLowerChars;
1711 if ( isset( $wikiUpperChars ) ) {
1712 return array( $wikiUpperChars, $wikiLowerChars );
1715 wfProfileIn( __METHOD__
);
1716 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
1717 if ( $arr === false ) {
1718 throw new MWException(
1719 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
1722 wfProfileOut( __METHOD__
);
1723 return array( $wikiUpperChars, $wikiLowerChars );