Skip to content
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
2 changes: 1 addition & 1 deletion .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

$finder = (new PhpCsFixer\Finder())
->in([__DIR__.'/src', __DIR__.'/tests'])
->in([__DIR__.'/config', __DIR__.'/src', __DIR__.'/tests'])
;

return (new PhpCsFixer\Config())
Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,29 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- Bundle configuration tree under the `api_platform_translation` alias:
`enabled_locales` (inherits `framework.enabled_locales` when empty),
`fallback_locale` (inherits `framework.default_locale` when null) and
`locale_resolution` (ordered locale sources, `query_param` and
`accept_language`; each can be removed or reordered) (#85)
- FQCN alias for the `Translator` service, so it can be autowired by type (#85)

### Changed
- Modern bundle layout: the bundle class extends `AbstractBundle` and services
are defined in `config/services.php`; `src/Resources/config/services.yml` is
gone (#85)
- A request without an `Accept-Language` header now falls through to the next
configured locale source instead of being resolved by header negotiation;
with the default resolution order the observable behavior is unchanged (#85)
- Dependencies: `symfony/config`, `symfony/http-kernel` and
`symfony/deprecation-contracts` are direct requirements now that the bundle
uses them directly; `symfony/yaml` is no longer required (#85)

### Deprecated
- `ApiPlatformTranslationExtension`, to be removed in 3.0; the bundle registers
its extension itself through `AbstractBundle` (#85)

## [2.0.1] - 2026-07-07

### Fixed
Expand Down
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,37 @@ Installation:
composer require locastic/api-platform-translation-bundle
```

Configuration:
--------------
The bundle works without any configuration. All options and their defaults:

```yaml
# config/packages/api_platform_translation.yaml
api_platform_translation:
# Locales accepted from the ?locale= query parameter and Accept-Language
# negotiation. Empty (the default) inherits framework.enabled_locales;
# when both are empty, any requested locale is accepted.
enabled_locales: []

# Locale used when a translation for the current locale does not exist.
# null (the default) inherits framework.default_locale.
fallback_locale: null

# Ordered sources the request locale is resolved from; the first source
# producing a locale wins. Remove a source to disable it.
locale_resolution:
- query_param
- accept_language
```

For example, to resolve the locale from the `Accept-Language` header only and
ignore the `?locale=` query parameter:

```yaml
api_platform_translation:
locale_resolution: [accept_language]
```

Implementation:
--------------
**Translatable entity:**
Expand Down Expand Up @@ -192,7 +223,7 @@ Usage:

`Accept-Language: de`

**Restricting locales:** if [`framework.enabled_locales`](https://symfony.com/doc/current/reference/configuration/framework.html#enabled-locales) is configured, only those locales are accepted: a `?locale=` value outside the list and non-matching `Accept-Language` headers fall back to the default locale. When `enabled_locales` is not configured (Symfony's default), any requested locale is accepted.
**Restricting locales:** if [`framework.enabled_locales`](https://symfony.com/doc/current/reference/configuration/framework.html#enabled-locales) or the bundle's own `enabled_locales` option (which takes precedence) is configured, only those locales are accepted: a `?locale=` value outside the list and non-matching `Accept-Language` headers fall back to the default locale. When neither is configured (Symfony's default), any requested locale is accepted.

**Serialization group for displaying all translations:**

Expand Down
27 changes: 27 additions & 0 deletions UPGRADE-2.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# UPGRADE FROM 2.0 TO 2.1

2.1 contains no BC breaks. Typical installs need no changes.

## Deprecations

1. `Locastic\ApiPlatformTranslationBundle\DependencyInjection\ApiPlatformTranslationExtension`
is deprecated and will be removed in 3.0. The bundle now extends
`AbstractBundle` and registers its extension itself. The class keeps working
if you instantiate it directly, but stop referencing it: registering the
bundle in `config/bundles.php` is all that is needed.

## Internal layout changes

These only affect you if you referenced bundle files by path:

1. Services are now defined in `config/services.php` at the bundle root;
`src/Resources/config/services.yml` no longer exists. Service IDs and
behavior are unchanged.
2. `symfony/yaml` is no longer a dependency of the bundle. If your application
used it without requiring it, add it to your own `composer.json`.

## New configuration

The bundle now has a configuration tree under `api_platform_translation`
(`enabled_locales`, `fallback_locale`, `locale_resolution`). All defaults
preserve 2.0 behavior; see the README "Configuration" section.
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
"api-platform/symfony": "^3.4 || ^4.0",
"doctrine/orm": "^3.0",
"doctrine/doctrine-bundle": "^2.13 || ^3.0",
"symfony/translation": "^6.4 || ^7.0 || ^8.0",
"symfony/config": "^6.4 || ^7.0 || ^8.0",
"symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0",
"symfony/yaml": "^6.4 || ^7.0 || ^8.0"
"symfony/deprecation-contracts": "^2.5 || ^3.0",
"symfony/http-kernel": "^6.4 || ^7.0 || ^8.0",
"symfony/translation": "^6.4 || ^7.0 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^10.5",
Expand Down
48 changes: 48 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Locastic\ApiPlatformTranslationBundle\EventListener\AssignLocaleListener;
use Locastic\ApiPlatformTranslationBundle\Serializer\TranslatableItemDenormalizer;
use Locastic\ApiPlatformTranslationBundle\Translation\Translator;

return static function (ContainerConfigurator $container): void {
$services = $container->services();

$services->set('locastic_api_platform_translation.translation.translator', Translator::class)
->args([
service('translator'),
service('request_stack'),
param('kernel.default_locale'),
param('locastic_api_platform_translation.enabled_locales'),
param('locastic_api_platform_translation.locale_resolution'),
]);

$services->alias(Translator::class, 'locastic_api_platform_translation.translation.translator');

$services->set('locastic_api_platform_translation.listener.assign_locale', AssignLocaleListener::class)
->args([
service('locastic_api_platform_translation.translation.translator'),
param('locastic_api_platform_translation.fallback_locale'),
])
->tag('doctrine.event_listener', ['event' => 'postLoad'])
->tag('doctrine.event_listener', ['event' => 'prePersist']);

// Serializer: in-place, merge denormalization of nested translations
$services->set('locastic_api_platform_translation.serializer.translatable_denormalizer', TranslatableItemDenormalizer::class)
->tag('serializer.normalizer', ['priority' => 100]);

$services->alias(TranslatableItemDenormalizer::class, 'locastic_api_platform_translation.serializer.translatable_denormalizer');

// Filters
$services->set('locastic_api_platform_translation.filter.translation_groups')
->parent('api_platform.serializer.group_filter')
->args([
'groups',
false,
['translations'],
])
->tag('api_platform.filter', ['id' => 'translation.groups']);
};
1 change: 1 addition & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
parameters:
level: 6
paths:
- config
- src
- tests
ignoreErrors:
Expand Down
50 changes: 48 additions & 2 deletions src/ApiPlatformTranslationBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,54 @@

namespace Locastic\ApiPlatformTranslationBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Locastic\ApiPlatformTranslationBundle\Translation\Translator;
use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;

class ApiPlatformTranslationBundle extends Bundle
class ApiPlatformTranslationBundle extends AbstractBundle
{
protected string $extensionAlias = 'api_platform_translation';

public function configure(DefinitionConfigurator $definition): void
{
$definition->rootNode()
->children()
->arrayNode('enabled_locales')
->info('Locales accepted from the ?locale= query parameter and Accept-Language negotiation. Empty inherits framework.enabled_locales; when both are empty, any locale is accepted.')
->scalarPrototype()->cannotBeEmpty()->end()
->defaultValue([])
->end()
->scalarNode('fallback_locale')
->info('Locale stamped as the fallback on loaded and persisted translatables. Defaults to kernel.default_locale.')
->defaultNull()
->end()
->arrayNode('locale_resolution')
->info('Ordered list of sources the request locale is resolved from; the first source producing a locale wins. Remove a source to disable it.')
->performNoDeepMerging()
->enumPrototype()
->values([Translator::RESOLUTION_QUERY_PARAM, Translator::RESOLUTION_ACCEPT_LANGUAGE])
->end()
->defaultValue([Translator::RESOLUTION_QUERY_PARAM, Translator::RESOLUTION_ACCEPT_LANGUAGE])
->validate()
->ifTrue(static fn (array $sources): bool => \count($sources) !== \count(array_unique($sources)))
->thenInvalid('Locale resolution sources must not repeat: %s')
->end()
->end()
->end();
}

/**
* @param array{enabled_locales: list<string>, fallback_locale: ?string, locale_resolution: list<string>} $config
*/
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
$container->parameters()
->set('locastic_api_platform_translation.enabled_locales', $config['enabled_locales'] ?: '%kernel.enabled_locales%')
->set('locastic_api_platform_translation.fallback_locale', $config['fallback_locale'] ?? '%kernel.default_locale%')
->set('locastic_api_platform_translation.locale_resolution', $config['locale_resolution']);

$container->import('../config/services.php');
}
}
26 changes: 18 additions & 8 deletions src/DependencyInjection/ApiPlatformTranslationExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,32 @@
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;

/**
* This is the class that loads and manages your bundle configuration.
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
* @deprecated since locastic/api-platform-translation-bundle 2.1, to be removed in 3.0;
* the bundle extends AbstractBundle and registers its extension itself
*/
class ApiPlatformTranslationExtension extends Extension
{
public function __construct()
{
trigger_deprecation(
'locastic/api-platform-translation-bundle',
'2.1',
'The "%s" class is deprecated and will be removed in 3.0, the bundle registers its own extension through AbstractBundle.',
self::class,
);
}

public function load(array $configs, ContainerBuilder $container): void
{
$directory =
__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'config';
$container->setParameter('locastic_api_platform_translation.enabled_locales', '%kernel.enabled_locales%');
$container->setParameter('locastic_api_platform_translation.fallback_locale', '%kernel.default_locale%');
$container->setParameter('locastic_api_platform_translation.locale_resolution', ['query_param', 'accept_language']);

$loader = new Loader\YamlFileLoader($container, new FileLocator($directory));
$loader->load('services.yml');
$loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__, 2).DIRECTORY_SEPARATOR.'config'));
$loader->load('services.php');
}

public function getAlias(): string
Expand Down
35 changes: 0 additions & 35 deletions src/Resources/config/services.yml

This file was deleted.

Loading
Loading