Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
94.74% |
54 / 57 |
|
33.33% |
1 / 3 |
CRAP | |
0.00% |
0 / 1 |
GettextPluralValidator | |
94.74% |
54 / 57 |
|
33.33% |
1 / 3 |
17.04 | |
0.00% |
0 / 1 |
getIssues | |
97.62% |
41 / 42 |
|
0.00% |
0 / 1 |
6 | |||
pluralPresenceCheck | |
77.78% |
7 / 9 |
|
0.00% |
0 / 1 |
8.70 | |||
pluralFormCountCheck | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 |
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\GettextPlural; |
8 | use MediaWiki\Extension\Translate\Validation\MessageValidator; |
9 | use MediaWiki\Extension\Translate\Validation\ValidationIssue; |
10 | use MediaWiki\Extension\Translate\Validation\ValidationIssues; |
11 | |
12 | /** |
13 | * @license GPL-2.0-or-later |
14 | * @since 2019.09 |
15 | */ |
16 | class GettextPluralValidator implements MessageValidator { |
17 | public function getIssues( Message $message, string $targetLanguage ): ValidationIssues { |
18 | $issues = new ValidationIssues(); |
19 | |
20 | $pluralRule = GettextPlural::getPluralRule( $targetLanguage ); |
21 | // Skip validation for languages for which we do not know the plural rule |
22 | if ( !$pluralRule ) { |
23 | return $issues; |
24 | } |
25 | |
26 | $definition = $message->definition(); |
27 | $translation = $message->translation(); |
28 | $expectedPluralCount = GettextPlural::getPluralCount( $pluralRule ); |
29 | $definitionHasPlural = GettextPlural::hasPlural( $definition ); |
30 | $translationHasPlural = GettextPlural::hasPlural( $translation ); |
31 | |
32 | $presence = $this->pluralPresenceCheck( |
33 | $definitionHasPlural, |
34 | $translationHasPlural, |
35 | $expectedPluralCount |
36 | ); |
37 | |
38 | if ( $presence === 'ok' ) { |
39 | [ $msgcode, $data ] = $this->pluralFormCountCheck( $translation, $expectedPluralCount ); |
40 | if ( $msgcode === 'invalid-count' ) { |
41 | $issue = new ValidationIssue( |
42 | 'plural', |
43 | 'forms', |
44 | 'translate-checks-gettext-plural-count', |
45 | [ |
46 | [ 'COUNT', $expectedPluralCount ], |
47 | [ 'COUNT', $data[ 'count' ] ], |
48 | ] |
49 | ); |
50 | $issues->add( $issue ); |
51 | } |
52 | } elseif ( $presence === 'missing' ) { |
53 | $issue = new ValidationIssue( |
54 | 'plural', |
55 | 'missing', |
56 | 'translate-checks-gettext-plural-missing' |
57 | ); |
58 | $issues->add( $issue ); |
59 | } elseif ( $presence === 'unsupported' ) { |
60 | $issue = new ValidationIssue( |
61 | 'plural', |
62 | 'unsupported', |
63 | 'translate-checks-gettext-plural-unsupported' |
64 | ); |
65 | $issues->add( $issue ); |
66 | } |
67 | // else not-applicable: Plural is not present in translation, but that is fine |
68 | |
69 | return $issues; |
70 | } |
71 | |
72 | private function pluralPresenceCheck( |
73 | $definitionHasPlural, |
74 | $translationHasPlural, |
75 | $expectedPluralCount |
76 | ) { |
77 | if ( !$definitionHasPlural && $translationHasPlural ) { |
78 | return 'unsupported'; |
79 | } elseif ( $definitionHasPlural && !$translationHasPlural ) { |
80 | if ( $expectedPluralCount > 1 ) { |
81 | return 'missing'; |
82 | } else { |
83 | // It's okay to omit plural completely for languages without variance |
84 | return 'not-applicable'; |
85 | } |
86 | } elseif ( !$definitionHasPlural && !$translationHasPlural ) { |
87 | return 'not-applicable'; |
88 | } |
89 | |
90 | // Both have plural |
91 | return 'ok'; |
92 | } |
93 | |
94 | private function pluralFormCountCheck( $text, $expectedPluralCount ) { |
95 | [ , $instanceMap ] = GettextPlural::parsePluralForms( $text ); |
96 | |
97 | foreach ( $instanceMap as $forms ) { |
98 | $formsCount = count( $forms ); |
99 | if ( $formsCount !== $expectedPluralCount ) { |
100 | return [ 'invalid-count', [ 'count' => $formsCount ] ]; |
101 | } |
102 | } |
103 | |
104 | return [ 'ok', [] ]; |
105 | } |
106 | } |