jenkins-bot has submitted this change and it was merged.
Change subject: (bug 1495) Enable on-wiki message language fallbacks
......................................................................
(bug 1495) Enable on-wiki message language fallbacks
The core function behind wfMessage() (MessageCache->get()) did not
apply the language fallback chain to on-wiki messages.
This patch has changed the behavior to iterate over all possible
on-wiki fallbacks (starting with the user's language) before
using the built-in language cache (CDB files). Previously we only looked
for the existence of an on-wiki message in the users's language.
Performance wise, using the 'ab' language ('ru', 'en' fallbacks)
MessageCache::get (Averaged over runs and calls)
New Code: ~8.5% TET (110us/call)
Old Code: ~6.5% TET ( 90us/call)
TET: Total Execution Time
Change-Id: Iaaf6ccebd8c40c9602748c58c3a5c73c29e7aa4d
---
M includes/cache/MessageCache.php
M languages/Language.php
A tests/phpunit/includes/cache/MessageCacheTest.php
3 files changed, 188 insertions(+), 33 deletions(-)
Approvals:
Adamw: Verified; Looks good to me, but someone else must approve
Siebrand: Looks good to me, approved
jenkins-bot: Verified
diff --git a/includes/cache/MessageCache.php b/includes/cache/MessageCache.php
index 89e5325..f24e8bc 100644
--- a/includes/cache/MessageCache.php
+++ b/includes/cache/MessageCache.php
@@ -578,27 +578,32 @@
}
/**
- * Get a message from either the content language or the user language.
+ * Get a message from either the content language or the user language.
The fallback
+ * language order is the users language fallback union the content
language fallback.
+ * This list is then applied to find keys in the following order
+ * 1) MediaWiki:$key/$langcode (for every language except the content
language where
+ * we look at MediaWiki:$key)
+ * 2) Built-in messages via the l10n cache which is also in fallback
order
*
* @param $key String: the message cache key
- * @param $useDB Boolean: get the message from the DB, false to use only
- * the localisation
- * @param bool|string $langcode Code of the language to get the message
for, if
- * it is a valid code create a language for that
language,
- * if it is a string but not a valid code then make a
basic
- * language object, if it is a false boolean then use
the
- * current users language (as a fallback for the old
- * parameter functionality), or if it is a true boolean
- * then use the wikis content language (also as a
- * fallback).
+ * @param $useDB Boolean: If true will look for the message in the DB,
false only
+ * get the message from the DB, false to use only the compiled
l10n cache.
+ * @param bool|string|object $langcode Code of the language to get the
message for.
+ * - If string and a valid code, will create a standard language
object
+ * - If string but not a valid code, will create a basic
language object
+ * - If boolean and false, create object from the current users
language
+ * - If boolean and true, create object from the wikis content
language
+ * - If language object, use it as given
* @param $isFullKey Boolean: specifies whether $key is a two part key
* "msg/lang".
*
* @throws MWException
- * @return string|bool
+ * @return string|bool False if the message doesn't exist, otherwise
the message
*/
function get( $key, $useDB = true, $langcode = true, $isFullKey = false
) {
global $wgLanguageCode, $wgContLang;
+
+ wfProfileIn( __METHOD__ );
if ( is_int( $key ) ) {
// "Non-string key given" exception sometimes happens
for numerical strings that become ints somewhere on their way here
@@ -606,22 +611,37 @@
}
if ( !is_string( $key ) ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( 'Non-string key given' );
}
if ( strval( $key ) === '' ) {
# Shortcut: the empty key is always missing
+ wfProfileOut( __METHOD__ );
return false;
}
- $lang = wfGetLangObj( $langcode );
- if ( !$lang ) {
- throw new MWException( "Bad lang code $langcode given"
);
+
+ # Obtain the initial language object
+ if ( $isFullKey ) {
+ $keyParts = explode( '/', $key );
+ if ( count( $keyParts ) < 2 ) {
+ throw new MWException( "Message key '$key' does
not appear to be a full key." );
+ }
+
+ $langcode = array_pop( $keyParts );
+ $key = implode( '/', $keyParts );
}
- $langcode = $lang->getCode();
-
- $message = false;
+ # Obtain a language object for the requested language from the
passed language code
+ # Note that the language code could in fact be a language
object already but we assume
+ # it's a string further below.
+ $requestedLangObj = wfGetLangObj( $langcode );
+ if ( !$requestedLangObj ) {
+ wfProfileOut( __METHOD__ );
+ throw new MWException( "Bad lang code $langcode given"
);
+ }
+ $langcode = $requestedLangObj->getCode();
# Normalise title-case input (with some inlining)
$lckey = str_replace( ' ', '_', $key );
@@ -633,24 +653,37 @@
$uckey = $wgContLang->ucfirst( $lckey );
}
+ # Loop through each language in the fallback list until we find
something useful
+ $message = false;
+
# Try the MediaWiki namespace
- if( !$this->mDisable && $useDB ) {
- $title = $uckey;
- if( !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
- $title .= '/' . $langcode;
+ if ( !$this->mDisable && $useDB ) {
+ $fallbackChain =
Language::getFallbacksIncludingSiteLanguage( $langcode );
+ array_unshift( $fallbackChain, $langcode );
+
+ foreach ( $fallbackChain as $langcode ) {
+ if ( $langcode === $wgLanguageCode ) {
+ # Messages created in the content
language will not have the /lang extension
+ $message = $this->getMsgFromNamespace(
$uckey, $langcode );
+ } else {
+ $message = $this->getMsgFromNamespace(
"$uckey/$langcode", $langcode );
+ }
+
+ if ( $message !== false ) {
+ break;
+ }
}
- $message = $this->getMsgFromNamespace( $title,
$langcode );
}
# Try the array in the language object
if ( $message === false ) {
- $message = $lang->getMessage( $lckey );
- if ( is_null( $message ) ) {
+ $message = $requestedLangObj->getMessage( $lckey );
+ if ( is_null ( $message ) ) {
$message = false;
}
}
- # Try the array of another language
+ # If we still have no message, maybe the key was in fact a full
key so try that
if( $message === false ) {
$parts = explode( '/', $lckey );
# We may get calls for things that are http-urls from
sidebar
@@ -664,15 +697,9 @@
}
}
- # Is this a custom message? Try the default language in the
db...
- if( ( $message === false || $message === '-' ) &&
- !$this->mDisable && $useDB &&
- !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
- $message = $this->getMsgFromNamespace( $uckey,
$wgLanguageCode );
- }
-
# Final fallback
if( $message === false ) {
+ wfProfileOut( __METHOD__ );
return false;
}
@@ -686,6 +713,7 @@
' ' => "\xc2\xa0",
) );
+ wfProfileOut( __METHOD__ );
return $message;
}
diff --git a/languages/Language.php b/languages/Language.php
index ef6a367..01751db 100644
--- a/languages/Language.php
+++ b/languages/Language.php
@@ -3985,6 +3985,45 @@
}
/**
+ * Get the ordered list of fallback languages, ending with the fallback
+ * language chain for the site language.
+ *
+ * @since 1.21
+ * @param $code string Language code
+ * @return array
+ */
+ public static function getFallbacksIncludingSiteLanguage( $code ) {
+ global $wgLanguageCode;
+
+ // Usually, we will only store a tiny number of fallback
chains, so we
+ // keep them in static memory.
+ static $fallbackLanguageCache = array();
+ $cacheKey = "{$code}-{$wgLanguageCode}";
+
+ if ( !array_key_exists( $cacheKey, $fallbackLanguageCache ) ) {
+ $fallbacks = self::getFallbacksFor( $code );
+
+ // Take the final 'en' off of the array before splicing
+ if ( end( $fallbacks ) === 'en' ) {
+ array_pop( $fallbacks );
+ }
+ // Append the site's fallback chain
+ $siteFallbacks = self::getFallbacksFor( $wgLanguageCode
);
+
+ // Eliminate any languages already included in the chain
+ $siteFallbacks = array_intersect( array_diff(
$siteFallbacks, $fallbacks ), $siteFallbacks );
+ if ( $siteFallbacks ) {
+ $fallbacks = array_merge( $fallbacks,
$siteFallbacks );
+ }
+ if ( end( $fallbacks ) !== 'en' ) {
+ $fallbacks[] = 'en';
+ }
+ $fallbackLanguageCache[$cacheKey] = $fallbacks;
+ }
+ return $fallbackLanguageCache[$cacheKey];
+ }
+
+ /**
* Get all messages for a given language
* WARNING: this may take a long time. If you just need all message
*keys*
* but need the *contents* of only a few messages, consider using
getMessageKeysFor().
diff --git a/tests/phpunit/includes/cache/MessageCacheTest.php
b/tests/phpunit/includes/cache/MessageCacheTest.php
new file mode 100644
index 0000000..89021dd
--- /dev/null
+++ b/tests/phpunit/includes/cache/MessageCacheTest.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @group Database
+ * @group Cache
+ */
+class MessageCacheTest extends MediaWikiLangTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+ MessageCache::singleton()->enable();
+ }
+
+ function addDBData() {
+ // Set up messages and fallbacks ab -> ru -> en
+ $this->makePage( 'FallbackLanguageTest-Full', 'ab' );
+ $this->makePage( 'FallbackLanguageTest-Full', 'ru' );
+ $this->makePage( 'FallbackLanguageTest-Full', 'en' );
+
+ // Fallbacks where ab does not exist
+ $this->makePage( 'FallbackLanguageTest-Partial', 'ru' );
+ $this->makePage( 'FallbackLanguageTest-Partial', 'en' );
+
+ // Fallback to english
+ $this->makePage( 'FallbackLanguageTest-English', 'en' );
+
+ // Full key tests -- always want russian
+ $this->makePage( 'MessageCacheTest-FullKeyTest', 'ab' );
+ $this->makePage( 'MessageCacheTest-FullKeyTest', 'ru' );
+ }
+
+ /**
+ * Helper function for addDBData -- adds a simple page to the database
+ *
+ * @param string $title Title of page to be created
+ * @param string $lang Language and content of the created page
+ */
+ protected function makePage( $title, $lang ) {
+ global $wgContLang;
+
+ $title = Title::newFromText(
+ ($lang == $wgContLang->getCode()) ? $title :
"$title/$lang",
+ NS_MEDIAWIKI
+ );
+ $wikiPage = new WikiPage( $title );
+ $content = ContentHandler::makeContent( $lang, $title );
+ $wikiPage->doEditContent( $content, "$lang translation test
case" );
+ }
+
+ /**
+ * Test message fallbacks, bug #1495
+ *
+ * @dataProvider provideMessagesForFallback
+ */
+ function testMessageFallbacks( $message, $lang, $expectedContent ) {
+ $result = MessageCache::singleton()->get( $message, true, $lang
);
+ $this->assertEquals( $expectedContent, $result, "Message
fallback failed." );
+ }
+
+ function provideMessagesForFallback() {
+ return array(
+ array( 'FallbackLanguageTest-Full', 'ab', 'ab' ),
+ array( 'FallbackLanguageTest-Partial', 'ab', 'ru' ),
+ array( 'FallbackLanguageTest-English', 'ab', 'en' ),
+ array( 'FallbackLanguageTest-None', 'ab', false ),
+ );
+ }
+
+ /**
+ * There's a fallback case where the message key is given as fully
qualified -- this
+ * should ignore the passed $lang and use the language from the key
+ *
+ * @dataProvider provideMessagesForFullKeys
+ */
+ function testFullKeyBehaviour( $message, $lang, $expectedContent ) {
+ $result = MessageCache::singleton()->get( $message, true,
$lang, true );
+ $this->assertEquals( $expectedContent, $result, "Full key
message fallback failed." );
+ }
+
+ function provideMessagesForFullKeys() {
+ return array(
+ array( 'MessageCacheTest-FullKeyTest/ru', 'ru', 'ru' ),
+ array( 'MessageCacheTest-FullKeyTest/ru', 'ab', 'ru' ),
+ array( 'MessageCacheTest-FullKeyTest/ru/foo', 'ru',
false ),
+ );
+ }
+
+}
--
To view, visit https://gerrit.wikimedia.org/r/44224
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Iaaf6ccebd8c40c9602748c58c3a5c73c29e7aa4d
Gerrit-PatchSet: 13
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Mwalker <[email protected]>
Gerrit-Reviewer: Adamw <[email protected]>
Gerrit-Reviewer: Brion VIBBER <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Mwalker <[email protected]>
Gerrit-Reviewer: Nikerabbit <[email protected]>
Gerrit-Reviewer: Platonides <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: Tim Starling <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits