Skip to content
Permalink
Browse files

Implement message formatter (#19)

Co-authored-by: Alexander Makarov <sam@rmcreative.ru>
  • Loading branch information
thenotsoft and samdark committed Feb 2, 2020
1 parent 1fc7ea9 commit 714bef28e563b053b116847f9e247c5cfdf5acff
Showing with 187 additions and 68 deletions.
  1. +58 −43 src/Translator/Translator.php
  2. +18 −1 src/TranslatorInterface.php
  3. +111 −24 tests/TranslatorTest.php
@@ -1,7 +1,10 @@
<?php

declare(strict_types=1);

namespace Yiisoft\I18n\Translator;

use Yiisoft\I18n\MessageFormatterInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Yiisoft\I18n\Event\MissingTranslationEvent;
use Yiisoft\I18n\Locale;
@@ -10,75 +13,87 @@

class Translator implements TranslatorInterface
{
/**
* @var \Yiisoft\I18n\MessageReaderInterface
*/
private $messageReader;
/**
* @var \Psr\EventDispatcher\EventDispatcherInterface
*/
private $eventDispatcher;

/**
* @var array
*/
private $messages = [];
private EventDispatcherInterface $eventDispatcher;
private MessageReaderInterface $messageReader;
private ?MessageFormatterInterface $messageFormatter;
private ?string $locale = null;
private ?string $defaultLocale = null;

public function __construct(
EventDispatcherInterface $eventDispatcher,
MessageReaderInterface $messageReader
MessageReaderInterface $messageReader,
MessageFormatterInterface $messageFormatter = null
) {
$this->messageReader = $messageReader;
$this->eventDispatcher = $eventDispatcher;
$this->messageFormatter = $messageFormatter;
}

/**
* Translates a message to the specified language.
* If a translation is not found, a {{@see \Yiisoft\I18n\Event\MissingTranslationEvent} event will be triggered.
* Sets the current locale.
*
* @param string $message the message to be translated
* @param string $category the message category
* @param string $localeString the target locale
* @return string|null the translated message or false if translation wasn't found or isn't required
* @param string $locale The locale
*/
public function translate(?string $message, string $category = null, string $localeString = null): ?string
public function setLocale(string $locale): void
{
$this->locale = $locale;
}

/**
* Returns the current locale.
*
* @return string The locale
*/
public function getLocale(): string
{
return $this->locale ?? $this->getDefaultLocale();
}

public function setDefaultLocale(string $locale): void
{
$this->defaultLocale = $locale;
}

public function translate(
string $id,
array $parameters = [],
string $category = null,
string $localeString = null
): ?string {
if ($localeString === null) {
$localeString = $this->getDefaultLocale();
$localeString = $this->getLocale();
}

if ($category === null) {
$category = $this->getDefaultCategory();
}

$messages = $this->getMessages($category, $localeString);
$message = $this->messageReader->one($id, $localeString . '/' . $category);
if ($message === null) {
$missingTranslation = new MissingTranslationEvent($category, $localeString, $id);
$this->eventDispatcher->dispatch($missingTranslation);

if (array_key_exists($message, $messages)) {
return $messages[$message];
}

$missingTranslation = new MissingTranslationEvent($category, $localeString, $message);
$this->eventDispatcher->dispatch($missingTranslation);
$locale = new Locale($localeString);
$fallback = $locale->fallbackLocale();

$locale = new Locale($localeString);
$fallback = $locale->fallbackLocale();
if ($fallback->asString() !== $locale->asString()) {
return $this->translate($id, $parameters, $category, $fallback->asString());
}

if ($fallback->asString() !== $locale->asString()) {
return $messages[$message] = $this->translate($message, $category, $fallback->asString());
}
$defaultFallback = (new Locale($this->getDefaultLocale()))->fallbackLocale();

return $messages[$message] = $message;
}
if ($defaultFallback->asString() !== $fallback->asString()) {
return $this->translate($id, $parameters, $category, $this->getDefaultLocale());
}

private function getMessages(string $category, string $language): array
{
$key = $language . '/' . $category;
$message = $id;
}

if (!array_key_exists($key, $this->messages)) {
$this->messages[$key] = $this->messageReader->all($key);
if ($this->messageFormatter === null) {
return $message;
}

return $this->messages[$key];
return $this->messageFormatter->format($message, $parameters, $localeString);
}

protected function getDefaultCategory(): string
@@ -88,6 +103,6 @@ protected function getDefaultCategory(): string

protected function getDefaultLocale(): string
{
return \Locale::getDefault();
return $this->defaultLocale ?? \Locale::getDefault();
}
}
@@ -1,8 +1,25 @@
<?php

declare(strict_types=1);

namespace Yiisoft\I18n;

interface TranslatorInterface
{
public function translate(?string $message, string $category = null, string $locale = null): ?string;
/**
* Translates a message to the specified language.
* If a translation is not found, a {{@see \Yiisoft\I18n\Event\MissingTranslationEvent} event will be triggered.
*
* @param string $id the id of the message to be translated. It can be either artificial ID or the source message.
* @param array $parameters An array of parameters for the message
* @param string $category the message category
* @param string $locale the target locale
* @return string|null the translated message or false if translation wasn't found or isn't required
*/
public function translate(
string $id,
array $parameters = [],
string $category = null,
string $locale = null
): ?string;
}
@@ -5,37 +5,82 @@
use PHPUnit\Framework\TestCase;
use Psr\EventDispatcher\EventDispatcherInterface;
use Yiisoft\I18n\Event\MissingTranslationEvent;
use Yiisoft\I18n\MessageFormatterInterface;
use Yiisoft\I18n\MessageReaderInterface;
use Yiisoft\I18n\Translator\Translator;

class TranslatorTest extends TestCase
final class TranslatorTest extends TestCase
{
/**
* @dataProvider getTranslations
* @param $message
* @param $translate
* @param string|null $id
* @param string|null $translation
* @param string|null $expected
* @param array $parameters
* @param string|null $category
*/
public function testTranslation($message, $translate)
{
$messageReader = $this->getMockBuilder(MessageReaderInterface::class)
public function testTranslation(
?string $id,
?string $translation,
?string $expected,
array $parameters,
?string $category
): void {
$messageReader = $this->createMessageReader([$id => $translation]);

$messageFormatter = null;
if ([] !== $parameters) {
$messageFormatter = $this->getMockBuilder(MessageFormatterInterface::class)->getMock();
$messageFormatter
->method('format')
->willReturn($this->formatMessage($translation, $parameters));
}

/**
* @var $translator Translator
*/
$translator = $this->getMockBuilder(Translator::class)
->setConstructorArgs(
[
$this->createMock(EventDispatcherInterface::class),
$messageReader,
$messageFormatter
]
)
->enableProxyingToOriginalMethods()
->getMock();

$this->assertEquals($expected, $translator->translate($id, $parameters, $category));
}

public function testFallbackLocale(): void
{
$category = 'test';
$message = 'test';
$fallbackMessage = 'test de locale';

$messageReader = $this->createMessageReader(['test' => $fallbackMessage]);

/**
* @var $translator Translator
*/
$translator = $this->getMockBuilder(Translator::class)
->setConstructorArgs([
$this->createMock(EventDispatcherInterface::class),
$messageReader,
])
->setConstructorArgs(
[
$this->createMock(EventDispatcherInterface::class),
$messageReader,
]
)
->enableProxyingToOriginalMethods()
->getMock();

$messageReader->expects($this->once())
->method('all')
->willReturn([$message => $translate]);
$translator->setDefaultLocale('de');


$this->assertEquals($translate, $translator->translate($message));
$this->assertEquals($fallbackMessage, $translator->translate($message, [], $category, 'en'));
}

public function testMissingEventTriggered()
public function testMissingEventTriggered(): void
{
$category = 'test';
$language = 'en';
@@ -45,28 +90,70 @@ public function testMissingEventTriggered()
->setMethods(['dispatch'])
->getMock();

/**
* @var $translator Translator
*/
$translator = $this->getMockBuilder(Translator::class)
->setConstructorArgs([
$eventDispatcher,
$this->createMock(MessageReaderInterface::class),
])
->setConstructorArgs(
[
$eventDispatcher,
$this->createMessageReader([]),
]
)
->enableProxyingToOriginalMethods()
->getMock();

$translator->setDefaultLocale('de');

$eventDispatcher
->expects($this->once())
->expects($this->at(0))
->method('dispatch')
->with(new MissingTranslationEvent($category, $language, $message));

$translator->translate($message, $category, $language);
$translator->translate($message, [], $category, $language);
}

public function getTranslations(): array
{
return [
[null, null],
[1, 1],
['test', 'test'],
['test', 'test', 'test', [], null],
['test {param}', 'translated {param}', 'translated param-value', ['param' => 'param-value'], null],
];
}

private function formatMessage(string $message, array $parameters): string
{
foreach ($parameters as $key => $value) {
$message = str_replace('{' . $key . '}', $value, $message);
}

return $message;
}

private function createMessageReader(array $messages): MessageReaderInterface
{
return new class($messages) implements MessageReaderInterface {
private array $messages;

public function __construct(array $messages)
{
$this->messages = $messages;
}

public function all($context = null): array
{
return $this->messages;
}

public function one(string $id, $context = null): ?string
{
return $this->messages[$id] ?? null;
}

public function plural(string $id, int $count, $context = null): ?string
{
return $this->messages[$id] ?? null;
}
};
}
}

0 comments on commit 714bef2

Please sign in to comment.
You can’t perform that action at this time.