Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.74% covered (success)
94.74%
54 / 57
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
GettextPluralValidator
94.74% covered (success)
94.74%
54 / 57
33.33% covered (danger)
33.33%
1 / 3
17.04
0.00% covered (danger)
0.00%
0 / 1
 getIssues
97.62% covered (success)
97.62%
41 / 42
0.00% covered (danger)
0.00%
0 / 1
6
 pluralPresenceCheck
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
8.70
 pluralFormCountCheck
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\Validation\Validators;
5
6use MediaWiki\Extension\Translate\MessageLoading\Message;
7use MediaWiki\Extension\Translate\Utilities\GettextPlural;
8use MediaWiki\Extension\Translate\Validation\MessageValidator;
9use MediaWiki\Extension\Translate\Validation\ValidationIssue;
10use MediaWiki\Extension\Translate\Validation\ValidationIssues;
11
12/**
13 * @license GPL-2.0-or-later
14 * @since 2019.09
15 */
16class 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}