95 $variantfallbacks = [], $flags = [],
98 $this->mLangObj = $langobj;
99 $this->mMainLanguageCode = $maincode;
101 $this->mVariantFallbacks = $variantfallbacks;
102 $this->mVariantNames = Language::fetchLanguageNames();
116 $this->mFlags = array_merge( $defaultflags, $flags );
117 foreach ( $this->mVariants as $v ) {
118 if ( array_key_exists( $v, $manualLevel ) ) {
119 $this->mManualLevel[$v] = $manualLevel[$v];
121 $this->mManualLevel[$v] =
'bidirectional';
123 $this->mFlags[$v] = $v;
134 return $this->mVariants;
149 return $this->mVariantFallbacks[$variant] ?? $this->mMainLanguageCode;
157 return $this->mConvRuleTitle;
169 Hooks::run(
'GetLangPreferredVariant', [ &$req ] );
171 if ( $wgUser->isSafeToLoad() && $wgUser->isLoggedIn() && !$req ) {
190 return $this->mMainLanguageCode;
214 return $this->mMainLanguageCode;
227 if ( $variant ===
null ) {
232 $variant = LanguageCode::replaceDeprecatedCodes( strtolower( $variant ) );
233 if ( in_array( $variant, $this->mVariants ) ) {
240 foreach ( $this->mVariants as $v ) {
242 if ( strtolower( LanguageCode::bcp47( $v ) ) === $variant ) {
257 if ( $this->mURLVariant ) {
258 return $this->mURLVariant;
269 return $this->mURLVariant;
290 if ( !$wgUser->isSafeToLoad() ) {
293 if ( $wgUser->isLoggedIn() ) {
295 $this->mMainLanguageCode ==
296 MediaWikiServices::getInstance()->getContentLanguage()->getCode()
298 $ret = $wgUser->getOption(
'variant' );
300 $ret = $wgUser->getOption(
'variant-' . $this->mMainLanguageCode );
305 $ret = $wgUser->getOption(
'language' );
309 return $this->mUserVariant;
320 if ( $this->mHeaderVariant ) {
321 return $this->mHeaderVariant;
331 $fallbackLanguages = [];
334 if ( $this->mHeaderVariant ) {
342 if ( is_string( $fallbacks ) && $fallbacks !== $this->mMainLanguageCode ) {
343 $fallbackLanguages[] = $fallbacks;
344 } elseif ( is_array( $fallbacks ) ) {
346 array_merge( $fallbackLanguages, $fallbacks );
350 if ( !$this->mHeaderVariant ) {
352 $fallback_languages = array_unique( $fallbackLanguages );
353 foreach ( $fallback_languages as $language ) {
355 if ( $this->mHeaderVariant ) {
361 return $this->mHeaderVariant;
395 if ( $reg ===
null ) {
396 $marker =
'|' . Parser::MARKER_PREFIX .
'[^\x7f]++\x7f';
399 $htmlfix =
'|<[^>\004]++(?=\004$)|^[^<>]*+>';
406 $codefix =
'<code>[^<]*+(?:(?:(?!<\/code>).)[^<]*+)*+<\/code>|';
408 $scriptfix =
'<script[^>]*+>[^<]*+(?:(?:(?!<\/script>).)[^<]*+)*+<\/script>|';
410 $prefix =
'<pre[^>]*+>[^<]*+(?:(?:(?!<\/pre>).)[^<]*+)*+<\/pre>|';
413 $htmlFullTag =
'<(?:[^>=]*+(?>[^>=]*+=\s*+(?:"[^"]*"|\'[^\']*\'|[^\'">\s]*+))*+[^>=]*+>|.*+)|';
415 $reg =
'/' . $codefix . $scriptfix . $prefix . $htmlFullTag .
416 '&[a-zA-Z#][a-z0-9]++;' . $marker . $htmlfix .
'|\004$/s';
424 $text = str_replace(
"\000",
'', $text );
425 $text = str_replace(
"\004",
'', $text );
427 $markupMatches =
null;
428 $elementMatches =
null;
432 $textWithMarker = $text .
"\004";
433 while ( $startPos < strlen( $text ) ) {
434 if ( preg_match( $reg, $textWithMarker, $markupMatches, PREG_OFFSET_CAPTURE, $startPos ) ) {
435 $elementPos = $markupMatches[0][1];
436 $element = $markupMatches[0][0];
437 if ( $element ===
"\004" ) {
439 $elementPos = strlen( $text );
441 } elseif ( substr( $element, -1 ) ===
"\004" ) {
447 $element = substr( $element, 0, -1 );
454 $log = LoggerFactory::getInstance(
'languageconverter' );
455 $log->error(
"Hit pcre.backtrack_limit in " . __METHOD__
456 .
". Disabling language conversion for this page.",
458 "method" => __METHOD__,
459 "variant" => $toVariant,
460 "startOfText" => substr( $text, 0, 500 )
466 $sourceBlob .= substr( $text, $startPos, $elementPos - $startPos ) .
"\000";
469 $startPos = $elementPos + strlen( $element );
473 && preg_match(
'/^(<[^>\s]*+)\s([^>]*+)(.*+)$/', $element, $elementMatches )
480 $attrs = Sanitizer::decodeTagAttributes( $elementMatches[2] );
482 $close = substr( $elementMatches[2], -1 ) ===
'/' ?
' /' :
'';
484 foreach ( [
'title',
'alt' ] as $attrName ) {
485 if ( !isset( $attrs[$attrName] ) ) {
488 $attr = $attrs[$attrName];
490 if ( !strpos( $attr,
'://' ) ) {
494 if ( $attr !== $attrs[$attrName] ) {
495 $attrs[$attrName] = $attr;
500 $element = $elementMatches[1] . Html::expandAttributes( $attrs ) .
501 $close . $elementMatches[3];
504 $literalBlob .= $element .
"\000";
508 $translatedBlob = $this->
translate( $sourceBlob, $toVariant );
511 $translatedIter = StringUtils::explode(
"\000", $translatedBlob );
512 $literalIter = StringUtils::explode(
"\000", $literalBlob );
514 while ( $translatedIter->valid() && $literalIter->valid() ) {
515 $output .= $translatedIter->current();
516 $output .= $literalIter->current();
517 $translatedIter->next();
518 $literalIter->next();
536 if ( trim( $text ) ) {
538 $text = $this->mTables[$variant]->replace( $text );
553 foreach ( $this->mVariants as $variant ) {
554 $ret[$variant] = $this->
translate( $text, $variant );
570 $newConvRuleTitle = $convRule->getTitle();
571 if ( $newConvRuleTitle ) {
573 $this->mConvRuleTitle = $newConvRuleTitle;
577 $convTable = $convRule->getConvTable();
578 $action = $convRule->getRulesAction();
579 foreach ( $convTable as $variant => $pair ) {
585 if ( $action ==
'add' ) {
587 foreach ( $pair as $from => $to ) {
588 $this->mTables[$v]->setPair( $from, $to );
590 } elseif ( $action ==
'remove' ) {
591 $this->mTables[$v]->removeArray( $pair );
605 $index =
$title->getNamespace();
627 if ( $variant ===
null ) {
631 $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
632 $key =
$cache->makeKey(
'languageconverter',
'namespace-text', $index, $variant );
633 $nsVariantText =
$cache->get( $key );
634 if ( $nsVariantText !==
false ) {
635 return $nsVariantText;
639 $nsConvMsg =
wfMessage(
'conversion-ns' . $index )->inLanguage( $variant );
640 if ( $nsConvMsg->exists() ) {
641 $nsVariantText = $nsConvMsg->plain();
646 if ( $nsVariantText ===
false ) {
647 $nsConvMsg =
wfMessage(
'conversion-ns' . $index )->inContentLanguage();
648 if ( $nsConvMsg->exists() ) {
649 $nsVariantText = $this->
translate( $nsConvMsg->plain(), $variant );
653 if ( $nsVariantText ===
false ) {
655 $langObj = $this->mLangObj->factory( $variant );
656 $nsVariantText = $langObj->getFormattedNsText( $index );
659 $cache->set( $key, $nsVariantText, 60 );
661 return $nsVariantText;
684 return $this->
convertTo( $text, $variant );
702 $this->mConvRuleTitle =
false;
718 $length = strlen( $text );
719 $shouldConvert = !$this->
guessVariant( $text, $variant );
722 $noScript =
'<script.*?>.*?<\/script>(*SKIP)(*FAIL)';
723 $noStyle =
'<style.*?>.*?<\/style>(*SKIP)(*FAIL)';
725 $noHtml =
'<(?:[^>=]*+(?>[^>=]*+=\s*+(?:"[^"]*"|\'[^\']*\'|[^\'">\s]*+))*+[^>=]*+>|.*+)(*SKIP)(*FAIL)';
726 while ( $startPos < $length && $continue ) {
727 $continue = preg_match(
729 "/$noScript|$noStyle|$noHtml|-\{/",
738 $fragment = substr( $text, $startPos );
739 $out .= $shouldConvert ? $this->
autoConvert( $fragment, $variant ) : $fragment;
747 $fragment = substr( $text, $startPos, $pos - $startPos );
748 $out .= $shouldConvert ? $this->
autoConvert( $fragment, $variant ) : $fragment;
772 if ( $text[$startPos] !==
'-' || $text[$startPos + 1] !==
'{' ) {
773 throw new MWException( __METHOD__ .
': invalid input string' );
778 $warningDone =
false;
779 $length = strlen( $text );
781 while ( $startPos < $length ) {
783 preg_match(
'/-\{|\}-/', $text, $m, PREG_OFFSET_CAPTURE, $startPos );
794 $inner .= substr( $text, $startPos, $pos - $startPos );
802 if ( $depth >= $this->mMaxDepth ) {
804 if ( !$warningDone ) {
805 $inner .=
'<span class="error">' .
806 wfMessage(
'language-converter-depth-warning' )
807 ->numParams( $this->mMaxDepth )->inContentLanguage()->text() .
821 $rule->parse( $variant );
823 return $rule->getDisplay();
825 throw new MWException( __METHOD__ .
': invalid regex match' );
830 if ( $startPos < $length ) {
831 $inner .= substr( $text, $startPos );
834 return '-{' . $this->
autoConvert( $inner, $variant );
849 # If the article has already existed, there is no need to
850 # check it again, otherwise it may cause a fault.
851 if ( is_object( $nt ) && $nt->exists() ) {
856 $isredir =
$wgRequest->getText(
'redirect',
'yes' );
858 if ( $action ==
'edit' &&
$wgRequest->getBool(
'redlink' ) ) {
861 $linkconvert =
$wgRequest->getText(
'linkconvert',
'yes' );
868 if ( $disableLinkConversion ||
869 ( !$ignoreOtherCond &&
872 || $action ==
'submit'
873 || $linkconvert ==
'no' ) ) ) {
877 if ( is_object( $nt ) ) {
878 $ns = $nt->getNamespace();
888 foreach ( $variants as $v ) {
890 $varnt = Title::newFromText( $v, $ns );
891 if ( !is_null( $varnt ) ) {
892 $linkBatch->addObj( $varnt );
899 $linkBatch->execute();
901 foreach ( $titles as $varnt ) {
902 if ( $varnt->getArticleID() > 0 ) {
904 $link = $varnt->getText();
918 return '!' . $variant;
943 $class = static::class;
944 throw new MWException(
"Must implement loadDefaultTables() method in class $class" );
955 if ( $this->mTablesLoaded ) {
959 $this->mTablesLoaded =
true;
960 $this->mTables =
null;
962 $cacheKey =
$cache->makeKey(
'conversiontables', $this->mMainLanguageCode );
964 $this->mTables =
$cache->get( $cacheKey );
966 if ( !$this->mTables || !array_key_exists( self::CACHE_VERSION_KEY, $this->mTables ) ) {
971 foreach ( $this->mVariants as $var ) {
973 $this->mTables[$var]->mergeArray( $cached );
977 $this->mTables[self::CACHE_VERSION_KEY] =
true;
979 $cache->set( $cacheKey, $this->mTables, 43200 );
997 if ( $this->mTables ) {
999 unset( $this->mTables );
1002 $this->mTablesLoaded =
false;
1026 static $parsed = [];
1028 $key =
'Conversiontable/' . $code;
1030 $key .=
'/' . $subpage;
1032 if ( array_key_exists( $key, $parsed ) ) {
1036 $parsed[$key] =
true;
1038 if ( $subpage ===
'' ) {
1039 $txt = MessageCache::singleton()->getMsgFromNamespace( $key, $code );
1044 $revision = Revision::newFromTitle(
$title );
1048 $txt = $revision->getContent( RevisionRecord::RAW )->getText();
1056 # Nothing to parse if there's no text
1057 if ( $txt ===
false || $txt ===
null || $txt ===
'' ) {
1063 $linkhead = $this->mLangObj->getNsText(
NS_MEDIAWIKI ) .
1065 $subs = StringUtils::explode(
'[[', $txt );
1067 foreach ( $subs as $sub ) {
1068 $link = explode(
']]', $sub, 2 );
1069 if ( count( $link ) != 2 ) {
1072 $b = explode(
'|', $link[0], 2 );
1073 $b = explode(
'/', trim( $b[0] ), 3 );
1074 if ( count( $b ) == 3 ) {
1080 if ( $b[0] == $linkhead && $b[1] == $code ) {
1081 $sublinks[] = $sublink;
1086 $blocks = StringUtils::explode(
'-{', $txt );
1089 foreach ( $blocks as $block ) {
1095 $mappings = explode(
'}-', $block, 2 )[0];
1096 $stripped = str_replace( [
"'",
'"',
'*',
'#' ],
'', $mappings );
1097 $table = StringUtils::explode(
';', $stripped );
1098 foreach ( $table as
$t ) {
1099 $m = explode(
'=>',
$t, 3 );
1100 if ( count( $m ) != 2 ) {
1104 $tt = explode(
'//', $m[1], 2 );
1105 $ret[trim( $m[0] )] = trim( $tt[0] );
1111 foreach ( $sublinks as $link ) {
1117 if ( $this->mUcfirst ) {
1118 foreach ( $ret as $k => $v ) {
1119 $ret[$this->mLangObj->ucfirst( $k )] = $this->mLangObj->ucfirst( $v );
1134 # don't mark if already marked
1135 if ( strpos( $text,
'-{' ) || strpos( $text,
'}-' ) ) {
1139 $ret =
"-{R|$text}-";
1166 if ( $c > 1 &&
$t[0] ==
'Conversiontable' ) {
1179 if ( is_null( $this->mVarSeparatorPattern ) ) {
1191 $expandedVariants = [];
1192 foreach ( $this->mVariants as $variant ) {
1193 $expandedVariants[ $variant ] = 1;
1195 $expandedVariants[ LanguageCode::bcp47( $variant ) ] = 1;
1198 foreach ( LanguageCode::getDeprecatedCodeMapping() as $old => $new ) {
1199 if ( isset( $expandedVariants[ $new ] ) ) {
1200 $expandedVariants[ $old ] = 1;
1205 foreach ( $expandedVariants as $variant => $ignore ) {
1207 $pat .= $variant .
'\s*:|';
1209 $pat .=
'[^;]*?=>\s*' . $variant .
'\s*:|';
1212 $this->mVarSeparatorPattern = $pat;
1214 return $this->mVarSeparatorPattern;
$wgDefaultLanguageVariant
Default variant code, if false, the default will be the language code.
$wgDisableLangConversion
Whether to enable language variant conversion.
$wgDisabledVariants
Disabled variants array of language variant conversion.
$wgLanguageConverterCacheType
The cache type for storing language conversion tables, which are used when parsing certain text and i...
$wgDisableTitleConversion
Whether to enable language variant conversion for links.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
if(! $wgDBerrorLogTZ) $wgRequest
Parser for rules of language conversion, parse rules in -{ }- tag.
Base class for language conversion.
getPreferredVariant()
Get preferred language variant.
convertTitle( $title)
Auto convert a Title object to a readable string in the preferred variant.
validateVariant( $variant=null)
Validate the variant and return an appropriate strict internal variant code if one exists.
getDefaultVariant()
Get default variant.
recursiveConvertTopLevel( $text, $variant, $depth=0)
Recursively convert text on the outside.
loadTables( $fromCache=true)
Load conversion tables either from the cache or the disk.
getHeaderVariant()
Determine the language variant from the Accept-Language header.
static array $languagesWithVariants
languages supporting variants
autoConvert( $text, $toVariant=false)
Dictionary-based conversion.
recursiveConvertRule( $text, $variant, &$startPos, $depth=0)
Recursively convert text on the inside.
__construct(Language $langobj, $maincode, $variants=[], $variantfallbacks=[], $flags=[], $manualLevel=[])
parseCachedTable( $code, $subpage='', $recursive=true)
Parse the conversion table stored in the cache.
getVarSeparatorPattern()
Get the cached separator pattern for ConverterRule::parseRules()
convertNamespace( $index, $variant=null)
Get the namespace display name in the preferred variant.
getExtraHashOptions()
Returns language specific hash options.
getVariantFallbacks( $variant)
In case some variant is not defined in the markup, we need to have some fallback.
updateConversionTable(Title $titleobj)
Refresh the cache of conversion tables when MediaWiki:Conversiontable* is updated.
markNoConversion( $text, $noParse=false)
Enclose a string with the "no conversion" tag.
applyManualConv( $convRule)
Apply manual conversion rules.
translate( $text, $variant)
Translate a string to a variant.
getVariants()
Get all valid variants.
findVariantLink(&$link, &$nt, $ignoreOtherCond=false)
If a language supports multiple variants, it is possible that non-existing link in one variant actual...
convert( $text)
Convert text to different variants of a language.
postLoadTables()
Hook for post processing after conversion tables are loaded.
getURLVariant()
Get the variant specified in the URL.
loadDefaultTables()
Load default conversion tables.
autoConvertToAllVariants( $text)
Call translate() to convert text to all valid variants.
guessVariant( $text, $variant)
Guess if a text is written in a variant.
getUserVariant()
Determine if the user has a variant set.
convertTo( $text, $variant)
Same as convert() except a extra parameter to custom variant.
convertCategoryKey( $key)
Convert the sorting key for category links.
getConvRuleTitle()
Get the title produced by the conversion rule.
ReplacementArray[] bool[] $mTables
reloadTables()
Reload the conversion tables.
Internationalisation code.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Wrapper around strtr() that holds replacements.
Represents a title within MediaWiki.
getNamespace()
Get the namespace index, i.e.
getDBkey()
Get the main part with underscores.
const CONTENT_MODEL_WIKITEXT
switch( $options['output']) $languages