6 if( !defined( 'MEDIAWIKI' ) ) {
7 echo "This file is part of MediaWiki, it is not a valid entry point.\n";
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
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
24 global $wgLanguageNames;
25 require_once( dirname(__FILE__
) . '/Names.php' ) ;
27 global $wgInputEncoding, $wgOutputEncoding;
30 * These are always UTF-8, they exist only for backwards compatibility
32 $wgInputEncoding = "UTF-8";
33 $wgOutputEncoding = "UTF-8";
35 if( function_exists( 'mb_strtoupper' ) ) {
36 mb_internal_encoding('UTF-8');
39 /* a fake language converter */
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; }
56 #--------------------------------------------------------------------------
57 # Internationalisation code
58 #--------------------------------------------------------------------------
61 var $mConverter, $mVariants, $mCode, $mLoaded = false;
63 static public $mLocalisationKeys = array( 'fallback', 'namespaceNames',
64 'quickbarSettings', 'skinNames', 'mathNames',
65 'bookstoreList', 'magicWords', 'messages', 'rtl', 'digitTransformTable',
66 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
67 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases',
68 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
69 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases' );
71 static public $mMergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames',
72 'dateFormats', 'defaultUserOptionOverrides', 'magicWords' );
74 static public $mMergeableListKeys = array( 'extraUserToggles' );
76 static public $mMergeableAliasListKeys = array( 'specialPageAliases' );
78 static public $mLocalisationCache = array();
80 static public $mWeekdayMsgs = array(
81 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
85 static public $mWeekdayAbbrevMsgs = array(
86 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
89 static public $mMonthMsgs = array(
90 'january', 'february', 'march', 'april', 'may_long', 'june',
91 'july', 'august', 'september', 'october', 'november',
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',
99 static public $mMonthAbbrevMsgs = array(
100 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
101 'sep', 'oct', 'nov', 'dec'
105 * Create a language object for a given language code
107 static function factory( $code ) {
109 static $recursionLevel = 0;
111 if ( $code == 'en' ) {
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");
119 if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
120 include_once("$IP/languages/classes/$class.php");
124 if ( $recursionLevel > 5 ) {
125 throw new MWException( "Language fallback loop detected when creating class $class\n" );
128 if( ! class_exists( $class ) ) {
129 $fallback = Language
::getFallbackFor( $code );
131 $lang = Language
::factory( $fallback );
133 $lang->setCode( $code );
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' ) {
147 $this->mCode
= str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
152 * Hook which will be called if this is the content language.
153 * Descendants can use this to register hook functions or modify globals
155 function initContLang() {}
161 function getDefaultUserOptions() {
162 return User
::getDefaultOptions();
165 function getFallbackLanguageCode() {
167 return $this->fallback
;
171 * Exports $wgBookstoreListEn
174 function getBookstoreList() {
176 return $this->bookstoreList
;
182 function getNamespaces() {
184 return $this->namespaceNames
;
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.
195 function getFormattedNamespaces() {
196 $ns = $this->getNamespaces();
197 foreach($ns as $k => $v) {
198 $ns[$k] = strtr($v, '_', ' ');
204 * Get a namespace value by key
206 * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
207 * echo $mw_ns; // prints 'MediaWiki'
210 * @param int $index the array key of the namespace to return
211 * @return mixed, string if the namespace value exists, otherwise false
213 function getNsText( $index ) {
214 $ns = $this->getNamespaces();
215 return isset( $ns[$index] ) ?
$ns[$index] : false;
219 * A convenience function that returns the same thing as
220 * getNsText() except with '_' changed to ' ', useful for
225 function getFormattedNsText( $index ) {
226 $ns = $this->getNsText( $index );
227 return strtr($ns, '_', ' ');
231 * Get a namespace key by value, case insensetive.
233 * @param string $text
234 * @return mixed An integer if $text is a valid value otherwise false
236 function getNsIndex( $text ) {
238 $lctext = $this->lc($text);
239 return isset( $this->mNamespaceIds
[$lctext] ) ?
$this->mNamespaceIds
[$lctext] : false;
243 * short names for language variants used for language conversion links.
245 * @param string $code
248 function getVariantname( $code ) {
249 return $this->getMessageFromDB( "variantname-$code" );
252 function specialPage( $name ) {
253 $aliases = $this->getSpecialPageAliases();
254 if ( isset( $aliases[$name][0] ) ) {
255 $name = $aliases[$name][0];
257 return $this->getNsText(NS_SPECIAL
) . ':' . $name;
260 function getQuickbarSettings() {
262 return $this->quickbarSettings
;
265 function getSkinNames() {
267 return $this->skinNames
;
270 function getMathNames() {
272 return $this->mathNames
;
275 function getDatePreferences() {
277 return $this->datePreferences
;
280 function getDateFormats() {
282 return $this->dateFormats
;
285 function getDefaultDateFormat() {
287 return $this->defaultDateFormat
;
290 function getDatePreferenceMigrationMap() {
292 return $this->datePreferenceMigrationMap
;
295 function getDefaultUserOptionOverrides() {
297 return $this->defaultUserOptionOverrides
;
300 function getExtraUserToggles() {
302 return $this->extraUserToggles
;
305 function getUserToggle( $tog ) {
306 return $this->getMessageFromDB( "tog-$tog" );
310 * Get language names, indexed by code.
311 * If $customisedOnly is true, only returns codes with a messages file
313 public static function getLanguageNames( $customisedOnly = false ) {
314 global $wgLanguageNames;
315 if ( !$customisedOnly ) {
316 return $wgLanguageNames;
320 $messageFiles = glob( "$IP/languages/messages/Messages*.php" );
322 foreach ( $messageFiles as $file ) {
324 if( preg_match( '/Messages([A-Z][a-z_]+)\.php$/', $file, $m ) ) {
325 $code = str_replace( '_', '-', strtolower( $m[1] ) );
326 if ( isset( $wgLanguageNames[$code] ) ) {
327 $names[$code] = $wgLanguageNames[$code];
335 * Ugly hack to get a message maybe from the MediaWiki namespace, if this
336 * language object is the content or user language.
338 function getMessageFromDB( $msg ) {
339 global $wgContLang, $wgLang;
340 if ( $wgContLang->getCode() == $this->getCode() ) {
342 return wfMsgForContent( $msg );
343 } elseif ( $wgLang->getCode() == $this->getCode() ) {
345 return wfMsg( $msg );
347 # Neither, get from localisation
348 return $this->getMessage( $msg );
352 function getLanguageName( $code ) {
353 global $wgLanguageNames;
354 if ( ! array_key_exists( $code, $wgLanguageNames ) ) {
357 return $wgLanguageNames[$code];
360 function getMonthName( $key ) {
361 return $this->getMessageFromDB( self
::$mMonthMsgs[$key-1] );
364 function getMonthNameGen( $key ) {
365 return $this->getMessageFromDB( self
::$mMonthGenMsgs[$key-1] );
368 function getMonthAbbreviation( $key ) {
369 return $this->getMessageFromDB( self
::$mMonthAbbrevMsgs[$key-1] );
372 function getWeekdayName( $key ) {
373 return $this->getMessageFromDB( self
::$mWeekdayMsgs[$key-1] );
376 function getWeekdayAbbreviation( $key ) {
377 return $this->getMessageFromDB( self
::$mWeekdayAbbrevMsgs[$key-1] );
381 * Used by date() and time() to adjust the time output.
383 * @param int $ts the time in date('YmdHis') format
384 * @param mixed $tz adjust the time by this amount (default false,
385 * mean we get user timecorrection setting)
388 function userAdjust( $ts, $tz = false ) {
389 global $wgUser, $wgLocalTZoffset;
392 $tz = $wgUser->getOption( 'timecorrection' );
395 # minutes and hours differences:
400 # Global offset in minutes.
401 if( isset($wgLocalTZoffset) ) {
402 $hrDiff = $wgLocalTZoffset %
60;
403 $minDiff = $wgLocalTZoffset - ($hrDiff * 60);
405 } elseif ( strpos( $tz, ':' ) !== false ) {
406 $tzArray = explode( ':', $tz );
407 $hrDiff = intval($tzArray[0]);
408 $minDiff = intval($hrDiff < 0 ?
-$tzArray[1] : $tzArray[1]);
410 $hrDiff = intval( $tz );
413 # No difference ? Return time unchanged
414 if ( 0 == $hrDiff && 0 == $minDiff ) { return $ts; }
416 # Generate an adjusted date
418 (int)substr( $ts, 8, 2) ) +
$hrDiff, # Hours
419 (int)substr( $ts, 10, 2 ) +
$minDiff, # Minutes
420 (int)substr( $ts, 12, 2 ), # Seconds
421 (int)substr( $ts, 4, 2 ), # Month
422 (int)substr( $ts, 6, 2 ), # Day
423 (int)substr( $ts, 0, 4 ) ); #Year
424 return date( 'YmdHis', $t );
428 * This is a workalike of PHP's date() function, but with better
429 * internationalisation, a reduced set of format characters, and a better
432 * Supported format characters are dDjlNwzWFmMntLYyaAgGhHiscrU. See the
433 * PHP manual for definitions. There are a number of extensions, which
436 * xn Do not translate digits of the next numeric format character
437 * xN Toggle raw digit (xn) flag, stays set until explicitly unset
438 * xr Use roman numerals for the next numeric format character
440 * xg Genitive month name
442 * Characters enclosed in double quotes will be considered literal (with
443 * the quotes themselves removed). Unmatched quotes will be considered
444 * literal quotes. Example:
446 * "The month is" F => The month is January
449 * Backslash escaping is also supported.
451 * @param string $format
452 * @param string $ts 14-character timestamp
456 function sprintfDate( $format, $ts ) {
462 for ( $p = 0; $p < strlen( $format ); $p++
) {
465 if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
466 $code .= $format[++
$p];
477 $rawToggle = !$rawToggle;
483 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
486 $num = substr( $ts, 6, 2 );
489 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
490 $s .= $this->getWeekdayAbbreviation( date( 'w', $unix ) +
1 );
493 $num = intval( substr( $ts, 6, 2 ) );
496 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
497 $s .= $this->getWeekdayName( date( 'w', $unix ) +
1 );
500 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
501 $w = date( 'w', $unix );
505 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
506 $num = date( 'w', $unix );
509 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
510 $num = date( 'z', $unix );
513 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
514 $num = date( 'W', $unix );
517 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
520 $num = substr( $ts, 4, 2 );
523 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
526 $num = intval( substr( $ts, 4, 2 ) );
529 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
530 $num = date( 't', $unix );
533 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
534 $num = date( 'L', $unix );
537 $num = substr( $ts, 0, 4 );
540 $num = substr( $ts, 2, 2 );
543 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'am' : 'pm';
546 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'AM' : 'PM';
549 $h = substr( $ts, 8, 2 );
550 $num = $h %
12 ?
$h %
12 : 12;
553 $num = intval( substr( $ts, 8, 2 ) );
556 $h = substr( $ts, 8, 2 );
557 $num = sprintf( '%02d', $h %
12 ?
$h %
12 : 12 );
560 $num = substr( $ts, 8, 2 );
563 $num = substr( $ts, 10, 2 );
566 $num = substr( $ts, 12, 2 );
569 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
570 $s .= date( 'c', $unix );
573 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
574 $s .= date( 'r', $unix );
577 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
582 if ( $p < strlen( $format ) - 1 ) {
590 if ( $p < strlen( $format ) - 1 ) {
591 $endQuote = strpos( $format, '"', $p +
1 );
592 if ( $endQuote === false ) {
593 # No terminating quote, assume literal "
596 $s .= substr( $format, $p +
1, $endQuote - $p - 1 );
600 # Quote at end of string, assume literal "
607 if ( $num !== false ) {
608 if ( $rawToggle ||
$raw ) {
611 } elseif ( $roman ) {
612 $s .= self
::romanNumeral( $num );
615 $s .= $this->formatNum( $num, true );
624 * Roman number formatting up to 3000
626 static function romanNumeral( $num ) {
627 static $table = array(
628 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
629 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
630 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
631 array( '', 'M', 'MM', 'MMM' )
634 $num = intval( $num );
635 if ( $num > 3000 ||
$num <= 0 ) {
640 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
641 if ( $num >= $pow10 ) {
642 $s .= $table[$i][floor($num / $pow10)];
644 $num = $num %
$pow10;
650 * This is meant to be used by time(), date(), and timeanddate() to get
651 * the date preference they're supposed to use, it should be used in
655 * function timeanddate([...], $format = true) {
656 * $datePreference = $this->dateFormat($format);
661 * @param mixed $usePrefs: if true, the user's preference is used
662 * if false, the site/language default is used
663 * if int/string, assumed to be a format.
666 function dateFormat( $usePrefs = true ) {
669 if( is_bool( $usePrefs ) ) {
671 $datePreference = $wgUser->getDatePreference();
673 $options = User
::getDefaultOptions();
674 $datePreference = (string)$options['date'];
677 $datePreference = (string)$usePrefs;
681 if( $datePreference == '' ) {
685 return $datePreference;
690 * @param mixed $ts the time format which needs to be turned into a
691 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
692 * @param bool $adj whether to adjust the time output according to the
693 * user configured offset ($timecorrection)
694 * @param mixed $format true to use user's date format preference
695 * @param string $timecorrection the time offset as returned by
696 * validateTimeZone() in Special:Preferences
699 function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
702 $ts = $this->userAdjust( $ts, $timecorrection );
705 $pref = $this->dateFormat( $format );
706 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref date"] ) ) {
707 $pref = $this->defaultDateFormat
;
709 return $this->sprintfDate( $this->dateFormats
["$pref date"], $ts );
714 * @param mixed $ts the time format which needs to be turned into a
715 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
716 * @param bool $adj whether to adjust the time output according to the
717 * user configured offset ($timecorrection)
718 * @param mixed $format true to use user's date format preference
719 * @param string $timecorrection the time offset as returned by
720 * validateTimeZone() in Special:Preferences
723 function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
726 $ts = $this->userAdjust( $ts, $timecorrection );
729 $pref = $this->dateFormat( $format );
730 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref time"] ) ) {
731 $pref = $this->defaultDateFormat
;
733 return $this->sprintfDate( $this->dateFormats
["$pref time"], $ts );
738 * @param mixed $ts the time format which needs to be turned into a
739 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
740 * @param bool $adj whether to adjust the time output according to the
741 * user configured offset ($timecorrection)
743 * @param mixed $format what format to return, if it's false output the
744 * default one (default true)
745 * @param string $timecorrection the time offset as returned by
746 * validateTimeZone() in Special:Preferences
749 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) {
752 $ts = $this->userAdjust( $ts, $timecorrection );
755 $pref = $this->dateFormat( $format );
756 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref both"] ) ) {
757 $pref = $this->defaultDateFormat
;
760 return $this->sprintfDate( $this->dateFormats
["$pref both"], $ts );
763 function getMessage( $key ) {
765 return isset( $this->messages
[$key] ) ?
$this->messages
[$key] : null;
768 function getAllMessages() {
770 return $this->messages
;
773 function iconv( $in, $out, $string ) {
774 # For most languages, this is a wrapper for iconv
775 return iconv( $in, $out . '//IGNORE', $string );
778 // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
779 function ucwordbreaksCallbackAscii($matches){
780 return $this->ucfirst($matches[1]);
783 function ucwordbreaksCallbackMB($matches){
784 return mb_strtoupper($matches[0]);
787 function ucCallback($matches){
788 list( $wikiUpperChars ) = self
::getCaseMaps();
789 return strtr( $matches[1], $wikiUpperChars );
792 function lcCallback($matches){
793 list( , $wikiLowerChars ) = self
::getCaseMaps();
794 return strtr( $matches[1], $wikiLowerChars );
797 function ucwordsCallbackMB($matches){
798 return mb_strtoupper($matches[0]);
801 function ucwordsCallbackWiki($matches){
802 list( $wikiUpperChars ) = self
::getCaseMaps();
803 return strtr( $matches[0], $wikiUpperChars );
806 function ucfirst( $str ) {
807 return self
::uc( $str, true );
810 function uc( $str, $first = false ) {
811 if ( function_exists( 'mb_strtoupper' ) ) {
813 if ( self
::isMultibyte( $str ) ) {
814 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
816 return ucfirst( $str );
819 return self
::isMultibyte( $str ) ?
mb_strtoupper( $str ) : strtoupper( $str );
822 if ( self
::isMultibyte( $str ) ) {
823 list( $wikiUpperChars ) = $this->getCaseMaps();
824 $x = $first ?
'^' : '';
825 return preg_replace_callback(
826 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
827 array($this,"ucCallback"),
831 return $first ?
ucfirst( $str ) : strtoupper( $str );
836 function lcfirst( $str ) {
837 return self
::lc( $str, true );
840 function lc( $str, $first = false ) {
841 if ( function_exists( 'mb_strtolower' ) )
843 if ( self
::isMultibyte( $str ) )
844 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
846 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
848 return self
::isMultibyte( $str ) ?
mb_strtolower( $str ) : strtolower( $str );
850 if ( self
::isMultibyte( $str ) ) {
851 list( , $wikiLowerChars ) = self
::getCaseMaps();
852 $x = $first ?
'^' : '';
853 return preg_replace_callback(
854 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
855 array($this,"lcCallback"),
859 return $first ?
strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
862 function isMultibyte( $str ) {
863 return (bool)preg_match( '/[\x80-\xff]/', $str );
866 function ucwords($str) {
867 if ( self
::isMultibyte( $str ) ) {
868 $str = self
::lc($str);
870 // regexp to find first letter in each word (i.e. after each space)
871 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
873 // function to use to capitalize a single char
874 if ( function_exists( 'mb_strtoupper' ) )
875 return preg_replace_callback(
877 array($this,"ucwordsCallbackMB"),
881 return preg_replace_callback(
883 array($this,"ucwordsCallbackWiki"),
888 return ucwords( strtolower( $str ) );
891 # capitalize words at word breaks
892 function ucwordbreaks($str){
893 if (self
::isMultibyte( $str ) ) {
894 $str = self
::lc($str);
896 // since \b doesn't work for UTF-8, we explicitely define word break chars
897 $breaks= "[ \-\(\)\}\{\.,\?!]";
899 // find first letter after word break
900 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
902 if ( function_exists( 'mb_strtoupper' ) )
903 return preg_replace_callback(
905 array($this,"ucwordbreaksCallbackMB"),
909 return preg_replace_callback(
911 array($this,"ucwordsCallbackWiki"),
916 return preg_replace_callback(
917 '/\b([\w\x80-\xff]+)\b/',
918 array($this,"ucwordbreaksCallbackAscii"),
923 * Return a case-folded representation of $s
925 * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
926 * and $s2 are the same except for the case of their characters. It is not
927 * necessary for the value returned to make sense when displayed.
929 * Do *not* perform any other normalisation in this function. If a caller
930 * uses this function when it should be using a more general normalisation
931 * function, then fix the caller.
933 function caseFold( $s ) {
934 return $this->uc( $s );
937 function checkTitleEncoding( $s ) {
938 if( is_array( $s ) ) {
939 wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
941 # Check for non-UTF-8 URLs
942 $ishigh = preg_match( '/[\x80-\xff]/', $s);
943 if(!$ishigh) return $s;
945 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
946 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
947 if( $isutf8 ) return $s;
949 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
952 function fallback8bitEncoding() {
954 return $this->fallback8bitEncoding
;
958 * Some languages have special punctuation to strip out
959 * or characters which need to be converted for MySQL's
960 * indexing to grok it correctly. Make such changes here.
965 function stripForSearch( $string ) {
967 if ( $wgDBtype != 'mysql' ) {
971 # MySQL fulltext index doesn't grok utf-8, so we
972 # need to fold cases and convert to hex
974 wfProfileIn( __METHOD__
);
975 if( function_exists( 'mb_strtolower' ) ) {
977 "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
978 "'U8' . bin2hex( \"$1\" )",
979 mb_strtolower( $string ) );
981 list( , $wikiLowerChars ) = self
::getCaseMaps();
983 "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
984 "'U8' . bin2hex( strtr( \"\$1\", \$wikiLowerChars ) )",
987 wfProfileOut( __METHOD__
);
991 function convertForSearchResult( $termsArray ) {
992 # some languages, e.g. Chinese, need to do a conversion
993 # in order for search results to be displayed correctly
998 * Get the first character of a string.
1003 function firstChar( $s ) {
1005 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1006 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
1008 return isset( $matches[1] ) ?
$matches[1] : "";
1011 function initEncoding() {
1012 # Some languages may have an alternate char encoding option
1013 # (Esperanto X-coding, Japanese furigana conversion, etc)
1014 # If this language is used as the primary content language,
1015 # an override to the defaults can be set here on startup.
1018 function recodeForEdit( $s ) {
1019 # For some languages we'll want to explicitly specify
1020 # which characters make it into the edit box raw
1021 # or are converted in some way or another.
1022 # Note that if wgOutputEncoding is different from
1023 # wgInputEncoding, this text will be further converted
1024 # to wgOutputEncoding.
1025 global $wgEditEncoding;
1026 if( $wgEditEncoding == '' or
1027 $wgEditEncoding == 'UTF-8' ) {
1030 return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1034 function recodeInput( $s ) {
1035 # Take the previous into account.
1036 global $wgEditEncoding;
1037 if($wgEditEncoding != "") {
1038 $enc = $wgEditEncoding;
1042 if( $enc == 'UTF-8' ) {
1045 return $this->iconv( $enc, 'UTF-8', $s );
1050 * For right-to-left language support
1060 * A hidden direction mark (LRM or RLM), depending on the language direction
1064 function getDirMark() {
1065 return $this->isRTL() ?
"\xE2\x80\x8F" : "\xE2\x80\x8E";
1069 * An arrow, depending on the language direction
1073 function getArrow() {
1074 return $this->isRTL() ?
'←' : '→';
1078 * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1082 function linkPrefixExtension() {
1084 return $this->linkPrefixExtension
;
1087 function &getMagicWords() {
1089 return $this->magicWords
;
1092 # Fill a MagicWord object with data from here
1093 function getMagic( &$mw ) {
1094 if ( !isset( $this->mMagicExtensions
) ) {
1095 $this->mMagicExtensions
= array();
1096 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions
, $this->getCode() ) );
1098 if ( isset( $this->mMagicExtensions
[$mw->mId
] ) ) {
1099 $rawEntry = $this->mMagicExtensions
[$mw->mId
];
1101 $magicWords =& $this->getMagicWords();
1102 if ( isset( $magicWords[$mw->mId
] ) ) {
1103 $rawEntry = $magicWords[$mw->mId
];
1105 # Fall back to English if local list is incomplete
1106 $magicWords =& Language
::getMagicWords();
1107 $rawEntry = $magicWords[$mw->mId
];
1111 if( !is_array( $rawEntry ) ) {
1112 error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1114 $mw->mCaseSensitive
= $rawEntry[0];
1115 $mw->mSynonyms
= array_slice( $rawEntry, 1 );
1119 * Get special page names, as an associative array
1120 * case folded alias => real name
1122 function getSpecialPageAliases() {
1124 if ( !isset( $this->mExtendedSpecialPageAliases
) ) {
1125 $this->mExtendedSpecialPageAliases
= $this->specialPageAliases
;
1126 wfRunHooks( 'LangugeGetSpecialPageAliases',
1127 array( &$this->mExtendedSpecialPageAliases
, $this->getCode() ) );
1129 return $this->mExtendedSpecialPageAliases
;
1133 * Italic is unsuitable for some languages
1137 * @param string $text The text to be emphasized.
1140 function emphasize( $text ) {
1141 return "<em>$text</em>";
1145 * Normally we output all numbers in plain en_US style, that is
1146 * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
1147 * point twohundredthirtyfive. However this is not sutable for all
1148 * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
1149 * Icelandic just want to use commas instead of dots, and dots instead
1150 * of commas like "293.291,235".
1152 * An example of this function being called:
1154 * wfMsg( 'message', $wgLang->formatNum( $num ) )
1157 * See LanguageGu.php for the Gujarati implementation and
1158 * LanguageIs.php for the , => . and . => , implementation.
1160 * @todo check if it's viable to use localeconv() for the decimal
1163 * @param mixed $number the string to be formatted, should be an integer or
1164 * a floating point number.
1165 * @param bool $nocommafy Set to true for special numbers like dates
1168 function formatNum( $number, $nocommafy = false ) {
1169 global $wgTranslateNumerals;
1171 $number = $this->commafy($number);
1172 $s = $this->separatorTransformTable();
1173 if (!is_null($s)) { $number = strtr($number, $s); }
1176 if ($wgTranslateNumerals) {
1177 $s = $this->digitTransformTable();
1178 if (!is_null($s)) { $number = strtr($number, $s); }
1184 function parseFormattedNumber( $number ) {
1185 $s = $this->digitTransformTable();
1186 if (!is_null($s)) { $number = strtr($number, array_flip($s)); }
1188 $s = $this->separatorTransformTable();
1189 if (!is_null($s)) { $number = strtr($number, array_flip($s)); }
1191 $number = strtr( $number, array (',' => '') );
1196 * Adds commas to a given number
1201 function commafy($_) {
1202 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
1205 function digitTransformTable() {
1207 return $this->digitTransformTable
;
1210 function separatorTransformTable() {
1212 return $this->separatorTransformTable
;
1217 * For the credit list in includes/Credits.php (action=credits)
1222 function listToText( $l ) {
1225 for ($i = $m; $i >= 0; $i--) {
1228 } else if ($i == $m - 1) {
1229 $s = $l[$i] . ' ' . $this->getMessageFromDB( 'and' ) . ' ' . $s;
1231 $s = $l[$i] . ', ' . $s;
1237 # Crop a string from the beginning or end to a certain number of bytes.
1238 # (Bytes are used because our storage has limited byte lengths for some
1239 # columns in the database.) Multibyte charsets will need to make sure that
1240 # only whole characters are included!
1242 # $length does not include the optional ellipsis.
1243 # If $length is negative, snip from the beginning
1244 function truncate( $string, $length, $ellipsis = "" ) {
1245 if( $length == 0 ) {
1248 if ( strlen( $string ) <= abs( $length ) ) {
1252 $string = substr( $string, 0, $length );
1253 $char = ord( $string[strlen( $string ) - 1] );
1255 if ($char >= 0xc0) {
1256 # We got the first byte only of a multibyte char; remove it.
1257 $string = substr( $string, 0, -1 );
1258 } elseif( $char >= 0x80 &&
1259 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
1260 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
1261 # We chopped in the middle of a character; remove it
1264 return $string . $ellipsis;
1266 $string = substr( $string, $length );
1267 $char = ord( $string[0] );
1268 if( $char >= 0x80 && $char < 0xc0 ) {
1269 # We chopped in the middle of a character; remove the whole thing
1270 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
1272 return $ellipsis . $string;
1277 * Grammatical transformations, needed for inflected languages
1278 * Invoked by putting {{grammar:case|word}} in a message
1280 * @param string $word
1281 * @param string $case
1284 function convertGrammar( $word, $case ) {
1285 global $wgGrammarForms;
1286 if ( isset($wgGrammarForms['en'][$case][$word]) ) {
1287 return $wgGrammarForms['en'][$case][$word];
1293 * Plural form transformations, needed for some languages.
1294 * For example, where are 3 form of plural in Russian and Polish,
1295 * depending on "count mod 10". See [[w:Plural]]
1296 * For English it is pretty simple.
1298 * Invoked by putting {{plural:count|wordform1|wordform2}}
1299 * or {{plural:count|wordform1|wordform2|wordform3}}
1301 * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
1303 * @param integer $count
1304 * @param string $wordform1
1305 * @param string $wordform2
1306 * @param string $wordform3 (optional)
1307 * @param string $wordform4 (optional)
1308 * @param string $wordform5 (optional)
1311 function convertPlural( $count, $w1, $w2, $w3, $w4, $w5) {
1312 return ( $count == '1' ||
$count == '-1' ) ?
$w1 : $w2;
1316 * For translaing of expiry times
1317 * @param string The validated block time in English
1318 * @param $forContent, avoid html?
1319 * @return Somehow translated block time
1320 * @see LanguageFi.php for example implementation
1322 function translateBlockExpiry( $str, $forContent=false ) {
1324 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
1326 if ( $scBlockExpiryOptions == '-') {
1330 foreach (explode(',', $scBlockExpiryOptions) as $option) {
1331 if ( strpos($option, ":") === false )
1333 list($show, $value) = explode(":", $option);
1334 if ( strcmp ( $str, $value) == 0 ) {
1336 return htmlspecialchars($str) . htmlspecialchars( trim( $show ) );
1338 return '<span title="' . htmlspecialchars($str). '">' . htmlspecialchars( trim( $show ) ) . '</span>';
1346 * languages like Chinese need to be segmented in order for the diff
1349 * @param string $text
1352 function segmentForDiff( $text ) {
1357 * and unsegment to show the result
1359 * @param string $text
1362 function unsegmentForDiff( $text ) {
1366 # convert text to different variants of a language.
1367 function convert( $text, $isTitle = false) {
1368 return $this->mConverter
->convert($text, $isTitle);
1371 # Convert text from within Parser
1372 function parserConvert( $text, &$parser ) {
1373 return $this->mConverter
->parserConvert( $text, $parser );
1376 # Check if this is a language with variants
1377 function hasVariants(){
1378 return sizeof($this->getVariants())>1;
1381 # Put custom tags (e.g. -{ }-) around math to prevent conversion
1382 function armourMath($text){
1383 return $this->mConverter
->armourMath($text);
1388 * Perform output conversion on a string, and encode for safe HTML output.
1389 * @param string $text
1390 * @param bool $isTitle -- wtf?
1392 * @todo this should get integrated somewhere sane
1394 function convertHtml( $text, $isTitle = false ) {
1395 return htmlspecialchars( $this->convert( $text, $isTitle ) );
1398 function convertCategoryKey( $key ) {
1399 return $this->mConverter
->convertCategoryKey( $key );
1403 * get the list of variants supported by this langauge
1404 * see sample implementation in LanguageZh.php
1406 * @return array an array of language codes
1408 function getVariants() {
1409 return $this->mConverter
->getVariants();
1413 function getPreferredVariant( $fromUser = true ) {
1414 return $this->mConverter
->getPreferredVariant( $fromUser );
1418 * if a language supports multiple variants, it is
1419 * possible that non-existing link in one variant
1420 * actually exists in another variant. this function
1421 * tries to find it. See e.g. LanguageZh.php
1423 * @param string $link the name of the link
1424 * @param mixed $nt the title object of the link
1425 * @return null the input parameters may be modified upon return
1427 function findVariantLink( &$link, &$nt ) {
1428 $this->mConverter
->findVariantLink($link, $nt);
1432 * If a language supports multiple variants, converts text
1433 * into an array of all possible variants of the text:
1434 * 'variant' => text in that variant
1437 function convertLinkToAllVariants($text){
1438 return $this->mConverter
->convertLinkToAllVariants($text);
1443 * returns language specific options used by User::getPageRenderHash()
1444 * for example, the preferred language variant
1449 function getExtraHashOptions() {
1450 return $this->mConverter
->getExtraHashOptions();
1454 * for languages that support multiple variants, the title of an
1455 * article may be displayed differently in different variants. this
1456 * function returns the apporiate title defined in the body of the article.
1460 function getParsedTitle() {
1461 return $this->mConverter
->getParsedTitle();
1465 * Enclose a string with the "no conversion" tag. This is used by
1466 * various functions in the Parser
1468 * @param string $text text to be tagged for no conversion
1469 * @return string the tagged text
1471 function markNoConversion( $text, $noParse=false ) {
1472 return $this->mConverter
->markNoConversion( $text, $noParse );
1476 * A regular expression to match legal word-trailing characters
1477 * which should be merged onto a link of the form [[foo]]bar.
1482 function linkTrail() {
1484 return $this->linkTrail
;
1487 function getLangObj() {
1492 * Get the RFC 3066 code for this language object
1494 function getCode() {
1495 return $this->mCode
;
1498 function setCode( $code ) {
1499 $this->mCode
= $code;
1502 static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
1503 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
1506 static function getMessagesFileName( $code ) {
1508 return self
::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
1511 static function getClassFileName( $code ) {
1513 return self
::getFileName( "$IP/languages/classes/Language", $code, '.php' );
1516 static function getLocalisationArray( $code, $disableCache = false ) {
1517 self
::loadLocalisation( $code, $disableCache );
1518 return self
::$mLocalisationCache[$code];
1522 * Load localisation data for a given code into the static cache
1524 * @return array Dependencies, map of filenames to mtimes
1526 static function loadLocalisation( $code, $disableCache = false ) {
1527 static $recursionGuard = array();
1531 throw new MWException( "Invalid language code requested" );
1534 if ( !$disableCache ) {
1535 # Try the per-process cache
1536 if ( isset( self
::$mLocalisationCache[$code] ) ) {
1537 return self
::$mLocalisationCache[$code]['deps'];
1540 wfProfileIn( __METHOD__
);
1542 # Try the serialized directory
1543 $cache = wfGetPrecompiledData( self
::getFileName( "Messages", $code, '.ser' ) );
1545 self
::$mLocalisationCache[$code] = $cache;
1546 wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" );
1547 wfProfileOut( __METHOD__
);
1548 return self
::$mLocalisationCache[$code]['deps'];
1551 # Try the global cache
1552 $memcKey = wfMemcKey('localisation', $code );
1553 $cache = $wgMemc->get( $memcKey );
1555 # Check file modification times
1556 foreach ( $cache['deps'] as $file => $mtime ) {
1557 if ( !file_exists( $file ) ||
filemtime( $file ) > $mtime ) {
1561 if ( self
::isLocalisationOutOfDate( $cache ) ) {
1562 $wgMemc->delete( $memcKey );
1564 wfDebug( "Language::loadLocalisation(): localisation cache for $code had expired due to update of $file\n" );
1566 self
::$mLocalisationCache[$code] = $cache;
1567 wfDebug( "Language::loadLocalisation(): got localisation for $code from cache\n" );
1568 wfProfileOut( __METHOD__
);
1569 return $cache['deps'];
1573 wfProfileIn( __METHOD__
);
1576 # Default fallback, may be overridden when the messages file is included
1577 if ( $code != 'en' ) {
1583 # Load the primary localisation from the source file
1584 $filename = self
::getMessagesFileName( $code );
1585 if ( !file_exists( $filename ) ) {
1586 wfDebug( "Language::loadLocalisation(): no localisation file for $code, using implicit fallback to en\n" );
1590 $deps = array( $filename => filemtime( $filename ) );
1591 require( $filename );
1592 $cache = compact( self
::$mLocalisationKeys );
1593 wfDebug( "Language::loadLocalisation(): got localisation for $code from source\n" );
1596 if ( !empty( $fallback ) ) {
1597 # Load the fallback localisation, with a circular reference guard
1598 if ( isset( $recursionGuard[$code] ) ) {
1599 throw new MWException( "Error: Circular fallback reference in language code $code" );
1601 $recursionGuard[$code] = true;
1602 $newDeps = self
::loadLocalisation( $fallback, $disableCache );
1603 unset( $recursionGuard[$code] );
1605 $secondary = self
::$mLocalisationCache[$fallback];
1606 $deps = array_merge( $deps, $newDeps );
1608 # Merge the fallback localisation with the current localisation
1609 foreach ( self
::$mLocalisationKeys as $key ) {
1610 if ( isset( $cache[$key] ) ) {
1611 if ( isset( $secondary[$key] ) ) {
1612 if ( in_array( $key, self
::$mMergeableMapKeys ) ) {
1613 $cache[$key] = $cache[$key] +
$secondary[$key];
1614 } elseif ( in_array( $key, self
::$mMergeableListKeys ) ) {
1615 $cache[$key] = array_merge( $secondary[$key], $cache[$key] );
1616 } elseif ( in_array( $key, self
::$mMergeableAliasListKeys ) ) {
1617 $cache[$key] = array_merge_recursive( $cache[$key], $secondary[$key] );
1621 $cache[$key] = $secondary[$key];
1625 # Merge bookstore lists if requested
1626 if ( !empty( $cache['bookstoreList']['inherit'] ) ) {
1627 $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] );
1629 if ( isset( $cache['bookstoreList']['inherit'] ) ) {
1630 unset( $cache['bookstoreList']['inherit'] );
1634 # Add dependencies to the cache entry
1635 $cache['deps'] = $deps;
1637 # Replace spaces with underscores in namespace names
1638 $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] );
1640 # Save to both caches
1641 self
::$mLocalisationCache[$code] = $cache;
1642 if ( !$disableCache ) {
1643 $wgMemc->set( $memcKey, $cache );
1646 wfProfileOut( __METHOD__
);
1651 * Test if a given localisation cache is out of date with respect to the
1652 * source Messages files. This is done automatically for the global cache
1653 * in $wgMemc, but is only done on certain occasions for the serialized
1656 * @param $cache mixed Either a language code or a cache array
1658 static function isLocalisationOutOfDate( $cache ) {
1659 if ( !is_array( $cache ) ) {
1660 self
::loadLocalisation( $cache );
1661 $cache = self
::$mLocalisationCache[$cache];
1664 foreach ( $cache['deps'] as $file => $mtime ) {
1665 if ( !file_exists( $file ) ||
filemtime( $file ) > $mtime ) {
1674 * Get the fallback for a given language
1676 static function getFallbackFor( $code ) {
1677 self
::loadLocalisation( $code );
1678 return self
::$mLocalisationCache[$code]['fallback'];
1682 * Get all messages for a given language
1684 static function getMessagesFor( $code ) {
1685 self
::loadLocalisation( $code );
1686 return self
::$mLocalisationCache[$code]['messages'];
1690 * Get a message for a given language
1692 static function getMessageFor( $key, $code ) {
1693 self
::loadLocalisation( $code );
1694 return isset( self
::$mLocalisationCache[$code]['messages'][$key] ) ? self
::$mLocalisationCache[$code]['messages'][$key] : null;
1698 * Load localisation data for this object
1701 if ( !$this->mLoaded
) {
1702 self
::loadLocalisation( $this->getCode() );
1703 $cache =& self
::$mLocalisationCache[$this->getCode()];
1704 foreach ( self
::$mLocalisationKeys as $key ) {
1705 $this->$key = $cache[$key];
1707 $this->mLoaded
= true;
1709 $this->fixUpSettings();
1714 * Do any necessary post-cache-load settings adjustment
1716 function fixUpSettings() {
1717 global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk,
1718 $wgNamespaceAliases, $wgAmericanDates;
1719 wfProfileIn( __METHOD__
);
1720 if ( $wgExtraNamespaces ) {
1721 $this->namespaceNames
= $wgExtraNamespaces +
$this->namespaceNames
;
1724 $this->namespaceNames
[NS_PROJECT
] = $wgMetaNamespace;
1725 if ( $wgMetaNamespaceTalk ) {
1726 $this->namespaceNames
[NS_PROJECT_TALK
] = $wgMetaNamespaceTalk;
1728 $talk = $this->namespaceNames
[NS_PROJECT_TALK
];
1729 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
1731 # Allow grammar transformations
1732 # Allowing full message-style parsing would make simple requests
1733 # such as action=raw much more expensive than they need to be.
1734 # This will hopefully cover most cases.
1735 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
1736 array( &$this, 'replaceGrammarInNamespace' ), $talk );
1737 $talk = str_replace( ' ', '_', $talk );
1738 $this->namespaceNames
[NS_PROJECT_TALK
] = $talk;
1741 # The above mixing may leave namespaces out of canonical order.
1742 # Re-order by namespace ID number...
1743 ksort( $this->namespaceNames
);
1745 # Put namespace names and aliases into a hashtable.
1746 # If this is too slow, then we should arrange it so that it is done
1747 # before caching. The catch is that at pre-cache time, the above
1748 # class-specific fixup hasn't been done.
1749 $this->mNamespaceIds
= array();
1750 foreach ( $this->namespaceNames
as $index => $name ) {
1751 $this->mNamespaceIds
[$this->lc($name)] = $index;
1753 if ( $this->namespaceAliases
) {
1754 foreach ( $this->namespaceAliases
as $name => $index ) {
1755 $this->mNamespaceIds
[$this->lc($name)] = $index;
1758 if ( $wgNamespaceAliases ) {
1759 foreach ( $wgNamespaceAliases as $name => $index ) {
1760 $this->mNamespaceIds
[$this->lc($name)] = $index;
1764 if ( $this->defaultDateFormat
== 'dmy or mdy' ) {
1765 $this->defaultDateFormat
= $wgAmericanDates ?
'mdy' : 'dmy';
1767 wfProfileOut( __METHOD__
);
1770 function replaceGrammarInNamespace( $m ) {
1771 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
1774 static function getCaseMaps() {
1775 static $wikiUpperChars, $wikiLowerChars;
1776 if ( isset( $wikiUpperChars ) ) {
1777 return array( $wikiUpperChars, $wikiLowerChars );
1780 wfProfileIn( __METHOD__
);
1781 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
1782 if ( $arr === false ) {
1783 throw new MWException(
1784 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
1787 wfProfileOut( __METHOD__
);
1788 return array( $wikiUpperChars, $wikiLowerChars );