Adam Roses Wight <awight@wikimedia.org>
Adam Roses Wight <awight@wikimedia.org> <spam@ludd.net>
addshore <addshorewiki@gmail.com>
+addshore <addshorewiki@gmail.com> <adamshorland@gmail.com>
Aditya Sastry <ganeshaditya1@gmail.com>
Adrian Heine <adrian.heine@wikimedia.de>
Alex Z. <mrzmanwiki@gmail.com> <mrzman@users.mediawiki.org>
Daniel Friesen <mediawiki@danielfriesen.name> <daniel@nadir-seen-fire.com>
Daniel Friesen <mediawiki@danielfriesen.name> <dantman@users.mediawiki.org>
Daniel Friesen <mediawiki@danielfriesen.name> <pub-github@nadir-seen-fire.com>
-Daniel Kinzler <daniel.kinzler@wikimedia.de>
-Daniel Kinzler <daniel.kinzler@wikimedia.de> <daniel@users.mediawiki.org>
+Daniel Kinzler <dkinzler@wikimedia.org>
+Daniel Kinzler <dkinzler@wikimedia.org> <daniel.kinzler@wikimedia.de>
+Daniel Kinzler <dkinzler@wikimedia.org> <daniel@users.mediawiki.org>
Daniel Renfro <bluecurio@gmail.com> <drenfro@vistaprint.com>
Danny B. <Wikipedia.Danny.B@email.cz>
Danny B. <Wikipedia.Danny.B@email.cz> <danny.b@email.cz>
Darkdragon09 <ubuntu@ip-172-31-39-38.us-west-2.compute.internal>
David Causse <dcausse@wikimedia.org>
David Chan <david@sheetmusic.org.uk>
+Dayllan Maza <dmaza@wikimedia.org>
+Dayllan Maza <dmaza@wikimedia.org> <dayllan.maza@gmail.com>
Dereckson <dereckson@espace-win.org>
Derk-Jan Hartman <hartman@videolan.org>
Derk-Jan Hartman <hartman@videolan.org> <hartman.wiki@gmail.com>
Katie Filbert <aude.wiki@gmail.com>
Katie Filbert <aude.wiki@gmail.com> <aude@users.mediawiki.org>
Kevin Israel <pleasestand@live.com>
+Kosta Harlan <kharlan@wikimedia.org>
+Kosta Harlan <kharlan@wikimedia.org> <kosta@fastmail.com>
Kunal Grover <kunalgrover05@gmail.com>
Kunal Mehta <legoktm@member.fsf.org>
Kunal Mehta <legoktm@member.fsf.org> <legoktm.wikipedia@gmail.com>
Patrick Reilly <preilly@wikimedia.org> <preilly@users.mediawiki.org>
Patrick Westerhoff <PatrickWesterhoff@gmail.com>
Paul Copperman <paul.copperman@gmail.com> <pcopp@users.mediawiki.org>
+Petar Petković <ppetkovic@wikimedia.org>
Peter Coombe <pcoombe@wikimedia.org>
Peter Coti <petercoti@gmail.com>
Peter Potrowl <peter017@gmail.com> <peter17@users.mediawiki.org>
* 'uppercase-se' (NorthernSamiUppercaseCollation) - use 'uca-se' instead
* 'xx-uca-et' (CollationEt) - use 'uca-et' instead
* 'xx-uca-fa' (CollationFa) - use 'uca-fa' instead
+* LanguageCode::bcp47() now always returns a valid BCP 47 code. This means
+ that some MediaWiki-specific language codes, such as `simple`, are mapped
+ into valid BCP 47 codes (eg `en-simple`).
* The hooks 'SpecialRecentChangesFilters' & 'SpecialWatchlistFilters' deprecated
in 1.23 were removed. Instead, use 'ChangesListSpecialPageStructuredFilters'.
The ChangesListSpecialPage code for these legacy hooks, and their use in
* File / MediaHandler::getStreamHeaders(), deprecated since 1.30, was removed.
* The hook 'DoEditSectionLink', deprecated since 1.25, has been removed. Use
the hook 'SkinEditSectionLinks' instead.
+* The hook 'UserGetImplicitGroups', deprecated since 1.25, has been removed.
+* The global function wfRunHooks, deprecated since 1.25, has now been removed.
+ Use Hooks::run().
+* The hook 'UnknownAction', deprecated since 1.19, has now been removed.
+* The hook 'ParserLimitReport', deprecated since 1.22, has been removed. Use
+ the hooks 'ParserLimitReportPrepare' and 'ParserLimitReportFormat' instead.
=== Deprecations in 1.32 ===
* HTMLForm::setSubmitProgressive() is deprecated. No need to call it. Submit
as a string. They should be given as a OOUI\FieldLayout object instead.
Notably, this affects fields defined in the 'GetPreferences' hook, because
Special:Preferences uses an OOUI form now. (If possible, don't use 'rawrow'.)
+* In Skin::doEditSectionLink omitting the parameters $tooltip and $lang is
+ deprecated. For the $lang parameter, types other than Language are
+ deprecated.
=== Other changes in 1.32 ===
* (T198811) The following tables have had their UNIQUE indexes turned into
&$parser: Parser object
&$varCache: variable cache (array)
-'ParserLimitReport': DEPRECATED since 1.22! Use ParserLimitReportPrepare and
-ParserLimitReportFormat instead.
-Called at the end of Parser:parse() when the parser will
-include comments about size of the text parsed.
-$parser: Parser object
-&$limitReport: text that will be included (without comment tags)
-
'ParserLimitReportFormat': Called for each row in the parser limit report that
needs formatting. If nothing handles this hook, the default is to use "$key" to
get the label, and "$key-value" or "$key-value-text"/"$key-value-html" to
test case files matching the suffix "Test.php".
&$paths: list of test cases and directories to search.
-'UnknownAction': DEPRECATED since 1.19! To add an action in an extension,
-create a subclass of Action, and add a new key to $wgActions.
-An unknown "action" has occurred (useful for defining your own actions).
-$action: action name
-$article: article "acted on"
-
'UnwatchArticle': Before a watch is removed from an article.
&$user: user watching
&$page: WikiPage object to be removed
&$timestamp: timestamp, change this to override local email authentication
timestamp
-'UserGetImplicitGroups': DEPRECATED since 1.25!
-Called in User::getImplicitGroups().
-&$groups: List of implicit (automatically-assigned) groups
-
'UserGetLanguageObject': Called when getting user's interface language object.
$user: User object
&$code: Language code that will be used to create the object
return ObjectCache::getInstance( $wgMessageCacheType );
}
-/**
- * Call hook functions defined in $wgHooks
- *
- * @param string $event Event name
- * @param array $args Parameters passed to hook functions
- * @param string|null $deprecatedVersion Optionally mark hook as deprecated with version number
- *
- * @return bool True if no handler aborted the hook
- * @deprecated since 1.25 - use Hooks::run
- */
-function wfRunHooks( $event, array $args = [], $deprecatedVersion = null ) {
- wfDeprecated( __METHOD__, '1.25' );
- return Hooks::run( $event, $args, $deprecatedVersion );
-}
-
/**
* Wrapper around php's unpack.
*
$action->show();
return;
}
- // NOTE: deprecated hook. Add to $wgActions instead
- if ( Hooks::run(
- 'UnknownAction',
- [
- $request->getVal( 'action', 'view' ),
- $page
- ],
- '1.19'
- ) ) {
- $output->setStatusCode( 404 );
- $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
- }
+
+ // If we've not found out which action it is by now, it's unknown
+ $output->setStatusCode( 404 );
+ $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
}
/**
use Revision;
use Title;
use User;
+use Content;
use Wikimedia\Assert\Assert;
/**
'Access to the content has been suppressed for this audience'
);
} else {
- $output = $content->getParserOutput(
- $this->title,
- $this->revision->getId(),
- $this->options,
- $withHtml
- );
+ $output = $this->getSlotParserOutputUncached( $content, $withHtml );
if ( $withHtml && !$output->hasText() ) {
throw new LogicException(
return $this->slotsOutput[$role];
}
+ /**
+ * @note This method exist to make duplicate parses easier to see during profiling
+ * @param Content $content
+ * @param bool $withHtml
+ * @return ParserOutput
+ */
+ private function getSlotParserOutputUncached( Content $content, $withHtml ) {
+ return $content->getParserOutput(
+ $this->title,
+ $this->revision->getId(),
+ $this->options,
+ $withHtml
+ );
+ }
+
/**
* Updates the RevisionRecord after the revision has been saved. This can be used to discard
* and cached ParserOutput so parser functions like {{REVISIONTIMESTAMP}} or {{REVISIONID}}
* are distinct from Special Pages because an action must apply to exactly one page.
*
* To add an action in an extension, create a subclass of Action, and add the key to
- * $wgActions. There is also the deprecated UnknownAction hook
+ * $wgActions.
*
* Actions generally fall into two groups: the show-a-form-then-do-something-with-the-input
* format (protect, delete, move, etc), and the just-do-something format (watch, rollback,
$data = [];
foreach ( $langNames as $code => $name ) {
- $lang = [ 'code' => $code ];
+ $lang = [
+ 'code' => $code,
+ 'bcp47' => LanguageCode::bcp47( $code ),
+ ];
ApiResult::setContentValue( $lang, 'name', $name );
$data[] = $lang;
}
return;
}
- // Reload messages from the database and pre-populate dc-local caches
- // as optimisation. Use the master DB to avoid race conditions.
- $cache = $this->loadFromDB( $code, self::FOR_UPDATE );
+ // Load the existing cache to update it in the local DC cache.
+ // The other DCs will see a hash mismatch.
+ if ( $this->load( $code, self::FOR_UPDATE ) ) {
+ $cache = $this->cache->get( $code );
+ } else {
+ // Err? Fall back to loading from the database.
+ $cache = $this->loadFromDB( $code, self::FOR_UPDATE );
+ }
// Check if individual cache keys should exist and update cache accordingly
$newTextByTitle = []; // map of (title => content)
+ $newBigTitles = []; // map of (title => latest revision ID), like EXCESSIVE in loadFromDB()
foreach ( $replacements as list( $title ) ) {
$page = WikiPage::factory( Title::makeTitle( NS_MEDIAWIKI, $title ) );
$page->loadPageData( $page::READ_LATEST );
// Remember the text for the blob store update later on
$newTextByTitle[$title] = $text;
// Note that if $text is false, then $cache should have a !NONEXISTANT entry
- if ( is_string( $text ) && strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
- // Match logic of loadCachedMessagePageEntry()
- $this->wanCache->set(
- $this->bigMessageCacheKey( $cache['HASH'], $title ),
- ' ' . $text,
- $this->mExpiry
- );
+ if ( !is_string( $text ) ) {
+ $cache[$title] = '!NONEXISTENT';
+ } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
+ $cache[$title] = '!TOO BIG';
+ $newBigTitles[$title] = $page->getLatest();
+ } else {
+ $cache[$title] = ' ' . $text;
}
}
+ // Update HASH for the new key. Incorporates various administrative keys,
+ // including the old HASH (and thereby the EXCESSIVE value from loadFromDB()
+ // and previous replace() calls), but that doesn't really matter since we
+ // only ever compare it for equality with a copy saved by saveToCaches().
+ $cache['HASH'] = md5( serialize( $cache + [ 'EXCESSIVE' => $newBigTitles ] ) );
+ // Update the too-big WAN cache entries now that we have the new HASH
+ foreach ( $newBigTitles as $title => $id ) {
+ // Match logic of loadCachedMessagePageEntry()
+ $this->wanCache->set(
+ $this->bigMessageCacheKey( $cache['HASH'], $title ),
+ ' ' . $newTextByTitle[$title],
+ $this->mExpiry
+ );
+ }
// Mark this cache as definitely being "latest" (non-volatile) so
// load() calls do not try to refresh the cache with replica DB data
$cache['LATEST'] = time();
"config-skins-screenshots": "$1 (لقطات شاشة: $2)",
"config-extensions-requires": "$1 (يتطلب $2)",
"config-screenshot": "لقطة شاشة",
+ "config-extension-not-found": "لم يمكن العثور على ملف التسجيل للامتداد \"$1\"",
+ "config-extension-dependency": "خطأ اعتماد حدث أثناء تنصيب الامتداد \"$1\": $2",
"mainpagetext": "<strong>تم تثبيت ميدياويكي بنجاح.</strong>",
"mainpagedocfooter": "استشر [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents دليل المستخدم] لمعلومات حول استخدام برنامج الويكي.\n\n== البداية ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings قائمة إعدادات الضبط]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ أسئلة متكررة حول ميدياويكي]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce القائمة البريدية الخاصة بإصدار ميدياويكي]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam تعلم كيفية مكافحة السخام في الويكي الخاص بك]"
}
"config-skins-screenshot": "$1 ($2)",
"config-extensions-requires": "$1 (erfordert $2)",
"config-screenshot": "Bildschirmfoto",
+ "config-extension-not-found": "Die Registrierungsdatei für die Erweiterung „$1“ konnte nicht gefunden werden",
+ "config-extension-dependency": "Bei der Installation der Erweiterung „$1“ ist ein Abhängigkeitsfehler aufgetreten: $2",
"mainpagetext": "<strong>MediaWiki wurde installiert.</strong>",
"mainpagedocfooter": "Hilfe zur Benutzung und Konfiguration der Wiki-Software findest du im [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Benutzerhandbuch].\n\n== Starthilfen ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Liste der Konfigurationsvariablen]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki-FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailingliste neuer MediaWiki-Versionen]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Übersetze MediaWiki für deine Sprache]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Erfahre, wie du Spam auf deinem Wiki bekämpfen kannst]"
}
"config-skins-screenshots": "$1 (captures d’écran : $2)",
"config-extensions-requires": "$1 (nécessite $2)",
"config-screenshot": "Captures d’écrans",
+ "config-extension-not-found": "Impossible de trouver le fichier d’inscription pour l’extension « $1 »",
+ "config-extension-dependency": "Une erreur de dépendance s’est produite en installant l’extension « $1 » : $2",
"mainpagetext": "<strong>MediaWiki a été installé.</strong>",
"mainpagedocfooter": "Consultez le [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guide de l’utilisateur] pour plus d’informations sur l’utilisation de ce logiciel de wiki.\n\n== Pour démarrer ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Liste des paramètres de configuration]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/fr Questions courantes sur MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liste de discussion sur les distributions de MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Adaptez MediaWiki dans votre langue]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Apprendre comment lutter contre le pourriel dans votre wiki]"
}
"config-nofile": "Il file \"$1\" non può essere trovato. È stato eliminato?",
"config-extension-link": "Sapevi che il tuo wiki supporta le [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions estensioni]?\n\nPuoi navigare tra le [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category estensioni per categoria].",
"config-extensions-requires": "$1 (richiesto $2)",
+ "config-extension-not-found": "Impossibile trovare il file di registrazione per l'estensione \"$1\"",
+ "config-extension-dependency": "Si è verificato un errore di dipendenza durante l'installazione dell'estensione \"$1\": $2",
"mainpagetext": "<strong>MediaWiki è stato installato.</strong>",
"mainpagedocfooter": "Consulta la [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents guida utente] per maggiori informazioni sull'uso di questo software wiki.\n\n== Per iniziare ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Impostazioni di configurazione]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Domande frequenti su MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list annunci MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Trova MediaWiki nella tua lingua]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Imparare a combattere lo spam sul tuo wiki]"
}
"config-skins-screenshots": "$1 (screenshots: $2)",
"config-extensions-requires": "$1 (requer $2)",
"config-screenshot": "screenshot",
+ "config-extension-not-found": "Não foi possível encontrar o arquivo de registo da extensão \"$1\"",
+ "config-extension-dependency": "Foi encontrado um erro de dependências ao instalar a extensão \"$1\": $2",
"mainpagetext": "<strong>O MediaWiki foi instalado.</strong>",
"mainpagedocfooter": "Consulte o [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Manual de Usuário] para informações de como usar o software wiki.\n\n== Começando ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista de opções de configuração]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ do MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussão com avisos de novas versões do MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Traduza o MediaWiki para seu idioma]"
}
"config-install-mainpage-failed": "Não foi possível inserir a página principal: $1",
"config-install-done": "<strong>Parabéns!</strong>\nTerminou a instalação do MediaWiki.\n\nO instalador gerou um ficheiro <code>LocalSettings.php</code>.\nEste ficheiro contém todas as configurações.\n\nPrecisa de descarregar o ficheiro e colocá-lo no diretório de raiz da sua instalação (o mesmo diretório onde está o ficheiro index.php). Este descarregamento deverá ter sido iniciado automaticamente.\n\nSe o descarregamento não foi iniciado, ou se o cancelou, pode recomeçá-lo clicando na hiperligação abaixo:\n\n$3\n\n<strong>Nota</strong>: Se não o descarregar agora, o ficheiro que foi gerado deixará de estar disponível quando sair do processo de instalação.\n\nDepois de terminar o passo anterior, pode <strong>[$2 entrar na wiki]</strong>.",
"config-install-done-path": "<strong>Parabéns!</strong>\nTerminou a instalação do MediaWiki.\n\nO instalador gerou um ficheiro <code>LocalSettings.php</code>.\nEste ficheiro contém todas as configurações.\n\nPrecisa de descarregar o ficheiro e colocá-lo no diretório <code>$4</code>. Este descarregamento deverá ter sido iniciado automaticamente.\n\nSe o descarregamento não foi iniciado, ou se o cancelou, pode recomeçá-lo clicando a hiperligação abaixo:\n\n$3\n\n<strong>Nota</strong>: Se não fizer o descarregamento agora, o ficheiro que foi gerado deixará de estar disponível quando sair do processo de instalação.\n\nDepois de terminar o passo anterior, pode <strong>[$2 entrar na wiki]</strong>.",
- "config-install-success": "O MediaWiki foi instalado. Já pode visitar <$1$2> para ver a sua wiki.\nSe tiver dúvidas, veja a nossa lista de perguntas frequentes,\n<https://www.mediawiki.org/wiki/Manual:FAQ/pt>, ou utilize um dos fóruns de suporte indicados nessa página.",
+ "config-install-success": "O MediaWiki foi instalado. Já pode visitar <$1$2> para ver a sua wiki.\nSe tiver dúvidas, veja a nossa lista de perguntas frequentes,\n<https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ>, ou utilize um dos fóruns de suporte indicados nessa página.",
"config-download-localsettings": "Descarregar <code>LocalSettings.php</code>",
"config-help": "ajuda",
"config-help-tooltip": "clique para expandir",
"config-skins-screenshots": "$1 (capturas de ecrã: $2)",
"config-extensions-requires": "$1 (requer $2)",
"config-screenshot": "captura de ecrã",
+ "config-extension-not-found": "Não foi possível encontrar o ficheiro de registo da extensão \"$1\"",
+ "config-extension-dependency": "Foi encontrado um erro de dependências ao instalar a extensão \"$1\": $2",
"mainpagetext": "<strong>O MediaWiki foi instalado.</strong>",
"mainpagedocfooter": "Consulte a [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Ajuda do MediaWiki] para informações sobre o uso do software wiki.\n\n== Onde começar ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista de opções de configuração]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Perguntas e respostas frequentes sobre o MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Subscreva a lista de divulgação de novas versões do MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Regionalize o MediaWiki para a sua língua]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Aprenda a combater <i>spam</i> na sua wiki]"
}
"config-skins-screenshot": "$1 ($2)",
"config-extensions-requires": "$1 (захтева $2)",
"config-screenshot": "снимак екрана",
+ "config-extension-not-found": "Није могуће пронаћи датотеку регистрације за додатак „$1”",
"mainpagetext": "<strong>Медијавики је инсталиран.</strong>",
"mainpagedocfooter": "Погледајте [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents кориснички водич] за коришћење програма.\n\n== Увод ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Помоћ у вези са подешавањима]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Често постављана питања]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Дописни списак о издањима Медијавикија]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Научите како да се борите против спама на свом викију]"
}
"config-skins-screenshots": "$1 (螢幕截圖: $2)",
"config-extensions-requires": "$1(需要 $2)",
"config-screenshot": "螢幕截圖",
+ "config-extension-not-found": "查無用於擴充功能「$1」的註冊檔案",
+ "config-extension-dependency": "當安裝擴充功能「$1」時發生相依性錯誤:$2",
"mainpagetext": "<strong>已安裝 MediaWiki。</strong>",
"mainpagedocfooter": "有關使用wiki的訊息,請參閱[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents 使用者指南]。\n\n== 新手入門 ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings 系統設定]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki常見問題]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki郵寄清單]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources 將MediaWiki翻譯至您的語言]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam 了解如何在您的wiki上防禦破壞]"
}
// Since we're not really outputting HTML, decode the entities and
// then re-encode the things that need hiding inside HTML comments.
$limitReport = htmlspecialchars_decode( $limitReport );
- // Run deprecated hook
- Hooks::run( 'ParserLimitReport', [ $this, &$limitReport ], '1.22' );
// Sanitize for comment. Note '‐' in the replacement is U+2010,
// which looks much like the problematic '-'.
return $skin->doEditSectionLink( $editsectionPage,
$editsectionSection,
$editsectionContent,
- $wgLang->getCode()
+ $wgLang
);
},
$text
*
* @param array $configuration List of configuration values keyed by variable name
* @return string JavaScript code
+ * @throws Exception
*/
public static function makeConfigSetScript( array $configuration ) {
- return Xml::encodeJsCall(
+ $js = Xml::encodeJsCall(
'mw.config.set',
[ $configuration ],
self::inDebugMode()
);
+ if ( $js === false ) {
+ throw new Exception(
+ 'JSON serialization of config data failed. ' .
+ 'This usually means the config data is not valid UTF-8.'
+ );
+ }
+ return $js;
}
/**
'pluralRules' => $language->getPluralRules(),
'digitGroupingPattern' => $language->digitGroupingPattern(),
'fallbackLanguages' => $language->getFallbackLanguages(),
+ 'bcp47Map' => LanguageCode::getNonstandardLanguageCodeMapping(),
];
}
* @param string $section The designation of the section being pointed to,
* to be included in the link, like "§ion=$section"
* @param string|null $tooltip The tooltip to use for the link: will be escaped
- * and wrapped in the 'editsectionhint' message
- * @param string $lang Language code
+ * and wrapped in the 'editsectionhint' message.
+ * Not setting this parameter is deprecated.
+ * @param Language|string $lang Language object or language code string.
+ * Type string is deprecated. Not setting this parameter is deprecated.
* @return string HTML to use for edit link
*/
public function doEditSectionLink( Title $nt, $section, $tooltip = null, $lang = false ) {
// HTML generated here should probably have userlangattributes
// added to it for LTR text on RTL pages
- $lang = wfGetLangObj( $lang );
+ if ( !$lang instanceof Language ) {
+ wfDeprecated( __METHOD__ . ' with other type than Language for $lang', '1.32' );
+ $lang = wfGetLangObj( $lang );
+ }
$attribs = [];
if ( !is_null( $tooltip ) ) {
/**
* Get a list of implicit groups
+ * TODO: Should we deprecate this? It's trivial, but we don't want to encourage use of globals.
+ *
* @return array Array of Strings Array of internal group names
*/
public static function getImplicitGroups() {
global $wgImplicitGroups;
-
- $groups = $wgImplicitGroups;
- # Deprecated, use $wgImplicitGroups instead
- Hooks::run( 'UserGetImplicitGroups', [ &$groups ], '1.25' );
-
- return $groups;
+ return $wgImplicitGroups;
}
/**
}
function validateVariant( $variant = null ) {
+ if ( $variant === null ) {
+ return null;
+ }
+ $variant = strtolower( $variant );
return $variant === $this->mLang->getCode() ? $variant : null;
}
/**
* Mapping of deprecated language codes that were used in previous
* versions of MediaWiki to up-to-date, current language codes.
+ * These may or may not be valid BCP 47 codes; they are included here
+ * because MediaWiki remapped these particular codes at some point.
*
* @var array Mapping from language code to language code
*
* @since 1.30
+ * @see https://meta.wikimedia.org/wiki/Special_language_codes
*/
private static $deprecatedLanguageCodeMapping = [
// Note that als is actually a valid ISO 639 code (Tosk Albanian), but it
// was previously used in MediaWiki for Alsatian, which comes under gsw
- 'als' => 'gsw',
- 'bat-smg' => 'sgs',
- 'be-x-old' => 'be-tarask',
- 'fiu-vro' => 'vro',
- 'roa-rup' => 'rup',
- 'zh-classical' => 'lzh',
- 'zh-min-nan' => 'nan',
- 'zh-yue' => 'yue',
+ 'als' => 'gsw', // T25215
+ 'bat-smg' => 'sgs', // T27522
+ 'be-x-old' => 'be-tarask', // T11823
+ 'fiu-vro' => 'vro', // T31186
+ 'roa-rup' => 'rup', // T17988
+ 'zh-classical' => 'lzh', // T30443
+ 'zh-min-nan' => 'nan', // T30442
+ 'zh-yue' => 'yue', // T30441
+ ];
+
+ /**
+ * Mapping of non-standard language codes used in MediaWiki to
+ * standardized BCP 47 codes. These are not deprecated (yet?):
+ * IANA may eventually recognize the subtag, in which case the `-x-`
+ * infix could be removed, or else we could rename the code in
+ * MediaWiki, in which case they'd move up to the above mapping
+ * of deprecated codes.
+ *
+ * As a rule, we preserve all distinctions made by MediaWiki
+ * internally. For example, `de-formal` becomes `de-x-formal`
+ * instead of just `de` because MediaWiki distinguishes `de-formal`
+ * from `de` (for example, for interface translations). Similarly,
+ * BCP 47 indicates that `kk-Cyrl` SHOULD not be used because it
+ * "typically does not add information", but in our case MediaWiki
+ * LanguageConverter distinguishes `kk` (render content in a mix of
+ * Kurdish variants) from `kk-Cyrl` (convert content to be uniformly
+ * Cyrillic). As the BCP 47 requirement is a SHOULD not a MUST,
+ * `kk-Cyrl` is a valid code, although some validators may emit
+ * a warning note.
+ *
+ * @var array Mapping from nonstandard codes to BCP 47 codes
+ *
+ * @since 1.32
+ * @see https://meta.wikimedia.org/wiki/Special_language_codes
+ * @see https://phabricator.wikimedia.org/T125073
+ */
+ private static $nonstandardLanguageCodeMapping = [
+ // All codes returned by Language::fetchLanguageNames() validated
+ // against IANA registry at
+ // https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
+ // with help of validator at
+ // http://schneegans.de/lv/
+ 'cbk-zam' => 'cbk', // T124657
+ 'de-formal' => 'de-x-formal',
+ 'eml' => 'egl', // T36217
+ 'en-rtl' => 'en-x-rtl',
+ 'es-formal' => 'es-x-formal',
+ 'hu-formal' => 'hu-x-formal',
+ 'map-bms' => 'jv-x-bms', // [[en:Banyumasan_dialect]] T125073
+ 'mo' => 'ro-Cyrl-MD', // T125073
+ 'nrm' => 'nrf', // [[en:Norman_language]] T25216
+ 'nl-informal' => 'nl-x-informal',
+ 'roa-tara' => 'nap-x-tara', // [[en:Tarantino_dialect]]
+ 'simple' => 'en-simple',
+ 'sr-ec' => 'sr-Cyrl', // T117845
+ 'sr-el' => 'sr-Latn', // T117845
+
+ // Although these next codes aren't *wrong* per se, including
+ // both the script and the country code helps compatibility with
+ // other BCP 47 users. Note that MW also uses `zh-Hans`/`zh-Hant`,
+ // without a country code, and those should be left alone.
+ // (See $variantfallbacks in LanguageZh.php for Hans/Hant id.)
+ 'zh-cn' => 'zh-Hans-CN',
+ 'zh-sg' => 'zh-Hans-SG',
+ 'zh-my' => 'zh-Hans-MY',
+ 'zh-tw' => 'zh-Hant-TW',
+ 'zh-hk' => 'zh-Hant-HK',
+ 'zh-mo' => 'zh-Hant-MO',
];
/**
return self::$deprecatedLanguageCodeMapping;
}
+ /**
+ * Returns a mapping of non-standard language codes used by
+ * (current and previous version of) MediaWiki, mapped to standard
+ * BCP 47 names.
+ *
+ * This array is exported to JavaScript to ensure
+ * mediawiki.language.bcp47 stays in sync with LanguageCode::bcp47().
+ *
+ * @return string[]
+ *
+ * @since 1.32
+ */
+ public static function getNonstandardLanguageCodeMapping() {
+ $result = [];
+ foreach ( self::$deprecatedLanguageCodeMapping as $code => $ignore ) {
+ $result[$code] = self::bcp47( $code );
+ }
+ foreach ( self::$nonstandardLanguageCodeMapping as $code => $ignore ) {
+ $result[$code] = self::bcp47( $code );
+ }
+ return $result;
+ }
+
/**
* Replace deprecated language codes that were used in previous
* versions of MediaWiki to up-to-date, current language codes.
* See mediawiki.language.bcp47 for the JavaScript implementation.
*
* @param string $code The language code.
- * @return string The language code which complying with BCP 47 standards.
+ * @return string A language code complying with BCP 47 standards.
*
* @since 1.31
*/
public static function bcp47( $code ) {
+ $code = self::replaceDeprecatedCodes( strtolower( $code ) );
+ if ( isset( self::$nonstandardLanguageCodeMapping[$code] ) ) {
+ $code = self::$nonstandardLanguageCodeMapping[$code];
+ }
$codeSegment = explode( '-', $code );
$codeBCP = [];
foreach ( $codeSegment as $segNo => $seg ) {
$req = $this->validateVariant( $wgDefaultLanguageVariant );
}
+ $req = $this->validateVariant( $req );
+
// This function, unlike the other get*Variant functions, is
// not memoized (i.e. there return value is not cached) since
// new information might appear during processing after this
// is first called.
- if ( $this->validateVariant( $req ) ) {
+ if ( $req ) {
return $req;
}
return $this->mMainLanguageCode;
* @return mixed Returns the variant if it is valid, null otherwise
*/
public function validateVariant( $variant = null ) {
- if ( $variant !== null && in_array( $variant, $this->mVariants ) ) {
+ if ( $variant === null ) {
+ return null;
+ }
+ // Our internal variants are always lower-case; the variant we
+ // are validating may have mixed case.
+ $variant = LanguageCode::replaceDeprecatedCodes( strtolower( $variant ) );
+ if ( in_array( $variant, $this->mVariants ) ) {
return $variant;
}
+ // Browsers are supposed to use BCP 47 standard in the
+ // Accept-Language header, but not all of our internal
+ // mediawiki variant codes are BCP 47. Map BCP 47 code
+ // to our internal code.
+ foreach ( $this->mVariants as $v ) {
+ // Case-insensitive match (BCP 47 is mixed case)
+ if ( strtolower( LanguageCode::bcp47( $v ) ) === $variant ) {
+ return $v;
+ }
+ }
return null;
}
return $this->mHeaderVariant;
}
- // see if some supported language variant is set in the
+ // See if some supported language variant is set in the
// HTTP header.
$languages = array_keys( $wgRequest->getAcceptLang() );
if ( empty( $languages ) ) {
$convTable = $convRule->getConvTable();
$action = $convRule->getRulesAction();
foreach ( $convTable as $variant => $pair ) {
- if ( !$this->validateVariant( $variant ) ) {
+ $v = $this->validateVariant( $variant );
+ if ( !$v ) {
continue;
}
if ( $action == 'add' ) {
// More efficient than array_merge(), about 2.5 times.
foreach ( $pair as $from => $to ) {
- $this->mTables[$variant]->setPair( $from, $to );
+ $this->mTables[$v]->setPair( $from, $to );
}
} elseif ( $action == 'remove' ) {
- $this->mTables[$variant]->removeArray( $pair );
+ $this->mTables[$v]->removeArray( $pair );
}
}
}
'ba' => 'башҡортса', # Bashkir
'ban' => 'Basa Bali', # Balinese
'bar' => 'Boarisch', # Bavarian (Austro-Bavarian and South Tyrolean)
- 'bat-smg' => 'žemaitėška', # Samogitian (deprecated code, 'sgs' in ISO 693-3 since 2010-06-30 )
+ 'bat-smg' => 'žemaitėška', # Samogitian (deprecated code, 'sgs' in ISO 639-3 since 2010-06-30 )
'bbc' => 'Batak Toba', # Batak Toba (falls back to bbc-latn)
'bbc-latn' => 'Batak Toba', # Batak Toba
'bcc' => 'جهلسری بلوچی', # Southern Balochi
'lzh' => '文言', # Literary Chinese, T10217
'lzz' => 'Lazuri', # Laz
'mai' => 'मैथिली', # Maithili
- 'map-bms' => 'Basa Banyumasan', # Banyumasan
+ 'map-bms' => 'Basa Banyumasan', # Banyumasan ('jv-x-bms')
'mdf' => 'мокшень', # Moksha
'mg' => 'Malagasy', # Malagasy
'mh' => 'Ebon', # Marshallese
'mn' => 'монгол', # Halh Mongolian (Cyrillic) (ISO 639-3: khk)
'mni' => 'মেইতেই লোন্', # Manipuri/Meitei
'mnw' => 'ဘာသာ မန်', # Mon, T201583
- 'mo' => 'молдовеняскэ', # Moldovan, deprecated
+ 'mo' => 'молдовеняскэ', # Moldovan, deprecated (ISO 639-2: ro-Cyrl-MD)
'mr' => 'मराठी', # Marathi
'mrj' => 'кырык мары', # Hill Mari
'ms' => 'Bahasa Melayu', # Malay
'myv' => 'эрзянь', # Erzya
'mzn' => 'مازِرونی', # Mazanderani
'na' => 'Dorerin Naoero', # Nauruan
- 'nah' => 'Nāhuatl', # Nahuatl (not in ISO 639-3)
+ 'nah' => 'Nāhuatl', # Nahuatl (added to ISO 639-3 on 2006-10-31)
'nan' => 'Bân-lâm-gú', # Min-nan, T10217
'nap' => 'Napulitano', # Neapolitan, T45793
'nb' => 'norsk bokmål', # Norwegian (Bokmal)
'nn' => 'norsk nynorsk', # Norwegian (Nynorsk)
'no' => 'norsk', # Norwegian macro language (falls back to nb).
'nov' => 'Novial', # Novial
- 'nrm' => 'Nouormand', # Norman
+ 'nrm' => 'Nouormand', # Norman (invalid code; 'nrf' in ISO 639 since 2014)
'nso' => 'Sesotho sa Leboa', # Northern Sotho
'nv' => 'Diné bizaad', # Navajo
'ny' => 'Chi-Chewa', # Chichewa
'rmy' => 'Romani', # Vlax Romany
'rn' => 'Kirundi', # Rundi/Kirundi/Urundi
'ro' => 'română', # Romanian
- 'roa-rup' => 'armãneashti', # Aromanian (deprecated code, 'rup' exists in ISO 693-3)
- 'roa-tara' => 'tarandíne', # Tarantino
+ 'roa-rup' => 'armãneashti', # Aromanian (deprecated code, 'rup' exists in ISO 639-3)
+ 'roa-tara' => 'tarandíne', # Tarantino ('nap-x-tara')
'ru' => 'русский', # Russian
'rue' => 'русиньскый', # Rusyn
'rup' => 'armãneashti', # Aromanian
'tt-cyrl' => 'татарча', # Tatar (Cyrillic script) (default)
'tt-latn' => 'tatarça', # Tatar (Latin script)
'tum' => 'chiTumbuka', # Tumbuka
- 'tw' => 'Twi', # Twi, (FIXME!)
+ 'tw' => 'Twi', # Twi
'ty' => 'reo tahiti', # Tahitian
'tyv' => 'тыва дыл', # Tyvan
'tzm' => 'ⵜⴰⵎⴰⵣⵉⵖⵜ', # Tamazight
"uploadstash-errclear": "Не атрымалася ачысьціць файлы.",
"uploadstash-refresh": "Абнавіць сьпіс файлаў",
"uploadstash-thumbnail": "прагляд мініятуры",
- "uploadstash-exception": "Ð\9dе магÑ\83 заÑ\85аваÑ\86Ñ\8c загÑ\80Ñ\83зкÑ\83 Ñ\9e Ñ\81Ñ\85овÑ\96Ñ\88Ñ\87Ñ\8b ($1): «$2».",
+ "uploadstash-exception": "Ð\9dе аÑ\82Ñ\80Ñ\8bмалаÑ\81Ñ\8f заÑ\85аваÑ\86Ñ\8c загÑ\80Ñ\83зкÑ\83 Ñ\9e Ñ\85ованкÑ\83 ($1): «$2».",
"uploadstash-bad-path": "Шлях не існуе.",
"uploadstash-bad-path-invalid": "Шлях не зьяўляецца слушным.",
"uploadstash-bad-path-unknown-type": "Невядомы тып «$1».",
"movepage-moved": "'''Старонка «$1» была перанесеная ў «$2»'''",
"movepage-moved-redirect": "Перанакіраваньне было створана.",
"movepage-moved-noredirect": "Перанакіраваньне не было створанае.",
+ "movepage-delete-first": "Мэтавая старонка мае зашмат вэрсіяў, каб выдаліць яе пры пераносе. Калі ласка, спачатку выдаліце старонку ўручную, а потым паспрабуйце яшчэ раз.",
"articleexists": "Старонка з такой назвай ужо існуе, альбо абраная Вамі назва недапушчальная. Калі ласка, абярыце іншую назву.",
"cantmove-titleprotected": "Немагчыма перанесьці старонку, таму што новая назва знаходзіцца ў сьпісе забароненых",
"movetalk": "Перанесьці таксама старонку абмеркаваньня",
"badarticleerror": "Cette action ne peut pas être effectuée sur cette page.",
"cannotdelete": "Impossible de supprimer la page ou le fichier « $1 ».\nLa suppression a peut-être déjà été effectuée par quelqu’un d’autre.",
"cannotdelete-title": "Impossible de supprimer la page « $1 »",
- "delete-scheduled": "La page « $1 » st programmée pour être supprimée.\nVeuillez patienter.",
+ "delete-scheduled": "La page « $1 » est programmée pour être supprimée.\nVeuillez patienter.",
"delete-hook-aborted": "Suppression annulée par une extension.\nAucune explication n’a été fournie.",
"no-null-revision": "Impossible de créer une nouvelle révision vide pour la page « $1 »",
"badtitle": "Mauvais titre",
"movepage-moved": "<strong>« $1 » a été renommée en « $2 »</strong>",
"movepage-moved-redirect": "Une redirection depuis l’ancien nom a été créée.",
"movepage-moved-noredirect": "La création d’une redirection depuis l’ancien nom a été annulée.",
- "movepage-delete-first": "La page cible a trop de révisions à supprimer en tant que faisant partie du déplacement d’une page.\nVeuillez d’abord supprimer la page manuellement, puis réessayer.",
+ "movepage-delete-first": "La page cible a trop de révisions à supprimer pour le déplacement de page. Veuillez d’abord supprimer la page manuellement, puis réessayer.",
"articleexists": "Il existe déjà une page portant ce titre, ou le titre que vous avez choisi n'est pas correct.\nVeuillez en choisir un autre.",
"cantmove-titleprotected": "Vous ne pouvez pas déplacer une page vers cet emplacement car la création de page avec ce nouveau titre a été protégée.",
"movetalk": "Renommer aussi la page de discussion associée",
"page-atom-feed": "Atom feed $1 ဍူ",
"red-link-title": "$1 (လိက်မေံ လ်ုအ်ှဍေၜး)",
"nstab-main": "လက်မေံသး",
- "nstab-user": "á\80\86á\80ºá\80¯á\80\9eá\80¯á\80²á\80¸á\80\80á\80ºá\80¯á\80\86á\80¬ á\80\9cá\80\80á\80ºá\80\99á\80±á\80¶",
+ "nstab-user": "á\80\86á\80ºá\80¯á\80\9eá\80¯á\80¶á\82\8bá\80\80á\80ºá\80¯á\80\86á\80¬á\82\8b á\80\9cá\80á\80\80á\80ºá\80\99á\80±á\80¶á\81\9cá\81 á\80¬á\80º",
"nstab-special": "လိက်မေံခေါဟ်",
"nstab-project": "ပ်ုရောဴဂျက်လိက်မေံၜၠါ်",
"nstab-image": "ဖိုင်",
"revdelete-unrestricted": "Limitatioune fir Administrateuren opgehuewen",
"logentry-block-block": "$1 {{GENDER:$2|huet}} {{GENDER:$4|$3}} fir eng Zäit vu(n) $5 $6 gespaart",
"logentry-block-unblock": "$1 {{GENDER:$2|huet}} d'Spär vum {{GENDER:$4|$3}} opgehuewen",
+ "logentry-block-reblock": "$1 {{GENDER:$2|huet}} d'Spärastellunge fir {{GENDER:$4|$3}} mat enger Spärdauer vu(n) $5 $6 geännert",
+ "logentry-suppress-reblock": "$1 {{GENDER:$2|huet}} d'Spärastellunge fir {{GENDER:$4|$3}} mat enger Spärdauer vu(n) $5 $6 geännert",
"logentry-import-upload": "$1 {{GENDER:$2|huet}} $3 duerch Eropluede vun engem Fichier importéiert",
"logentry-import-interwiki": "$1 huet $3 vun enger anerer Wiki {{GENDER:$2|importéiert}}",
"logentry-import-interwiki-details": "$1 {{GENDER:$2|huet}} $3 vu(n) $5 importéiert ({{PLURAL:$4|Eng Versioun|$4 Versiounen}})",
"action-editmyprivateinfo": "уредување на вашите лични податоци",
"action-editcontentmodel": "уредување на содржинскиот модел на страница",
"action-managechangetags": "создавање или (де)активирање на ознаки",
- "action-applychangetags": "Ñ\81Ñ\82аваÑ\9aе на ознаки заедно Ñ\81о напÑ\80евените промени",
+ "action-applychangetags": "Ñ\81Ñ\82аваÑ\9aе на ознаки заедно Ñ\81о ваÑ\88ите промени",
"action-changetags": "додавање и отстранување на произволни ознаки во поединечни преработки и дневнички записи",
"action-deletechangetags": "бришење ознаки од базата",
"action-purge": "превчитување на оваа страница",
"recentchangeslinked-page": "Sidenamn:",
"recentchangeslinked-to": "Vis endringar på sider som lenkjar til den gitte sida i staden",
"recentchanges-page-added-to-category": "[[:$1]] vart lagd til kategorien",
+ "recentchanges-page-added-to-category-bundled": "[[:$1]] lagt til i kategorien; [[Special:WhatLinksHere/$1|denne sida er inkludert i andre sider]]",
"recentchanges-page-removed-from-category": "[[:$1]] fjerna frå kategori",
+ "recentchanges-page-removed-from-category-bundled": "[[:$1]] fjernat frå kategori, [[Special:WhatLinksHere/$1|denne sida er inkludert i andre sider]]",
"upload": "Last opp fil",
"uploadbtn": "Last opp fil",
"reuploaddesc": "Attende til opplastingsskjemaet.",
"badarticleerror": "Esta ação não pode ser realizada nesta página.",
"cannotdelete": "Não foi possível eliminar a página ou arquivo $1.\nÉ possível que ele já tenha sido eliminado por outra pessoa.",
"cannotdelete-title": "Não é possível eliminar a página \"$1\"",
+ "delete-scheduled": "A página \"$1\" está agendada para eliminação.\nAguarde a mesma, por favor.",
"delete-hook-aborted": "A eliminação foi cancelada por um \"hook\".\nNão foi dada nenhuma explicação.",
"no-null-revision": "Não foi possível criar nova revisão nula para a página \"$1\"",
"badtitle": "Título inválido",
"movepage-moved": "'''\"$1\" foi movida para \"$2\"'''",
"movepage-moved-redirect": "Um redirecionamento foi criado.",
"movepage-moved-noredirect": "A criação de um redirecionamento foi suprimida.",
+ "movepage-delete-first": "A página de destino tem muitas revisões para excluir como parte de uma movimentação de página. Primeiro, exclua a página manualmente e tente novamente.",
"articleexists": "Uma página com este título já existe, ou o título que escolheu é inválido.\nPor favor, escolha outro nome.",
"cantmove-titleprotected": "Você não pode mover uma página para tal denominação uma vez que o novo título se encontra protegido contra criação",
"movetalk": "Mover também a página de discussão associada",
"badarticleerror": "Esta operação não pode ser realizada nesta página.",
"cannotdelete": "Não foi possível eliminar a página ou ficheiro \"$1\".\nPode já ter sido eliminado por outro utilizador.",
"cannotdelete-title": "Não é possível eliminar a página \"$1\"",
+ "delete-scheduled": "A página \"$1\" está agendada para eliminação.\nAguarde a mesma, por favor.",
"delete-hook-aborted": "A eliminação foi cancelada por um \"hook\".\nNão foi dada nenhuma explicação.",
"no-null-revision": "Não foi possível criar uma nova revisão nula para a página \"$1\"",
"badtitle": "Título inválido",
"movepage-moved": "<strong>\"$1\" foi movida para \"$2\"</strong>",
"movepage-moved-redirect": "Foi criado um redirecionamento.",
"movepage-moved-noredirect": "A criação de um redirecionamento foi suprimida.",
+ "movepage-delete-first": "A página de destino tem demasiadas revisões para apagá-las como parte de uma movimentação de página. Elimine-a primeiro manualmente e depois tente novamente, por favor.",
"articleexists": "Ou já existe uma página com este nome ou o nome que escolheu é inválido.\nEscolha outro nome, por favor.",
"cantmove-titleprotected": "Não pode mover uma página para esse destino, porque o novo título foi protegido para evitar a sua criação",
"movetalk": "Mover também a página de discussão associada",
"variantname-gan-hans": "{{Optional}}\n\nVariant option for wikis with variants conversion enabled.",
"variantname-gan-hant": "{{Optional}}\n\nVariant option for wikis with variants conversion enabled.",
"variantname-gan": "{{Optional}}\n\nVariant option for wikis with variants conversion enabled.",
- "variantname-sr-ec": "{{optional}}\nVariant Option for wikis with variants conversion enabled.\n\nNote that <code>sr-ec</code> is not a conforming BCP47 language tag. Wikis should be migrated by:\n* allowing it only as a legacy alias of the preferred tag <code>sr-cyrl</code> (possibly insert a tracking category in templates as long as they must support the legacy tag),\n* making the new tag the default to look first, before looking for the old tag,\n* moving the translations to the new code by renaming them,\n* checking links in source pages still using the legacy tag to change it to the new tag,\n* possibly cleanup the redirect pages.",
- "variantname-sr-el": "{{optional}}\nVariant Option for wikis with variants conversion enabled.\n\nNote that <code>sr-el</code> is not a conforming BCP47 language tag. Wikis should be migrated by:\n* allowing it only as a legacy alias of the preferred tag <code>sr-latn</code> (possibly insert a tracking category in templates as long as they must support the legacy tag),\n* making the new tag the default to look first, before looking for the old tag,\n* moving the translations to the new code by renaming them,\n* checking links in source pages still using the legacy tag to change it to the new tag,\n* possibly cleanup the redirect pages.",
+ "variantname-sr-ec": "{{optional}}\nVariant Option for wikis with variants conversion enabled.\n\nNote that <code>sr-ec</code> is not a conforming BCP 47 language tag. Wikis should be migrated by:\n* allowing it only as a legacy alias of the preferred tag <code>sr-cyrl</code> (possibly insert a tracking category in templates as long as they must support the legacy tag),\n* making the new tag the default to look first, before looking for the old tag,\n* moving the translations to the new code by renaming them,\n* checking links in source pages still using the legacy tag to change it to the new tag,\n* possibly cleanup the redirect pages.",
+ "variantname-sr-el": "{{optional}}\nVariant Option for wikis with variants conversion enabled.\n\nNote that <code>sr-el</code> is not a conforming BCP 47 language tag. Wikis should be migrated by:\n* allowing it only as a legacy alias of the preferred tag <code>sr-latn</code> (possibly insert a tracking category in templates as long as they must support the legacy tag),\n* making the new tag the default to look first, before looking for the old tag,\n* moving the translations to the new code by renaming them,\n* checking links in source pages still using the legacy tag to change it to the new tag,\n* possibly cleanup the redirect pages.",
"variantname-sr": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
"variantname-kk-kz": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
"variantname-kk-tr": "{{optional}}\nVariant Option for wikis with variants conversion enabled.",
"action-editmyprivateinfo": "редактирование вашей частной информации",
"action-editcontentmodel": "редактирование контентной модели страницы",
"action-managechangetags": "создание и (де)активацию меток",
- "action-applychangetags": " применять теги наряду с Вашими изменениями",
+ "action-applychangetags": "применение меток вместе с Вашими изменениями",
"action-changetags": "добавление и удаление произвольных меток на отдельных изменениях и записях в журнале",
"action-deletechangetags": "удаление меток из базы данных",
"action-purge": "очистку кэша этой страницы",
"externaldberror": "Дошло је до грешке при потврди идентитета базе података или вам није дозвољено да ажурирате свој спољни налог.",
"login": "Пријава",
"login-security": "Потврда вашег индентитета",
- "nav-login-createaccount": "Ð\9fÑ\80иÑ\98ави ме / оÑ\82воÑ\80и налог",
+ "nav-login-createaccount": "Ð\9fÑ\80иÑ\98ава / Ñ\80егиÑ\81Ñ\82Ñ\80аÑ\86иÑ\98а",
"logout": "Одјава",
"userlogout": "Одјава",
"notloggedin": "Нисте пријављени",
* - `pluralRules`
* - `digitGroupingPattern`
* - `fallbackLanguages`
+ * - `bcp47Map`
+ * - `languageNames`
*
* @property
*/
},
/**
- * Formats language tags according the BCP47 standard.
+ * Formats language tags according the BCP 47 standard.
* See LanguageCode::bcp47 for the PHP implementation.
*
* @param {string} languageTag Well-formed language tag
* @return {string}
*/
bcp47: function ( languageTag ) {
- var formatted,
+ var bcp47Map,
+ formatted,
+ segments,
isFirstSegment = true,
- isPrivate = false,
- segments = languageTag.split( '-' );
+ isPrivate = false;
+ languageTag = languageTag.toLowerCase();
+
+ bcp47Map = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'bcp47Map' );
+ if ( bcp47Map && Object.prototype.hasOwnProperty.call( bcp47Map, languageTag ) ) {
+ languageTag = bcp47Map[ languageTag ];
+ }
+
+ segments = languageTag.split( '-' );
formatted = segments.map( function ( segment ) {
var newSegment;
];
}
- /**
- * @dataProvider provideHooks
- * @covers ::wfRunHooks
- */
- public function testOldStyleHooks( $msg, array $hook, $expectedFoo, $expectedBar ) {
- global $wgHooks;
-
- $this->hideDeprecated( 'wfRunHooks' );
- $foo = $bar = 'original';
-
- $wgHooks['MediaWikiHooksTest001'][] = $hook;
- wfRunHooks( 'MediaWikiHooksTest001', [ &$foo, &$bar ] );
-
- $this->assertSame( $expectedFoo, $foo, $msg );
- $this->assertSame( $expectedBar, $bar, $msg );
- }
-
/**
* @dataProvider provideHooks
* @covers Hooks::register
function ( $code, $name ) {
return [
'code' => $code,
+ 'bcp47' => LanguageCode::bcp47( $code ),
'name' => $name
];
},
$this->assertEquals( $oldText, $messageCache->get( $message ), 'Content restored' );
}
+ public function testReplaceCache() {
+ global $wgWANObjectCaches;
+
+ // We need a WAN cache for this.
+ $this->setMwGlobals( [
+ 'wgMainWANCache' => 'hash',
+ 'wgWANObjectCaches' => $wgWANObjectCaches + [
+ 'hash' => [
+ 'class' => WANObjectCache::class,
+ 'cacheId' => 'hash',
+ 'channels' => []
+ ]
+ ]
+ ] );
+ $this->overrideMwServices();
+
+ MessageCache::destroyInstance();
+ $messageCache = MessageCache::singleton();
+ $messageCache->enable();
+
+ // Populate one key
+ $this->makePage( 'Key1', 'de', 'Value1' );
+ $this->assertEquals( 0,
+ DeferredUpdates::pendingUpdatesCount(),
+ 'Post-commit deferred update triggers a run of all updates' );
+ $this->assertEquals( 'Value1', $messageCache->get( 'Key1' ), 'Key1 was successfully edited' );
+
+ // Screw up the database so MessageCache::loadFromDB() will
+ // produce the wrong result for reloading Key1
+ $this->db->delete(
+ 'page', [ 'page_namespace' => NS_MEDIAWIKI, 'page_title' => 'Key1' ], __METHOD__
+ );
+
+ // Populate the second key
+ $this->makePage( 'Key2', 'de', 'Value2' );
+ $this->assertEquals( 0,
+ DeferredUpdates::pendingUpdatesCount(),
+ 'Post-commit deferred update triggers a run of all updates' );
+ $this->assertEquals( 'Value2', $messageCache->get( 'Key2' ), 'Key2 was successfully edited' );
+
+ // Now test that the second edit didn't reload Key1
+ $this->assertEquals( 'Value1', $messageCache->get( 'Key1' ),
+ 'Key1 wasn\'t reloaded by edit of Key2' );
+ }
+
/**
* @dataProvider provideNormalizeKey
*/
* @dataProvider provideLanguageCodes()
*/
public function testBcp47( $code, $expected ) {
+ $this->assertEquals( $expected, LanguageCode::bcp47( $code ),
+ "Applying BCP 47 standard to '$code'"
+ );
+
$code = strtolower( $code );
$this->assertEquals( $expected, LanguageCode::bcp47( $code ),
- "Applying BCP47 standard to lower case '$code'"
+ "Applying BCP 47 standard to lower case '$code'"
);
$code = strtoupper( $code );
$this->assertEquals( $expected, LanguageCode::bcp47( $code ),
- "Applying BCP47 standard to upper case '$code'"
+ "Applying BCP 47 standard to upper case '$code'"
);
}
// de-419-DE
// a-DE
// ar-a-aaa-b-bbb-a-ccc
+
+ # Non-standard and deprecated language codes used by MediaWiki
+ [ 'als', 'gsw' ],
+ [ 'bat-smg', 'sgs' ],
+ [ 'be-x-old', 'be-tarask' ],
+ [ 'fiu-vro', 'vro' ],
+ [ 'roa-rup', 'rup' ],
+ [ 'zh-classical', 'lzh' ],
+ [ 'zh-min-nan', 'nan' ],
+ [ 'zh-yue', 'yue' ],
+ [ 'cbk-zam', 'cbk' ],
+ [ 'de-formal', 'de-x-formal' ],
+ [ 'eml', 'egl' ],
+ [ 'en-rtl', 'en-x-rtl' ],
+ [ 'es-formal', 'es-x-formal' ],
+ [ 'hu-formal', 'hu-x-formal' ],
+ [ 'kk-Arab', 'kk-Arab' ],
+ [ 'kk-Cyrl', 'kk-Cyrl' ],
+ [ 'kk-Latn', 'kk-Latn' ],
+ [ 'map-bms', 'jv-x-bms' ],
+ [ 'mo', 'ro-Cyrl-MD' ],
+ [ 'nrm', 'nrf' ],
+ [ 'nl-informal', 'nl-x-informal' ],
+ [ 'roa-tara', 'nap-x-tara' ],
+ [ 'simple', 'en-simple' ],
+ [ 'sr-ec', 'sr-Cyrl' ],
+ [ 'sr-el', 'sr-Latn' ],
+ [ 'zh-cn', 'zh-Hans-CN' ],
+ [ 'zh-sg', 'zh-Hans-SG' ],
+ [ 'zh-my', 'zh-Hans-MY' ],
+ [ 'zh-tw', 'zh-Hant-TW' ],
+ [ 'zh-hk', 'zh-Hant-HK' ],
+ [ 'zh-mo', 'zh-Hant-MO' ],
+ [ 'zh-hans', 'zh-Hans' ],
+ [ 'zh-hant', 'zh-Hant' ],
];
}
$this->lang = new LanguageToTest();
$this->lc = new TestConverter(
$this->lang, 'tg',
- [ 'tg', 'tg-latn' ]
+ # Adding 'sgs' as a variant to ensure we handle deprecated codes
+ # adding 'simple' as a variant to ensure we handle non BCP 47 codes
+ [ 'tg', 'tg-latn', 'sgs', 'simple' ]
);
}
$this->assertEquals( 'tg', $this->lc->getPreferredVariant() );
}
+ /**
+ * @covers LanguageConverter::getPreferredVariant
+ * @covers LanguageConverter::getURLVariant
+ */
+ public function testGetPreferredVariantUrl() {
+ global $wgRequest;
+ $wgRequest->setVal( 'variant', 'tg-latn' );
+
+ $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() );
+ }
+
+ /**
+ * @covers LanguageConverter::getPreferredVariant
+ * @covers LanguageConverter::getURLVariant
+ */
+ public function testGetPreferredVariantUrlDeprecated() {
+ global $wgRequest;
+ $wgRequest->setVal( 'variant', 'bat-smg' );
+
+ $this->assertEquals( 'sgs', $this->lc->getPreferredVariant() );
+ }
+
+ /**
+ * @covers LanguageConverter::getPreferredVariant
+ * @covers LanguageConverter::getURLVariant
+ */
+ public function testGetPreferredVariantUrlBCP47() {
+ global $wgRequest;
+ $wgRequest->setVal( 'variant', 'en-simple' );
+
+ $this->assertEquals( 'simple', $this->lc->getPreferredVariant() );
+ }
+
/**
* @covers LanguageConverter::getPreferredVariant
* @covers LanguageConverter::getHeaderVariant
$this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() );
}
+ /**
+ * @covers LanguageConverter::getPreferredVariant
+ * @covers LanguageConverter::getHeaderVariant
+ */
+ public function testGetPreferredVariantHeadersBCP47() {
+ global $wgRequest;
+ $wgRequest->setHeader( 'Accept-Language', 'en-simple' );
+
+ $this->assertEquals( 'simple', $this->lc->getPreferredVariant() );
+ }
+
/**
* @covers LanguageConverter::getPreferredVariant
* @covers LanguageConverter::getHeaderVariant
$this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() );
}
+ /**
+ * @covers LanguageConverter::getPreferredVariant
+ */
+ public function testGetPreferredVariantUserOptionDeprecated() {
+ global $wgUser;
+
+ $wgUser = new User;
+ $wgUser->load(); // from 'defaults'
+ $wgUser->mId = 1;
+ $wgUser->mDataLoaded = true;
+ $wgUser->mOptionsLoaded = true;
+ $wgUser->setOption( 'variant', 'bat-smg' );
+
+ $this->assertEquals( 'sgs', $this->lc->getPreferredVariant() );
+ }
+
+ /**
+ * @covers LanguageConverter::getPreferredVariant
+ */
+ public function testGetPreferredVariantUserOptionBCP47() {
+ global $wgUser;
+
+ $wgUser = new User;
+ $wgUser->load(); // from 'defaults'
+ $wgUser->mId = 1;
+ $wgUser->mDataLoaded = true;
+ $wgUser->mOptionsLoaded = true;
+ $wgUser->setOption( 'variant', 'en-simple' );
+
+ $this->assertEquals( 'simple', $this->lc->getPreferredVariant() );
+ }
+
/**
* @covers LanguageConverter::getPreferredVariant
* @covers LanguageConverter::getUserVariant
$this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() );
}
+ /**
+ * @covers LanguageConverter::getPreferredVariant
+ * @covers LanguageConverter::getUserVariant
+ */
+ public function testGetPreferredVariantUserOptionForForeignLanguageDeprecated() {
+ global $wgUser;
+
+ $this->setContentLang( 'en' );
+ $wgUser = new User;
+ $wgUser->load(); // from 'defaults'
+ $wgUser->mId = 1;
+ $wgUser->mDataLoaded = true;
+ $wgUser->mOptionsLoaded = true;
+ $wgUser->setOption( 'variant-tg', 'bat-smg' );
+
+ $this->assertEquals( 'sgs', $this->lc->getPreferredVariant() );
+ }
+
+ /**
+ * @covers LanguageConverter::getPreferredVariant
+ * @covers LanguageConverter::getUserVariant
+ */
+ public function testGetPreferredVariantUserOptionForForeignLanguageBCP47() {
+ global $wgUser;
+
+ $this->setContentLang( 'en' );
+ $wgUser = new User;
+ $wgUser->load(); // from 'defaults'
+ $wgUser->mId = 1;
+ $wgUser->mDataLoaded = true;
+ $wgUser->mOptionsLoaded = true;
+ $wgUser->setOption( 'variant-tg', 'en-simple' );
+
+ $this->assertEquals( 'simple', $this->lc->getPreferredVariant() );
+ }
+
/**
* @covers LanguageConverter::getPreferredVariant
* @covers LanguageConverter::getUserVariant
$this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() );
}
+ /**
+ * @covers LanguageConverter::getPreferredVariant
+ */
+ public function testGetPreferredVariantDefaultLanguageVariantDeprecated() {
+ global $wgDefaultLanguageVariant;
+
+ $wgDefaultLanguageVariant = 'bat-smg';
+ $this->assertEquals( 'sgs', $this->lc->getPreferredVariant() );
+ }
+
+ /**
+ * @covers LanguageConverter::getPreferredVariant
+ */
+ public function testGetPreferredVariantDefaultLanguageVariantBCP47() {
+ global $wgDefaultLanguageVariant;
+
+ $wgDefaultLanguageVariant = 'en-simple';
+ $this->assertEquals( 'simple', $this->lc->getPreferredVariant() );
+ }
+
/**
* @covers LanguageConverter::getPreferredVariant
* @covers LanguageConverter::getURLVariant
function loadDefaultTables() {
$this->mTables = [
+ 'sgs' => new ReplacementArray(),
+ 'simple' => new ReplacementArray(),
'tg-latn' => new ReplacementArray( $this->table ),
'tg' => new ReplacementArray()
];
// # Tags that use extensions
[ 'en-us-u-islamcal', 'en-US-u-islamcal' ],
[ 'zh-cn-a-myext-x-private', 'zh-CN-a-myext-x-private' ],
- [ 'en-a-myext-b-another', 'en-a-myext-b-another' ]
+ [ 'en-a-myext-b-another', 'en-a-myext-b-another' ],
// # Invalid:
// de-419-DE
// a-DE
// ar-a-aaa-b-bbb-a-ccc
+
+ // Non-standard and deprecated language codes used by MediaWiki
+ [ 'als', 'gsw' ],
+ [ 'bat-smg', 'sgs' ],
+ [ 'be-x-old', 'be-tarask' ],
+ [ 'fiu-vro', 'vro' ],
+ [ 'roa-rup', 'rup' ],
+ [ 'zh-classical', 'lzh' ],
+ [ 'zh-min-nan', 'nan' ],
+ [ 'zh-yue', 'yue' ],
+ [ 'cbk-zam', 'cbk' ],
+ [ 'de-formal', 'de-x-formal' ],
+ [ 'eml', 'egl' ],
+ [ 'en-rtl', 'en-x-rtl' ],
+ [ 'es-formal', 'es-x-formal' ],
+ [ 'hu-formal', 'hu-x-formal' ],
+ [ 'kk-Arab', 'kk-Arab' ],
+ [ 'kk-Cyrl', 'kk-Cyrl' ],
+ [ 'kk-Latn', 'kk-Latn' ],
+ [ 'map-bms', 'jv-x-bms' ],
+ [ 'mo', 'ro-Cyrl-MD' ],
+ [ 'nrm', 'nrf' ],
+ [ 'nl-informal', 'nl-x-informal' ],
+ [ 'roa-tara', 'nap-x-tara' ],
+ [ 'simple', 'en-simple' ],
+ [ 'sr-ec', 'sr-Cyrl' ],
+ [ 'sr-el', 'sr-Latn' ],
+ [ 'zh-cn', 'zh-Hans-CN' ],
+ [ 'zh-sg', 'zh-Hans-SG' ],
+ [ 'zh-my', 'zh-Hans-MY' ],
+ [ 'zh-tw', 'zh-Hant-TW' ],
+ [ 'zh-hk', 'zh-Hant-HK' ],
+ [ 'zh-mo', 'zh-Hant-MO' ],
+ [ 'zh-hans', 'zh-Hans' ],
+ [ 'zh-hant', 'zh-Hant' ]
];
QUnit.test( 'mw.language.bcp47', function ( assert ) {
+ mw.language.data = this.liveLangData;
bcp47Tests.forEach( function ( data ) {
var input = data[ 0 ],
expected = data[ 1 ];
assert.strictEqual( mw.language.bcp47( input ), expected );
+ assert.strictEqual( mw.language.bcp47( input.toLowerCase() ), expected );
+ assert.strictEqual( mw.language.bcp47( input.toUpperCase() ), expected );
} );
} );
}() );