Skip to content

[Translation] Extract locale fallback computation into a dedicated class #45553

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

Open
wants to merge 10 commits into
base: 7.4
Choose a base branch
from

Conversation

mpdude
Copy link
Contributor

@mpdude mpdude commented Feb 24, 2022

Q A
Branch? 6.2
Bug fix? no
New feature? yes
Deprecations? yes
Tickets
License MIT
Doc PR

I am working on an application that in addition to Symfony-based translations needs to work with its own translation system. It would be 💯 if both parts could easily use the same locale hierarchy and fallback rules.

Since fallback resolution is in fact very involved, this PR aims to expose the logic in a dedicated new class, the FallbackLocaleProvider.

In the future, when the fallback locales are to be changed, a new instance of FallbackLocaleProviderInterface needs to be set on the Translator.

TODO

  • Update CHANGELOG
  • Add to docs if required
  • Provide interface for the new class
  • Add service definition to FrameworkBundle
  • Check if tests can be refactored/split up with this
  • Move \Symfony\Component\Translation\Translator::assertValidLocale() to a place where it can also be used by the FallbackLocaleProvider
  • Make sure deprecations/transition path is sane and add necessary documentation

@carsonbot
Copy link

Hey!

I think @rvanlaak has recently worked with this code. Maybe they can help review this?

Cheers!

Carsonbot

Copy link
Contributor

@rvanlaak rvanlaak left a comment

Choose a reason for hiding this comment

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

I see value in the usecase where you want to be able to take more control over the fallback locales. Besides that it also abstracts out a responsibility from the big Translator class, so 👍 for continuing with the remaining TODOs from the PR description.

@mpdude mpdude force-pushed the fallback-locale-provider branch 3 times, most recently from 7ff72be to 1913770 Compare February 28, 2022 07:49
@mpdude mpdude force-pushed the fallback-locale-provider branch from 826daa0 to 532a72c Compare February 28, 2022 07:58
@@ -59,6 +61,7 @@
'debug' => param('kernel.debug'),
],
abstract_arg('enabled locales'),
service('translation.fallback_locale_provider')
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this correct, or should I use FallbackLocaleProviderInterface::class here?

@mpdude mpdude force-pushed the fallback-locale-provider branch from 48bd240 to 9c39534 Compare February 28, 2022 08:07
@mpdude mpdude force-pushed the fallback-locale-provider branch from 9c39534 to 47fe72a Compare February 28, 2022 08:34
@mpdude
Copy link
Contributor Author

mpdude commented Feb 28, 2022

There is a possible mistake people might run into when they start using the FallbackLocaleProvider:

The Translator needs to know when the fallback locales are changed in order to reset built-in caching. Re-configuring the FallbackLocaleProvider directly without going through Translator::setFallbackLocales() may lead to wrong results.

One solution I see would be:

  • Deprecate FallbackLocaleProvider::setFallbackLocales(), create a new FallbackLocaleProvider instance instead. In 7.0, this will leave FallbackLocaleProvider immutable.
  • Deprecate Translator::setFallbackLocales(), use a new setFallbackLocaleProvider() method instead that sets the new instance.

Would that be acceptable?

@mpdude
Copy link
Contributor Author

mpdude commented Feb 28, 2022

Additionally, we might get rid of getFallbackLocales() in DataCollectorTranslator and LoggingTranslator if the FallbackLocaleProvider were used directly.

@mpdude
Copy link
Contributor Author

mpdude commented Mar 3, 2022

I think I need some opinions and/or feedback on this before I can proceed. 👀 🙏🏻

@fabpot fabpot modified the milestones: 6.1, 6.2 May 20, 2022
@carsonbot carsonbot changed the title Extract locale fallback computation into a dedicated class [Translation] Extract locale fallback computation into a dedicated class Jul 29, 2022
@welcoMattic
Copy link
Member

There is a possible mistake people might run into when they start using the FallbackLocaleProvider:

The Translator needs to know when the fallback locales are changed in order to reset built-in caching. Re-configuring the FallbackLocaleProvider directly without going through Translator::setFallbackLocales() may lead to wrong results.

One solution I see would be:

* Deprecate `FallbackLocaleProvider::setFallbackLocales()`, create a new `FallbackLocaleProvider` instance instead. In 7.0, this will leave `FallbackLocaleProvider` immutable.

As FallbackLocaleProvider is a new feature, we do not have to deprecate anything, just change it. IMHO, it's better to design well this feature, to avoid known issues later which makes us support a BC layer.

* Deprecate `Translator::setFallbackLocales()`, use a new `setFallbackLocaleProvider()` method instead that sets the new instance.

Would that be acceptable?

I guess it's acceptable, but we need to provide a BC layer, to let Translator::setFallbackLocales() works until 7.0.

mpdude and others added 3 commits August 1, 2022 09:28
Co-authored-by: Mathieu Santostefano <msantostefano@protonmail.com>
@mpdude
Copy link
Contributor Author

mpdude commented Aug 1, 2022

@welcoMattic and @rvanlaak Thanks for your initial reviews!

I haven't dealt with deprecations and replacement code in Symfony for a long time. Do you think you could help me to get this straight?

The getFallbackLocales() methods on the DataCollectorTranslator and LoggingTranslator are also somewhat strange, since they need to proxy methods that are not part of the TranslatorInterface... maybe this will get easier as well?

@mpdude
Copy link
Contributor Author

mpdude commented Aug 1, 2022

I also renamed fallbackLocales to ultimateFallbackLocales to make it more clear that in fact, these are not the only fallbacks – they are the last fallbacks after the ones derived based on ICU data.

*/
private array $ultimateFallbackLocales;

private ?array $parentLocales = null;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should this be static? It is used to cache parsed JSON data, so we might want some extra efficiency.

Copy link
Member

Choose a reason for hiding this comment

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

It could be, I've no strong opinion on it. @nicolas-grekas WDYT?

*
* @return string[]
*/
public function getUltimateFallbackLocales(): array;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Given that it is @internal, should it even be part of the interface?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you have callees from external? If not, feel free to indeed remove from the interface. If we have calls from outside the class, do we really need those calls because calling internal classes might be considered a violation.


// needed as the fallback locales are linked to the already loaded catalogues
$this->catalogues = [];
$this->cacheVary['fallback_locales'] = $fallbackLocaleProvider->getUltimateFallbackLocales();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't like this for the following reasons:

When, in general, a FallbackLocaleProvider is responsible for giving the sequence of locales to try for a given starting locale, we should not assume it has this list of "last resort" locales, and we should also not assume that this configurable setting is the only thing affecting the computation results.

It might not be relevant in practice, but what if the JSON file with the locale hierarchy changes?

If the FallbackLocaleProvider just provides something like a cache key/suffix or similar (not exposing the ultimateFallbackLocales list), what would that mean for the "fallback locale" information gathered in the DataCollectorTranslator?

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure to understand your concerns here. What you did seems really similar to what was done before FallbackLocaleProvider introduction.

@@ -102,6 +102,8 @@ public function warmUp(string $cacheDir): array
*/
public function getFallbackLocales(): array
{
trigger_deprecation('symfony/translation', '6.2', 'DataCollectorTranslator::getFallbackLocales() is deprecated. Get the FallbackLocaleProvider instead.');
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
trigger_deprecation('symfony/translation', '6.2', 'DataCollectorTranslator::getFallbackLocales() is deprecated. Get the FallbackLocaleProvider instead.');
trigger_deprecation('symfony/translation', '6.2', '"%s::getFallbackLocales()" is deprecated, use "%s::getUltimateFallbackLocales()" instead.', DataCollectorTranslator::class, FallbackLocaleProvider::class);

Copy link
Member

Choose a reason for hiding this comment

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

6.2 must be replaced with 6.4 here and in all other trigger_deprecation occurrences.

@nicolas-grekas nicolas-grekas modified the milestones: 6.2, 6.3 Nov 5, 2022
@nicolas-grekas
Copy link
Member

What's the status here?

@mpdude
Copy link
Contributor Author

mpdude commented Mar 10, 2023

I am still interested in working on it and the use case still exists. Will have to think this through again, forgot about most of the parts. I hope I have all the feedback and information regarding the deprecation steps that I need.

@nicolas-grekas nicolas-grekas modified the milestones: 6.3, 6.4 May 23, 2023
@@ -6,6 +6,7 @@ CHANGELOG

* Parameters implementing `TranslatableInterface` are processed
* Add the file extension to the `XliffFileDumper` constructor
* Add `FallbackLocaleProvider` to configure the fallback locale chain in `Translator`
Copy link
Member

Choose a reason for hiding this comment

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

This branch must be rebased on 6.4 and this line moves to 6.4 section

@@ -102,6 +102,8 @@ public function warmUp(string $cacheDir): array
*/
public function getFallbackLocales(): array
{
trigger_deprecation('symfony/translation', '6.2', 'DataCollectorTranslator::getFallbackLocales() is deprecated. Get the FallbackLocaleProvider instead.');
Copy link
Member

Choose a reason for hiding this comment

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

6.2 must be replaced with 6.4 here and in all other trigger_deprecation occurrences.

*/
private array $ultimateFallbackLocales;

private ?array $parentLocales = null;
Copy link
Member

Choose a reason for hiding this comment

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

It could be, I've no strong opinion on it. @nicolas-grekas WDYT?


// needed as the fallback locales are linked to the already loaded catalogues
$this->catalogues = [];
$this->cacheVary['fallback_locales'] = $fallbackLocaleProvider->getUltimateFallbackLocales();
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure to understand your concerns here. What you did seems really similar to what was done before FallbackLocaleProvider introduction.

@welcoMattic
Copy link
Member

This branch needs to be rebased on 6.4.

Otherwise, I guess we are close to an acceptable feature. I think here we must stick to the existing feature but moved to its own class FallbackLocaleProvider. Then later another PR could be done to improve the fallback locales mechanism if needed.

@nicolas-grekas nicolas-grekas modified the milestones: 6.4, 7.1 Nov 15, 2023
@xabbuh xabbuh modified the milestones: 7.1, 7.2 May 15, 2024
@fabpot fabpot modified the milestones: 7.2, 7.3 Nov 20, 2024
@fabpot fabpot modified the milestones: 7.3, 7.4 May 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants