Skip to content
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

Translation of HTML context (e.g. paragraph with links inside) #48

Open
infinite-dao opened this issue Nov 20, 2017 · 16 comments
Open

Translation of HTML context (e.g. paragraph with links inside) #48

infinite-dao opened this issue Nov 20, 2017 · 16 comments

Comments

@infinite-dao
Copy link

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?

@tikiatua
Copy link
Member

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.

@infinite-dao
Copy link
Author

infinite-dao commented Nov 21, 2017

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>

@tqwewe
Copy link

tqwewe commented Aug 9, 2018

Bump! This is a must have feature for us.

@tikiatua
Copy link
Member

tikiatua commented Aug 9, 2018

I will try to look into this sometime during the next two weeks.

@mnedoszytko
Copy link

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

@tikiatua
Copy link
Member

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.

@awakash
Copy link

awakash commented Oct 25, 2018

Bump, same here, thanks!

@dcshiman
Copy link

dcshiman commented Apr 3, 2019

You can use v-html to html elements as follows

<span v-html="$t('term', {
    'link': `<a href='/link.html'>${$t('link')}</a>`
  })" 
/>

@eliranmal
Copy link

eliranmal commented May 7, 2019

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

@eliranmal
Copy link

eliranmal commented May 7, 2019

@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).

@tikiatua
Copy link
Member

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>!'
}

@eliranmal
Copy link

eliranmal commented Jul 25, 2019

@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.

@tikiatua
Copy link
Member

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" )
    ])
}

@eliranmal
Copy link

eliranmal commented Jul 27, 2019

@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?).

@tikiatua tikiatua added this to To do in Version 2.0.0 Aug 21, 2019
@Gvade
Copy link

Gvade commented Sep 18, 2019

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).

@BlakeHancock
Copy link

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 '}}')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Version 2.0.0
  
To do
Development

No branches or pull requests

9 participants