angular-i18n work-around for translations in code?
Asked Answered
P

6

9

We have to wait until Angular 6 for angular-i18n to support translations in code for error messages and such.

For those that are using angular-i18n (instead of ngx-translate for instance) what are you doing in the meantime to handle translations in code? It occurs to me that if there are not many strings then a simple language service with methods to get translations by language code and an id would work, but I am interested in something more elegant and "angular".

I do not know what the promised code translations support will look like but any temporary solution would ideally be easily converted to the angular-i18n way when it goes live.

What are people out there doing to handle this issue? Any ideas?

Prudhoe answered 18/1, 2018 at 17:47 Comment(0)
P
12

This polyfill seems like the best way to go right now:

https://github.com/ngx-translate/i18n-polyfill

It allows you to wrap anything you want to translate in an i18n() function (this API is likely to be preserved in a future release of Angular - see my notes at the bottom of this answer).

The polyfill is mainly written by Olivier Combe, a member of the Angular team responsible for i18n:


For Angular 5, you'll need version 0.2.0 when you install:

npm install @ngx-translate/[email protected] --save

For Angular 6, get the latest version - currently 1.0.0:

npm install @ngx-translate/[email protected] --save

I got the polyfill working for both JIT and AOT compilation, for Angular 5 (it will also work for Angular 6). Here's what you need to do to translate to a single language (this is a good way to get this working - you can then get multiple languages working later, which I explain further down):


app.module.ts

Add the following imports to your root Angular module:

import { TRANSLATIONS, TRANSLATIONS_FORMAT } from '@angular/core';
import { I18n } from '@ngx-translate/i18n-polyfill';

add the following constant, and specify the providers in your root module:

// add this after import + export statements
// you need to specify the location for your translations file
// this is the translations file that will be used for translations in .ts files

const translations = require(`raw-loader!../locale/messages.fr.xlf`);

@NgModule({ ....

  providers:
  [
    I18n,
    {provide: TRANSLATIONS, useValue: translations},
    {provide: TRANSLATIONS_FORMAT, useValue: 'xlf'},
    ...

Note on using AOT compilation: If you're using AOT compilation to translate your templates, translation of the messages in .ts files will still be done at runtime using JIT compilation (that's why you need to reference TRANSLATIONS and TRANSLATIONS_FORMAT instead of just specifying these in your build scripts).


*.ts

In the .ts file where you want to provide a translation, add this:

import { I18n } from '@ngx-translate/i18n-polyfill';

constructor(private i18n: I18n) {
    console.log(i18n("This is a test {{myVar}} !", {myVar: "^_^"}));
}

This demonstrates that you can even include interpolations in the messages that you want to translate.

You can use i18n definitions (i.e. using specifying the translation 'source' id, meaning, description) like this:

this.i18n({value: 'Some message', id: 'Some message id', meaning: 'Meaning of some message', description: 'Description of some message'})

You'll still need to extract the messages, and you can use the ngx-extractor tool to do this. This is included when you install the polyfill, and I've added an example below on its usage inside an npm script. See also the readme on the polyfill page.


Multiple languages

To support switching between multiple languages, you'll need a factory provider for your translations. There are details on the readme of the polyfill page. You'll need something like this in your root module (or for AOT compilation, replace the return value for localeFactory with a function that detects which AOT compiled language variant of your app is currently running):

  export function localeFactory(): string {
    return (window.clientInformation && window.clientInformation.language) || window.navigator.language;
  }

  providers:
  [
    {
      provide: TRANSLATIONS,
      useFactory: (locale) => {
        locale = locale || 'en'; // default to english if no locale provided
        return require(`raw-loader!../locale/messages.${locale}.xlf`);
      },
      deps: [LOCALE_ID]
    },
    {
      provide: LOCALE_ID,
      useFactory: localeFactory
    },

Message extraction and xliffmerge

All of this is compatible with xliffmerge, which is a great tool for automatically merging any new translations you add, without overwriting existing translations. Xliffmerge can also automatically perform translations using Google translate (you'll need a Google translate API key). For this to work, I do the extraction and merging/translation in the following order, before I do the actual AOT build:

"extract-i18n-template-messages": "ng xi18n --outputPath=src/locale --i18n-format=xlf",
"extract-i18n-ts-messages": "ngx-extractor --input=\"src/**/*.ts\" --format=xlf --out-file=src/locale/messages.xlf",
"generate-new-translations": "xliffmerge --profile xliffmerge.json en fr es de zh"

The AOT build for a specific language version of the site looks like this:

"build:fr": "ng build --aot --output-path=dist/fr --base-href /fr/ --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr",

Current status of this polyfill:

This is mainly written by Olivier Combe, a member of the Angular team responsible for i18n. At this stage this it's a 'speculative' polyfill for translating variables or strings in the .ts file. It's likely to be replaced by an API built into Angular which will be very similar, so upgrading later should be reasonably manageable. Here's the diclaimer from the Github page:

This library is a speculative polyfill, it means that it's supposed to replace an API that is coming in the future. If the API is different, a migration tool will be provided if it's possible and necessary.

There's been some discussion around support in forthcoming minor versions of Angular 6 for translations of variables/strings in code.

Here's a quote from Olivier Combe (from March this year), from the following discussion on Github:

https://github.com/angular/angular/issues/11405

The first PR for runtime i18n has been merged into master, along with a hello world demo app that we will use to test the features. It works at runtime, and support theoretically code translations, even if there is no service for it yet. For now it's very minimal support (static strings), we're working on adding new features (I'll make the extraction work next week, and then dynamic string with placeholders and variables). After that we'll do the service for code translations. As soon as a new feature is finished it gets merged into master, you won't have to wait for a new major.

Pringle answered 21/6, 2018 at 4:44 Comment(2)
Hey Chris Halcrow, this is possibly one of the best explanations I found so far on this topic. However... it does not allow you to translate variables, that are out of your hand, let's say API responses. I know, I know, in a perfect world, the API should return translated responses, this is sadly often not possible, due to the API being out of the dev's hand. You have any hint on how to solve this? I don't want to be forced to use multiple i18n plugins. Also the ngx-translate solution cannot translate routes.Jolo
The only way I can think of doing this is to have a manual translation based on the response values, which have to be predictable. I do quite a lot of this by handling a value within a switch statement, where if the value matches one of the cases then I do return this.i18n('My expected value'); - and I use a default case that returns the original value verbatim. Hope this helps.Pringle
C
2

It has been a long time since my last response and it can be simplified.

if our .html is like

<!--at first we put all the translation in a hidden div-->
<div *ngIf="!yet" style="display: none">
    <span #t id="message1">Translation app</span>
    <span #t id="message2">Hola Mundo</span>
</div>

We have in our .ts a simple ViewChildren, a variable "yet" and a variable "translation"

  yet: boolean = false;
  translation:any={}
  @ViewChildren("t")
  set translations(values: QueryList<any>) {
    //when yet becomes true, values.length=0
    if (values.length) {
      values.forEach(c => {
        this.translation[c.nativeElement.id]=c.nativeElement.innerHTML
      })
      //it's necesary enclosed this.yet=true in a setTime to avoid 
      //the error expresion changes After Exec
      setTimeout(()=>{
        this.yet=true;
      })

    }
  }

then we can write some like

alert(this.translation['message1']);

Update The same idea: a component implementation you have a component

import { Component, QueryList, AfterViewInit, ContentChildren } from '@angular/core';
@Component({
    selector: 'ng-translation',
    template: `
    <div *ngIf="!yet" [style.display]="'none'">
    <ng-content></ng-content>
    </div>
    `
})
export class TranslationComponent implements AfterViewInit {
    @ContentChildren("t") translations: QueryList<any>
    data: any = {}
    yet: boolean = false;

    get(text: string) {
        return this.data[text];
    }
    ngAfterViewInit(): void {
        if (this.translations.length) {
            this.translations.forEach(c => {
                this.data[c.nativeElement.id] = c.nativeElement.innerHTML
            })
            setTimeout(() => {
                this.yet = true;
            })
        }
    }
}

In any other component

<ng-translation #translation>
  <span #t id="message1">Translation app</span>
  <span #t id="message2">Hola Mundo</span>
</ng-translation>

@ViewChild("translation") translation:TranslationComponent
  click()
  {
    alert(this.translation.get('message1'));
  }

Example in stackblitz

Carpophagous answered 26/9, 2018 at 22:51 Comment(2)
Your "Stackblitz" link is pointing to your previous answer... could you fix that. I would really like to see the demo :-)Overlook
::glups:: @Jette, thanks for the advise (just corrected)Carpophagous
S
2

See this blog entry: https://blog.ninja-squad.com/2019/12/10/angular-localize/

In a nutshell:

There is $localize which can be used to do this.

@Component({
  template: '{{ title }}'
})
export class HomeComponent {
   title = $localize`You have 10 users`;
}

Unfortunately the feature is not realy complete yet:

You can then translate the message the same way you would for a template. But, right now (v9.0.0), the CLI does not extract these messages with the xi18n command as it does for templates.

Slunk answered 8/5, 2020 at 7:49 Comment(0)
C
1

I have a "bizarro" work-around We can have two components

app-text.component.ts

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

@Component({
  selector: 'text',
  template:`<ng-content></ng-content>`
})
export class AppTextComponent{}

and app-translation.component.ts

import { Component, QueryList, ElementRef, ContentChildren } from '@angular/core';
import { AppTextComponent } from './app-text.component';

@Component({
  selector: 'app-translation',
  template: `<ng-content></ng-content>`
})
export class AppTranslationComponent{
  @ContentChildren(AppTextComponent, { read: ElementRef }) divs: QueryList<ElementRef>;
  constructor() { }

  translate(id: string): string {
    let nativeElement: any = this.divs.find(e => e.nativeElement.id == id);
    return nativeElement ? nativeElement.nativeElement.innerText : "";
  }
}

Then, in a component we can have some like

  <app-translation #translations style="visibility:collapsed">
    <text id="message1">Translation app</text>
    <text id="message2">Hola Mundo</text>
  </app-translation>

//In your code you can use a ViewChild and the function "traslate"
  @ViewChild('translations') t;

  alert(this.t.translate("message1"));
Carpophagous answered 5/5, 2018 at 16:30 Comment(4)
I Like this workaround because it's simple and I only need a temporary solution until Angular gets a more complete built in Translation frameworkDuplet
@Diemauerdk, I add a new simplified answer using ViewChildren :)Carpophagous
Just remember, ViewChildren cannot be used in the component onInit(), because it is not yet initialized. If you need it right away, use it in ngAfterContentInit()Nil
check my response in #48327901 (I forgot this answer). It's not necesary AfterContentInit if you was to use in a (click) of a button, e.g. but in general it's trueCarpophagous
E
1

you can try this package ngx-dy-i18n

it support the native solution

Enisle answered 29/8, 2019 at 23:53 Comment(0)
C
1

As of Angular 11 and using Ivy, you can just use $localize function in .ts files:

$localize `your text`

Running extract-i18n extracts this text to XLIFF file (in my case) just like using the i18n property in templates.

Source: https://github.com/angular/angular/issues/11405#issuecomment-519697830

and this blog article: https://blog.angular.io/angular-localization-with-ivy-4d8becefb6aa

Convenance answered 12/3, 2021 at 17:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.