src/Validation/Validators/UnicodePluralValidator.php
<?php
declare( strict_types = 1 );
namespace MediaWiki\Extension\Translate\Validation\Validators;
use MediaWiki\Extension\Translate\MessageLoading\Message;
use MediaWiki\Extension\Translate\Utilities\UnicodePlural;
use MediaWiki\Extension\Translate\Validation\MessageValidator;
use MediaWiki\Extension\Translate\Validation\ValidationIssue;
use MediaWiki\Extension\Translate\Validation\ValidationIssues;
/**
* This is a very strict validator class for Unicode CLDR based plural markup.
*
* It requires all forms to be present and in correct order. Whitespace around keywords
* and values is trimmed. The keyword `other` is left out, though it is allowed in input.
* @since 2019.09
* @license GPL-2.0-or-later
*/
class UnicodePluralValidator implements MessageValidator {
public function getIssues( Message $message, string $targetLanguage ): ValidationIssues {
$issues = new ValidationIssues();
// We skip certain validations if we don't know the expected keywords
$expectedKeywords = UnicodePlural::getPluralKeywords( $targetLanguage );
$definition = $message->definition();
$translation = $message->translation();
$definitionHasPlural = UnicodePlural::hasPlural( $definition );
$translationHasPlural = UnicodePlural::hasPlural( $translation );
$presence = $this->pluralPresenceCheck(
$definitionHasPlural,
$translationHasPlural
);
// Using same check keys as MediaWikiPluralValidator
if ( $presence === 'missing' && $expectedKeywords !== null ) {
$issue = new ValidationIssue( 'plural', 'missing', 'translate-checks-unicode-plural-missing' );
$issues->add( $issue );
} elseif ( $presence === 'unsupported' ) {
$issue = new ValidationIssue( 'plural', 'unsupported', 'translate-checks-unicode-plural-unsupported' );
$issues->add( $issue );
} elseif ( $presence === 'ok' ) {
[ $msgcode, $actualKeywords ] = $this->pluralFormCheck( $translation, $expectedKeywords );
if ( $msgcode === 'invalid' ) {
$formatter = fn ( string $x ) => [ $x, '…' ];
$expectedExample = UnicodePlural::flattenList(
array_map( $formatter, $expectedKeywords ?? UnicodePlural::KEYWORDS )
);
$actualExample = UnicodePlural::flattenList(
array_map( $formatter, $actualKeywords )
);
$issue = new ValidationIssue(
'plural',
'forms',
'translate-checks-unicode-plural-invalid',
[
[ 'PLAIN', $expectedExample ],
[ 'PLAIN', $actualExample ],
]
);
$issues->add( $issue );
}
}
// else: not-applicable
return $issues;
}
private function pluralPresenceCheck(
bool $definitionHasPlural,
bool $translationHasPlural
): string {
if ( !$definitionHasPlural && $translationHasPlural ) {
return 'unsupported';
} elseif ( $definitionHasPlural && !$translationHasPlural ) {
return 'missing';
} elseif ( !$definitionHasPlural && !$translationHasPlural ) {
return 'not-applicable';
}
// Both have plural
return 'ok';
}
private function pluralFormCheck( string $text, ?array $expectedKeywords ): array {
[ , $instanceMap ] = UnicodePlural::parsePluralForms( $text );
foreach ( $instanceMap as $forms ) {
$actualKeywords = [];
foreach ( $forms as [ $keyword, ] ) {
$actualKeywords[] = $keyword;
}
if ( $expectedKeywords !== null ) {
if ( $actualKeywords !== $expectedKeywords ) {
return [ 'invalid', $actualKeywords ];
}
} elseif ( array_diff( $actualKeywords, UnicodePlural::KEYWORDS ) !== [] ) {
// We don't know the actual forms for the language, but complain about those
// that are not valid in any language
return [ 'invalid', $actualKeywords ];
}
}
return [ 'ok', [] ];
}
}