Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x

TranslatorTest.php

Namespace

Symfony\Contracts\Translation\Test

File

vendor/symfony/translation-contracts/Test/TranslatorTest.php

View source
<?php


/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Symfony\Contracts\Translation\Test;

use PHPUnit\Framework\TestCase;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorTrait;

/**
 * Test should cover all languages mentioned on http://translate.sourceforge.net/wiki/l10n/pluralforms
 * and Plural forms mentioned on http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms.
 *
 * See also https://developer.mozilla.org/en/Localization_and_Plurals which mentions 15 rules having a maximum of 6 forms.
 * The mozilla code is also interesting to check for.
 *
 * As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199
 *
 * The goal to cover all languages is to far fetched so this test case is smaller.
 *
 * @author Clemens Tolboom clemens@build2be.nl
 */
class TranslatorTest extends TestCase {
    private string $defaultLocale;
    protected function setUp() : void {
        $this->defaultLocale = \Locale::getDefault();
        \Locale::setDefault('en');
    }
    protected function tearDown() : void {
        \Locale::setDefault($this->defaultLocale);
    }
    public function getTranslator() : TranslatorInterface {
        return new class  implements TranslatorInterface {
            use TranslatorTrait;

};
    }
    
    /**
     * @dataProvider getTransTests
     */
    public function testTrans($expected, $id, $parameters) {
        $translator = $this->getTranslator();
        $this->assertEquals($expected, $translator->trans($id, $parameters));
    }
    
    /**
     * @dataProvider getTransChoiceTests
     */
    public function testTransChoiceWithExplicitLocale($expected, $id, $number) {
        $translator = $this->getTranslator();
        $this->assertEquals($expected, $translator->trans($id, [
            '%count%' => $number,
        ]));
    }
    
    /**
     * @requires extension intl
     *
     * @dataProvider getTransChoiceTests
     */
    public function testTransChoiceWithDefaultLocale($expected, $id, $number) {
        $translator = $this->getTranslator();
        $this->assertEquals($expected, $translator->trans($id, [
            '%count%' => $number,
        ]));
    }
    
    /**
     * @dataProvider getTransChoiceTests
     */
    public function testTransChoiceWithEnUsPosix($expected, $id, $number) {
        $translator = $this->getTranslator();
        $translator->setLocale('en_US_POSIX');
        $this->assertEquals($expected, $translator->trans($id, [
            '%count%' => $number,
        ]));
    }
    public function testGetSetLocale() {
        $translator = $this->getTranslator();
        $this->assertEquals('en', $translator->getLocale());
    }
    
    /**
     * @requires extension intl
     */
    public function testGetLocaleReturnsDefaultLocaleIfNotSet() {
        $translator = $this->getTranslator();
        \Locale::setDefault('pt_BR');
        $this->assertEquals('pt_BR', $translator->getLocale());
        \Locale::setDefault('en');
        $this->assertEquals('en', $translator->getLocale());
    }
    public static function getTransTests() {
        return [
            [
                'Symfony is great!',
                'Symfony is great!',
                [],
            ],
            [
                'Symfony is awesome!',
                'Symfony is %what%!',
                [
                    '%what%' => 'awesome',
                ],
            ],
        ];
    }
    public static function getTransChoiceTests() {
        return [
            [
                'There are no apples',
                '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples',
                0,
            ],
            [
                'There is one apple',
                '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples',
                1,
            ],
            [
                'There are 10 apples',
                '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples',
                10,
            ],
            [
                'There are 0 apples',
                'There is 1 apple|There are %count% apples',
                0,
            ],
            [
                'There is 1 apple',
                'There is 1 apple|There are %count% apples',
                1,
            ],
            [
                'There are 10 apples',
                'There is 1 apple|There are %count% apples',
                10,
            ],
            // custom validation messages may be coded with a fixed value
[
                'There are 2 apples',
                'There are 2 apples',
                2,
            ],
        ];
    }
    
    /**
     * @dataProvider getInterval
     */
    public function testInterval($expected, $number, $interval) {
        $translator = $this->getTranslator();
        $this->assertEquals($expected, $translator->trans($interval . ' foo|[1,Inf[ bar', [
            '%count%' => $number,
        ]));
    }
    public static function getInterval() {
        return [
            [
                'foo',
                3,
                '{1,2, 3 ,4}',
            ],
            [
                'bar',
                10,
                '{1,2, 3 ,4}',
            ],
            [
                'bar',
                3,
                '[1,2]',
            ],
            [
                'foo',
                1,
                '[1,2]',
            ],
            [
                'foo',
                2,
                '[1,2]',
            ],
            [
                'bar',
                1,
                ']1,2[',
            ],
            [
                'bar',
                2,
                ']1,2[',
            ],
            [
                'foo',
                log(0),
                '[-Inf,2[',
            ],
            [
                'foo',
                -log(0),
                '[-2,+Inf]',
            ],
        ];
    }
    
    /**
     * @dataProvider getChooseTests
     */
    public function testChoose($expected, $id, $number, $locale = null) {
        $translator = $this->getTranslator();
        $this->assertEquals($expected, $translator->trans($id, [
            '%count%' => $number,
        ], null, $locale));
    }
    public function testReturnMessageIfExactlyOneStandardRuleIsGiven() {
        $translator = $this->getTranslator();
        $this->assertEquals('There are two apples', $translator->trans('There are two apples', [
            '%count%' => 2,
        ]));
    }
    
    /**
     * @dataProvider getNonMatchingMessages
     */
    public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number) {
        $translator = $this->getTranslator();
        $this->expectException(\InvalidArgumentException::class);
        $translator->trans($id, [
            '%count%' => $number,
        ]);
    }
    public static function getNonMatchingMessages() {
        return [
            [
                '{0} There are no apples|{1} There is one apple',
                2,
            ],
            [
                '{1} There is one apple|]1,Inf] There are %count% apples',
                0,
            ],
            [
                '{1} There is one apple|]2,Inf] There are %count% apples',
                2,
            ],
            [
                '{0} There are no apples|There is one apple',
                2,
            ],
        ];
    }
    public static function getChooseTests() {
        return [
            [
                'There are no apples',
                '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples',
                0,
            ],
            [
                'There are no apples',
                '{0}     There are no apples|{1} There is one apple|]1,Inf] There are %count% apples',
                0,
            ],
            [
                'There are no apples',
                '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples',
                0,
            ],
            [
                'There is one apple',
                '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples',
                1,
            ],
            [
                'There are 10 apples',
                '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples',
                10,
            ],
            [
                'There are 10 apples',
                '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples',
                10,
            ],
            [
                'There are 10 apples',
                '{0} There are no apples|{1} There is one apple|]1,Inf]     There are %count% apples',
                10,
            ],
            [
                'There are 0 apples',
                'There is one apple|There are %count% apples',
                0,
            ],
            [
                'There is one apple',
                'There is one apple|There are %count% apples',
                1,
            ],
            [
                'There are 10 apples',
                'There is one apple|There are %count% apples',
                10,
            ],
            [
                'There are 0 apples',
                'one: There is one apple|more: There are %count% apples',
                0,
            ],
            [
                'There is one apple',
                'one: There is one apple|more: There are %count% apples',
                1,
            ],
            [
                'There are 10 apples',
                'one: There is one apple|more: There are %count% apples',
                10,
            ],
            [
                'There are no apples',
                '{0} There are no apples|one: There is one apple|more: There are %count% apples',
                0,
            ],
            [
                'There is one apple',
                '{0} There are no apples|one: There is one apple|more: There are %count% apples',
                1,
            ],
            [
                'There are 10 apples',
                '{0} There are no apples|one: There is one apple|more: There are %count% apples',
                10,
            ],
            [
                '',
                '{0}|{1} There is one apple|]1,Inf] There are %count% apples',
                0,
            ],
            [
                '',
                '{0} There are no apples|{1}|]1,Inf] There are %count% apples',
                1,
            ],
            // Indexed only tests which are Gettext PoFile* compatible strings.
[
                'There are 0 apples',
                'There is one apple|There are %count% apples',
                0,
            ],
            [
                'There is one apple',
                'There is one apple|There are %count% apples',
                1,
            ],
            [
                'There are 2 apples',
                'There is one apple|There are %count% apples',
                2,
            ],
            // Tests for float numbers
[
                'There is almost one apple',
                '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple',
                0.7,
            ],
            [
                'There is one apple',
                '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple',
                1,
            ],
            [
                'There is more than one apple',
                '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple',
                1.7,
            ],
            [
                'There are no apples',
                '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple',
                0,
            ],
            [
                'There are no apples',
                '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple',
                0.0,
            ],
            [
                'There are no apples',
                '{0.0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple',
                0,
            ],
            // Test texts with new-lines
            // with double-quotes and \n in id & double-quotes and actual newlines in text
[
                "This is a text with a\n            new-line in it. Selector = 0.",
                '{0}This is a text with a
            new-line in it. Selector = 0.|{1}This is a text with a
            new-line in it. Selector = 1.|[1,Inf]This is a text with a
            new-line in it. Selector > 1.',
                0,
            ],
            // with double-quotes and \n in id and single-quotes and actual newlines in text
[
                "This is a text with a\n            new-line in it. Selector = 1.",
                '{0}This is a text with a
            new-line in it. Selector = 0.|{1}This is a text with a
            new-line in it. Selector = 1.|[1,Inf]This is a text with a
            new-line in it. Selector > 1.',
                1,
            ],
            [
                "This is a text with a\n            new-line in it. Selector > 1.",
                '{0}This is a text with a
            new-line in it. Selector = 0.|{1}This is a text with a
            new-line in it. Selector = 1.|[1,Inf]This is a text with a
            new-line in it. Selector > 1.',
                5,
            ],
            // with double-quotes and id split across lines
[
                'This is a text with a
            new-line in it. Selector = 1.',
                '{0}This is a text with a
            new-line in it. Selector = 0.|{1}This is a text with a
            new-line in it. Selector = 1.|[1,Inf]This is a text with a
            new-line in it. Selector > 1.',
                1,
            ],
            // with single-quotes and id split across lines
[
                'This is a text with a
            new-line in it. Selector > 1.',
                '{0}This is a text with a
            new-line in it. Selector = 0.|{1}This is a text with a
            new-line in it. Selector = 1.|[1,Inf]This is a text with a
            new-line in it. Selector > 1.',
                5,
            ],
            // with single-quotes and \n in text
[
                'This is a text with a\\nnew-line in it. Selector = 0.',
                '{0}This is a text with a\\nnew-line in it. Selector = 0.|{1}This is a text with a\\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\\nnew-line in it. Selector > 1.',
                0,
            ],
            // with double-quotes and id split across lines
[
                "This is a text with a\nnew-line in it. Selector = 1.",
                "{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.",
                1,
            ],
            // escape pipe
[
                'This is a text with | in it. Selector = 0.',
                '{0}This is a text with || in it. Selector = 0.|{1}This is a text with || in it. Selector = 1.',
                0,
            ],
            // Empty plural set (2 plural forms) from a .PO file
[
                '',
                '|',
                1,
            ],
            // Empty plural set (3 plural forms) from a .PO file
[
                '',
                '||',
                1,
            ],
            // Floating values
[
                '1.5 liters',
                '%count% liter|%count% liters',
                1.5,
            ],
            [
                '1.5 litre',
                '%count% litre|%count% litres',
                1.5,
                'fr',
            ],
            // Negative values
[
                '-1 degree',
                '%count% degree|%count% degrees',
                -1,
            ],
            [
                '-1 degré',
                '%count% degré|%count% degrés',
                -1,
            ],
            [
                '-1.5 degrees',
                '%count% degree|%count% degrees',
                -1.5,
            ],
            [
                '-1.5 degré',
                '%count% degré|%count% degrés',
                -1.5,
                'fr',
            ],
            [
                '-2 degrees',
                '%count% degree|%count% degrees',
                -2,
            ],
            [
                '-2 degrés',
                '%count% degré|%count% degrés',
                -2,
            ],
        ];
    }
    
    /**
     * @dataProvider failingLangcodes
     */
    public function testFailedLangcodes($nplural, $langCodes) {
        $matrix = $this->generateTestData($langCodes);
        $this->validateMatrix($nplural, $matrix, false);
    }
    
    /**
     * @dataProvider successLangcodes
     */
    public function testLangcodes($nplural, $langCodes) {
        $matrix = $this->generateTestData($langCodes);
        $this->validateMatrix($nplural, $matrix);
    }
    
    /**
     * This array should contain all currently known langcodes.
     *
     * As it is impossible to have this ever complete we should try as hard as possible to have it almost complete.
     */
    public static function successLangcodes() : array {
        return [
            [
                '1',
                [
                    'ay',
                    'bo',
                    'cgg',
                    'dz',
                    'id',
                    'ja',
                    'jbo',
                    'ka',
                    'kk',
                    'km',
                    'ko',
                    'ky',
                ],
            ],
            [
                '2',
                [
                    'nl',
                    'fr',
                    'en',
                    'de',
                    'de_GE',
                    'hy',
                    'hy_AM',
                    'en_US_POSIX',
                ],
            ],
            [
                '3',
                [
                    'be',
                    'bs',
                    'cs',
                    'hr',
                ],
            ],
            [
                '4',
                [
                    'cy',
                    'mt',
                    'sl',
                ],
            ],
            [
                '6',
                [
                    'ar',
                ],
            ],
        ];
    }
    
    /**
     * This array should be at least empty within the near future.
     *
     * This both depends on a complete list trying to add above as understanding
     * the plural rules of the current failing languages.
     *
     * @return array with nplural together with langcodes
     */
    public static function failingLangcodes() : array {
        return [
            [
                '1',
                [
                    'fa',
                ],
            ],
            [
                '2',
                [
                    'jbo',
                ],
            ],
            [
                '3',
                [
                    'cbs',
                ],
            ],
            [
                '4',
                [
                    'gd',
                    'kw',
                ],
            ],
            [
                '5',
                [
                    'ga',
                ],
            ],
        ];
    }
    
    /**
     * We validate only on the plural coverage. Thus the real rules is not tested.
     *
     * @param string $nplural Plural expected
     * @param array  $matrix  Containing langcodes and their plural index values
     */
    protected function validateMatrix(string $nplural, array $matrix, bool $expectSuccess = true) {
        foreach ($matrix as $langCode => $data) {
            $indexes = array_flip($data);
            if ($expectSuccess) {
                $this->assertCount($nplural, $indexes, "Langcode '{$langCode}' has '{$nplural}' plural forms.");
            }
            else {
                $this->assertNotEquals((int) $nplural, \count($indexes), "Langcode '{$langCode}' has '{$nplural}' plural forms.");
            }
        }
    }
    protected function generateTestData($langCodes) {
        $translator = new class  {
            use TranslatorTrait {
                getPluralizationRule as public;
            }

};
        $matrix = [];
        foreach ($langCodes as $langCode) {
            for ($count = 0; $count < 200; ++$count) {
                $plural = $translator->getPluralizationRule($count, $langCode);
                $matrix[$langCode][$count] = $plural;
            }
        }
        return $matrix;
    }

}

Classes

Title Deprecated Summary
TranslatorTest Test should cover all languages mentioned on http://translate.sourceforge.net/wiki/l10n/pluralforms and Plural forms mentioned on http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms.
RSS feed
Powered by Drupal