jenkins-bot has submitted this change and it was merged.
Change subject: Implement getInterfaceTextInLanguage and use API and Parser
......................................................................
Implement getInterfaceTextInLanguage and use API and Parser
Fallback for user language and content language is easiest to
resolve server-side. Also saves sending a lot of data to the
client that it doesn't need. Similar to how ResourceLoader only
sends 1 set of message values.
Bug: 50431
Bug: 52922
Change-Id: If8317ed6522a05d5a48a210ff43c97277b950a97
---
M TemplateDataBlob.php
M api/ApiTemplateData.php
M tests/TemplateDataBlobTest.php
3 files changed, 338 insertions(+), 13 deletions(-)
Approvals:
Catrope: Looks good to me, approved
jenkins-bot: Verified
diff --git a/TemplateDataBlob.php b/TemplateDataBlob.php
index 0eb9ce4..fd18527 100644
--- a/TemplateDataBlob.php
+++ b/TemplateDataBlob.php
@@ -154,7 +154,7 @@
// Param.label
if ( isset( $paramObj->label ) ) {
if ( !is_object( $paramObj->label ) &&
!is_string( $paramObj->label ) ) {
- // TODO: Also validate that the keys are valid
lang codes and the values strings.
+ // TODO: Also validate that the keys
are valid lang codes and the values strings.
return Status::newFatal(
'templatedata-invalid-type',
"params.{$paramName}.label",
@@ -349,13 +349,101 @@
return $text;
}
+ /**
+ * Get a single localized string from an InterfaceText object.
+ *
+ * Uses the preferred language passed to this function, or one of its
fallbacks,
+ * or the site content language, or its fallbacks.
+ *
+ * @param stdClass $text An InterfaceText object
+ * @param string $langCode Preferred language
+ * @return null|string Text value from the InterfaceText object or null
if no suitable
+ * match was found
+ */
+ protected static function getInterfaceTextInLanguage( stdClass $text,
$langCode ) {
+ if ( isset( $text->$langCode ) ) {
+ return $text->$langCode;
+ }
+
+ list( $userlangs, $sitelangs ) =
Language::getFallbacksIncludingSiteLanguage( $langCode );
+
+ foreach ( $userlangs as $lang ) {
+ if ( isset( $text->$lang ) ) {
+ return $text->$lang;
+ }
+ }
+
+ foreach ( $sitelangs as $lang ) {
+ if ( isset( $text->$lang ) ) {
+ return $text->$lang;
+ }
+ }
+
+ // If none of the languages are found fallback to null.
Alternatively we could fallback to
+ // reset( $text ) which will return whatever key there is, but
we should't give the user a
+ // "random" language with no context (e.g. could be RTL/Hebrew
for an LTR/Japanese user).
+ return null;
+ }
+
+ /**
+ * @return Status
+ */
public function getStatus() {
return $this->status;
}
+ /**
+ * @return object
+ */
public function getData() {
- // Returned by reference. Data is a private member. Use clone
instead?
+ // TODO: Returned by reference. Data is a private member. Use
clone instead?
return $this->data;
+ }
+
+ /**
+ * Get data with all InterfaceText objects resolved to a single string
to the
+ * appropriate language.
+ *
+ * @param string $langCode Preferred language
+ * @return object
+ */
+ public function getDataInLanguage( $langCode ) {
+ // Deep clone, also need to clone ->params and all
interfacetext objects
+ // within param properties.
+ $data = unserialize( serialize( $this->data ) );
+
+ // Root.description
+ if ( $data->description !== null ) {
+ $data->description = self::getInterfaceTextInLanguage(
$data->description, $langCode );
+ }
+
+ foreach ( $data->params as $paramObj ) {
+ // Param.label
+ if ( $paramObj->label !== null ) {
+ $paramObj->label =
self::getInterfaceTextInLanguage( $paramObj->label, $langCode );
+ }
+
+ // Param.description
+ if ( $paramObj->description !== null ) {
+ $paramObj->description =
self::getInterfaceTextInLanguage( $paramObj->description, $langCode );
+ }
+ }
+
+ foreach ( $data->sets as $setObj ) {
+ $label = self::getInterfaceTextInLanguage(
$setObj->label, $langCode );
+ if ( $label === null ) {
+ // Contrary to other InterfaceTexts, set label
is not optional. If we're here it
+ // means the template data from the wiki
doesn't contain either the user language,
+ // site language or any of its fallbacks. Wikis
should fix data that is in this
+ // condition (TODO: Disallow during saving?).
For now, fallback to whatever we can
+ // get that does exist in the text object.
+ $label = reset( $setObj->label );
+ }
+
+ $setObj->label = $label;
+ }
+
+ return $data;
}
/**
@@ -373,9 +461,7 @@
}
public function getHtml( Language $lang ) {
- global $wgContLang;
- $langCode = $wgContLang->getCode();
- $data = $this->data;
+ $data = $this->getDataInLanguage( $lang->getCode() );
$html =
Html::openElement( 'div', array( 'class' =>
'mw-templatedata-doc-wrap' ) )
. Html::element(
@@ -387,7 +473,7 @@
)
),
$data->description !== null ?
- $data->description->$langCode :
+ $data->description :
wfMessage(
'templatedata-doc-desc-empty' )->inLanguage( $lang )
)
. '<table class="wikitable mw-templatedata-doc-params">'
@@ -441,8 +527,8 @@
$html .= '<tr>'
// Label
. Html::element( 'th', array(),
- isset( $paramObj->label->$langCode ) ?
- $paramObj->label->$langCode :
+ $paramObj->label !== null ?
+ $paramObj->label :
ucfirst( $paramName )
)
// Parameters and aliases
@@ -453,12 +539,12 @@
. Html::element( 'td', array(
'class' => array(
'mw-templatedata-doc-muted' => (
- !isset(
$paramObj->description->$langCode ) && $paramObj->deprecated === false
+ $paramObj->description
=== null && $paramObj->deprecated === false
)
)
),
- $paramObj->description->$langCode !== null ?
- $paramObj->description->$langCode :
+ $paramObj->description !== null ?
+ $paramObj->description :
wfMessage(
'templatedata-doc-param-desc-empty' )->inLanguage( $lang )
)
// Type
diff --git a/api/ApiTemplateData.php b/api/ApiTemplateData.php
index dc3b5a1..1a568d8 100644
--- a/api/ApiTemplateData.php
+++ b/api/ApiTemplateData.php
@@ -57,6 +57,14 @@
$params = $this->extractRequestParams();
$result = $this->getResult();
+ if ( is_null( $params['lang'] ) ) {
+ $langCode = false;
+ } elseif ( !Language::isValidCode( $params['lang'] ) ) {
+ $this->dieUsage( 'Invalid language code for parameter
lang', 'invalidlang' );
+ } else {
+ $langCode = $params['lang'];
+ }
+
$pageSet = $this->getPageSet();
$pageSet->execute();
$titles = $pageSet->getGoodTitles(); // page_id => Title object
@@ -82,13 +90,20 @@
$rawData = $row->pp_value;
$tdb = TemplateDataBlob::newFromDatabase( $rawData );
$status = $tdb->getStatus();
+
if ( !$status->isOK() ) {
$this->dieUsage(
'Page #' . intval( $row->pp_page ) . '
templatedata contains invalid data: '
. $status->getMessage(),
'templatedata-corrupt'
);
}
- $data = $tdb->getData();
+
+ if ( $langCode ) {
+ $data = $tdb->getDataInLanguage( $langCode );
+ } else {
+ $data = $tdb->getData();
+ }
+
$resp[$row->pp_page] = array(
'title' => strval( $titles[$row->pp_page] ),
'description' => $data->description,
@@ -110,13 +125,16 @@
'format' => array(
ApiBase::PARAM_DFLT => 'json',
ApiBase::PARAM_TYPE => array( 'json', 'jsonfm'
),
- )
+ ),
+ 'lang' => null
);
}
public function getParamDescription() {
return $this->getPageSet()->getParamDescription() + array(
'format' => 'The format of the output',
+ 'lang' => 'Return localized values in this language (by
default all available' .
+ ' translations are returned)',
);
}
diff --git a/tests/TemplateDataBlobTest.php b/tests/TemplateDataBlobTest.php
index e652e85..4648790 100644
--- a/tests/TemplateDataBlobTest.php
+++ b/tests/TemplateDataBlobTest.php
@@ -29,6 +29,7 @@
return $string;
}
+
public static function provideParse() {
$cases = array(
array(
@@ -397,4 +398,224 @@
$templateData = TemplateDataBlob::newFromDatabase( $gzJson );
$this->assertInstanceOf( 'TemplateDataBlob', $templateData );
}
+
+ public static function provideGetDataInLanguage() {
+ $cases = array(
+ array(
+ 'input' => '{
+ "description": {
+ "de": "German",
+ "nl": "Dutch",
+ "en": "English",
+ "de-formal": "German (formal
address)"
+ },
+ "params": {}
+ }
+ ',
+ 'output' => '{
+ "description": "German",
+ "params": {},
+ "sets": []
+ }
+ ',
+ 'lang' => 'de',
+ 'msg' => 'Simple description'
+ ),
+ array(
+ 'input' => '{
+ "description": "Hi",
+ "params": {}
+ }
+ ',
+ 'output' => '{
+ "description": "Hi",
+ "params": {},
+ "sets": []
+ }
+ ',
+ 'lang' => 'fr',
+ 'msg' => 'Non multi-language value returned as
is (expands to { "en": value } for' .
+ ' content-lang, "fr" falls back to
"en")'
+ ),
+ array(
+ 'input' => '{
+ "description": {
+ "nl": "Dutch",
+ "de": "German"
+ },
+ "params": {}
+ }
+ ',
+ 'output' => '{
+ "description": "Dutch",
+ "params": {},
+ "sets": []
+ }
+ ',
+ 'lang' => 'fr',
+ 'msg' => 'Try content language before giving up
on user language and fallbacks'
+ ),
+ array(
+ 'input' => '{
+ "description": {
+ "es": "Spanish",
+ "de": "German"
+ },
+ "params": {}
+ }
+ ',
+ 'output' => '{
+ "description": null,
+ "params": {},
+ "sets": []
+ }
+ ',
+ 'lang' => 'fr',
+ 'msg' => 'Description is optional, use null if
no suitable fallback'
+ ),
+ array(
+ 'input' => '{
+ "description": {
+ "de": "German",
+ "nl": "Dutch",
+ "en": "English"
+ },
+ "params": {}
+ }
+ ',
+ 'output' => '{
+ "description": "German",
+ "params": {},
+ "sets": []
+ }
+ ',
+ 'lang' => 'de-formal',
+ 'msg' => '"de-formal" falls back to "de"'
+ ),
+ array(
+ 'input' => '{
+ "params": {
+ "foo": {
+ "label": {
+ "fr": "French",
+ "en": "English"
+ }
+ }
+ }
+ }
+ ',
+ 'output' => '{
+ "description": null,
+ "params": {
+ "foo": {
+ "label": "French",
+ "required": false,
+ "description": null,
+ "deprecated": false,
+ "aliases": [],
+ "default": "",
+ "type": "unknown"
+ }
+ },
+ "sets": []
+ }
+ ',
+ 'lang' => 'fr',
+ 'msg' => 'Simple parameter label'
+ ),
+ array(
+ 'input' => '{
+ "params": {
+ "foo": {
+ "label": {
+ "es": "Spanish",
+ "de": "German"
+ }
+ }
+ }
+ }
+ ',
+ 'output' => '{
+ "description": null,
+ "params": {
+ "foo": {
+ "label": null,
+ "required": false,
+ "description": null,
+ "deprecated": false,
+ "aliases": [],
+ "default": "",
+ "type": "unknown"
+ }
+ },
+ "sets": []
+ }
+ ',
+ 'lang' => 'fr',
+ 'msg' => 'Parameter label is optional, use null
if no matching fallback'
+ ),
+ array(
+ 'input' => '{
+ "params": {},
+ "sets": [
+ {
+ "label": {
+ "es": "Spanish",
+ "de": "German"
+ },
+ "params": []
+ }
+ ]
+ }
+ ',
+ 'output' => '{
+ "description": null,
+ "params": {},
+ "sets": [
+ {
+ "label": "Spanish",
+ "params": []
+ }
+ ]
+ }
+ ',
+ 'lang' => 'fr',
+ 'msg' => 'Set label is not optional, choose
first available key as final fallback'
+ ),
+ );
+ $calls = array();
+ foreach ( $cases as $case ) {
+ $calls[] = array( $case );
+ }
+ return $calls;
+ }
+
+ /**
+ * @dataProvider provideGetDataInLanguage
+ */
+ public function testGetDataInLanguage( Array $case ) {
+
+ // Change content-language to be non-English so we can
distinguish between the
+ // last 'en' fallback and the content language in our tests
+ $this->setMwGlobals( array(
+ 'wgLanguageCode' => 'nl',
+ 'wgContLang' => Language::factory( 'nl' ),
+ ) );
+
+ if ( !isset( $case['msg'] ) ) {
+ $case['msg'] = is_string( $case['status'] ) ?
$case['status'] : 'TemplateData assertion';
+ }
+
+ $t = TemplateDataBlob::newFromJSON( $case['input'] );
+ $status = $t->getStatus();
+
+ $this->assertTrue( $status->isGood(), 'Status is good: ' .
$case['msg'] );
+
+ $actual = $t->getDataInLanguage( $case['lang'] );
+ $this->assertJsonStringEqualsJsonString(
+ $case['output'],
+ json_encode( $actual ),
+ $case['msg']
+ );
+ }
}
--
To view, visit https://gerrit.wikimedia.org/r/87724
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: If8317ed6522a05d5a48a210ff43c97277b950a97
Gerrit-PatchSet: 9
Gerrit-Project: mediawiki/extensions/TemplateData
Gerrit-Branch: master
Gerrit-Owner: Krinkle <[email protected]>
Gerrit-Reviewer: Bartosz DziewoĆski <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits