From 8c41d486dc2cc750551edc08bab8f79001749f8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 12 Aug 2025 09:35:30 +0000 Subject: [PATCH 1/4] Implement fallback --- src/Locale/Locale.php | 34 ++++++++++++++++++++++++++++++---- tests/Locale/LocaleTest.php | 13 +++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/Locale/Locale.php b/src/Locale/Locale.php index 7d5640a..28767b8 100644 --- a/src/Locale/Locale.php +++ b/src/Locale/Locale.php @@ -25,6 +25,14 @@ class Locale */ public $default; + /** + * Fallback locale. Used when specific or default locale is missing translation. + * Should always be set to locale that includes all translations. + * + * @var string + */ + public $fallback; + /** * Set New Locale from an array * @@ -44,7 +52,7 @@ public static function setLanguageFromArray(string $name, array $translations): */ public static function setLanguageFromJSON(string $name, string $path): void { - if (! file_exists($path)) { + if (! file_exists($path) && self::$exceptions) { throw new Exception('Translation file not found.'); } @@ -55,13 +63,31 @@ public static function setLanguageFromJSON(string $name, string $path): void public function __construct(string $default) { - if (! \array_key_exists($default, self::$language)) { + if (! \array_key_exists($default, self::$language) && self::$exceptions) { throw new Exception('Locale not found'); } $this->default = $default; } + /** + * Change fallback Locale + * + * @param $name + * + * @throws Exception + */ + public function setFallback(string $name): self + { + if (! \array_key_exists($name, self::$language) && self::$exceptions) { + throw new Exception('Locale not found'); + } + + $this->fallback = $name; + + return $this; + } + /** * Change Default Locale * @@ -71,7 +97,7 @@ public function __construct(string $default) */ public function setDefault(string $name): self { - if (! \array_key_exists($name, self::$language)) { + if (! \array_key_exists($name, self::$language) && self::$exceptions) { throw new Exception('Locale not found'); } @@ -91,7 +117,7 @@ public function setDefault(string $name): self */ public function getText(string $key, array $placeholders = []) { - $default = '{{'.$key.'}}'; + $default = (self::$language[$this->fallback ?? ''] ?? [])[$key] ?? '{{'.$key.'}}'; if (! \array_key_exists($key, self::$language[$this->default])) { if (self::$exceptions) { diff --git a/tests/Locale/LocaleTest.php b/tests/Locale/LocaleTest.php index a012f73..af146dd 100755 --- a/tests/Locale/LocaleTest.php +++ b/tests/Locale/LocaleTest.php @@ -77,4 +77,17 @@ public function testTexts(): void $this->fail('No exception was thrown'); } + + public function testFallback(): void + { + $locale = new Locale('he-IL'); + + $this->assertEquals('שלום', $locale->getText('hello')); + $this->assertEquals('{{world}}', $locale->getText('world')); + + $locale->setFallback('en-US'); + + $this->assertEquals('שלום', $locale->getText('hello')); + $this->assertEquals('World', $locale->getText('world')); + } } From 63ae95462f79af142a99661154da9482eda12bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 12 Aug 2025 10:32:24 +0000 Subject: [PATCH 2/4] Fix codeQL check --- composer.json | 2 +- src/Locale/Locale.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index c1f8ac7..1b69ef7 100755 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "phpunit/phpunit": "^9.3", "vimeo/psalm": "4.0.1", "laravel/pint": "1.2.*", - "phpstan/phpstan": "1.9.x-dev" + "phpstan/phpstan": "1.*" }, "scripts": { "lint": "./vendor/bin/pint --test", diff --git a/src/Locale/Locale.php b/src/Locale/Locale.php index 28767b8..09563c7 100644 --- a/src/Locale/Locale.php +++ b/src/Locale/Locale.php @@ -29,9 +29,9 @@ class Locale * Fallback locale. Used when specific or default locale is missing translation. * Should always be set to locale that includes all translations. * - * @var string + * @var string|null */ - public $fallback; + public $fallback = null; /** * Set New Locale from an array From 02bed774be7a715c4224de7dd7ae3b35b838f97b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 12 Aug 2025 10:50:25 +0000 Subject: [PATCH 3/4] Improve logic --- src/Locale/Locale.php | 19 ++++++++++++------- tests/Locale/LocaleTest.php | 10 ++++++++++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Locale/Locale.php b/src/Locale/Locale.php index 09563c7..0a10bc6 100644 --- a/src/Locale/Locale.php +++ b/src/Locale/Locale.php @@ -117,17 +117,22 @@ public function setDefault(string $name): self */ public function getText(string $key, array $placeholders = []) { - $default = (self::$language[$this->fallback ?? ''] ?? [])[$key] ?? '{{'.$key.'}}'; + $defaultExists = \array_key_exists($key, self::$language[$this->default]); + $fallbackExists = \array_key_exists($key, self::$language[$this->fallback ?? ''] ?? []); - if (! \array_key_exists($key, self::$language[$this->default])) { - if (self::$exceptions) { - throw new Exception('Key named "'.$key.'" not found'); - } + $translation = '{{'.$key.'}}'; - return $default; + if ($fallbackExists) { + $translation = self::$language[$this->fallback ?? ''][$key]; } - $translation = self::$language[$this->default][$key]; + if ($defaultExists) { + $translation = self::$language[$this->default][$key]; + } + + if (! $defaultExists && ! $fallbackExists && self::$exceptions) { + throw new Exception('Key named "'.$key.'" not found'); + } foreach ($placeholders as $placeholderKey => $placeholderValue) { $translation = str_replace('{{'.$placeholderKey.'}}', (string) $placeholderValue, $translation); diff --git a/tests/Locale/LocaleTest.php b/tests/Locale/LocaleTest.php index af146dd..e0822ea 100755 --- a/tests/Locale/LocaleTest.php +++ b/tests/Locale/LocaleTest.php @@ -84,10 +84,20 @@ public function testFallback(): void $this->assertEquals('שלום', $locale->getText('hello')); $this->assertEquals('{{world}}', $locale->getText('world')); + $this->assertEquals('{{missing}}', $locale->getText('missing')); $locale->setFallback('en-US'); $this->assertEquals('שלום', $locale->getText('hello')); $this->assertEquals('World', $locale->getText('world')); + $this->assertEquals('{{missing}}', $locale->getText('missing')); + + Locale::$exceptions = true; + try { + $locale->getText('missing'); + $this->fail('Failed to throw exception when translation is missing'); + } catch (Exception $e) { + $this->assertInstanceOf(Exception::class, $e); + } } } From 8c4801cf89b7077f91a7661df088f59229b9326b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 12 Aug 2025 12:20:13 +0000 Subject: [PATCH 4/4] Fix tests post-merge --- src/Locale/Locale.php | 2 +- tests/Locale/LocaleTest.php | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Locale/Locale.php b/src/Locale/Locale.php index 6875cd0..0e610a2 100644 --- a/src/Locale/Locale.php +++ b/src/Locale/Locale.php @@ -33,7 +33,7 @@ class Locale */ public $fallback = null; - /** + /** * Get list of configured languages * * @return array diff --git a/tests/Locale/LocaleTest.php b/tests/Locale/LocaleTest.php index 067f3c5..57709f0 100755 --- a/tests/Locale/LocaleTest.php +++ b/tests/Locale/LocaleTest.php @@ -17,16 +17,17 @@ public function setUp(): void { Locale::$exceptions = false; // Disable exceptions - $this->assertCount(0, Locale::getLanguages()); - - Locale::setLanguageFromArray('en-US', ['hello' => 'Hello', 'world' => 'World', 'helloPlaceholder' => 'Hello {{name}} {{surname}}!', 'numericPlaceholder' => 'We have {{usersAmount}} users registered.', 'multiplePlaceholders' => 'Lets repeat: {{word}}, {{word}}, {{word}}']); // Set English - - $this->assertCount(1, Locale::getLanguages()); + // Set English + Locale::setLanguageFromArray('en-US', [ + 'hello' => 'Hello', + 'world' => 'World', + 'helloPlaceholder' => 'Hello {{name}} {{surname}}!', + 'numericPlaceholder' => 'We have {{usersAmount}} users registered.', + 'multiplePlaceholders' => 'Lets repeat: {{word}}, {{word}}, {{word}}', + ]); Locale::setLanguageFromArray('he-IL', ['hello' => 'שלום']); // Set Hebrew - $this->assertCount(2, Locale::getLanguages()); - Locale::setLanguageFromJSON('hi-IN', realpath(__DIR__.'/../hi-IN.json') ?: ''); // Set Hindi $this->assertCount(3, Locale::getLanguages());