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

added simple directive #31

Closed
wants to merge 8 commits into from
Closed

Conversation

sclausen
Copy link

The translation key can be passed as an attribute, or the content of the element. It's also possible to pass an object with interpolation key-value pairs.

@ocombe
Copy link
Collaborator

ocombe commented Feb 11, 2016

Hello, thanks for this PR, it's very complete and I like that !

I'm a bit worried about the fact that it can use the content of the element because of the risks of injection.
Also I think that it's not a good idea to use "nativeElement" because you cannot use webworkers: https://angular.io/docs/ts/latest/api/core/ElementRef-class.html

@sclausen
Copy link
Author

Yeah, you're totally right about nativeElement, but I didn't found out how to adequately manipulate the content from a Directive or Component.
Could you explain your concerns regarding injection? Maybe, we can drop this feature because of this security problem alone not digging into the nativeElement issue, although I really like to have my translation keys as innerHTML.

@ocombe
Copy link
Collaborator

ocombe commented Feb 12, 2016

Well you're using innerHTML, which means that you don't check the content of the element. In angular 1 we had $sce that did that for us.
Let's say that your element contains the name of your users, one user changes his name to some js or sql code, and then you get the content with innerHTML and you try to use that as a key in the TranslateService & TranslateParser, I have no idea what could happen but it could be a problem somehow.

@hpop
Copy link

hpop commented Feb 29, 2016

There is not much documentation about the right way to manipulate the DOM, so I just took a look how the angular native directives do it. (for example ngClass )
This seems to be the right way:

constructor(
  public translateService: TranslateService,
  public ref: ElementRef,
  public renderer:Renderer
) {
}

updateValue(key: string) {
  this.translateService.get(key, this.interpolateParams).subscribe((res: string) => {
    this.renderer.setText(this.ref.nativeElement, res);
  });
}

@sclausen
Copy link
Author

sclausen commented Mar 1, 2016

@hpop: but your code prevents using html in translations.

@hpop
Copy link

hpop commented Mar 1, 2016

Is html output necessary? As far as I understand it, there is no html support in pipes, so ng2-translate does't support html output right now. Or am I missing something?
Bonus: If we are not allowing html in translations, we don't have to build a implementation of $sce. :)

@sclausen
Copy link
Author

sclausen commented Mar 1, 2016

Apart from questioning html support, everything you wrote is correct. The thing is, I'm pretty certain, html support is essential.
You would have to translate every part of longer texts with markup or links separately which is in some languages not possible at all, because of different sentence structures.

@hpop
Copy link

hpop commented Mar 1, 2016

Ok. Good point.

@NathanWalker
Copy link
Contributor

If what we need is an Angular2 sanitizer to make this directive a reality, then I could make an Angular2 html sanitizer library based off this excellent one:
https://github.com/google/caja/blob/master/src/com/google/caja/plugin/html-sanitizer.js
?

Try it out here:
http://codepen.io/devilish/pen/aCJuH

There's also this alternative:
https://github.com/punkave/sanitize-html

If everyone is in agreement that having one would solve this and then integrating such a library here would allow this PR to be merged, I could create this lib this week.

@sclausen
Copy link
Author

@NathanWalker That would be great!I had a look into html-sanitizer and unfortunately it has some other dependencies to the caja tool :-/ I hope this is not a showstopper. On the other hand, sanitize-html is browserified and minified still 125K, so maybe resolving those dependencies in cajas html-sanizier would be a good start.

@ocombe
Copy link
Collaborator

ocombe commented Mar 15, 2016

Maybe we could just add a note in the docs saying: "hey be careful" and go on with it ?

@NathanWalker
Copy link
Contributor

Be careful warning may suffice for time being. I think if a sanitizer is created, we could add a wiki here that described how to use the sanitizer with the library if desired. Maybe just take consideration in how this directive is implemented to allow easy plug/play with a 3rd party sanitizer 👍

@ocombe
Copy link
Collaborator

ocombe commented Mar 15, 2016

Yeah I think that's a better way to do that. I'll try to merge this tonight

*/
updateValue(key: string) {
this.translateService.get(key, this.interpolateParams).subscribe((res: string) => {
this.ref.nativeElement.innerHTML = res;
Copy link
Contributor

Choose a reason for hiding this comment

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

I would be weary of this as this would not work in a native environment like NativeScript for instance. May be a way to let the Renderer handle this?

Copy link
Collaborator

Choose a reason for hiding this comment

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

yeah the PR is old and a lot of things have changed and need to be updated :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe allow this to go, and then I could make a {N} plugin that would provide a native alternative to this directive that could be provided for at runtime.
I wouldn't let it hold up a merge.

Copy link
Author

Choose a reason for hiding this comment

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

If someone could help me with the Renderer approach, I would be really glad :D

@ocombe
Copy link
Collaborator

ocombe commented Mar 15, 2016

Now that I've fixed the OnPush bug with the Pipe, do we still need this directive ?

@NathanWalker
Copy link
Contributor

@ocombe Awesome! Not sure we do need the directive anymore. @sclausen I like the additional flexibility it adds but may add unneeded complexity at this point since OnPush is now possible?

@sclausen
Copy link
Author

I would really enjoy to see my templates more like <td translate>cool_translate_key</td> than <td>{{'cool_translate_key' | translate}}</td>, but I think its just a matter of aesthetics. There is no real 'need' for this and to gain simplicity in code, we should omit this directive.

@sclausen sclausen closed this Mar 16, 2016
@sclausen sclausen reopened this Mar 16, 2016
@sclausen
Copy link
Author

Oh, I totally forgot about the html thing. How can we achieve the rendering of html-translations (e.g. for formatted message bodies) without having the directive?

@ocombe
Copy link
Collaborator

ocombe commented Mar 16, 2016

What are the html translations you're talking about ?

@sclausen
Copy link
Author

I thought of html translations e.g. for messages with variables.

The following could be the translation value:

<p>Dear {{username}},</p>
<p>Your order "{{order.name}}" has been shipped and will arrive est. {{order.estArrive}}</p>
<p>Sincerly<br />
Your [cool-company-name] Shipping Team
</p>

This could be the view:

<message-body translate-params="{order:order, username: username}" translate>
</message-body>

If this wouldn't be possible, it would be totally cumbersome to create multiple translation values to compose such a message and furthermore, not every language has the same sentence construction, so the variables are sometimes at a different place in the sentence.

Do you have a different solution for this problem?

@deepu105
Copy link
Contributor

deepu105 commented Oct 12, 2016

btw we currently use the below as a workaroud as I didnt want to write a complete directive from scratch. Works perfectly

import { Component } from '@angular/core';

@Component({
    selector: '[jhi-translate],[jhiTranslate]',
    template: '<span [innerHTML]="key | translate:args"></span>',
    inputs: ['key:jhi-translate', 'args:translate-values']
})
export class <%=jhiPrefixCapitalized%>Translate {

    private key: string;
    private args: any;
    //FIXME add support to pass translate-compile/ directives in translated content doesnt work
}

@sclausen
Copy link
Author

@deepu105 I don't think there is a need of prefixing the translate attribute-component with ng. When I created this, I had the compatibility to pascal precht's angular-translate in mind.

@deepu105
Copy link
Contributor

@sclausen take a look at this thread angular-translate/angular-translate#849

@sclausen
Copy link
Author

@deepu105 I'm not sure, what you want to tell me with this thread. They discuss prefixing because of a very narrow corner case and don't come to a conclusion to implement this, so angular-translate still works like always and if I want to be compatible, I shouldn't implement prefixing.
Btw. the in the w3c document mentioned google translate service ignores this flag.

@deepu105
Copy link
Contributor

@sclausen what I'm trying to tell you is that the translate directive doesnt work when you use it in protractor coz protractor treats its as a html5 attribute and expects a boolean and not string as value. So I understand you want to maintain backword compatibility so what say you support both like below

@Component({
    selector: '[ng-translate],[translate]',
    ...
})

So that people who are not bothered by the issue can use translate and we (Jhipster folks, which is a huge user base btw, and others using this with protractor) can use ng-translate

@sclausen
Copy link
Author

@deepu105 that sounds really reasoned, I will add this. For the rest of my problems though I still have no solution.

@deepu105
Copy link
Contributor

@sclausen what do you think of the wrapper directive approach I posted above. It works without any problem for me. But im no ng2 expert so wouldn't know if it has any downfall compared to your original service ?

@sclausen
Copy link
Author

@deepu105 I don't think your wrapper would work for my desired featureset. The wrapper needs to have <span translate="key"></span> so it's not compatible to angular-translate anymore, since it was possible in angular-translate to use it also like this <span translate>key</span> and that <span translate>{{'keys.' + variable}}</span>.

@gitnik
Copy link

gitnik commented Nov 8, 2016

So what's left to be done here? I'll be glad to help if some resources are needed

@sclausen
Copy link
Author

sclausen commented Nov 8, 2016

@gitnik I still have the problem, that
<p translate>hello_world</p> doesn't update after first language change, but <p translate [translate-values]="{value:'my-name'}">greetings</p> works properly.
One strange thing I discovered is, that translate.get doesn't return after the first language change in the first example. Do you have any idea, why this happens?

@gitnik
Copy link

gitnik commented Nov 8, 2016 via email

Copy link
Collaborator

@SamVerschueren SamVerschueren left a comment

Choose a reason for hiding this comment

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

Good PR! Still have to check how it performs though. Added some change requests in the meantime.

* Clean any existing subscription to change events
* @private
*/
_dispose(): void {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add the private modifier and remove the _.

private dispose(): void {

* @param changes
*/
ngOnChanges(changes: { [key: string]: SimpleChange; }) {
if (changes["interpolationParams"] && this.key) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

single quotes -> changes['interpolationParams']

constructor(
public sanitizer: Sanitizer,
public translate: TranslateService,
public _elRef: ElementRef,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why the underscores?

template: '<ng-content></ng-content>'
})
export class TranslateComponent implements OnInit, OnChanges, OnDestroy {
@Input('translate')
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's bad practice to rename the bindings like this. Why not @Input() translate: string directly?

@@ -315,4 +315,3 @@ Ionic 2 is still using angular 2 RC4, but ng2-translate uses RC5. You should fix
## Additional Framework Support

* [NativeScript](https://www.nativescript.org/) via [nativescript-ng2-translate](https://github.com/NathanWalker/nativescript-ng2-translate)

Copy link
Collaborator

Choose a reason for hiding this comment

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

No unrelated changes

@sclausen
Copy link
Author

@SamVerschueren thanks for the remarks. I will fix this minor codestyle change requests along with fixing the faulty functionality. I'm currently a little out of spare time.

@SamVerschueren
Copy link
Collaborator

No problem, we all have busy lives!

@ocombe
Copy link
Collaborator

ocombe commented Nov 30, 2016

I have a question about this, let's say that you have the following html code:

<div translate>
  Some text
  <span>some inner text</span>
</div>

then using the innerHTML as a key will receive:


  Some text
  <span _ngcontent-bdn-1046="">some inner text</span>

Two problems here: the first is that we have the empty lines at the beginning and end which can be removed if you compress your html or use aot, and then it won't work anymore, but you probably won't use that as a key anyway because that'd be insane (so you'll use the translate attribute to specify a key).
Still, someone who uses:

<div translate>
  Some text
</div>

might expect the key to be "Some text", and that will not be the case.

And the second problem is that angular adds attributes to inner elements, so that means that you can never use inner html elements in your keys because it won't work.
I won't even start with the innerHTML of elements that use bindings (like *ngIf), it adds comments all over the html...

This means that this directive will only work on really simple elements that only contain some text, that's still useful but I would love if it could do more. And to make sure that it works we should trim the content to remove leading/trailing spaces.

Do you think we could do something like that:

<div translate>
  Some text
  <span translate>some inner text</span>
</div>

And in this case replace only the text at the first level, which would mean that here we have 2 translations: Some text and some inner text ? We replace those and ignore all the inner html?

@sclausen
Copy link
Author

sclausen commented Dec 1, 2016

@ocombe I think your example of

Some text
<span _ngcontent-bdn-1046="">some inner text</span>

as a key is a corner case which shoudln't be handled. Your translation keys are keys in a json file and if someone want's to have something like that as a translation key, I would be curious to hear the explanation why. In your third example, I agree, I would trim al the whitespace, like in angular-translate from @PascalPrecht (translate.js#L167), too.
I wanted to make this directive compatible to angular-translate and ultimately support all its options, so something like this

<pre translate="TRANSLATION_ID"></pre>
<pre translate>TRANSLATION_ID</pre>
<pre translate translate-attr-title="TRANSLATION_ID"></pre>
<pre translate="{{translationId}}"></pre>
<pre translate>{{translationId}}</pre>
<pre translate="WITH_VALUES" translate-values="{value: 5}"></pre>
<pre translate translate-values="{value: 5}">WITH_VALUES</pre>
<pre translate="WITH_VALUES" translate-values="{{values}}"></pre>
<pre translate translate-values="{{values}}">WITH_VALUES</pre>
<pre translate translate-attr-title="WITH_VALUES" translate-values="{{values}}"></pre>
<pre translate="WITH_CAMEL_CASE_KEY" translate-value-camel-case-key="Hi"></pre>

@SamVerschueren that's also the reason I used @Input('translate'), because I didn't wanted to have translate to be the variable name of the key in some cases.

A lot has happened since I opened the pull request and much work has to be done to finish this directive/component. Currently my focus in angular2 is on other things, and I think I should rewrite this whole pr, but this may take a while.

@ocombe
Copy link
Collaborator

ocombe commented Dec 1, 2016

No problem, I started rewriting it, using some of your work, but transforming it in a directive instead of a component :)
I've got a working version, I'm gonna beta test it a bit and if I'm ok with it I'll merge it

@sclausen
Copy link
Author

sclausen commented Dec 1, 2016

@ocombe That's great! 👍

@sclausen sclausen closed this Dec 1, 2016
@ocombe ocombe reopened this Dec 1, 2016
@ocombe
Copy link
Collaborator

ocombe commented Dec 1, 2016

Let's keep this opened, I'll work from your PR so that you get the credits that you deserve, also this will help me not to forget to merge it :D

ocombe added a commit that referenced this pull request Dec 5, 2016
@ocombe ocombe mentioned this pull request Dec 5, 2016
@ocombe ocombe closed this in #344 Dec 5, 2016
ocombe added a commit that referenced this pull request Dec 5, 2016
@ocombe
Copy link
Collaborator

ocombe commented Dec 5, 2016

Done !
Thanks a lot to all of you and especially @sclausen for this, sorry it took so long !

@sclausen
Copy link
Author

sclausen commented Dec 5, 2016

So awesome! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Features
PR to check
Development

Successfully merging this pull request may close these issues.

None yet

8 participants