Commit 179be322 authored by Michael Chaudhary's avatar Michael Chaudhary Committed by Robert Nix

Update Translate for 1.33

parent 1699aab1
{
"root": true,
"extends": "wikimedia",
"extends": [
"wikimedia",
"wikimedia/client"
],
"env": {
"browser": true
},
......
*~
.*.swp
*.kate-swp
.classpath
.idea
.metadata*
.project
.settings
*.bak
tests/pagetranslation/*.fail
composer.lock
extensions/
vendor/
node_modules/
.eslintcache
[gerrit]
host=gerrit.wikimedia.org
port=29418
project=mediawiki/extensions/Translate.git
track=1
defaultrebase=0
......@@ -9,10 +9,9 @@
<exclude name="MediaWiki.Commenting.MissingCovers.MissingCovers" />
<exclude name="MediaWiki.Files.ClassMatchesFilename.NotMatch" />
<exclude name="MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName" />
<exclude name="MediaWiki.Usage.DeprecatedGlobalVariables.Deprecated$wgContLang" />
<exclude name="MediaWiki.Usage.DeprecatedGlobalVariables.Deprecated$wgParser" />
<exclude name="MediaWiki.Usage.ForbiddenFunctions.escapeshellarg" />
<exclude name="MediaWiki.Usage.NullableType.PHP71NullableStyle" />
<exclude name="MediaWiki.WhiteSpace.SpaceBeforeSingleLineComment.NewLineComment" />
<exclude name="PSR12.Properties.ConstantVisibility.NotFound" />
</rule>
<rule ref="Generic.Files.LineLength">
<exclude-pattern>Translate\.alias\.php</exclude-pattern>
......
......@@ -3,26 +3,21 @@ module.exports = function ( grunt ) {
'use strict';
grunt.loadNpmTasks( 'grunt-eslint' );
grunt.loadNpmTasks( 'grunt-jsonlint' );
grunt.loadNpmTasks( 'grunt-banana-checker' );
grunt.loadNpmTasks( 'grunt-stylelint' );
grunt.initConfig( {
eslint: {
options: {
extensions: [ '.js', '.json' ],
cache: true,
reportUnusedDisableDirectives: true
},
all: [
'**/*.js',
'!node_modules/**',
'**/*.js{,on}',
'!extensions/**',
'!resources/js/jquery.autosize.js',
'!vendor/**'
]
},
jsonlint: {
all: [
'**/*.json',
'!node_modules/**',
'!extensions/**',
'!vendor/**'
'!{vendor,node_modules}/**'
]
},
stylelint: {
......@@ -45,6 +40,6 @@ module.exports = function ( grunt ) {
}
} );
grunt.registerTask( 'test', [ 'eslint', 'jsonlint', 'banana', 'stylelint' ] );
grunt.registerTask( 'test', [ 'eslint', 'banana', 'stylelint' ] );
grunt.registerTask( 'default', 'test' );
};
<?php
/**
* Classes for message objects TMessage and ThinMessage.
* Classes for message objects TMessage, ThinMessage and FatMessage.
*
* @file
* @author Niklas Laxström
......@@ -93,7 +93,7 @@ abstract class TMessage {
/**
* Return all tags for this message;
* @return array of strings
* @return string[]
*/
public function getTags() {
return $this->tags;
......@@ -141,20 +141,36 @@ class ThinMessage extends TMessage {
*/
protected $row;
/** @var string Stored translation. */
protected $translation;
/**
* Set the database row this message is based on.
* @param array $row Database Result Row
* @param stdClass $row Database Result Row
*/
public function setRow( $row ) {
$this->row = $row;
}
/**
* Set the current translation of this message.
* @param string $text
*/
public function setTranslation( $text ) {
$this->translation = $text;
}
public function translation() {
if ( !isset( $this->row ) ) {
return $this->infile();
}
return Revision::getRevisionText( $this->row );
if ( $this->translation === null ) {
// Should only happen with MW < 1.34. Slow if the text table hasn't been joined in!
$this->translation = Revision::getRevisionText( $this->row );
}
return $this->translation;
}
// Re-implemented
......
......@@ -191,7 +191,7 @@ class MessageChecker {
continue;
}
// If all of the aboce match, filter the check
// If all of the above match, filter the check
unset( $warningsArray[$mkey][$wkey] );
}
}
......@@ -202,9 +202,9 @@ class MessageChecker {
/**
* Matches check information against blacklist pattern.
* @param string $pattern
* @param string|array $pattern
* @param string $value The actual value in the warnings produces by the check
* @return bool True of the pattern matches the value.
* @return bool True if the pattern matches the value.
*/
protected function match( $pattern, $value ) {
if ( $pattern === '#' ) {
......@@ -239,6 +239,8 @@ class MessageChecker {
$message[] = $lang->formatNum( $value );
} elseif ( $type === 'PARAMS' ) {
$message[] = $lang->commaList( $value );
} elseif ( $type === 'PLAIN' ) {
$message[] = wfEscapeWikiText( $value );
} else {
throw new MWException( "Unknown type $type" );
}
......@@ -251,7 +253,7 @@ class MessageChecker {
}
/**
* Compares two arrays return items that don't exist in the latter.
* Compares two arrays and return items that don't exist in the latter.
* @param array $defs
* @param array $trans
* @return array Items of $defs that are not in $trans.
......@@ -287,7 +289,7 @@ class MessageChecker {
* @param array &$warnings Array where warnings are appended to.
*/
protected function rubyVariableCheck( $messages, $code, array &$warnings ) {
$this->parameterCheck( $messages, $code, $warnings, '/%{[a-zA-Z_]+}/' );
$this->parameterCheck( $messages, $code, $warnings, '/%\{[a-z_]+}/i' );
}
/**
......@@ -397,7 +399,7 @@ class MessageChecker {
];
}
// Check for unknown variables in the translatio
// Check for unknown variables in the translation
$subcheck = 'unknown';
$params = self::compareArrays( $transVars[0], $defVars[0] );
......
......@@ -8,6 +8,9 @@
* @license GPL-2.0-or-later
*/
use MediaWiki\MediaWikiServices;
use MediaWiki\Storage\RevisionRecord;
use MediaWiki\Storage\SlotRecord;
use Wikimedia\Rdbms\IResultWrapper;
/**
......@@ -139,15 +142,15 @@ class MessageCollection implements ArrayAccess, Iterator, Countable {
/**
* Returns list of available message keys. This is affected by filtering.
* @return array List of database keys indexed by display keys.
* @return array List of database keys indexed by display keys (TitleValue).
*/
public function keys() {
return $this->keys;
}
/**
* Returns list of titles of messages that are used in this collection after filtering.
* @return Title[]
* Returns list of TitleValues of messages that are used in this collection after filtering.
* @return TitleValue[]
* @since 2011-12-28
*/
public function getTitles() {
......@@ -523,22 +526,50 @@ class MessageCollection implements ArrayAccess, Iterator, Countable {
$origKeys = $keys;
}
foreach ( $this->dbData as $row ) {
$mkey = $this->rowToKey( $row );
if ( !isset( $this->infile[$mkey] ) ) {
continue;
$revStore = MediaWikiServices::getInstance()->getRevisionStore();
if ( is_callable( [ $revStore, 'newRevisionsFromBatch' ] ) ) {
$infileRows = [];
foreach ( $this->dbData as $row ) {
$mkey = $this->rowToKey( $row );
if ( isset( $this->infile[$mkey] ) ) {
$infileRows[] = $row;
}
}
$text = Revision::getRevisionText( $row );
if ( $this->infile[$mkey] === $text ) {
// Remove unchanged messages from the list
unset( $keys[$mkey] );
$revisions = $revStore->newRevisionsFromBatch( $infileRows, [
'slots' => [ SlotRecord::MAIN ],
'content' => true
] )->getValue();
foreach ( $infileRows as $row ) {
/** @var RevisionRecord|null $rev */
$rev = $revisions[$row->rev_id];
if ( $rev && $rev->getContent( SlotRecord::MAIN ) ) {
$mkey = $this->rowToKey( $row );
if ( $this->infile[$mkey] === $rev->getContent( SlotRecord::MAIN )->getText() ) {
// Remove unchanged messages from the list
unset( $keys[$mkey] );
}
}
}
} else {
// Pre 1.34 compatibility
foreach ( $this->dbData as $row ) {
$mkey = $this->rowToKey( $row );
if ( !isset( $this->infile[$mkey] ) ) {
continue;
}
$text = Revision::getRevisionText( $row );
if ( $this->infile[$mkey] === $text ) {
// Remove unchanged messages from the list
unset( $keys[$mkey] );
}
}
}
// Remove the messages which have not changed from the list
// Remove the messages which have changed from the original list
if ( $condition === false ) {
$keys = $this->filterOnCondition( $keys, $origKeys, false );
$keys = $this->filterOnCondition( $origKeys, $keys );
}
return $keys;
......@@ -603,18 +634,13 @@ class MessageCollection implements ArrayAccess, Iterator, Countable {
*/
protected function fixKeys() {
$newkeys = [];
// array( namespace, pagename )
$pages = $this->definitions->getPages();
$code = $this->code;
foreach ( $pages as $key => $page ) {
list( $namespace, $pagename ) = $page;
$title = Title::makeTitleSafe( $namespace, "$pagename/$code" );
if ( !$title ) {
wfWarn( "Invalid title $namespace:$pagename/$code" );
continue;
}
$newkeys[$key] = $title;
$pages = $this->definitions->getPages();
foreach ( $pages as $key => $baseTitle ) {
$newkeys[$key] = new TitleValue(
$baseTitle->getNamespace(),
$baseTitle->getDBkey() . '/' . $this->code
);
}
return $newkeys;
......@@ -694,26 +720,12 @@ class MessageCollection implements ArrayAccess, Iterator, Countable {
}
$dbr = TranslateUtils::getSafeReadDB();
if ( is_callable( Revision::class, 'getQueryInfo' ) ) {
$revQuery = Revision::getQueryInfo( [ 'page', 'text' ] );
$revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
if ( is_callable( [ $revisionStore, 'newRevisionsFromBatch' ] ) ) {
$revQuery = $revisionStore->getQueryInfo( [ 'page' ] );
} else {
$revQuery = [
'tables' => [ 'page', 'revision', 'text' ],
'fields' => [
'page_namespace',
'page_title',
'page_latest',
'rev_user',
'rev_user_text',
'old_flags',
'old_text'
],
'joins' => [
'revision' => [ 'JOIN', 'page_latest = rev_id' ],
'text' => [ 'JOIN', 'old_id = rev_text_id' ],
],
];
// Pre MW 1.34 compatibility
$revQuery = $revisionStore->getQueryInfo( [ 'page', 'text' ] );
}
$conds = [ 'page_latest = rev_id' ];
$conds[] = $this->getTitleConds( $dbr );
......@@ -721,7 +733,6 @@ class MessageCollection implements ArrayAccess, Iterator, Countable {
$res = $dbr->select(
$revQuery['tables'], $revQuery['fields'], $conds, __METHOD__, [], $revQuery['joins']
);
$this->dbData = $res;
}
......@@ -784,7 +795,7 @@ class MessageCollection implements ArrayAccess, Iterator, Countable {
$map = [];
/**
* @var Title $title
* @var TitleValue $title
*/
foreach ( $this->keys as $mkey => $title ) {
$map[$title->getNamespace()][$title->getDBkey()] = $mkey;
......@@ -805,19 +816,47 @@ class MessageCollection implements ArrayAccess, Iterator, Countable {
$messages = [];
$definitions = $this->definitions->getDefinitions();
foreach ( array_keys( $this->keys ) as $mkey ) {
$messages[$mkey] = new ThinMessage( $mkey, $definitions[$mkey] );
}
$revStore = MediaWikiServices::getInstance()->getRevisionStore();
$queryFlags = TranslateUtils::shouldReadFromMaster() ? $revStore::READ_LATEST : 0;
if ( is_callable( [ $revStore, 'getContentBlobsForBatch' ] ) ) {
foreach ( array_keys( $this->keys ) as $mkey ) {
$messages[$mkey] = new ThinMessage( $mkey, $definitions[$mkey] );
}
if ( $this->dbData !== null ) {
$slotRows = $revStore->getContentBlobsForBatch(
$this->dbData, [ SlotRecord::MAIN ], $queryFlags
)->getValue();
foreach ( $this->dbData as $row ) {
$mkey = $this->rowToKey( $row );
if ( !isset( $messages[$mkey] ) ) {
continue;
}
$messages[$mkey]->setRow( $row );
$messages[$mkey]->setProperty( 'revision', $row->page_latest );
if ( isset( $slotRows[$row->rev_id][SlotRecord::MAIN] ) ) {
$slot = $slotRows[$row->rev_id][SlotRecord::MAIN];
$messages[$mkey]->setTranslation( $slot->blob_data );
}
}
}
} else {
// Pre MW 1.34 compatibility
foreach ( array_keys( $this->keys ) as $mkey ) {
$messages[$mkey] = new ThinMessage( $mkey, $definitions[$mkey] );
}
// Copy rows if any.
if ( $this->dbData !== null ) {
foreach ( $this->dbData as $row ) {
$mkey = $this->rowToKey( $row );
if ( !isset( $messages[$mkey] ) ) {
continue;
// Copy rows if any.
if ( $this->dbData !== null ) {
foreach ( $this->dbData as $row ) {
$mkey = $this->rowToKey( $row );
if ( !isset( $messages[$mkey] ) ) {
continue;
}
$messages[$mkey]->setRow( $row );
$messages[$mkey]->setProperty( 'revision', $row->page_latest );
}
$messages[$mkey]->setRow( $row );
$messages[$mkey]->setProperty( 'revision', $row->page_latest );
}
}
......@@ -971,6 +1010,7 @@ class MessageCollection implements ArrayAccess, Iterator, Countable {
class MessageDefinitions {
protected $namespace;
protected $messages;
protected $pages;
public function __construct( array $messages, $namespace = false ) {
$this->namespace = $namespace;
......@@ -982,20 +1022,34 @@ class MessageDefinitions {
}
/**
* @return Array of Array( namespace, pagename )
* @return Title[] List of title indexed by message key.
*/
public function getPages() {
$namespace = $this->namespace;
if ( $this->pages !== null ) {
return $this->pages;
}
$pages = [];
foreach ( array_keys( $this->messages ) as $key ) {
if ( $namespace === false ) {
// pages are in format ex. "8:jan"
$pages[$key] = explode( ':', $key, 2 );
list( $tns, $tkey ) = explode( ':', $key, 2 );
$title = Title::makeTitleSafe( $tns, $tkey );
} else {
$pages[$key] = [ $namespace, $key ];
$title = Title::makeTitleSafe( $namespace, $key );
}
if ( !$title ) {
wfWarn( "Invalid title ($namespace:)$key" );
continue;
}
$pages[$key] = $title;
}
return $pages;
$this->pages = $pages;
return $this->pages;
}
}
......@@ -97,7 +97,7 @@ class MessageGroupConfigurationParser {
foreach ( $groups as $i => $group ) {
$groups[$i] = self::mergeTemplate( $template, $group );
// Little hack to allow aggregate groups to be defined in same file with other groups.
if ( $groups[$i]['BASIC']['class'] === 'AggregateMessageGroup' ) {
if ( $groups[$i]['BASIC']['class'] === AggregateMessageGroup::class ) {
unset( $groups[$i]['FILES'] );
}
}
......@@ -119,14 +119,14 @@ class MessageGroupConfigurationParser {
public function validate( array $config ) {
$schema = $this->baseSchema;
foreach ( $config as $sectionName => $section ) {
foreach ( $config as $section ) {
if ( !isset( $section['class'] ) ) {
continue;
}
$class = $section['class'];
// There is no sane way to check whether *class* implements interface in PHP
if ( !method_exists( $class, 'getExtraSchema' ) ) {
if ( !is_callable( [ $class, 'getExtraSchema' ] ) ) {
continue;
}
......
......@@ -8,7 +8,7 @@
* @copyright Copyright © 2008-2013, Niklas Laxström, Siebrand Mazeland
* @license GPL-2.0-or-later
*/