PeterFromCologne
PeterFromCologne

Reputation: 10483

How to use i18t for texts in the source code of Angular project?

I am using the i18n features of Angular 4 and building the project successfully with angular-cli in the target language. HTML templates are properly translated. However I got some texts in the javascript code.

What's the recommended way of localizing strings used in js source for things like validations? Can we expect i18n to come with a solution?

Currently I am using the locale to determine which translation to use. The Locale gets set from the ng serve --locale fr or ng build --locale fr

Building/serving like this:

ng serve --aot --locale fr  ...

and using the locale in the code like this:

import { LOCALE_ID } from '@angular/core';
// ...
constructor (@Inject(LOCALE_ID) locale: string) {
}

(I was following the great hints on http://blog.danieleghidoli.it/2017/01/15/i18n-angular-cli-aot/)

Upvotes: 4

Views: 2668

Answers (3)

tromgy
tromgy

Reputation: 5833

We're now (early 2019) using Angular 7 and it seems there's still no support for localizing static strings in TypeScript source files.

So we come up with a simple (if "hacky") way of doing it:

  1. Add a hidden HTML element to the template with its content set to the static string from JavaScript code:
<!-- string table -->
<span #toolTipText hidden i18n="Tool tip text|Text displayed on the tooltip@@TOOLTIP_TEXT">Do not click this or bad things will happen.</span>
<span #errorOccured hidden i18n="Error notification|Error message displayed when the user ignores the warning on the tooltip@@ERROR_MESSAGE">A very bad thing just happened.</span>
  1. Reference this hidden element in the code with the @ViewChild() decorator:
@Component({
  // ...
})
export class BadThingsComponent implements OnInit {

  // "string table" elements for i18n
  @ViewChild('toolTipText') warningMsg;
  @ViewChild('errorOccurred') errorMsg;

  // ...
  1. Get the string value where it was previously a static string in the code by addressing the decorated class member's .nativeElement.innerText:
onTryToPreventBadThings(event: CustomEvent) {
    // ...

    this.preventer.messageToDisplay = this.warningMsg.nativeElement.innerText;

    // ...
  }

  onBadThingDidHappen(event: CustomEvent) {
    // ...

    this.notifier.message = this.errorMsg.nativeElement.innerText;

    // ...
  }

Upvotes: 1

k_o_
k_o_

Reputation: 6298

It seems that Angular is not supporting this yet. I use a mixture of the default Angular i18n approach because the toolchain is quite nice and a 3rd party tool like angular-l10n. The benefit of later is that it is automatically doing what is common for fetching resource files with translations, i.e. falling back from fr-FR -> fr -> en if no translation is available and offering other convenient functions like placeholders in translations. A manual approach does not offer this.

I'm using this in my service like the following:

constructor(translationService: TranslationService) {}
...
foo() {
    this.translationService.translate('httpService.pagingError')
}

In the app starter app.module.ts I modified the setup a bit to load the default locale from the app. Note the constructor.

import { L10nConfig, L10nLoader, TranslationModule, StorageStrategy, ProviderType } from 'angular-l10n';

const l10nConfig: L10nConfig = {
    locale: {
        languages: [
            { code: 'en', dir: 'ltr' },
            { code: 'de', dir: 'ltr' }
        ],
        language: 'en',
        storage: StorageStrategy.Cookie
    },
    translation: {
        providers: [
            { type: ProviderType.Static, prefix: './assets/locale-' }
        ],
        caching: true,
        missingValue: 'No key'
    }
};

@NgModule({
    imports: [
        BrowserModule,
        HttpClientModule,
        TranslationModule.forRoot(l10nConfig)
    ],
    declarations: [AppComponent, HomeComponent],
    bootstrap: [AppComponent]
})
export class AppModule {

    constructor(public l10nLoader: L10nLoader, public localeService: LocaleService,
              @Inject(LOCALE_ID) locale: string) {
        this.l10nLoader.load().then(() => this.localeService.setDefaultLocale(locale));
    }

}

Upvotes: 0

SrAxi
SrAxi

Reputation: 20015

Use the I18nPipe inside your code:

constructor(private translator: I18nPipe) {
    let translatedText = this.translator.transform('Hello');
}

Where transform() is a method within your I18nPipe class that given a string parameter translates it. Example:

i18n Pipe:

@Pipe({
    name: 'i18n',
    pure: false
})
export class I18nPipe implements PipeTransform {

    constructor(public i18nService: I18nService) {
    }

    transform(phrase: any, args?: any): any {
        return this.i18nService.getTranslation(phrase);
    }

}

i18nService:

@Injectable()
export class I18nService {

    public state;
    public data: {};

   getTranslation(phrase: string): string {
        return this.data && this.data[phrase] ? this.data[phrase] : phrase;
    }

    private fetch(locale: any) {
        this.jsonApiService.fetch(`/langs/${locale}.json`)
            .subscribe((data: any) => {
                this.data = data;
                this.state.next(data);
                this.ref.tick();
            });
    }
}

In your i18nService you are getting the current language in the fetch() method and through a custom API service (in my case is jsonApiService) you get the data from a es.json, en.json, de.json, etc. (depending on your local parameter) and in getTranslation() you are actually translating a given parameter and returning it's translated value.

Update 1:

With this, you can have a file like es.json:

"hello": "Hola",
"sentence1": "This is the sentence 1",
"goodbye": "Adiós"

And this @Pipe can be used in the code to apply a translation in your .component.ts file like I have shown above (this is useful for DataTables rendered with Ajax, for example).

Or can be applied in your template, simply:

{{ 'hello' | i18n }}
{{ this.goodbyeStringVariable | i18n }}

Upvotes: 4

Related Questions