Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.18% |
54 / 55 |
|
66.67% |
2 / 3 |
CRAP | |
0.00% |
0 / 1 |
UnicodePluralValidator | |
98.18% |
54 / 55 |
|
66.67% |
2 / 3 |
19 | |
0.00% |
0 / 1 |
getIssues | |
100.00% |
37 / 37 |
|
100.00% |
1 / 1 |
6 | |||
pluralPresenceCheck | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
7.14 | |||
pluralFormCheck | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\Validation\Validators; |
5 | |
6 | use MediaWiki\Extension\Translate\MessageLoading\Message; |
7 | use MediaWiki\Extension\Translate\Utilities\UnicodePlural; |
8 | use MediaWiki\Extension\Translate\Validation\MessageValidator; |
9 | use MediaWiki\Extension\Translate\Validation\ValidationIssue; |
10 | use MediaWiki\Extension\Translate\Validation\ValidationIssues; |
11 | |
12 | /** |
13 | * This is a very strict validator class for Unicode CLDR based plural markup. |
14 | * |
15 | * It requires all forms to be present and in correct order. Whitespace around keywords |
16 | * and values is trimmed. The keyword `other` is left out, though it is allowed in input. |
17 | * @since 2019.09 |
18 | * @license GPL-2.0-or-later |
19 | */ |
20 | class UnicodePluralValidator implements MessageValidator { |
21 | public function getIssues( Message $message, string $targetLanguage ): ValidationIssues { |
22 | $issues = new ValidationIssues(); |
23 | |
24 | // We skip certain validations if we don't know the expected keywords |
25 | $expectedKeywords = UnicodePlural::getPluralKeywords( $targetLanguage ); |
26 | |
27 | $definition = $message->definition(); |
28 | $translation = $message->translation(); |
29 | $definitionHasPlural = UnicodePlural::hasPlural( $definition ); |
30 | $translationHasPlural = UnicodePlural::hasPlural( $translation ); |
31 | |
32 | $presence = $this->pluralPresenceCheck( |
33 | $definitionHasPlural, |
34 | $translationHasPlural |
35 | ); |
36 | |
37 | // Using same check keys as MediaWikiPluralValidator |
38 | if ( $presence === 'missing' && $expectedKeywords !== null ) { |
39 | $issue = new ValidationIssue( 'plural', 'missing', 'translate-checks-unicode-plural-missing' ); |
40 | $issues->add( $issue ); |
41 | } elseif ( $presence === 'unsupported' ) { |
42 | $issue = new ValidationIssue( 'plural', 'unsupported', 'translate-checks-unicode-plural-unsupported' ); |
43 | $issues->add( $issue ); |
44 | } elseif ( $presence === 'ok' ) { |
45 | [ $msgcode, $actualKeywords ] = $this->pluralFormCheck( $translation, $expectedKeywords ); |
46 | if ( $msgcode === 'invalid' ) { |
47 | $formatter = fn ( string $x ) => [ $x, '…' ]; |
48 | $expectedExample = UnicodePlural::flattenList( |
49 | array_map( $formatter, $expectedKeywords ?? UnicodePlural::KEYWORDS ) |
50 | ); |
51 | $actualExample = UnicodePlural::flattenList( |
52 | array_map( $formatter, $actualKeywords ) |
53 | ); |
54 | |
55 | $issue = new ValidationIssue( |
56 | 'plural', |
57 | 'forms', |
58 | 'translate-checks-unicode-plural-invalid', |
59 | [ |
60 | [ 'PLAIN', $expectedExample ], |
61 | [ 'PLAIN', $actualExample ], |
62 | ] |
63 | ); |
64 | $issues->add( $issue ); |
65 | } |
66 | } |
67 | // else: not-applicable |
68 | |
69 | return $issues; |
70 | } |
71 | |
72 | private function pluralPresenceCheck( |
73 | bool $definitionHasPlural, |
74 | bool $translationHasPlural |
75 | ): string { |
76 | if ( !$definitionHasPlural && $translationHasPlural ) { |
77 | return 'unsupported'; |
78 | } elseif ( $definitionHasPlural && !$translationHasPlural ) { |
79 | return 'missing'; |
80 | } elseif ( !$definitionHasPlural && !$translationHasPlural ) { |
81 | return 'not-applicable'; |
82 | } |
83 | |
84 | // Both have plural |
85 | return 'ok'; |
86 | } |
87 | |
88 | private function pluralFormCheck( string $text, ?array $expectedKeywords ): array { |
89 | [ , $instanceMap ] = UnicodePlural::parsePluralForms( $text ); |
90 | |
91 | foreach ( $instanceMap as $forms ) { |
92 | $actualKeywords = []; |
93 | foreach ( $forms as [ $keyword, ] ) { |
94 | $actualKeywords[] = $keyword; |
95 | } |
96 | |
97 | if ( $expectedKeywords !== null ) { |
98 | if ( $actualKeywords !== $expectedKeywords ) { |
99 | return [ 'invalid', $actualKeywords ]; |
100 | } |
101 | } elseif ( array_diff( $actualKeywords, UnicodePlural::KEYWORDS ) !== [] ) { |
102 | // We don't know the actual forms for the language, but complain about those |
103 | // that are not valid in any language |
104 | return [ 'invalid', $actualKeywords ]; |
105 | } |
106 | } |
107 | |
108 | return [ 'ok', [] ]; |
109 | } |
110 | } |