Nikerabbit has uploaded a new change for review. https://gerrit.wikimedia.org/r/319571
Change subject: Implement plural aware message content comparison ...................................................................... Implement plural aware message content comparison When someone uses an inline plural in a messages that is exported in a format that uses CLDR plural format without support for inline syntax, on next sync that message is shown as changed. Since the translation is in fact equal and we would like to keep the inline version in the wiki, amend the comparison function to compare some kind of canonical representation instead of plain string equality. I moved this from ExternalMessageSourceStateComparator to FFS since this only applies currently to few specific formats, and that there might be other things that need similar handling in the future. For example the fuzzy handling could also be moved to FFS classes. Change-Id: I6417ce19332ee90346a00d5b658d45b39c3a691b --- M ffs/FFS.php M ffs/JsonFFS.php M ffs/SimpleFFS.php M ffs/YamlFFS.php M resources/css/ext.translate.messagetable.less M tests/phpunit/utils/ArrayFlattenerTest.php M utils/ArrayFlattener.php M utils/ExternalMessageSourceStateComparator.php 8 files changed, 175 insertions(+), 29 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Translate refs/changes/71/319571/1 diff --git a/ffs/FFS.php b/ffs/FFS.php index 3c425e9..ccfc3c3 100644 --- a/ffs/FFS.php +++ b/ffs/FFS.php @@ -76,6 +76,16 @@ public function supportsFuzzy(); /** + * Checks whether two strings are equal. Sometimes same content might + * have multiple representations. The main case are inline plurals, + * which in some formats require expansion at export time. + * + * @return bool + * @since 2016.11 + */ + public function isContentEqual(); + + /** * Return the commonly used file extensions for these formats. * Include the dot. * @return string[] diff --git a/ffs/JsonFFS.php b/ffs/JsonFFS.php index b94a7ba..425879b 100644 --- a/ffs/JsonFFS.php +++ b/ffs/JsonFFS.php @@ -24,6 +24,14 @@ return is_array( FormatJson::decode( $data, /*as array*/true ) ); } + /** + * @param $group FileBasedMessageGroup + */ + public function __construct( FileBasedMessageGroup $group ) { + parent::__construct( $group ); + $this->flattener = $this->getFlattener(); + } + public function getFileExtensions() { return array( '.json' ); } @@ -48,12 +56,8 @@ unset( $messages['@metadata'] ); - if ( isset( $this->extra['nestingSeparator'] ) ) { - $parseCLDRPlurals = isset( $this->extra['parseCLDRPlurals'] ) ? - $this->extra['parseCLDRPlurals'] : false; - $flattener = new ArrayFlattener( $this->extra['nestingSeparator'], - $parseCLDRPlurals ); - $messages = $flattener->flatten( $messages ); + if ( $this->flattener ) { + $messages = $this->flattener->flatten( $messages ); } $messages = $this->group->getMangler()->mangle( $messages ); @@ -113,17 +117,33 @@ return ''; } - if ( isset( $this->extra['nestingSeparator'] ) ) { - $parseCLDRPlurals = isset( $this->extra['parseCLDRPlurals'] ) ? - $this->extra['parseCLDRPlurals'] : false; - $flattener = new ArrayFlattener( $this->extra['nestingSeparator'], - $parseCLDRPlurals ); - $messages = $flattener->unflatten( $messages ); + if ( $this->flattener ) { + $messages = $this->flattener->unflatten( $messages ); } return FormatJson::encode( $messages, "\t", FormatJson::ALL_OK ) . "\n"; } + protected function getFlattener() { + if ( !isset( $this->extra['nestingSeparator'] ) ) { + return null; + } + + $parseCLDRPlurals = isset( $this->extra['parseCLDRPlurals'] ) ? + $this->extra['parseCLDRPlurals'] : false; + $flattener = new ArrayFlattener( $this->extra['nestingSeparator'], $parseCLDRPlurals ); + + return $flattener; + } + + public function isContentEqual( $a, $b ) { + if ( $this->flattener ) { + return $this->flattener->compareContent( $a, $b ); + } else { + return parent::isContentEqual( $a, $b ); + } + } + public static function getExtraSchema() { $schema = array( 'root' => array( diff --git a/ffs/SimpleFFS.php b/ffs/SimpleFFS.php index 444cd2e..fefef3e 100644 --- a/ffs/SimpleFFS.php +++ b/ffs/SimpleFFS.php @@ -351,4 +351,8 @@ return $data; } + + public function isContentEqual( $a, $b ) { + return $a === $b; + } } diff --git a/ffs/YamlFFS.php b/ffs/YamlFFS.php index 7cfce2b..bf45002 100644 --- a/ffs/YamlFFS.php +++ b/ffs/YamlFFS.php @@ -171,6 +171,10 @@ return $this->flattener->unflatten( $messages ); } + public function isContentEqual( $a, $b ) { + return $this->flattener->compareContent( $a, $b ); + } + public static function getExtraSchema() { $schema = array( 'root' => array( diff --git a/resources/css/ext.translate.messagetable.less b/resources/css/ext.translate.messagetable.less index 97ae577..5810d8b 100644 --- a/resources/css/ext.translate.messagetable.less +++ b/resources/css/ext.translate.messagetable.less @@ -7,6 +7,20 @@ max-width: 800px; } +.grid.ext-translate-container .row { + min-width: 300px !important; +} + +@media screen and (max-width: 600px) { + .grid.ext-translate-container .tux-messagelist .tux-list-message { + width: 100%; + } + + .tux-list-status, .tux-list-edit { + display: none; + } +} + .tux-message { height: auto; cursor: pointer; diff --git a/tests/phpunit/utils/ArrayFlattenerTest.php b/tests/phpunit/utils/ArrayFlattenerTest.php index 84984ff..630f03d 100644 --- a/tests/phpunit/utils/ArrayFlattenerTest.php +++ b/tests/phpunit/utils/ArrayFlattenerTest.php @@ -7,7 +7,7 @@ * @license GPL-2.0+ */ -class ArrayFlattenerTest extends MediaWikiTestCase { +class ArrayFlattenerTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provideTestFlatten */ @@ -163,4 +163,87 @@ return $cases; } + /** + * @dataProvider provideMatchingValues + */ + public function testCompareTrue( $input1, $input2 ) { + $flattener = new ArrayFlattener( '.', true ); + + $this->assertTrue( + $flattener->compareContent( $input1, $input2, $flattener ) + ); + } + + /** + * @dataProvider provideNonMatchingValues + */ + public function testCompareFalse( $input1, $input2 ) { + $flattener = new ArrayFlattener( '.', true ); + + $this->assertfalse( + $flattener->compareContent( $input1, $input2, $flattener ) + ); + } + + public static function provideMatchingValues() { + $cases = array(); + + // We include some non-plural data to ensure it is processed correctly + $cases[] = array( + 'a', + 'a' + ); + + $cases[] = array( + '{{PLURAL|one=cat|cats}}', + '{{PLURAL|one=cat|cats}}', + ); + + $cases[] = array( + 'Give me {{PLURAL|one=a cat|cats}}', + '{{PLURAL|one=Give me a cat|Give me cats}}', + ); + + // Order should not matter + $cases[] = array( + '{{PLURAL|one=Give me a cat|Give me cats}}', + 'Give me {{PLURAL|one=a cat|cats}}', + ); + + // Multiple inlines + $cases[] = array( + 'Test {{PLURAL|one=one|other}} and {{PLURAL|one=one|other}} and {{PLURAL|one=one|other}}!', + '{{PLURAL|one=Test one and one and one|Test other and other and other}}!', + ); + + // Lots of keys + $cases[] = array( + 'Is {{PLURAL|zero=zero|one=one|two=two|few=few|many=many|other}}', + '{{PLURAL|zero=Is zero|one=Is one|two=Is two|few=Is few|many=Is many|Is other}}', + ); + + return $cases; + } + + public static function provideNonMatchingValues() { + $cases = array(); + + $cases[] = array( + 'a', + 'b' + ); + + $cases[] = array( + '{{PLURAL|one=cat|cats}}', + '{{PLURAL|one=dog|dogs}}', + ); + + // Different set of keys + $cases[] = array( + 'Is {{PLURAL|zero=zero|one=one|two=two|few=few|other}}', + '{{PLURAL|zero=Is zero|two=Is two|few=Is few|many=Is many|Is other}}', + ); + + return $cases; + } } diff --git a/utils/ArrayFlattener.php b/utils/ArrayFlattener.php index c65081c..7c96691 100644 --- a/utils/ArrayFlattener.php +++ b/utils/ArrayFlattener.php @@ -274,4 +274,27 @@ return $alts; } + /** + * Compares two strings for equal content, taking PLURAL expansion into account. + * + * @param string $a + * @param string $b + * @return bool Whether two strings are equal + */ + public function compareContent( $a, $b ) { + if ( !$this->parseCLDRPlurals ) { + return $a === $b; + } + + $a2 = $this->unflattenCLDRPlurals( 'prefix', $a ); + $b2 = $this->unflattenCLDRPlurals( 'prefix', $b ); + + // Fall back to regular comparison if parsing fails. + if ( $a2 === false || $b2 === false ) { + return $a === $b; + } + + // Require key-value pairs to match, but ignore order and types (all should be strings). + return $a2 == $b2; + } } diff --git a/utils/ExternalMessageSourceStateComparator.php b/utils/ExternalMessageSourceStateComparator.php index 56de79d..9efe6a0 100644 --- a/utils/ExternalMessageSourceStateComparator.php +++ b/utils/ExternalMessageSourceStateComparator.php @@ -149,6 +149,7 @@ $wikiMessage = $wiki[$key]; $wikiContent = $wikiMessage->translation(); + // @todo: Fuzzy checking can also be moved to $ffs->isContentEqual(); // If FFS doesn't support it, ignore fuzziness as difference $wikiContent = str_replace( TRANSLATE_FUZZY, '', $wikiContent ); @@ -157,7 +158,7 @@ $wikiContent = TRANSLATE_FUZZY . $wikiContent; } - if ( self::compareContent( $sourceContent, $wikiContent ) ) { + if ( $ffs->isContentEqual( $sourceContent, $wikiContent ) ) { // File and wiki stage agree, nothing to do continue; } @@ -173,8 +174,8 @@ * Hence we check that source === cache && cache !== wiki * and if so we skip this string. */ if ( - !self::compareContent( $wikiContent, $cacheContent ) && - self::compareContent( $sourceContent, $cacheContent ) + !$ffs->isContentEqual( $wikiContent, $cacheContent ) && + $ffs->isContentEqual( $sourceContent, $cacheContent ) ) { continue; } @@ -215,18 +216,5 @@ 'key' => $key, 'content' => $content, ); - } - - /** - * Compares two strings. - * @todo Ignore changes in different way inlined plurals. - * @todo Handle fuzzy state changes if FFS supports it. - * - * @param string $a - * @param string $b - * @return bool Whether two strings are equal - */ - protected static function compareContent( $a, $b ) { - return $a === $b; } } -- To view, visit https://gerrit.wikimedia.org/r/319571 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I6417ce19332ee90346a00d5b658d45b39c3a691b Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Translate Gerrit-Branch: master Gerrit-Owner: Nikerabbit <niklas.laxst...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits