diff --git a/.gitignore b/.gitignore index 170c7e2..bbfceca 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ composer.lock .php_cs.cache _meta .serenata +.vscode diff --git a/.travis.yml b/.travis.yml index a6d7fef..a51505b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ -sudo: false dist: xenial language: php @@ -8,9 +7,6 @@ php: - 7.3 - 7.4 -matrix: - fast_finish: true - cache: directories: - $HOME/.composer/cache diff --git a/CHANGELOG.md b/CHANGELOG.md index 27f62ce..8bed647 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [4.4.1] +- Throws an exception if `translate` placeholder are not numeric or array. +- Fix issue with numeric placeholder with non plural keys. See [userfrosting#1090](https://github.com/userfrosting/UserFrosting/issues/1090#issuecomment-620832985). + ## [4.4.0] Complete rewrite of the Translator. @@ -23,7 +27,7 @@ All methods of the `Translator` are the same for backward compatibility. The onl - `@PLURAL_RULE` special key removed. Use the Locale configuration file (`locale.yaml`) `plural_rule` attribute instead. - Translator can't load multiple locale anymore. Use the Locale configuration file `parents` attribute instead. -See updated [documentation](README.md) for more details on how to use the new Translator, Locale and Dictionary. +See updated [documentation](README.md) for more details on how to use the new Translator, Locale and Dictionary. ## [4.3.0] - Dropping support for PHP 5.6 & 7.0 @@ -57,6 +61,7 @@ See updated [documentation](README.md) for more details on how to use the new Tr ## 4.0.0 - Initial release +[4.4.1]: https://github.com/userfrosting/i18n/compare/4.4.0...4.4.1 [4.4.0]: https://github.com/userfrosting/i18n/compare/4.3.0...4.4.0 [4.3.0]: https://github.com/userfrosting/i18n/compare/4.2.1...4.3.0 [4.2.1]: https://github.com/userfrosting/i18n/compare/4.2.0...4.2.1 diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..5c03ad2 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,3 @@ +parameters: + level: max + treatPhpDocTypesAsCertain: false \ No newline at end of file diff --git a/src/Translator.php b/src/Translator.php index ebb2e60..577df8a 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -79,13 +79,17 @@ public function getLocale(): LocaleInterface * * Return the $messageKey if not match is found * - * @param string $messageKey The id of the message id to translate. can use dot notation for array - * @param array|int $placeholders An optional hash of placeholder names => placeholder values to substitute (default : []) + * @param string $messageKey The id of the message id to translate. can use dot notation for array + * @param mixed[]|int $placeholders An optional hash of placeholder names => placeholder values to substitute (default : []) * * @return string The translated message. */ public function translate(string $messageKey, $placeholders = []): string { + if (!is_numeric($placeholders) && !is_array($placeholders)) { + throw new \InvalidArgumentException('Placeholders must be array or numeric value.'); + } + // Get the correct message from the specified key $message = $this->getMessageFromKey($messageKey, $placeholders); @@ -100,8 +104,8 @@ public function translate(string $messageKey, $placeholders = []): string * Go throught all registered language keys avaiable and find the correct * one to use, using the placeholders to select the correct plural form. * - * @param string $messageKey The key to find the message for - * @param array|int $placeholders Passed by reference, since plural placeholder will be added for later processing + * @param string $messageKey The key to find the message for + * @param mixed[]|int $placeholders Passed by reference, since plural placeholder will be added for later processing * * @return string The message string */ @@ -176,7 +180,7 @@ protected function getMessageFromKey(string $messageKey, &$placeholders): string * Return the plural key from a translation array. * If no plural key is defined in the `@PLURAL` instruction of the message array, we fallback to the default one. * - * @param array $messageArray + * @param mixed[] $messageArray * * @return string */ @@ -192,14 +196,14 @@ protected function getPluralKey(array $messageArray): string /** * Return the plural value, aka the nummber to display, from the placeholder values. * - * @param array|int $placeholders Placeholder - * @param string $pluralKey The plural key, for key => value match + * @param mixed[]|int $placeholders Placeholder + * @param string $pluralKey The plural key, for key => value match * * @return int|null The number, null if not found */ protected function getPluralValue($placeholders, string $pluralKey): ?int { - if (isset($placeholders[$pluralKey])) { + if (is_array($placeholders) && isset($placeholders[$pluralKey])) { return (int) $placeholders[$pluralKey]; } @@ -215,8 +219,8 @@ protected function getPluralValue($placeholders, string $pluralKey): ?int * Return the correct plural message form to use. * When multiple plural form are available for a message, this method will return the correct oen to use based on the numeric value. * - * @param array $messageArray The array with all the form inside ($pluralRule => $message) - * @param int $pluralValue The numeric value used to select the correct message + * @param mixed[] $messageArray The array with all the form inside ($pluralRule => $message) + * @param int $pluralValue The numeric value used to select the correct message * * @return int|null Returns which key from $messageArray to use */ @@ -253,13 +257,18 @@ protected function getPluralMessageKey(array $messageArray, int $pluralValue): ? * Parse Placeholder. * Replace placeholders in the message with their values from the passed argument. * - * @param string $message The message to replace placeholders in - * @param array $placeholders An optional hash of placeholder (names => placeholder) values to substitute (default : []) + * @param string $message The message to replace placeholders in + * @param mixed[]|int $placeholders An optional hash of placeholder (names => placeholder) values to substitute (default : []) * * @return string The message with replaced placeholders */ - protected function parsePlaceHolders(string $message, array $placeholders): string + protected function parsePlaceHolders(string $message, $placeholders): string { + // If $placeholders is not an array at this point, we make it an array, using `plural` as the key + if (!is_array($placeholders)) { + $placeholders = [$this->defaultPluralKey => $placeholders]; + } + // Interpolate translatable placeholders values. This allows to // pre-translate placeholder which value starts with the `&` caracter foreach ($placeholders as $name => $value) { @@ -304,15 +313,18 @@ protected function parsePlaceHolders(string $message, array $placeholders): stri * @see https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals * * @param int|float $number The number we want to get the plural case for. Float numbers are floored. - * @param mixed $forceRule False to use the plural rule of the language package + * @param int|bool $forceRule False to use the plural rule of the language package * or an integer to force a certain plural rule * * @return int The plural-case we need to use for the number plural-rule combination */ public function getPluralForm($number, $forceRule = false) { - // Default to English rule (1) or the forced one - $ruleNumber = $this->getPluralRuleNumber($forceRule); + if (is_int($forceRule)) { + $ruleNumber = $forceRule; + } else { + $ruleNumber = $this->dictionary->getLocale()->getPluralRule(); + } // Get the rule class $class = "\UserFrosting\I18n\PluralRules\Rule$ruleNumber"; @@ -322,20 +334,4 @@ public function getPluralForm($number, $forceRule = false) return $class::getRule((int) $number); } - - /** - * Return the correct rule number to use. - * - * @param int|bool $forceRule Force to use a particular rule. Otherwise, use the language defined one - * - * @return int - */ - protected function getPluralRuleNumber($forceRule): int - { - if ($forceRule !== false) { - return $forceRule; - } - - return $this->dictionary->getLocale()->getPluralRule(); - } } diff --git a/tests/TranslatorTest.php b/tests/TranslatorTest.php index 9313340..645733a 100644 --- a/tests/TranslatorTest.php +++ b/tests/TranslatorTest.php @@ -87,6 +87,15 @@ public function testGetPluralForm(): void $this->assertEquals($translator->translate('X_CARS', 2), '2 cars'); } + /** + * @depends testGetPluralForm + */ + public function testGetPluralFormForced(): void + { + $translator = $this->getTranslator('en_US'); + $this->assertSame(1, $translator->getPluralForm(2, 0)); + } + /** * @depends testGetPluralForm * Test locale wihtout a plural rule. @@ -131,6 +140,31 @@ public function testTranslate(string $key, $placeholders, string $expectedResult $this->assertEquals($expectedResultFrench, $frenchTranslator->translate($key, $placeholders)); } + /** + * @depends testTranslate + */ + public function testTranslateKeyWithNoPlural(): void + { + $translator = $this->getTranslator(); + $this->assertEquals($translator->translate('USERNAME', 123), 'Username'); + $this->assertEquals($translator->translate('X_FOO'), 'x foos'); + $this->assertEquals($translator->translate('X_FOO', ['plural' => 1]), '1x foos'); + $this->assertEquals($translator->translate('X_FOO', 1), '1x foos'); + $this->assertEquals($translator->translate('X_FOO', 123), '123x foos'); + } + + /** + * Force test a non-array / non-int placeholder. + * + * @depends testTranslate + */ + public function testTranslateKeyWithBadPlaceholder(): void + { + $translator = $this->getTranslator(); + $this->expectException(\InvalidArgumentException::class); + $this->assertEquals($translator->translate('X_FOO', 'xx'), 'xxx foos'); + } + /** * Basic test to see if triple dependency works. */ diff --git a/tests/data/sprinkles/core/locale/en_US/test.php b/tests/data/sprinkles/core/locale/en_US/test.php index 0ca7c8b..6a5b000 100644 --- a/tests/data/sprinkles/core/locale/en_US/test.php +++ b/tests/data/sprinkles/core/locale/en_US/test.php @@ -11,6 +11,8 @@ return [ 'USERNAME' => 'Username', + 'X_FOO' => '{{plural}}x foos', + 'BASE_FALLBACK' => 'Base fallback', 'ACCOUNT' => [