vuex-i18n
vuex-i18n copied to clipboard
Translation of HTML context (e.g. paragraph with links inside)
Hi,
by default all HTML in a translation key is rendered as string and no possibility is given to render also translated HTML within a translation key. Right? How is it possible to capacitate the package and having something like the component interpolation of <i18n>
in vue-i18n? So the code would look like:
<i18n path="term" tag="label" for="tos">
<a :href="url" target="_blank">{{ $t('tos') }}</a>
</i18n>
… that renders to something like this:
<label for="tos">
I accept xxx <a href="/term" target="_blank">Term of Service</a>.
</label>
Or more advanced to put HTML to named target-“places” in the parent translation context:
en: {
info: 'You can {action} until {limit} minutes from departure.',
change: 'change your flight'
}
… coded as:
<i18n path="info" tag="p">
<span place="limit">{{ changeLimit }}</span>
<a place="action" :href="changeUrl">{{ $t('change') }}</a>
</i18n>
… renders to:
<p>
You can <a href="/change">change your flight</a> until <span>15</span> minutes from departure.
</p>
What can I code to get this functionality? Or how can one implement this?
Or is there another work around to come to this?
Hi @infinite-dao,
I have seen this functionality in vue-i18n and think it is an important use case. Unfortunately, vuex-i18n does currently not support this. In my opinion, the solution of vue-i18n might also have some room for improvement.
For now, we are using the locale detection with $i18n.locale() and render different components based on the respective locale.
One quick work around I could find was to use v-html
directive of Vue.js but it is not safe, messages like this:
en: {
info: 'You can {action} until {limit} minutes from departure.',
change: 'change your flight'
}
… can be coded as:
<p v-html="$t(
'info', {
'action': 'html composed string',
'limit': 'html composed string'
})">
</p>
Bump! This is a must have feature for us.
I will try to look into this sometime during the next two weeks.
bump on that. I need to translate a text with an inside, like this
<li>{{ $t('schedules.set_interesting_options', { icon: 'fa-gear'}) }}</li>
translate source:
{ 'en: { 'set_interesting_options' => 'Set some interesting options <i class="fa fa-icon :icon"></i>, ... more text' }}
currently it doesnt even render the translate output and outs $t( in the html
Hi everyone,
I will look into this again as I am preparing a presentation of the vuex-i18n library for a vue meet up in Switzerland in November. It will probably boil down to using a custom component that will take component functions as props.
Bump, same here, thanks!
You can use v-html
to html elements as follows
<span v-html="$t('term', {
'link': `<a href='/link.html'>${$t('link')}</a>`
})"
/>
an issue on the i18next-node repository points at a similar problem (albeit from another point of view, and at a lower level). Jan Mühlemann, a core member, offered to use post-processing of markdown content, which IMHO suits here pretty well.
the idea is to add a markdown plugin (though you can also write your own), so you wouldn't have to parse HTML (it's not a regular language!), but only markdown. the template output would still be HTML, after the i18next post-processor had done its job.
any thoughts?
@tikiatua @infinite-dao @Acidic9 @mnedoszytko @awakash @dcshiman
@dcshiman, note that the v-html
solution may shadow issues with input validation, increasing the chances of missing potential XSS vulnerabilities in the code (as hinted by @infinite-dao in the above comment).
Hi @eliranmal
Thank you for your input. I am finally getting around to tackle some of the harder problems with this library. Interpolation of html and components is definitely on the list.
I was thinking that interpolation could be achieved on an opt-in basis. Where the developer defines, which tags are allowed to be used for interpolation and all others are automatically stripped (with a warning in debug mode).
This could probably look something like this
<div>
{{ $t(
'lookupIdentifierForMessage', {
// components to make available in the translation
components: {
// strings can be used to interpolate html elements
title: 'h1',
// custom components can be used with a different tag
bold: MyBoldComponent,
// or with the default tag, i.e. <blinking>..</blinking>
Blinking
},
// data to make available in the translation
data: {
what: 'something like this'
}
) }}
</div>
And in the translation, one would be allowed to use only the specified tags.
const translations = {
'lookupIdentifierForMessage': '<title>this is important</title>\nWe should <bold>not</bold>forget the security implications of <blinking>{what}</blinking>!'
}
@tikiatua, thanx, it's great to hear that you're on it!
the imperative API is well done - it's both very expressive and flexible.
i agree that the white-list approach is preferable when mitigating XSS-related issues, however, choosing which tags to render is not the whole solution; should we now check for element attributes as well?
IMHO, introducing markup inside translation values would unnecessarily complicate the code, as it will include new responsibilities to bare (sanitizing the HTML). we can solve it by delegating the job of rendering tags to the API consumer (e.g. the UI framework layer). my suggestion is to leave hook functions in the chain for that purpose, something like:
$t(
'lookupIdentifierForMessage', {
plugins: {
beforeRender(translation) {
// a chance to use an external library for keeping all the XSS shenanigans out
return HtmlCleaner.sanitize(translation);
}
},
// ...
}
)
or maybe just incorporate the above implementation (a general sanitizing library) into vuex-i18n, as long as only placeholders are used, not tags.
i hope that makes sense in the context of this issue; i'm not very familiar with vuex-i18n, and i only swept through the code.
Good point. My idea was, that element attributes would be limited to props of the respective components. In addition, we would not actually evaluate the html, but parse the content and build a custom render function to create the respective virtual dom.
Something like this:
message: '<h1>This is my <blinking cadence="10">title</blinking></h1>'
// is complied to
render: function(createElement){
return createElement('h1', [
"This is my",
createElement(MyBlinkingComponent, {
props: { cadence: 10 }
}, "title" )
])
}
@tikiatua, that sounds like a great idea - if we don't evaluate any markup, we lower the risk of injections.
also, if the code eventually prepares a render
function, we do delegate the job to a higher level, and we can still leave hooks for user intervention (enabling points of integration to actually evaluate the markup).
the added complexity is presumably still there - we'd have to keep so the parsing and keep a whitelist of tags, but if all attributes are just derived from the component's props
, the task becomes a lot easier; i love that idea.
by the way - it's worth investigating how does vue itself interprets templates to generate imperative instructions for building the virtual dom - you may be able reuse that code for your purposes (will probably be found in some auxilary code, like vue-template-loader?).
I've made a quick solution to this problem. It's forked here.
The example: doc.
It assumes that you're using {
and }
as the identifiers (I didn't manage to access plugin config value from the external file in order to pick the actual ones).
I liked how @Gvade's solution worked, but it wasn't very practical for doing simple things like bolding a word in a sentence.
I expanded on it by having it check if the translation key + '__html' exists in the JSON translation file and then compiling it as HTML instead of text, but otherwise working the same way. The only caveat is you can't use slot arguments in attributes, but regular arguments work.
Gist of the component can be found here
Note that I have my translation files grouped by modules so there are a few extra component props to handle that.
There is also a whitelist of elements and attributes at the top as well as a way to specify what vuex-i18n identifiers you want to use. (Currently '{{' and '}}')