Skip to content

Symfony translator shows curly braces around vars #39854

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

Closed
Ser5 opened this issue Jan 16, 2021 · 13 comments
Closed

Symfony translator shows curly braces around vars #39854

Ser5 opened this issue Jan 16, 2021 · 13 comments

Comments

@Ser5
Copy link
Contributor

Ser5 commented Jan 16, 2021

Symfony version(s) affected: 4.4.18

Description
See there for the reproduction project:
https://github.com/Ser5/SymfonyTranslatorBug

I want to use the Symfony validators library in standalone mode. Therefore, AFAIK, I should also use the translator library, but during experiments with it I found that it doesn't work as I expected.

According to these docs
https://symfony.com/doc/current/components/form.html
I installed symfony/config and symfony/translation packages via Composer.

According to these
https://symfony.com/doc/current/translation/message_format.html
created messages file and index.php file.

Then ran index.php.
expected:

Hello Fabien!
Hello Symfony!

got:

Hello {Fabien}!
Hello {Symfony}!

How to reproduce

git clone https://github.com/Ser5/SymfonyTranslatorBug.git
cd SymfonyTranslatorBug/lib/
composer install
cd ../
php index.php

Possible Solution
May be one of these problems:

  • I have read the docs wrong - where?
  • Docs are unclear - clear them
  • Docs are outdated - update them
  • Libs are buggy - fix them

Additional context
Windows 10
PHP 7.2.4

@ro0NL
Copy link
Contributor

ro0NL commented Jan 16, 2021

see #36461 + #37797

before ICU format the variables were literals: trans('some %key%', ['%key%' => 'value'])

i think from the ICU format perspective any variable key not wrapped in {...}, should be automatically wrapped actually.

@Ser5
Copy link
Contributor Author

Ser5 commented Jan 17, 2021

After some digging I have found a few suspicious points.

1. File names

Having messages.en.php for "normal" format and messages+intl-icu.en.php for ICU format is "convention over configuration", which is unacceptable for frameworks like Symfony, consisting of standalone components, which should be able to used as mere libraries, not enforcing any conventions, allowing developers to configure these libraries as they wish. For example, loading messages from /any/path/blahblah.php or simple arrays.

2. Translator::trans()

\Symfony\Component\Translation\Translator::trans() has this code:

if ($this->hasIntlFormatter && $catalogue->defines($id, $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX)) {
	return $this->formatter->formatIntl($catalogue->get($id, $domain), $locale, $parameters);
}

return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters);

As I understand, it selects between ICU format and "normal". For our example it should choose the formatIntl() branch. It doesn't: it runs past the branch because while $this->hasIntlFormatter gives true, $catalogue->defines($id, $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX) gives false.

3. MessageCatalogue

Symfony\Component\Translation\MessageCatalogue::defines() contains a shady code

public function defines($id, $domain = 'messages')
{
    return isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]);
}

It seems that if you pass normal, non-ICU domain, it checks for both normal and ICU messages.

But if you pass ICU domain, it can check only ICU messages.

I find this asymmetry confusing and bug-like.

4. Translator::addResource()

It's domain actually depends not on filename passed as 2nd argument, but on 4th argument.

If, for my example project, we change this line

$translator->addResource('php', __DIR__.'/translations/messages+intl-icu.en.php', 'en');

to this

$translator->addResource('php', __DIR__.'/translations/messages+intl-icu.en.php', 'en', 'messages+intl-icu');

everything starts work as expected: $catalogue->defines() returns true, the ICU code branch launches, and we get the expected output:

Hello Fabien!
Hello Symfony!

Final thoughts

Something doesn't wire together. I'm not able to wrap my head around Symfony architecture, as I always find it overengineered, and indeed it's quite complex and hard to understand. So I cannot propose super useful ideas here. The authors can invent solutions and fixes the best.

I suppose "convention over configuration" thingie should be removed in favor of clear "configuration". At least with this line of code

$translator->addResource('php', __DIR__.'/translations/messages+intl-icu.en.php', 'en', 'messages+intl-icu');

messages+intl-icu looks like totally obscure shaman language, Better to have a clean flag somewhere, indicating whether we are loading "normal" or "ICU" messages. Flag like $isICU, or$isIntlMessages, or something.

@ro0NL
Copy link
Contributor

ro0NL commented Jan 17, 2021

hm, see #37371 that seems to be a bugfix for 5.x only 🤔 can you try v5 of the translator component?

i believe it'll then work, as the formatter will also handle the {} trimming on vars:

$parameters[trim($key, '%{ }')] = $value;

@wouterj
Copy link
Member

wouterj commented Jan 17, 2021

As you discovered, the +intl-icu prefix must be defined on the domain and not the filename. In the Symfony framework, you configure the domain of a message catalogue by filename. I think that's where the confusion occurred here.

If you don't add the +intl-icu suffix to the domain, the old formatter will be used in 4.4. This means that variable names will be replaced as-is. E.g. $translator->trans('say_hello', ['name' => 'Fabien']); replaces "name", leaving the {} in the string. Using $translator->trans('say_hello', ['{name}' => 'Fabien']); will also replace the curly braces.

In 5.0, the old formatter is removed and intl is the only supported format. That resolves all inconsistencies. I think it makes sense to deprecate the +intl-icu suffix in 5.x (if it isn't done before). We cannot change this in 4.4, as this does not qualify as a bugfix and we need to stay backwards compatible. I would highly recommend updating to 5.2 instead of using 4.4, especially if you want to use the intl format without the need for backwards compatibility flags.

I'm going to close this issue, as I don't think there is anything to do here. However, if you think I'm wrong please leave a comment and we can always reopen. Thanks for understanding!

@ro0NL #37371 is unrelated afaics. That PR was about passing intl format strings to trans() directly ($translator->trans('Hello {name}', ['name' => 'Fabien'])), whereas this is about loading message catalogues.

@wouterj wouterj closed this as completed Jan 17, 2021
@ro0NL
Copy link
Contributor

ro0NL commented Jan 17, 2021

@wouterj yes you're right, i confused addResource() with trans()

im tempted if we can infer the icu modifier from the resource, applying the domain suffix as needed.

AFAIK we cant just deprecate this +intl-icu modifier, given translator component may need to support both type of formats

@wouterj
Copy link
Member

wouterj commented Jan 17, 2021

AFAIK we cant just deprecate this +intl-icu modifier, given translator component may need to support both type of formats

If I remember correctly, intl is the only supported format in Symfony 5+.

@Ser5
Copy link
Contributor Author

Ser5 commented Jan 17, 2021

Hey, thanks for the elaborations! But I still don't understand completely.

In 5.0, the old formatter is removed and intl is the only supported format. That resolves all inconsistencies. I think it makes sense to deprecate the +intl-icu suffix in 5.x (if it isn't done before). We cannot change this in 4.4, as this does not qualify as a bugfix and we need to stay backwards compatible. I would highly recommend updating to 5.2 instead of using 4.4, especially if you want to use the intl format without the need for backwards compatibility flags.

https://symfony.com/doc/current/translation/message_format.html
This page shows version 5.2, and the text still states that ICU format supported as well as "normal". Then should we suppose that the docs are outdated and needs to be fixed?

Whenever, as pointed there, I write

composer require symfony/translation

I get composer.json with content

{
	"require": {
		"symfony/translation": "^4.4"
	}
}

I had no idea that symfony/translation has versions of 5.*.

Looking at https://github.com/symfony/translation/releases I now see that they actually do exist.

Should the symfony/translation github page be updated also? As for me, poorly familiar with Symfony, it was quite confusing.

Indeed, I don't need any backward compatibility, I would be happy to stick with ICU format only. But, I read the github page, installed the package as directed, got version 4.4, read the docs, which states that there are two formats supported and what I should do to enable ICU format, did the stuff - and got the error.

I guess I may reopen this issue and now ask the docs to be updated by someone?

@ro0NL
Copy link
Contributor

ro0NL commented Jan 17, 2021

the issue is https://symfony.com/doc/current/translation.html relies on framework integration, eg. translator as a service - ready to use out-of-the box

Setting up the translator standalone is still briefly documented at the component level: https://github.com/symfony/translation/blob/5.x/README.md

It could use a mention how to enable intl icu formatter i agree.

btw, for me composer installs latest stable :)

$ composer require symfony/translation
Using version ^5.2 for symfony/translation

@Ser5
Copy link
Contributor Author

Ser5 commented Jan 17, 2021

btw, for me composer installs latest stable :)

Uh, seems version 4.4 was installed because of outdated PHP version 7.2.4 on my Windows 10 - tried installing on Ubuntu with PHP 7.4.3 - composer indeed pulled symfony/translation v5.2.1. Thanks for the hint!

Then, finally we have only the problem https://symfony.com/doc/current/translation/message_format.html , pointing out about availability of both "normal" and ICU formats, right?

@wouterj
Copy link
Member

wouterj commented Jan 17, 2021

Then, finally we have only the problem https://symfony.com/doc/current/translation/message_format.html , pointing out about availability of both "normal" and ICU formats, right?

I'll take care of investigating this (and either reporting back here or fixing the doc). Thanks a lot for your time and detailed information!

@ro0NL
Copy link
Contributor

ro0NL commented Jan 17, 2021

see also https://symfony.com/doc/current/translation/templates.html ;)

AFAIK only transChoice was deprecated, not sure deprecating the old format was initially intended? if so, i think we need to provide a conversion tool as well

@Ser5
Copy link
Contributor Author

Ser5 commented Jan 18, 2021

OK, I updated my PHP to version 8, updated packages as well - composer pulled out symfony/translation v5.2.1, then I tried luck with this setup.

Welp, barely any improvements.

https://github.com/symfony/translation/blob/5.x/README.md
Readme is not very functional, as it gives Fatal error: Uncaught Symfony\Component\Translation\Exception\RuntimeException: No loader is registered for the "array" format. error.

$translator->addLoader('array', new \Symfony\Component\Translation\Loader\ArrayLoader());

^ this needs to be added for the example to work.

https://github.com/symfony/translation/blob/5.x/Translator.php
Then, there are still branches for "normal" and ICU formats:

if ($this->hasIntlFormatter
    && ($catalogue->defines($id, $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX)
    || (\strlen($domain) > $len && 0 === substr_compare($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX, -$len, $len)))
) {
    return $this->formatter->formatIntl($catalogue->get($id, $domain), $locale, $parameters);
}

return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters);

They look more complex than of 4.4 though.

And finally

$translator->addResource('array', ['say_hello'=>'Hello, {name}'], 'en');

still doesn't work as expected, producing an output with curly braces. It still requires messages+intl-icu:

$translator->addResource('array', ['say_hello'=>'Hello, {name}'], 'en', 'messages+intl-icu');

^ this thing surely works.

@wouterj
Copy link
Member

wouterj commented Jan 18, 2021

Ah, yes. Both formats are still supported - the non-intl format is only handling the simple placeholder replacements. In that case, the +intl-icu suffix on the domain is still needed.

$translator->addLoader('array', new \Symfony\Component\Translation\Loader\ArrayLoader());

^ this needs to be added for the example to work.

Can you make a PR to the 4.4 branch fixing this in https://github.com/symfony/symfony/blob/4.4/src/Symfony/Component/Translation/README.md ?

Ser5 added a commit to Ser5/symfony that referenced this issue Jan 19, 2021
For the example to work it needs the line with $translator->addLoader().
Fixed it for request at symfony#39854 (comment)
derrabus added a commit that referenced this issue Jan 19, 2021
…ample (Ser5)

This PR was merged into the 4.4 branch.

Discussion
----------

[Translator] Added $translator->addLoader() to README example

For the example to work it needs the line with $translator->addLoader().
Fixed it for request at #39854 (comment)

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| License       | MIT
| Doc PR        | symfony/symfony-docs#... <!-- required for new features -->
<!--
Replace this notice by a short README for your feature/bugfix. This will help people
understand your PR and can be used as a start for the documentation.

Additionally (see https://symfony.com/releases):
 - Always add tests and ensure they pass.
 - Never break backward compatibility (see https://symfony.com/bc).
 - Bug fixes must be submitted against the lowest maintained branch where they apply
   (lowest branches are regularly merged to upper ones so they get the fixes too.)
 - Features and deprecations must be submitted against branch 5.x.
-->

Commits
-------

ba29d2a Added $translator->addLoader()
symfony-splitter pushed a commit to symfony/translation that referenced this issue Jan 19, 2021
For the example to work it needs the line with $translator->addLoader().
Fixed it for request at symfony/symfony#39854 (comment)
SerhiyMytrovtsiy added a commit to SerhiyMytrovtsiy/translation that referenced this issue Oct 19, 2022
For the example to work it needs the line with $translator->addLoader().
Fixed it for request at symfony/symfony#39854 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants