From 1b9bef0a4067667f998c2e4f4595b323bc8716b3 Mon Sep 17 00:00:00 2001 From: spiralbot Date: Wed, 20 Sep 2023 13:24:49 +0000 Subject: [PATCH] Merge pull request #996 from spiral/feature/translation-directories --- src/Catalogue/CatalogueLoader.php | 48 ++++++++++--- src/Config/TranslatorConfig.php | 35 ++++++++-- tests/ConfigTest.php | 35 ++++++++++ tests/LoaderTest.php | 72 ++++++++++++++++++++ tests/ManagerTest.php | 6 +- tests/fixtures/additional/fr/messages.fr.php | 7 ++ tests/fixtures/additional/fr/views.fr.po | 18 +++++ tests/fixtures/additional/ru/messages.ru.php | 7 ++ tests/fixtures/locales/ru/messages.ru.php | 3 +- 9 files changed, 214 insertions(+), 17 deletions(-) create mode 100644 tests/fixtures/additional/fr/messages.fr.php create mode 100644 tests/fixtures/additional/fr/views.fr.po create mode 100644 tests/fixtures/additional/ru/messages.ru.php diff --git a/src/Catalogue/CatalogueLoader.php b/src/Catalogue/CatalogueLoader.php index 4f9f205b..2323e052 100644 --- a/src/Catalogue/CatalogueLoader.php +++ b/src/Catalogue/CatalogueLoader.php @@ -23,25 +23,29 @@ public function hasLocale(string $locale): bool { $locale = \preg_replace('/[^a-zA-Z_]/', '', \mb_strtolower($locale)); - return \is_dir($this->config->getLocaleDirectory($locale)); + foreach ($this->getDirectories() as $directory) { + if (\is_dir($this->config->getLocaleDirectory($locale, $directory))) { + return true; + } + } + + return false; } public function getLocales(): array { - if (!\is_dir($this->config->getLocalesDirectory())) { + $directories = $this->getDirectories(); + if ($directories === []) { return []; } $finder = new Finder(); - $finder->in($this->config->getLocalesDirectory())->directories(); - $locales = []; - - foreach ($finder->directories() as $directory) { + foreach ($finder->in($directories)->directories() as $directory) { $locales[] = $directory->getFilename(); } - return $locales; + return \array_unique($locales); } public function loadCatalogue(string $locale): CatalogueInterface @@ -53,10 +57,15 @@ public function loadCatalogue(string $locale): CatalogueInterface return $catalogue; } - $finder = new Finder(); - $finder->in($this->config->getLocaleDirectory($locale)); + $directories = []; + foreach ($this->getDirectories() as $directory) { + if (\is_dir($this->config->getLocaleDirectory($locale, $directory))) { + $directories[] = $this->config->getLocaleDirectory($locale, $directory); + } + } - foreach ($finder->getIterator() as $file) { + $finder = new Finder(); + foreach ($finder->in($directories)->files() as $file) { $this->getLogger()->info( \sprintf( "found locale domain file '%s'", @@ -91,4 +100,23 @@ public function loadCatalogue(string $locale): CatalogueInterface return $catalogue; } + + /** + * @return array + */ + private function getDirectories(): array + { + $directories = []; + if (\is_dir($this->config->getLocalesDirectory())) { + $directories[] = $this->config->getLocalesDirectory(); + } + + foreach ($this->config->getDirectories() as $directory) { + if (\is_dir($directory)) { + $directories[] = $directory; + } + } + + return $directories; + } } diff --git a/src/Config/TranslatorConfig.php b/src/Config/TranslatorConfig.php index ab8d78a6..28a48e1f 100644 --- a/src/Config/TranslatorConfig.php +++ b/src/Config/TranslatorConfig.php @@ -20,8 +20,9 @@ final class TranslatorConfig extends InjectableConfig * @var array{ * locale: string, * fallbackLocale?: string, - * directory: string, - * localesDirectory?: string, + * directory: non-empty-string, + * directories: array, + * localesDirectory?: non-empty-string, * registerMessages?: bool, * cacheLocales: bool, * autoRegister: bool, @@ -33,6 +34,7 @@ final class TranslatorConfig extends InjectableConfig protected array $config = [ 'locale' => '', 'directory' => '', + 'directories' => [], 'cacheLocales' => true, 'autoRegister' => true, 'domains' => [], @@ -71,14 +73,39 @@ public function isAutoRegisterMessages(): bool return !empty($this->config['autoRegister']) || !empty($this->config['registerMessages']); } + /** + * Returns application locales directory. + * + * @return non-empty-string + */ public function getLocalesDirectory(): string { return $this->config['localesDirectory'] ?? $this->config['directory'] ?? ''; } - public function getLocaleDirectory(string $locale): string + /** + * Returns additional locales directories. + * + * @return array + */ + public function getDirectories(): array { - return $this->getLocalesDirectory() . $locale . '/'; + return $this->config['directories'] ?? []; + } + + /** + * @param non-empty-string $locale + * @param non-empty-string|null $directory + * + * @return non-empty-string + */ + public function getLocaleDirectory(string $locale, ?string $directory = null): string + { + if ($directory !== null) { + return \rtrim($directory, '/') . '/' . $locale . '/'; + } + + return \trim($this->getLocalesDirectory(), '/') . '/' . $locale . '/'; } /** diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index e5cff89a..e8072588 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php @@ -80,6 +80,27 @@ public function testLocaleDirectoryShort(): void $this->assertSame('directory/ru/', $config->getLocaleDirectory('ru')); } + public function testLocaleDirectoryWithoutSlash(): void + { + $config = new TranslatorConfig([ + 'localesDirectory' => 'directory' + ]); + $this->assertSame('directory/en/', $config->getLocaleDirectory('en')); + + $config = new TranslatorConfig([ + 'directory' => 'directory' + ]); + $this->assertSame('directory/en/', $config->getLocaleDirectory('en')); + } + + public function testLocaleDirectoryWithDirectoryParam(): void + { + $config = new TranslatorConfig(); + + $this->assertSame('directory/en/', $config->getLocaleDirectory('en', 'directory')); + $this->assertSame('directory/en/', $config->getLocaleDirectory('en', 'directory/')); + } + public function testDomains(): void { $config = new TranslatorConfig([ @@ -145,4 +166,18 @@ public function testGetDumper(): void $this->assertInstanceOf(DumperInterface::class, $config->getDumper('po')); } + + public function testGetDirectories(): void + { + $config = new TranslatorConfig(); + $this->assertSame([], $config->getDirectories()); + + $config = new TranslatorConfig([ + 'directories' => [ + 'foo', + 'bar/' + ] + ]); + $this->assertSame(['foo', 'bar/'], $config->getDirectories()); + } } diff --git a/tests/LoaderTest.php b/tests/LoaderTest.php index 08adc994..2c33383c 100644 --- a/tests/LoaderTest.php +++ b/tests/LoaderTest.php @@ -23,6 +23,18 @@ public function testHasLocale(): void $this->assertTrue($loader->hasLocale('ru')); $this->assertTrue($loader->hasLocale('RU')); + $this->assertFalse($loader->hasLocale('fr')); + $this->assertFalse($loader->hasLocale('FR')); + + $loader = new CatalogueLoader(new TranslatorConfig([ + 'directory' => __DIR__ . '/fixtures/locales/', + 'directories' => [__DIR__ . '/fixtures/additional'], + ])); + + $this->assertTrue($loader->hasLocale('ru')); + $this->assertTrue($loader->hasLocale('RU')); + $this->assertTrue($loader->hasLocale('fr')); + $this->assertTrue($loader->hasLocale('FR')); } public function testGetLocales(): void @@ -39,6 +51,21 @@ public function testGetLocales(): void $this->assertSame($shouldBe, $compared); } + public function testGetLocalesWithAdditionalDirectories(): void + { + $loader = new CatalogueLoader(new TranslatorConfig([ + 'directory' => __DIR__ . '/fixtures/locales/', + 'directories' => [__DIR__ . '/fixtures/additional'], + ])); + + $compared = $loader->getLocales(); + $shouldBe = ['en', 'ru', 'fr']; + sort($shouldBe); + sort($compared); + + $this->assertSame($shouldBe, $compared); + } + public function testLoadCatalogue(): void { $loader = new CatalogueLoader(new TranslatorConfig([ @@ -77,6 +104,51 @@ public function testLoadCatalogue(): void 'Twig версия', $mc->get('Twig Version', 'views') ); + + $this->assertFalse($loader->hasLocale('fr')); + } + + public function testLoadCatalogueWithAdditionalDirectories(): void + { + $loader = new CatalogueLoader(new TranslatorConfig([ + 'directory' => __DIR__ . '/fixtures/locales/', + 'directories' => [__DIR__ . '/fixtures/additional'], + 'loaders' => [ + 'php' => PhpFileLoader::class, + 'po' => PoFileLoader::class, + ], + ])); + + $catalogue = $loader->loadCatalogue('fr'); + $mc = $catalogue->toMessageCatalogue(); + $this->assertTrue($mc->has('Welcome To Spiral', 'views')); + $this->assertSame( + 'Bienvenue à Spirale', + $mc->get('Welcome To Spiral', 'views') + ); + + $this->assertTrue($loader->hasLocale('fr')); + $this->assertTrue($loader->hasLocale('FR')); + $this->assertTrue($loader->hasLocale('ru')); + $this->assertTrue($loader->hasLocale('RU')); + } + + public function testApplicationTranslationShouldOverrideAdditionalTranslations(): void + { + $loader = new CatalogueLoader(new TranslatorConfig([ + 'directory' => __DIR__ . '/fixtures/locales/', + 'directories' => [__DIR__ . '/fixtures/additional'], + 'loaders' => [ + 'php' => PhpFileLoader::class, + 'po' => PoFileLoader::class, + ], + ])); + + $catalogue = $loader->loadCatalogue('ru'); + $mc = $catalogue->toMessageCatalogue(); + + $this->assertTrue($mc->has('should_be_override')); + $this->assertSame('changed by application translation', $mc->get('should_be_override')); } public function testLoadCatalogueNoLoader(): void diff --git a/tests/ManagerTest.php b/tests/ManagerTest.php index c19f9dd0..6f49a9fa 100644 --- a/tests/ManagerTest.php +++ b/tests/ManagerTest.php @@ -78,7 +78,8 @@ public function testCatalogue(): void 'ru', [ 'messages' => [ - 'message' => 'translation' + 'message' => 'translation', + 'should_be_override' => 'changed by application translation' ], 'views' => [ 'Welcome To Spiral' => 'Добро пожаловать в Spiral Framework', @@ -91,7 +92,8 @@ public function testCatalogue(): void 'ru', [ 'messages' => [ - 'message' => 'new message' + 'message' => 'new message', + 'should_be_override' => 'changed by application translation' ], 'views' => [ 'Welcome To Spiral' => 'Добро пожаловать в Spiral Framework', diff --git a/tests/fixtures/additional/fr/messages.fr.php b/tests/fixtures/additional/fr/messages.fr.php new file mode 100644 index 00000000..2aef0f82 --- /dev/null +++ b/tests/fixtures/additional/fr/messages.fr.php @@ -0,0 +1,7 @@ + 'translation', +]; diff --git a/tests/fixtures/additional/fr/views.fr.po b/tests/fixtures/additional/fr/views.fr.po new file mode 100644 index 00000000..51d2333d --- /dev/null +++ b/tests/fixtures/additional/fr/views.fr.po @@ -0,0 +1,18 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fr\n" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"X-Generator: Poedit 1.8.6\n" + +msgid "Welcome To Spiral" +msgstr "Bienvenue à Spirale" + +msgid "Twig Version" +msgstr "Twig Version" diff --git a/tests/fixtures/additional/ru/messages.ru.php b/tests/fixtures/additional/ru/messages.ru.php new file mode 100644 index 00000000..1f7a2cda --- /dev/null +++ b/tests/fixtures/additional/ru/messages.ru.php @@ -0,0 +1,7 @@ + 'original' +]; diff --git a/tests/fixtures/locales/ru/messages.ru.php b/tests/fixtures/locales/ru/messages.ru.php index 00ec5aaf..51941997 100644 --- a/tests/fixtures/locales/ru/messages.ru.php +++ b/tests/fixtures/locales/ru/messages.ru.php @@ -3,5 +3,6 @@ declare(strict_types=1); return [ - 'message' => 'translation' + 'message' => 'translation', + 'should_be_override' => 'changed by application translation' ];