Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Translator] Add configuration to filter dumped translations by domain #1930

Merged
merged 1 commit into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Translator/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 2.19.0

- Add configuration to filter dumped translations by domain.

## 2.16.0

- Increase version range of `intl-messageformat` to `^10.5.11`, in order to see
Expand Down
20 changes: 19 additions & 1 deletion src/Translator/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ After installing the bundle, the following file should be created, thanks to the
Usage
-----

When warming up the Symfony cache, all of your translations will be dumped as JavaScript into the ``var/translations/`` directory.
When warming up the Symfony cache, your translations will be dumped as JavaScript into the ``var/translations/`` directory.
For a better developer experience, TypeScript types definitions are also generated aside those JavaScript files.

Then, you will be able to import those JavaScript translations in your assets.
Expand All @@ -75,6 +75,24 @@ Don't worry about your final bundle size, only the translations you use will be

This package requires the `translator` to be enabled in your Symfony application. If you don't use the `translator` service, the warmup command will not generate any translations.

Configuring the dumped translations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

By default, all your translations will be exported. You can restrict the dumped messages by either
including or excluding translation domains in your ``config/packages/ux_translator.yaml`` file:

.. code-block:: yaml

ux_translator:
domains: ~ # Include all the domains

domains: foo # Include only domain 'foo'
domains: '!foo' # Include all domains, except 'foo'

domains: [foo, bar] # Include only domains 'foo' and 'bar'
domains: ['!foo', '!bar'] # Include all domains, except 'foo' and 'bar'


Configuring the default locale
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
54 changes: 54 additions & 0 deletions src/Translator/src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;

/**
* @author Hugo Alliaume <hugo@alliau.me>
Expand All @@ -28,6 +29,59 @@ public function getConfigTreeBuilder(): TreeBuilder
$rootNode
->children()
->scalarNode('dump_directory')->defaultValue('%kernel.project_dir%/var/translations')->end()
->arrayNode('domains')
->info('List of domains to include/exclude from the generated translations. Prefix with a `!` to exclude a domain.')
->children()
->scalarNode('type')
->validate()
->ifNotInArray(['inclusive', 'exclusive'])
->thenInvalid('The type of domains has to be inclusive or exclusive')
->end()
->end()
->arrayNode('elements')
->scalarPrototype()->end()
->end()
->end()
->canBeUnset()
->beforeNormalization()
->ifString()
->then(fn ($v) => ['elements' => [$v]])
->end()
->beforeNormalization()
->ifTrue(function ($v) { return \is_array($v) && is_numeric(key($v)); })
->then(function ($v) { return ['elements' => $v]; })
->end()
->validate()
->always(function ($v) {
$isExclusive = null;
$elements = [];
if (isset($v['type'])) {
$isExclusive = 'exclusive' === $v['type'];
}
foreach ($v['elements'] as $domain) {
if (str_starts_with($domain, '!')) {
if (false === $isExclusive) {
throw new InvalidConfigurationException('You cannot mix included and excluded domains.');
}
$isExclusive = true;
$elements[] = substr($domain, 1);
} else {
if (true === $isExclusive) {
throw new InvalidConfigurationException('You cannot mix included and excluded domains.');
}
$isExclusive = false;
$elements[] = $domain;
}
}

if (!\count($elements)) {
return null;
}

return ['type' => $isExclusive ? 'exclusive' : 'inclusive', 'elements' => array_unique($elements)];
})
->end()
->end()
->end()
;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,15 @@ public function load(array $configs, ContainerBuilder $container)
$loader = (new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/../config')));
$loader->load('services.php');

$container->getDefinition('ux.translator.translations_dumper')->setArgument(0, $config['dump_directory']);
$dumperDefinition = $container->getDefinition('ux.translator.translations_dumper');
$dumperDefinition->setArgument(0, $config['dump_directory']);

if (isset($config['domains'])) {
$method = 'inclusive' === $config['domains']['type'] ? 'addIncludedDomain' : 'addExcludedDomain';
foreach ($config['domains']['elements'] as $domainName) {
$dumperDefinition->addMethodCall($method, [$domainName]);
}
}
}

public function prepend(ContainerBuilder $container)
Expand Down
25 changes: 25 additions & 0 deletions src/Translator/src/TranslationsDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
*/
class TranslationsDumper
{
private array $excludedDomains = [];
private array $includedDomains = [];

public function __construct(
private string $dumpDir,
private MessageParametersExtractor $messageParametersExtractor,
Expand Down Expand Up @@ -84,6 +87,22 @@ public function dump(MessageCatalogueInterface ...$catalogues): void
);
}

public function addExcludedDomain(string $domain): void
{
if ($this->includedDomains) {
throw new \LogicException('You cannot set both "excluded_domains" and "included_domains" at the same time.');
}
$this->excludedDomains[] = $domain;
}

public function addIncludedDomain(string $domain): void
{
if ($this->excludedDomains) {
throw new \LogicException('You cannot set both "excluded_domains" and "included_domains" at the same time.');
}
$this->includedDomains[] = $domain;
}

/**
* @return array<MessageId, array<Domain, array<Locale, string>>>
*/
Expand All @@ -94,6 +113,12 @@ private function getTranslations(MessageCatalogueInterface ...$catalogues): arra
foreach ($catalogues as $catalogue) {
$locale = $catalogue->getLocale();
foreach ($catalogue->getDomains() as $domain) {
if (\in_array($domain, $this->excludedDomains, true)) {
continue;
}
if ($this->includedDomains && !\in_array($domain, $this->includedDomains, true)) {
continue;
}
foreach ($catalogue->all($domain) as $id => $message) {
$realDomain = $catalogue->has($id, $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe leverage CatalogueMetadataAwareInterface here ?

? $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX
Expand Down
Loading