Reputation: 1031
According to this symfony documentation article it should be possible to load your own translations format. So I'm trying to load them from the database.
But whatever I try to make this work. It just doesn't.
Is using a Database Loader the right way to go or should I do something else to load translations from the database?
App\Translation\Loader\DatabaseLoader.php:
namespace App\Translation\Loader;
use App\Entity\Translation;
use App\Domain\TranslationManagerInterface;
use Symfony\Component\Translation\Loader\LoaderInterface;
use Symfony\Component\Translation\MessageCatalogue;
/**
* Database Loader
*/
class DatabaseLoader implements LoaderInterface
{
/**
* Translation Manager
*
* @var TranslationManagerInterface
*/
private $_translationManager;
/**
* Constructor
*
* @param TranslationManagerInterface $translationManager
*/
public function __construct(TranslationManagerInterface $translationManager)
{
$this->_translationManager = $translationManager;
}
/**
* {@inheritDoc}
*/
public function load($resource, $locale, $domain = 'general')
{
$translations = $this->_translationManager->findByLocaleAndDomain($locale, $domain);
$catalogue = new MessageCatalogue($locale);
/* @var Translation $translation */
foreach($translations as $translation)
{
$catalogue->set(
$translation->getToken(),
$translation->getContent(),
$translation->getDomain()()
);
}
}
}
config/services.yaml:
# database loader
translation.loader.database:
class: 'App\Translation\Loader\DatabaseLoader'
arguments: [ 'App\Domain\TranslationManager' ]
tags:
- { name: translation.loader, alias: database, priority: 100 }
I've even tried manually adding the the loader to the translator with a event listener on the kernel request. I don't want to use a full bundle as I already a database filled with translations
Upvotes: 2
Views: 7702
Reputation: 1031
I extended the Translator class to avoid having to create a dummy translation file for every domain in the app.
To override the standard Translator component a Compiler Pass needs to be loaded.
Translator Compiler Pass:
namespace App\DependencyInjection\Compiler;
use App\Domain\TranslationManagerInterface;
use App\Translation\Translator;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class TranslatorCompilerPass implements CompilerPassInterface
{
private $translatorServiceId;
private $readerServiceId;
private $loaderTag;
private $debugCommandServiceId;
private $updateCommandServiceId;
/**
* @param string $translatorServiceId
* @param string $readerServiceId
* @param string $loaderTag
* @param string $debugCommandServiceId
* @param string $updateCommandServiceId
*/
public function __construct(
string $translatorServiceId = 'translator.default',
string $readerServiceId = 'translation.reader',
string $loaderTag = 'translation.loader',
string $debugCommandServiceId = 'console.command.translation_debug',
string $updateCommandServiceId = 'console.command.translation_update'
) {
$this->translatorServiceId = $translatorServiceId;
$this->readerServiceId = $readerServiceId;
$this->loaderTag = $loaderTag;
$this->debugCommandServiceId = $debugCommandServiceId;
$this->updateCommandServiceId = $updateCommandServiceId;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$loaders = [];
$loaderRefs = [];
foreach ($container->findTaggedServiceIds($this->loaderTag, true) as $id => $attributes) {
$loaderRefs[$id] = new Reference($id);
$loaders[$id][] = $attributes[0]['alias'];
if (isset($attributes[0]['legacy-alias'])) {
$loaders[$id][] = $attributes[0]['legacy-alias'];
}
}
if ($container->hasDefinition($this->readerServiceId)) {
$definition = $container->getDefinition($this->readerServiceId);
foreach ($loaders as $id => $formats) {
foreach ($formats as $format) {
$definition->addMethodCall('addLoader', [$format, $loaderRefs[$id]]);
}
}
}
$definition = $container->findDefinition($this->translatorServiceId);
$definition
->setClass(Translator::class)
->replaceArgument(0, null)
->replaceArgument(1, $definition->getArgument(1))
->replaceArgument(2, $definition->getArgument(4)["cache_dir"])
->replaceArgument(3, $definition->getArgument(4)["debug"])
->replaceArgument(4, $definition->getArgument(4)["resource_files"])
->addMethodCall("setTranslationManager", [new Reference(TranslationManagerInterface::class)])
;
if (!$container->hasParameter('twig.default_path')) {
return;
}
if ($container->hasDefinition($this->debugCommandServiceId)) {
$container->getDefinition($this->debugCommandServiceId)->replaceArgument(4, $container->getParameter('twig.default_path'));
}
if ($container->hasDefinition($this->updateCommandServiceId)) {
$container->getDefinition($this->updateCommandServiceId)->replaceArgument(5, $container->getParameter('twig.default_path'));
}
}
}
Translator class:
namespace App\Translation;
use App\Domain\TranslationManagerInterface;
use App\Entity\Model\Translation\Translation;
use Symfony\Component\Translation\Translator as SymfonyTranslator;
class Translator extends SymfonyTranslator
{
/**
* @var TranslationManagerInterface
*/
private $_translationManager;
/**
* @param TranslationManagerInterface $translationManager
*/
public function setTranslationManager(TranslationManagerInterface $translationManager)
{
$this->_translationManager = $translationManager;
}
/**
* {@inheritdoc}
*/
public function trans($id, array $parameters = [], $domain = null, $locale = null)
{
if (null === $locale && null !== $this->getLocale()) {
$locale = $this->getLocale();
}
if (null === $locale) {
$locale = $this->getFallbackLocales()[0]; // fallback locale
}
// the translation manager automagically creates new translation entries if it doesn't exist yet
$translation = $this->_translationManager->findOneByTokenLocaleAndDomain($id, $locale, $domain);
// check if it exists
if (isset($translation) && null !== $translation && $translation instanceof Translation) {
return strtr($translation->getContent(), $parameters);
}
// fallback
return parent::trans($id, $parameters, $domain, $locale);
}
}
Upvotes: 2
Reputation: 51
Tested with Symfony 4.2.4: Maybe this answer comes a little late, but I wanted to help other visitors with same problem who were as desperate as I was.
There is a small error in your class DatabaseLoader. You must return $catalogue in function load().
public function load($resource, $locale, $domain = 'general')
{
$translations = $this->_translationManager->findByLocaleAndDomain($locale, $domain);
$catalogue = new MessageCatalogue($locale);
/* @var Translation $translation */
foreach($translations as $translation)
{
$catalogue->set(
$translation->getToken(),
$translation->getContent(),
$translation->getDomain()()
);
}
// you must return $catalogue here
return $catalogue;
}
You must create "fake" translation files to trigger your loader. See here: https://stackoverflow.com/a/33300593/6709953
Example: For translation domain "messages" and locale "en" there must be an empty file translations/messages.en.database
(Small hint: in config/services.yaml you set "alias: database", this means the suffix of the translation file must be .database)
You must remove old competing translation files if they exist in folder translation/. So for example if there is also file translations/messages.en.yaml you must delete it.
Upvotes: 4