diff options
author | Igor Scheller <igor.scheller@igorshp.de> | 2018-08-28 22:23:59 +0200 |
---|---|---|
committer | Igor Scheller <igor.scheller@igorshp.de> | 2018-08-29 23:46:31 +0200 |
commit | 427315195bdd379a0207fc9b2aaf69a5b5b86c79 (patch) | |
tree | 678245351333a16c5a5bca129aada4bfd311c23c | |
parent | df6360044b5c2396b2bee0dfa9e8d744bfa424d5 (diff) |
Moved translation/internationalization to Helpers\Translator class
-rw-r--r-- | config/app.php | 2 | ||||
-rw-r--r-- | includes/engelsystem.php | 6 | ||||
-rw-r--r-- | includes/helper/email_helper.php | 8 | ||||
-rw-r--r-- | includes/helper/internationalization_helper.php | 80 | ||||
-rw-r--r-- | includes/includes.php | 1 | ||||
-rw-r--r-- | includes/sys_form.php | 5 | ||||
-rw-r--r-- | includes/sys_menu.php | 26 | ||||
-rw-r--r-- | src/Helpers/TranslationServiceProvider.php | 58 | ||||
-rw-r--r-- | src/Helpers/Translator.php | 105 | ||||
-rw-r--r-- | src/Middleware/SetLocale.php | 49 | ||||
-rw-r--r-- | src/helpers.php | 35 | ||||
-rw-r--r-- | tests/Unit/Helpers/TranslationServiceProviderTest.php | 86 | ||||
-rw-r--r-- | tests/Unit/Helpers/TranslatorTest.php | 69 | ||||
-rw-r--r-- | tests/Unit/HelpersTest.php | 26 | ||||
-rw-r--r-- | tests/Unit/Middleware/SetLocaleTest.php | 71 |
15 files changed, 534 insertions, 93 deletions
diff --git a/config/app.php b/config/app.php index 13d4d22b..ea394b8e 100644 --- a/config/app.php +++ b/config/app.php @@ -14,6 +14,7 @@ return [ \Engelsystem\Database\DatabaseServiceProvider::class, \Engelsystem\Http\RequestServiceProvider::class, \Engelsystem\Http\SessionServiceProvider::class, + \Engelsystem\Helpers\TranslationServiceProvider::class, \Engelsystem\Http\ResponseServiceProvider::class, \Engelsystem\Http\Psr7ServiceProvider::class, \Engelsystem\Middleware\RouteDispatcherServiceProvider::class, @@ -24,6 +25,7 @@ return [ 'middleware' => [ \Engelsystem\Middleware\SendResponseHandler::class, \Engelsystem\Middleware\ExceptionHandler::class, + \Engelsystem\Middleware\SetLocale::class, \Engelsystem\Middleware\RouteDispatcher::class, \Engelsystem\Middleware\RequestHandler::class, ], diff --git a/includes/engelsystem.php b/includes/engelsystem.php index eb5b220a..4c096b43 100644 --- a/includes/engelsystem.php +++ b/includes/engelsystem.php @@ -22,12 +22,6 @@ if ($app->get('config')->get('maintenance')) { /** - * Init translations - */ -gettext_init(); - - -/** * Init authorization */ load_auth(); diff --git a/includes/helper/email_helper.php b/includes/helper/email_helper.php index c223d026..8d1cb794 100644 --- a/includes/helper/email_helper.php +++ b/includes/helper/email_helper.php @@ -15,14 +15,16 @@ function engelsystem_email_to_user($recipient_user, $title, $message, $not_if_it return true; } - gettext_locale($recipient_user['Sprache']); + /** @var \Engelsystem\Helpers\Translator $translator */ + $translator = app()->get('translator'); + $locale = $translator->getLocale(); + $translator->setLocale($recipient_user['Sprache']); $message = sprintf(_('Hi %s,'), $recipient_user['Nick']) . "\n\n" . _('here is a message for you from the engelsystem:') . "\n\n" . $message . "\n\n" . _('This email is autogenerated and has not been signed. You got this email because you are registered in the engelsystem.'); - - gettext_locale(); + $translator->setLocale($locale); return engelsystem_email($recipient_user['email'], $title, $message); } diff --git a/includes/helper/internationalization_helper.php b/includes/helper/internationalization_helper.php deleted file mode 100644 index bb6d0abd..00000000 --- a/includes/helper/internationalization_helper.php +++ /dev/null @@ -1,80 +0,0 @@ -<?php - -/** - * Return currently active locale - * - * @return string - */ -function locale() -{ - return session()->get('locale'); -} - -/** - * Returns two letter language code from currently active locale - * - * @return string - */ -function locale_short() -{ - return substr(locale(), 0, 2); -} - -/** - * Initializes gettext for internationalization and updates the sessions locale to use for translation. - */ -function gettext_init() -{ - $locales = config('locales'); - $request = request(); - $session = session(); - - if ($request->has('set_locale') && isset($locales[$request->input('set_locale')])) { - $session->set('locale', $request->input('set_locale')); - } elseif (!$session->has('locale')) { - $session->set('locale', config('default_locale')); - } - - gettext_locale(); - bindtextdomain('default', app('path.lang')); - bind_textdomain_codeset('default', 'UTF-8'); - textdomain('default'); -} - -/** - * Swich gettext locale. - * - * @param string $locale - */ -function gettext_locale($locale = null) -{ - if (empty($locale)) { - $locale = session()->get('locale'); - } - - putenv('LC_ALL=' . $locale); - setlocale(LC_ALL, $locale); -} - -/** - * Renders language selection. - * - * @return array - */ -function make_langselect() -{ - $request = app('request'); - - $items = []; - foreach (config('locales') as $locale => $name) { - $url = url($request->getPathInfo(), ['set_locale' => $locale]); - - $items[] = toolbar_item_link( - htmlspecialchars($url), - '', - $name, - $locale == locale() - ); - } - return $items; -} diff --git a/includes/includes.php b/includes/includes.php index e316e550..7a68c3c9 100644 --- a/includes/includes.php +++ b/includes/includes.php @@ -61,7 +61,6 @@ $includeFiles = [ __DIR__ . '/../includes/controller/user_worklog_controller.php', __DIR__ . '/../includes/helper/graph_helper.php', - __DIR__ . '/../includes/helper/internationalization_helper.php', __DIR__ . '/../includes/helper/message_helper.php', __DIR__ . '/../includes/helper/error_helper.php', __DIR__ . '/../includes/helper/email_helper.php', diff --git a/includes/sys_form.php b/includes/sys_form.php index 05df4c15..9cb6f38d 100644 --- a/includes/sys_form.php +++ b/includes/sys_form.php @@ -66,6 +66,9 @@ function form_date($name, $label, $value, $start_date = '', $end_date = '') $value = is_numeric($value) ? date('Y-m-d', $value) : ''; $start_date = is_numeric($start_date) ? date('Y-m-d', $start_date) : ''; $end_date = is_numeric($end_date) ? date('Y-m-d', $end_date) : ''; + $locale = $locale = session()->get('locale'); + $shortLocale = substr($locale, 0, 2); + return form_element($label, ' <div class="input-group date" id="' . $dom_id . '"> <input name="' . $name . '" class="form-control" value="' . htmlspecialchars($value) . '">' @@ -74,7 +77,7 @@ function form_date($name, $label, $value, $start_date = '', $end_date = '') <script type="text/javascript"> $(function(){ $("#' . $dom_id . '").datepicker({ - language: "' . locale_short() . '", + language: "' . $shortLocale . '", todayBtn: "linked", format: "yyyy-mm-dd", startDate: "' . $start_date . '", diff --git a/includes/sys_menu.php b/includes/sys_menu.php index 85ef1287..58d4bab3 100644 --- a/includes/sys_menu.php +++ b/includes/sys_menu.php @@ -110,7 +110,7 @@ function make_user_submenu() { global $privileges, $page; - $user_submenu = make_langselect(); + $user_submenu = make_language_select(); if (in_array('user_settings', $privileges) || in_array('logout', $privileges)) { $user_submenu[] = toolbar_item_divider(); @@ -228,6 +228,30 @@ function make_room_navigation($menu) } /** + * Renders language selection. + * + * @return array + */ +function make_language_select() +{ + $request = app('request'); + $activeLocale = session()->get('locale'); + + $items = []; + foreach (config('locales') as $locale => $name) { + $url = url($request->getPathInfo(), ['set-locale' => $locale]); + + $items[] = toolbar_item_link( + htmlspecialchars($url), + '', + $name, + $locale == $activeLocale + ); + } + return $items; +} + +/** * @return string */ function make_menu() diff --git a/src/Helpers/TranslationServiceProvider.php b/src/Helpers/TranslationServiceProvider.php new file mode 100644 index 00000000..9d86df7d --- /dev/null +++ b/src/Helpers/TranslationServiceProvider.php @@ -0,0 +1,58 @@ +<?php + +namespace Engelsystem\Helpers; + +use Engelsystem\Config\Config; +use Engelsystem\Container\ServiceProvider; +use Symfony\Component\HttpFoundation\Session\Session; + +class TranslationServiceProvider extends ServiceProvider +{ + public function register() + { + /** @var Config $config */ + $config = $this->app->get('config'); + /** @var Session $session */ + $session = $this->app->get('session'); + + $locales = $config->get('locales'); + $locale = $config->get('default_locale'); + + $sessionLocale = $session->get('locale', $locale); + if (isset($locales[$sessionLocale])) { + $locale = $sessionLocale; + } + + $this->initGettext(); + $session->set('locale', $locale); + + $translator = $this->app->make( + Translator::class, + ['locale' => $locale, 'locales' => $locales, 'localeChangeCallback' => [$this, 'setLocale']] + ); + $this->app->instance(Translator::class, $translator); + $this->app->instance('translator', $translator); + } + + /** + * @param string $textDomain + * @param string $encoding + * @codeCoverageIgnore + */ + protected function initGettext($textDomain = 'default', $encoding = 'UTF-8') + { + bindtextdomain($textDomain, $this->app->get('path.lang')); + bind_textdomain_codeset($textDomain, $encoding); + textdomain($textDomain); + } + + /** + * @param string $locale + * @codeCoverageIgnore + */ + public function setLocale($locale) + { + putenv('LC_ALL=' . $locale); + setlocale(LC_ALL, $locale); + } +} diff --git a/src/Helpers/Translator.php b/src/Helpers/Translator.php new file mode 100644 index 00000000..1e953a21 --- /dev/null +++ b/src/Helpers/Translator.php @@ -0,0 +1,105 @@ +<?php + +namespace Engelsystem\Helpers; + +class Translator +{ + /** @var string[] */ + protected $locales; + + /** @var string */ + protected $locale; + + /** @var callable */ + protected $localeChangeCallback; + + /** + * Translator constructor. + * + * @param string $locale + * @param string[] $locales + * @param callable $localeChangeCallback + */ + public function __construct(string $locale, array $locales = [], callable $localeChangeCallback = null) + { + $this->localeChangeCallback = $localeChangeCallback; + + $this->setLocale($locale); + $this->setLocales($locales); + } + + /** + * Get the translation for a given key + * + * @param string $key + * @param array $replace + * @return string + */ + public function translate(string $key, array $replace = []): string + { + $translated = $this->translateGettext($key); + + if (!empty($replace)) { + $translated = call_user_func_array('sprintf', array_merge([$translated], $replace)); + } + + return $translated; + } + + /** + * Translate the key via gettext + * + * @param string $key + * @return string + * @codeCoverageIgnore + */ + protected function translateGettext(string $key): string + { + return _($key); + } + + /** + * @return string + */ + public function getLocale(): string + { + return $this->locale; + } + + /** + * @param string $locale + */ + public function setLocale(string $locale) + { + $this->locale = $locale; + + if (is_callable($this->localeChangeCallback)) { + call_user_func_array($this->localeChangeCallback, [$locale]); + } + } + + /** + * @return string[] + */ + public function getLocales(): array + { + return $this->locales; + } + + /** + * @param string $locale + * @return bool + */ + public function hasLocale(string $locale): bool + { + return isset($this->locales[$locale]); + } + + /** + * @param string[] $locales + */ + public function setLocales(array $locales) + { + $this->locales = $locales; + } +} diff --git a/src/Middleware/SetLocale.php b/src/Middleware/SetLocale.php new file mode 100644 index 00000000..86fa0b7f --- /dev/null +++ b/src/Middleware/SetLocale.php @@ -0,0 +1,49 @@ +<?php + +namespace Engelsystem\Middleware; + +use Engelsystem\Helpers\Translator; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Symfony\Component\HttpFoundation\Session\Session; + +class SetLocale implements MiddlewareInterface +{ + /** @var Translator */ + protected $translator; + + /** @var Session */ + protected $session; + + /** + * @param Translator $translator + * @param Session $session + */ + public function __construct(Translator $translator, Session $session) + { + $this->translator = $translator; + $this->session = $session; + } + + /** + * Process an incoming server request and setting the locale if required + * + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $query = $request->getQueryParams(); + if (isset($query['set-locale']) && $this->translator->hasLocale($query['set-locale'])) { + $locale = $query['set-locale']; + + $this->translator->setLocale($locale); + $this->session->set('locale', $locale); + } + + return $handler->handle($request); + } +} diff --git a/src/helpers.php b/src/helpers.php index 336f81fe..3a182bf7 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -3,6 +3,7 @@ use Engelsystem\Application; use Engelsystem\Config\Config; +use Engelsystem\Helpers\Translator; use Engelsystem\Http\Request; use Engelsystem\Http\Response; use Engelsystem\Http\UrlGenerator; @@ -119,6 +120,40 @@ function session($key = null, $default = null) } /** + * Translate the given message + * + * @param string $key + * @param array $replace + * @return string|Translator + */ +function trans($key = null, $replace = []) +{ + /** @var Translator $translator */ + $translator = app('translator'); + + if (is_null($key)) { + return $translator; + } + + return $translator->translate($key, $replace); +} + +/** + * Translate the given message + * + * @param string $key + * @param array $replace + * @return string + */ +function __($key, $replace = []) +{ + /** @var Translator $translator */ + $translator = app('translator'); + + return $translator->translate($key, $replace); +} + +/** * @param string $path * @param array $parameters * @return UrlGenerator|string diff --git a/tests/Unit/Helpers/TranslationServiceProviderTest.php b/tests/Unit/Helpers/TranslationServiceProviderTest.php new file mode 100644 index 00000000..c5ba7386 --- /dev/null +++ b/tests/Unit/Helpers/TranslationServiceProviderTest.php @@ -0,0 +1,86 @@ +<?php + +namespace Engelsystem\Test\Unit\Helpers; + +use Engelsystem\Config\Config; +use Engelsystem\Helpers\TranslationServiceProvider; +use Engelsystem\Helpers\Translator; +use Engelsystem\Test\Unit\ServiceProviderTest; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\HttpFoundation\Session\Session; + +class TranslationServiceProviderTest extends ServiceProviderTest +{ + /** + * @covers \Engelsystem\Helpers\TranslationServiceProvider::register() + */ + public function testRegister() + { + $app = $this->getApp(['make', 'instance', 'get']); + /** @var Config|MockObject $config */ + $config = $this->createMock(Config::class); + /** @var Session|MockObject $session */ + $session = $this->createMock(Session::class); + /** @var Translator|MockObject $translator */ + $translator = $this->createMock(Translator::class); + + /** @var TranslationServiceProvider|MockObject $serviceProvider */ + $serviceProvider = $this->getMockBuilder(TranslationServiceProvider::class) + ->setConstructorArgs([$app]) + ->setMethods(['initGettext', 'setLocale']) + ->getMock(); + + $serviceProvider->expects($this->once()) + ->method('initGettext'); + + $app->expects($this->exactly(2)) + ->method('get') + ->withConsecutive(['config'], ['session']) + ->willReturnOnConsecutiveCalls($config, $session); + + $defaultLocale = 'fo_OO'; + $locale = 'te_ST.WTF-9'; + $locales = ['fo_OO' => 'Foo', 'fo_OO.BAR' => 'Foo (Bar)', 'te_ST.WTF-9' => 'WTF\'s Testing?']; + $config->expects($this->exactly(2)) + ->method('get') + ->withConsecutive( + ['locales'], + ['default_locale'] + ) + ->willReturnOnConsecutiveCalls( + $locales, + $defaultLocale + ); + + $session->expects($this->once()) + ->method('get') + ->with('locale', $defaultLocale) + ->willReturn($locale); + $session->expects($this->once()) + ->method('set') + ->with('locale', $locale); + + $app->expects($this->once()) + ->method('make') + ->with( + Translator::class, + [ + 'locale' => $locale, + 'locales' => $locales, + 'localeChangeCallback' => [$serviceProvider, 'setLocale'] + ] + ) + ->willReturn($translator); + + $app->expects($this->exactly(2)) + ->method('instance') + ->withConsecutive( + [Translator::class, $translator], + ['translator', $translator] + ); + + $serviceProvider->register(); + } +} + + diff --git a/tests/Unit/Helpers/TranslatorTest.php b/tests/Unit/Helpers/TranslatorTest.php new file mode 100644 index 00000000..396d2b65 --- /dev/null +++ b/tests/Unit/Helpers/TranslatorTest.php @@ -0,0 +1,69 @@ +<?php + +namespace Engelsystem\Test\Unit\Helpers; + +use Engelsystem\Helpers\Translator; +use Engelsystem\Test\Unit\ServiceProviderTest; +use PHPUnit\Framework\MockObject\MockObject; +use stdClass; + +class TranslatorTest extends ServiceProviderTest +{ + /** + * @covers \Engelsystem\Helpers\Translator::__construct() + * @covers \Engelsystem\Helpers\Translator::setLocale() + * @covers \Engelsystem\Helpers\Translator::setLocales() + * @covers \Engelsystem\Helpers\Translator::getLocale() + * @covers \Engelsystem\Helpers\Translator::getLocales() + * @covers \Engelsystem\Helpers\Translator::hasLocale() + */ + public function testInit() + { + $locales = ['te_ST.ER-01' => 'Tests', 'fo_OO' => 'SomeFOO']; + $locale = 'te_ST.ER-01'; + + /** @var callable|MockObject $callable */ + $callable = $this->getMockBuilder(stdClass::class) + ->setMethods(['__invoke']) + ->getMock(); + $callable->expects($this->exactly(2)) + ->method('__invoke') + ->withConsecutive(['te_ST.ER-01'], ['fo_OO']); + + $translator = new Translator($locale, $locales, $callable); + + $this->assertEquals($locales, $translator->getLocales()); + $this->assertEquals($locale, $translator->getLocale()); + + $translator->setLocale('fo_OO'); + $this->assertEquals('fo_OO', $translator->getLocale()); + + $newLocales = ['lo_RM' => 'Lorem', 'ip_SU-M' => 'Ipsum']; + $translator->setLocales($newLocales); + $this->assertEquals($newLocales, $translator->getLocales()); + + $this->assertTrue($translator->hasLocale('ip_SU-M')); + $this->assertFalse($translator->hasLocale('te_ST.ER-01')); + } + + /** + * @covers \Engelsystem\Helpers\Translator::translate() + */ + public function testTranslate() + { + /** @var Translator|MockObject $translator */ + $translator = $this->getMockBuilder(Translator::class) + ->setConstructorArgs(['de_DE.UTF-8', ['de_DE.UTF-8' => 'Deutsch']]) + ->setMethods(['translateGettext']) + ->getMock(); + $translator->expects($this->once()) + ->method('translateGettext') + ->with('My favourite number is %u!') + ->willReturn('Meine Lieblingszahl ist die %u!'); + + $return = $translator->translate('My favourite number is %u!', [3]); + $this->assertEquals('Meine Lieblingszahl ist die 3!', $return); + } +} + + diff --git a/tests/Unit/HelpersTest.php b/tests/Unit/HelpersTest.php index b59b11ee..20d3c2de 100644 --- a/tests/Unit/HelpersTest.php +++ b/tests/Unit/HelpersTest.php @@ -5,10 +5,11 @@ namespace Engelsystem\Test\Unit; use Engelsystem\Application; use Engelsystem\Config\Config; use Engelsystem\Container\Container; +use Engelsystem\Helpers\Translator; use Engelsystem\Http\Request; use Engelsystem\Http\Response; -use Engelsystem\Renderer\Renderer; use Engelsystem\Http\UrlGenerator; +use Engelsystem\Renderer\Renderer; use PHPUnit\Framework\TestCase; use PHPUnit_Framework_MockObject_MockObject as MockObject; use Symfony\Component\HttpFoundation\Session\Session; @@ -195,6 +196,29 @@ class HelpersTest extends TestCase } /** + * @covers \__ + * @covers \trans + */ + public function testTrans() + { + /** @var Translator|MockObject $translator */ + $translator = $this->getMockBuilder(Translator::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->getAppMock('translator', $translator); + + $translator->expects($this->exactly(2)) + ->method('translate') + ->with('Lorem %s Ipsum', ['foo']) + ->willReturn('Lorem foo Ipsum'); + + $this->assertEquals($translator, trans()); + $this->assertEquals('Lorem foo Ipsum', trans('Lorem %s Ipsum', ['foo'])); + $this->assertEquals('Lorem foo Ipsum', __('Lorem %s Ipsum', ['foo'])); + } + + /** * @covers \url */ public function testUrl() diff --git a/tests/Unit/Middleware/SetLocaleTest.php b/tests/Unit/Middleware/SetLocaleTest.php new file mode 100644 index 00000000..c4e9d2a4 --- /dev/null +++ b/tests/Unit/Middleware/SetLocaleTest.php @@ -0,0 +1,71 @@ +<?php + +namespace Engelsystem\Test\Unit\Middleware; + +use Engelsystem\Helpers\Translator; +use Engelsystem\Middleware\SetLocale; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Symfony\Component\HttpFoundation\Session\Session; + +class SetLocaleTest extends TestCase +{ + /** + * @covers \Engelsystem\Middleware\SetLocale::__construct + * @covers \Engelsystem\Middleware\SetLocale::process + */ + public function testRegister() + { + /** @var Translator|MockObject $translator */ + $translator = $this->createMock(Translator::class); + /** @var Session|MockObject $session */ + $session = $this->createMock(Session::class); + /** @var ServerRequestInterface|MockObject $request */ + $request = $this->getMockForAbstractClass(ServerRequestInterface::class); + /** @var RequestHandlerInterface|MockObject $handler */ + $handler = $this->getMockForAbstractClass(RequestHandlerInterface::class); + /** @var ResponseInterface|MockObject $response */ + $response = $this->getMockForAbstractClass(ResponseInterface::class); + + $locale = 'te_ST.UTF8'; + + $request->expects($this->exactly(3)) + ->method('getQueryParams') + ->willReturnOnConsecutiveCalls( + [], + ['set-locale' => 'en_US.UTF8'], + ['set-locale' => $locale] + ); + + $translator->expects($this->exactly(2)) + ->method('hasLocale') + ->withConsecutive( + ['en_US.UTF8'], + [$locale] + ) + ->willReturnOnConsecutiveCalls( + false, + true + ); + $translator->expects($this->once()) + ->method('setLocale') + ->with($locale); + + $session->expects($this->once()) + ->method('set') + ->with('locale', $locale); + + $handler->expects($this->exactly(3)) + ->method('handle') + ->with($request) + ->willReturn($response); + + $middleware = new SetLocale($translator, $session); + $middleware->process($request, $handler); + $middleware->process($request, $handler); + $middleware->process($request, $handler); + } +} |