From 824be8768f51e3b67489a6c0920b4553ecd4b337 Mon Sep 17 00:00:00 2001 From: Ivan Vermeyen Date: Tue, 14 Mar 2023 15:45:07 +0100 Subject: [PATCH] First commit --- README.md | 76 ++++++++++++++- composer.json | 2 +- phpunit.xml.dist | 3 - src/Macros/Lang/UriMacro.php | 22 +++++ src/UriTranslator.php | 89 +++++++++++++++++ src/UriTranslatorServiceProvider.php | 3 +- tests/Feature/UriTranslatorTest.php | 140 +++++++++++++++++++++++++++ tests/TestCase.php | 32 ++++++ 8 files changed, 359 insertions(+), 8 deletions(-) create mode 100644 src/Macros/Lang/UriMacro.php create mode 100644 src/UriTranslator.php create mode 100644 tests/Feature/UriTranslatorTest.php diff --git a/README.md b/README.md index 79c4dfd..8672fe2 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,11 @@ [![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R3UQ8V) -Translate a URI or individual slugs, excluding route parameters. +Translate a URI or individual slugs. + +This package registers a macro for the Laravel `Translator` class. +This will allow you to translate individual URI slugs, while ignoring parameter placeholders. +Parameters will not be translated by this macro. That remains the responsibility of your code. ## ✅ Requirements @@ -27,9 +31,75 @@ composer require codezero/laravel-uri-translator Laravel will automatically register the ServiceProvider. -### Usage +Then in your app's `lang` folder, create subdirectories for every locale you want to have translations for. +Next create a `routes.php` file in each of those directories. + +``` +lang/ + ├── en/ + │ └── routes.php + └── nl/ + └── routes.php +``` + +Return an array of translations from the `routes.php` file. + +### 🚀 Usage + +Use the `Lang::uri()` macro when registering routes: + +```php +Route::get(Lang::uri('hello/world'), [Controller::class, 'index']); +``` + +The URI macro accepts 2 additional parameters: + +1. A locale, in case you need translations to a locale other than the current app locale. +2. A namespace, in case your translation files reside in a package. + +```php +Lang::uri('hello/world', 'fr', 'my-package'); +``` + +You can also use `trans()->uri('hello/world')` instead of `Lang::uri()`. + +### 🔌 Example + +Using these example translations: + +```php +// lang/nl/routes.php +return [ + 'hello' => 'hallo', + 'world' => 'wereld', + 'override/hello/world' => 'something/very/different', + 'hello/world/{parameter}' => 'uri/with/{parameter}', +]; +``` -... +These are possible translation results: + +```php +// Translate every slug individually +// Translates to: 'hallo/wereld' +Lang::uri('hello/world'); + +// Keep original slug when missing translation +// Translates to: 'hallo/big/wereld' +Lang::uri('hello/big/world'); + +// Translate slugs, but not parameter placeholders +// Translates to: 'hallo/{world}' +Lang::uri('hello/{world}'); + +// Translate full URIs if an exact translation exists +// Translates to: 'something/very/different' +Lang::uri('override/hello/world'); + +// Translate full URIs if an exact translation exists (with placeholder) +// Translates to: 'uri/with/{parameter}' +Lang::uri('hello/world/{parameter}'); +``` ## 🚧 Testing diff --git a/composer.json b/composer.json index c7a4a2c..b324806 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "codezero/laravel-uri-translator", - "description": "Translate a URI or individual slugs, excluding route parameters.", + "description": "Translate a URI or individual slugs.", "keywords": [ "php", "laravel", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e1988a6..461f1f9 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -9,9 +9,6 @@ processIsolation="false" stopOnFailure="false"> - - ./tests/Unit - ./tests/Feature diff --git a/src/Macros/Lang/UriMacro.php b/src/Macros/Lang/UriMacro.php new file mode 100644 index 0000000..4fb17e1 --- /dev/null +++ b/src/Macros/Lang/UriMacro.php @@ -0,0 +1,22 @@ +translate($uri, $locale, $namespace); + }); + } +} diff --git a/src/UriTranslator.php b/src/UriTranslator.php new file mode 100644 index 0000000..466cd0d --- /dev/null +++ b/src/UriTranslator.php @@ -0,0 +1,89 @@ +buildTranslationKey($uri, $namespace); + + // Attempt to translate the full URI. + if (Lang::has($fullUriKey, $locale)) { + return Lang::get($fullUriKey, [], $locale); + } + + $segments = $this->splitUriIntoSegments($uri); + + // Attempt to translate each segment individually. If there is no translation + // for a specific segment, then its original value will be used. + $translations = $segments->map(function ($segment) use ($locale, $namespace) { + $segmentKey = $this->buildTranslationKey($segment, $namespace); + + // If the segment is not a placeholder and the segment + // has a translation, then update the segment. + if ( ! Str::startsWith($segment, '{') && Lang::has($segmentKey, $locale)) { + $segment = Lang::get($segmentKey, [], $locale); + } + + return $segment; + }); + + // Rebuild the URI from the translated segments. + return $translations->implode('/'); + } + + /** + * Split the URI into a Collection of segments. + * + * @param string $uri + * + * @return \Illuminate\Support\Collection + */ + protected function splitUriIntoSegments($uri) + { + $uri = trim($uri, '/'); + $segments = explode('/', $uri); + + return Collection::make($segments); + } + + /** + * Build a translation key, including the namespace and file name. + * + * @param string $key + * @param string|null $namespace + * + * @return string + */ + protected function buildTranslationKey($key, $namespace) + { + $namespace = $namespace ? "{$namespace}::" : ''; + $file = $this->getTranslationFileName(); + + return "{$namespace}{$file}.{$key}"; + } + + /** + * Get the file name that holds the URI translations. + * + * @return string + */ + protected function getTranslationFileName() + { + return 'routes'; + } +} diff --git a/src/UriTranslatorServiceProvider.php b/src/UriTranslatorServiceProvider.php index 1a2ec35..7475005 100644 --- a/src/UriTranslatorServiceProvider.php +++ b/src/UriTranslatorServiceProvider.php @@ -2,6 +2,7 @@ namespace CodeZero\UriTranslator; +use CodeZero\UriTranslator\Macros\Lang\UriMacro; use Illuminate\Support\ServiceProvider; class UriTranslatorServiceProvider extends ServiceProvider @@ -13,7 +14,7 @@ class UriTranslatorServiceProvider extends ServiceProvider */ public function boot() { - // + UriMacro::register(); } /** diff --git a/tests/Feature/UriTranslatorTest.php b/tests/Feature/UriTranslatorTest.php new file mode 100644 index 0000000..41267b6 --- /dev/null +++ b/tests/Feature/UriTranslatorTest.php @@ -0,0 +1,140 @@ +setTranslations([ + 'nl' => [ + 'my' => 'mijn', + 'new' => 'nieuwe', + 'page' => 'pagina', + ] + ]); + + $this->setAppLocale('en'); + $this->assertEquals('my/new/page', Lang::uri('my/new/page')); + + $this->setAppLocale('nl'); + $this->assertEquals('mijn/nieuwe/pagina', Lang::uri('my/new/page')); + $this->assertEquals('mijn/nieuwe/pagina', trans()->uri('my/new/page')); + } + + /** @test */ + public function it_translates_every_segment_in_a_uri_to_the_given_locale() + { + $this->setTranslations([ + 'nl' => [ + 'my' => 'mijn', + 'new' => 'nieuwe', + 'page' => 'pagina', + ] + ]); + + $this->assertEquals('mijn/nieuwe/pagina', Lang::uri('my/new/page', 'nl')); + } + + /** @test */ + public function it_uses_the_original_values_if_a_translation_does_not_exist() + { + $this->setTranslations([ + 'nl' => [ + 'my' => 'mijn', + 'new' => 'nieuwe', + ] + ]); + + $this->assertEquals('mijn/nieuwe/page', Lang::uri('my/new/page', 'nl')); + $this->assertEquals('my/new/page', Lang::uri('my/new/page', 'fr')); + } + + /** @test */ + public function it_ignores_trailing_slashes() + { + $this->setTranslations([ + 'nl' => [ + 'my' => 'mijn', + 'new' => 'nieuwe', + 'page' => 'pagina', + ] + ]); + + $this->assertEquals('mijn/nieuwe/pagina', Lang::uri('/my/new/page/', 'nl')); + } + + /** @test */ + public function it_skips_placeholders_in_a_uri() + { + $this->setTranslations([ + 'nl' => [ + 'articles' => 'artikels', + ] + ]); + + $this->assertEquals('artikels/{articles}', Lang::uri('articles/{articles}', 'nl')); + } + + /** @test */ + public function you_can_translate_a_full_uri() + { + $this->setTranslations([ + 'nl' => [ + 'glass' => 'glas', + 'products' => 'producten', + 'products/glass' => 'producten/glazen' + ] + ]); + + $this->assertEquals('producten/glazen', Lang::uri('products/glass', 'nl')); + } + + /** @test */ + public function you_can_translate_a_full_uri_with_placeholder() + { + $this->setTranslations([ + 'nl' => [ + 'glass' => 'glas', + 'products' => 'producten', + 'products/glass/{type}' => 'producten/glazen/{type}' + ] + ]); + + $this->assertEquals('producten/glazen/{type}', Lang::uri('products/glass/{type}', 'nl')); + } + + /** @test */ + public function you_can_specify_a_namespace() + { + $this->setTranslations([ + 'nl' => [ + 'articles' => 'artikels', + ] + ], 'blog'); + + $this->assertEquals('artikels/{article}', Lang::uri('articles/{article}', 'nl', 'blog')); + } + + /** @test */ + public function the_uri_macro_is_available_via_the_trans_helper() + { + $this->setTranslations([ + 'nl' => [ + 'my' => 'mijn', + 'new' => 'nieuwe', + 'page' => 'pagina', + ] + ]); + + $this->setAppLocale('en'); + $this->assertEquals('my/new/page', trans()->uri('my/new/page')); + + $this->setAppLocale('nl'); + $this->assertEquals('mijn/nieuwe/pagina', trans()->uri('my/new/page')); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 2ad55d0..e30571a 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,10 +3,42 @@ namespace CodeZero\UriTranslator\Tests; use CodeZero\UriTranslator\UriTranslatorServiceProvider; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Lang; use Orchestra\Testbench\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { + /** + * Set the app locale. + * + * @param string $locale + * + * @return void + */ + protected function setAppLocale($locale) + { + App::setLocale($locale); + } + + /** + * Fake that we created a routes.php file in the 'lang' folder + * for each language with the given translations. + * + * @param $translations + * @param string $namespace + * + * @return void + */ + protected function setTranslations($translations, $namespace = '*') + { + Lang::setLoaded([ + $namespace => [ + 'routes' => $translations + ] + ]); + } + /** * Get the packages service providers. *