I have a Symfony 5 website that uses Twig templates containing messages translated with
{% trans %}some.message.key{% endtrans %}
Is there a way to have Symfony output the message key itself instead of the translation? This can be helpful during development and translation work.
To demonstrate what I want, have a look at https://en.wikipedia.org/wiki/Main_Page?uselang=qqx, which shows such behavior in the MediaWiki software.
The following seems to use the proper translations for comment/comments in languages such as German, French and Portuguese:
{% transchoice newsItem.commentCount from 'frontpage' %}news_comments{% endtranschoice %}
For English, instead of selecting one from comment|comments, it shows "1 comment|comments" if newsItem.commentCount == 1. If it is more than 1, it shows only "comments" properly.
Anyone have an idea what could be happening?
EDIT: English translations file:
"slug","source","translation","context"
"comment","comment|comments","comment|comments",""
Other languages follow same structure.
I have the locale set to one language. Let's say german "de" and I want to have a part of the whole text translated into different languages (Ex. This happened 100 years ago -> This happened vor 100 Jahre). For this I am trying the following code:
//this returns 'site.dates.years'
{% set yearsText = yearsCount|displayTimeDivisions() %}
( {{ yearsCount }} {% trans with {'%content%': yearsText } from "messages" into "en" %}%content%{% endtrans %}
after using the trans method i get the string from yearsText ('site.dates.years') and not the translated content. Is this even possible to translate or I should drop it?
This works as expected, what you are trying to translate is %content%, not site.dates.years. Try this:
//this returns 'site.dates.years'
{% set yearsText = yearsCount|displayTimeDivisions() %}
( {{ yearsCount }} {% trans from "messages" into "en" %}{{ yearsText }}{% endtrans %}
Edit
The previous suggestion doesn't work as using trans in that way only works with simple text, not with variables.
This works for me:
{{ yearsText | trans({}, 'messages', 'en') }}
EDIT: clear cache
Sometimes Symfony has a too strong caching mechanism. If you work without locales first, and then modify controllers and twigs to be multi-locale and you also add a new messages file, Symfony loads all new controller/twigs but somehow misses that there are new message files. A simple cache clear, done ONLY ONCE (!) right after you add the message files, solves this problem. And thenceforth you don't need to clear the cache every time you change a translation.
Try this as a last resort. If this doesn't help I'd recommend to read carefully the documentation (link in the last line of this answer):
app/console cache:clear --env=dev
You may try one or all (!) of these, it's impossible to tell what is going wrong.
{{ yearsCount|trans }}
{{ 'site.dates.years'|trans }}
See if these translate. If the second does, you're setting the variable wrong, somehow.
If not, pass the _locale along in the route:
defaults: { _controller: Bundle:Controller:action, _locale: de }
Or have it as a route parameter:
path: /a/path/{_locale}/whatever
defaults: { _controller: Bundle:Controller:action }
requirements:
_locale: en|de
Print the locale to be sure it's properly set in the twig:
{{ app.request.locale }}
{{ app.request.getLocale() }}
If the locale prints out fine but the translation still does not work, it's because Symfony does not find the identifier. Go to the translations file and check that it's written correctly:
site:
dates:
years: Jahre
If you find it in messages.de.yml then maybe it's the wrong placement of the file. The loader searches for these files:
Symfony looks for message files (i.e. translations) in the following default locations:
the app/Resources/translations directory;
the app/Resources/MyAppBundle/translations directory;
the Resources/translations/ directory inside of any bundle.
If fiddling around with these files doesn't yield results, it's a configuration error in config.yml, or many overlapping errors (all of the above?) that work against you.
Here's the source of all pain/info about translations, but you've probably memorized every paragraph, by now: Symfony translations.
In Drupal 7, I have three languages set up: English, French and German. To translate the contact form URL, I've done the following for the French language:
Go to: admin/config/search/path
Click "Add Alias"
Language: select "French"
Existing system path: enter "contact"
Path alias: enter "contactez-nous"
Save
This is working fine. Then I'm trying to do the same for German using the "kontakt" path alias, but each time I go to the translated path (/de/kontakt) I see the following message:
Oops, looks like this request tried to create an infinite loop. We do
not allow such things here. We are a professional website!
This doesn't happen for the French language.
As I've setup the French language a long time ago, perhaps another step is involved but I can't figure it. Any idea ?
Fixed on production site: this only happened on staging environment but I still can't understand why!
In messages.en.yml, I have
confirmed: Congrats %username%, your account is now activated.
But I want to 'bold' username to example ... how can I made this ?
confirmed: Congrats <span class='bold'>%username%</span>, your account is now activated.
Of course I could use two sentence in this example like
first: Congrats
second: , your account ...
and inside twig use the html tag but this seems very dirty.
Update 2
In such cases, I started to use like this:
confirmed: Congrats %start_link%%username%%end_link%, your account is now activated
Since separation of concerns is maintained, this way is strongly recommended.
Update
In YAML, I have used translations like this without any problem:
trans.key: click here to continue
Although translations and design should be kept separated there are always some situations that you must use html tags inside translation files as it is also seen in huge projects like Facebook and Twitter.
In such situations, you can use XLIFF format which is being recommended by Symfony. Inside translation file:
<trans-unit id="1">
<source>confirmed</source>
<target>Congrats <![CDATA[<span class='bold'>%username%</span>]]> , your account is now activated.</target>
</trans-unit>
Twig's Raw Filter
I don't know if this was an option back in 2013 but when using translation, you can apply the raw twig filter having this translation string:
confirmed: Congrats <span class='bold'>%username%</span>,
your account is now activated.
And use it in twig like this:
{{ 'confirmed'|trans|raw }}
This will not escape the html inside the string and will display the username as bold.
Update: I haven't seen the comment the first time, but Rvanlaak had proposed the raw filter solution in the first place.
Security issues
Note that the content of those translation strings must not be user provided, because it could open up your application to XSS attacks. Using the raw filter allows JavaScript to be executed if a malicious user is able to input custom data into the translation strings (Community based translations for example)
Separation of concerns
Using the raw filter does not comply with separation of concerns as the content and styling are bound together. As Ferhad mentioned, using his method, separation of concern will be maintained. But in my case, I preferred using a simple raw filter. I felt that for my case, Ferhad's method was a bit overkill for me, though it would be more recommended his way
My approach is although still ugly, but at least respects the separation of concerns. Escape filter is used to escape a variable, making the final result is pretty safe from XSS, because all other sources considered to be hard-coded.
translations.yml
points: You have %num% points left.
template.html.twig
{% set pointsFormatted = '<span class="points">' ~ num | escape ~ '</span>' %}
{{ 'pages.score.points' | trans({'%num%' : pointsFormatted}) | raw }}
I've just found something out, you can use this in your YAML file:
mind: >
<i>Mind is a nice thing to have</i>
So this ">" sign in the first row achieves it. I think this would be the preferred way, better than handling the escapes etc in TWIG.
I've looked it up now and it is actually a YAML feature. Check here :)
Also, there's an earlier question with similar subject: How can I get YAML to ignore raw HTML in same file.
some yml:
dashboard:
hello: Hello <b>%username%</b>
+
{{ 'dashboard.hello'|trans({'%username%': app.user.username}, 'General') | raw }}
this | raw part worked for me
In my opinion, this is the best solution today:
'key'|trans({'%username%': '<strong>' ~ suspiciousVar|escape ~ '</strong>'})|raw
The only risk here is stored XSS in your translation files.
Holding HTML stuff in translations is wrong, because translators usually break it. But if you really need it:
Twig:
{% trans %}confirmed{% endtrans %}
Yaml translation file:
confirmed: 'Congrats <span class="bold">%username%</span>, your account is now activated.'
Discussion about this:
https://github.com/symfony/symfony/issues/2713
We could use separate twig snippets for different languages if situation requires heavy formatting differences. I wrote a little blog on this.
{# templates/translations/user_message.pl.html.twig #}
{{ 'msg.my_favourite_language_is' }}<b>{{ 'langnames.elfic_language' | trans | lower }}</b>!
{# templates/translations/user_message.en.html.twig #}
{{ 'msg.my_favourite_language_is' }}<i>{{ 'langnames.elfic_language' | trans | ucfirst }}</i>!
{# templates/pages/index.html.twig #}
{% set locale=app.request.locale[:2] %}
{% include 'translations/calculator_message.' ~ locale ~ '.html.twig' %}
Translation files are for translations, design and layout is part of the view layer (namely: template engine (twig)). You can split it into two pieces: congrats and account.activated.