ngx-translate / core

The internationalization (i18n) library for Angular
MIT License
4.51k stars 577 forks source link

innerHTML ignores my angular directives #512

Closed mrin9 closed 7 years ago

mrin9 commented 7 years ago

I'm submitting a ... (check one with "x")

[ ] bug report => check the FAQ and search github for a similar issue or PR before submitting
[ ] support request => check the FAQ and search github for a similar issue before submitting
[x] feature request

Current behavior

I have the below translation token (includes HTML tag with an angular directive) "MSG":"You may <a routerLink='/login'> Sign in </a> again"

and my component HTML is <div [innerHTML]="'MSG' | translate"></div>

The final translation strips of the routerLink directive

Expected/desired behavior The final translation should have routerLink directive

ng2-translate version: 6.0.1

do-web commented 7 years ago

[translateParams] also not working with [innerHTML]

Jotakuun commented 7 years ago

I'm having same problem, any update on this?

ocombe commented 7 years ago

You cannot have angular directive / components / pipes / ... in your translations, it's a limitation of angular because you cannot call the compiler after bootstrap (unless you're lazy loading a module, and that's just in JIT). I'm sorry but it's not possible with this library. You can do it with angular i18n (the official thing), but not with any external library.

vadost commented 7 years ago

import { AfterViewInit, Directive, ElementRef, Input, OnDestroy, Renderer2 } from '@angular/core'; import { TranslateService } from 'translate-core'; import { Router } from '@angular/router';

@Directive({ selector: '[appTranslate]' }) export class TranslateDirective implements AfterViewInit, OnDestroy { @Input() appTranslate: string; listenClickFunc: Function;

constructor(private element: ElementRef, private translate: TranslateService, private router: Router, private renderer: Renderer2) {}

ngAfterViewInit() { this.translate.get(this.appTranslate).subscribe((translateText: string) => { this.element.nativeElement.innerHTML = translateText; // this.cdRef.detectChanges();

  const navigationElements = Array.prototype.slice.call(this.element.nativeElement.querySelectorAll('a[routerLink]'));
  navigationElements.forEach(elem => {
    this.listenClickFunc = this.renderer.listen(elem, 'click', (event) => {
      event.preventDefault();
      this.router.navigate([elem.getAttribute('routerLink')]);
    });
  });
});

}

ngOnDestroy() { this.listenClickFunc(); }

}

vadost commented 7 years ago

<div [appTranslate]="'MSG'"></div>

mrin9 commented 7 years ago

@vadost thanks for the workaround, I am doing something similar at present.

mrin9 commented 7 years ago

@ocombe, First of all thanks for such an awesome and simple to use framework, we found it much more easier than angular's own i18n.

Though I cannot fully comprehend how it is a limitation, as I didnt got into the internal workings of it, but If it is a limitation, was wondering would it be possible to take care of only few special/popular directives to preserve them as is in the final output?

ocombe commented 7 years ago

At runtime (when the library runs), the compiler is not available, which means that you cannot compile the templates. If you need a directive / component / pipe, you have to put it outside of your translations (by splitting the translations for example). In the case of You may <a routerLink='/login'> Sign in </a> again you would need to split it in 3: "You may", "Sign in" and "again". It's a pain, I know, but there's no other way.

mrin9 commented 7 years ago

now I see why thats an issue, Thanks for explaining this !

duall commented 7 years ago

@ocombe That would not work for many languages as they need to shift words in sentence like: <a routerLink='/login'> Sign in </a>you may again. It's very common to do that in Chinese.

christarczon commented 7 years ago

@duall That's exactly why you split it into 3. Languages that don't need text before the link have a blank value for the first key.

duall commented 7 years ago

@christarczon Do you really want to put a blank value before every element in your website ?

vadost commented 7 years ago

@duall Create your custom directive! An example you can see from above!

jcdsr commented 6 years ago

@vadost Sorry, for angular4 who is

import { TranslateService } from 'translate-core';

I'm trying to apply the directive but something is missing... could you have a plunker??

voice-technologies commented 6 years ago

So, to be clear, does this mean that an existing, working angularJS app that features custom directives inserted by ng-bind-html, then compiled with $compile, cannot be upgraded to angular 2+? And that there isn't even a workaround? Given that for back-compatibilty reasons the inserted HTML content cannot be changed, is the app now stuck with the end-of-life angularJS forever? I'm sorry, but that seems to be a major hole in angular, which I would hope to see addressed in a release sooner rather than later.

ocombe commented 6 years ago

Yes, there is no alternative to $compile. Custom runtime-created templates will be possible with the next renderer (ivy) in Angular 7, but probably not for 7.0, it will just be "possible" with the new architecture, which doesn't mean that it'll be available immediately when ivy is released

voice-technologies commented 6 years ago

Thanks for your reply. As I have already invested a lot of time in upgrading the static HTML parts of the app (by far the bigger part of it) to angular 5, I was wondering if a workaround along the following lines might be possible to avoid throwing all that work away.

Keep the angular 5 upgraded components that use static HTML templates. Refactor the angularJS element directive that hosts dynamically added attribute directives, as an angularJS component. So the app now functions as an angular 5/angularJS hybrid, at least until angular 7.x brings runtime template creation.

My question is, will an angularJS component in a hybrid app support the use of $compile in the same way that the element directive in a pure angularJS app does?

ocombe commented 6 years ago

I have no idea, maybe @samjulien could tell you

MartinMa commented 6 years ago

The workaround proposed by @vadost is not ideal from an accessibility point of view, because the href attribute is missing from the anchor element. Usually the routerLink directive adds the correct href attribute (see router_link.ts). But since custom directives in translation strings are not possible with with library I propose the following workaround using translate parameters.

HTML

<div [innerHTML]="'MSG' | translate:{ path: '#/login' }"></div>

TRANSLATION

"MSG": "You may <a href=\"{{ path }}\">Sign in</a> again."

Based on the locationStrategy used in your project, the path parameter has preceding hash symbol (like here) or not.