Angular: i18n: Able to use translation strings outside a template

Created on 7 Sep 2016  ·  204Comments  ·  Source: angular/angular

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

[x] feature request

Current behavior
https://github.com/angular/angular/issues/9104#issuecomment-244909246

I don't think it is possible, like I said before it only works with static text, it won't parse text on the js code, only templates

Expected/desired behavior
Be able to translate strings used anywhere in the code, using an API.

Reproduction of the problem

What is the expected behavior?
I'm referencing the usage of $translate.instant to exposure real use cases:

  • Custom rendered text:
if (data._current_results === data._total) {
                content = this.$translate.instant('CLIPPINGS__LIST__SUMMARY_ALL', {'num': data._current_results});
            } else {
                if (undefined === data._total) {
                    data._total = '...';
                }

                content = this.$translate.instant('CLIPPINGS__LIST__SUMMARY', {
                    'num': data._current_results,
                    'total': data._total
                });
            }

            // Put 'mentions' first
            data = angular.merge({}, {
                mentions: mentions
            }, data);

            _.each(data, (value:number, key:string):void => {
                if (value) {
                    details += value + ' ' + this.$translate.instant('CLIPPINGS__LIST__SUMMARY_TYPE_' + key) + ', ';
                }
            });

            if (details) {
                details = '(' + _.trim(details, ', ') + ')';
            }

            content = content.replace(':details', details);

More examples:

  • Getting image's file name from exported image of a HTML rendered report:
getExportImageName(hideExtension:boolean):string {
        let fileName:string;

        fileName = this.$translate.instant('D_CHART_FACET_authors__EXPORT_FILENAME', {
            'profileName': this.ExportService.GetProfileName(),
            'period': this.ExportService.GetPeriodString(this.SearchFilter.GetPeriodFromInterval())
        });

        if (!Boolean(hideExtension)) {
            fileName += '.png';
        }

        return fileName;
    }
  • Sometimes you're translating, sometimes use model data (could be very verbose in a template):
private _getTitle():string {
        if (this.inShareColumn) {
            return this.$translate.instant('COMPARISONS__SHARE_COLUMN_share_of_voice_TITLE');
        } else if (this.inTotalsColumn) {
            return this.$translate.instant('COMPARISONS__TOTAL_COLUMN_share_of_voice_TITLE');
        } else {
            return _.get<string>(this.group, 'profileName', '');
        }
    }
  • Using a third party chart plugin (Highcharts)
this.chart = new Highcharts.Chart(<any>{
            title: {
                text: this.$translate.instant('REPORTS_BLOG_MAPPING_CHART_TITLE_tone').toUpperCase(),
            },
            xAxis: {
                title: {
                    text: this.$translate.instant('REPORTS_BLOG_MAPPING_CHART_TITLE_tone_xaxis')
                }
            },
            yAxis: {
                min: 0,
                title: {
                    text: this.$translate.instant('REPORTS_BLOG_MAPPING_CHART_TITLE_tone_yaxis')
                }
            },
            plotOptions: {
                scatter: {
                    tooltip: {
                        headerFormat: '<b>{point.key}</b><br>',
                        pointFormat: '{point.y} ' + this.$translate.instant('REPORTS_BLOG_MAPPING_CHART_mentions')
                    }
                }
            }
        });
  • To setup config variables and render the text without pipes as it's not always a translated string
this.config = {
            requiredList: true,
            bannedList: false,
            allowSpaces: false,
            allowComma: false,
            colorsType: false,
            defaultEnterAction: 'required',
            requiredTooltip: this.$translate.instant('D_CLIPPING_TAGS__REQUIRED_TOOLTIP'),
            bannedTooltip: this.$translate.instant('D_CLIPPING_TAGS__BANNED_TOOLTIP')
        };
  • To set window.title :) :
SetWindowTitle(title:string) {
        if (!!title) {
            this.$window.document.title = this.$translate.instant(title);
        }
    }
  • Custom date formatting:
dateHuman(date:Date):string {
        return date.getDate() + ' ' + this.$translate.instant('GLOBAL_CALENDAR_MONTH_' + date.getMonth())
            + ' ' + date.getFullYear();
    }
  • Sort things based on translated values:
// Sort types
            tmpTypes = _.sortBy(tmpTypes, (type:string):string => {
                // 'MISC' at the end
                if ('MISC' === type) {
                    return 'zzzzz';
                }

                return this.$translate.instant('FACET_phrases2__TYPE_' + type);
            });
GetSortedLanguages():IFacetLangDetectedCommonServiceLanguageObject[] {
        // We have to sort by translated languages!
        return _.sortBy(_.map(this.facetOptions, (item:string):any => {
            return {
                key: item,
                label: this.$translate.instant('FACET_langDetected_' + item),
                cssStyle: (_.includes(['english', 'catalan', 'spanish', 'french', 'italian'], item))
                    ? {'font-weight': 'bold'} : null,
                flag: _.get(this.lutFlags, item, null)
            };
        }), (item):string => {
            return item.label.toLowerCase();
        });
    }
  • Export raw data to CSV or Excel with translated values:
getDataExportStacked(inputData:any):any {
        let exportData = angular.copy(inputData);

        if (angular.isArray(exportData) && exportData.length) {
            exportData[0].name = this.$translate.instant('CLIPPINGS__CHARTS_volume_TITLE');

            exportData[0].data = _.map(exportData[0].data, (inputDataItem:any):any => {
                return {
                    'label': inputDataItem.association.profileName,
                    'value': inputDataItem.value
                };
            });
        }

        return exportData;
    }
  • Set config strings to third party plugins:
UpdateCalendarStrings():void {
        Highcharts.setOptions({
            lang: {
                months: [
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_January'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_February'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_March'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_April'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_May'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_June'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_July'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_August'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_September'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_October'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_November'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_December')
                ],
                shortMonths: [
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Jan'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Feb'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Mar'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Apr'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_May'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Jun'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Jul'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Aug'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Sep'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Oct'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Nov'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Dec')
                ],
                weekdays: [
                    this.$translate.instant('GLOBAL_CALENDAR_DAY_Sunday'),
                    this.$translate.instant('GLOBAL_CALENDAR_DAY_Monday'),
                    this.$translate.instant('GLOBAL_CALENDAR_DAY_Tuesday'),
                    this.$translate.instant('GLOBAL_CALENDAR_DAY_Wednesday'),
                    this.$translate.instant('GLOBAL_CALENDAR_DAY_Thursday'),
                    this.$translate.instant('GLOBAL_CALENDAR_DAY_Friday'),
                    this.$translate.instant('GLOBAL_CALENDAR_DAY_Saturday')
                ]
            }
        });
    }


What is the motivation / use case for changing the behavior?
Be able to translate strings outside templates.

Please tell us about your environment:

  • Angular version: 2.0.0-rc.6
  • Browser: [all]
  • Language: [TypeScript 2.0.2 | ES5 | SystemJS]

@vicb

i18n feature high

Most helpful comment

I think this is a real showstopper, i18n is not ready to use until this is implemented.
E.g. I am not able to set translated validation messages in code

All 204 comments

I think this is a real showstopper, i18n is not ready to use until this is implemented.
E.g. I am not able to set translated validation messages in code

@Mattes83 Did you try this way?

<p [hidden]="!alertManagerRowForm.controls.sendTo.hasError('validateEmail')" class="help-error">
    {{ 'ALERTMANAGER__FORM__FIELD__sendTo__ERROR__validateEmail' | translate }}
</p>

:confused: The documentation tells you how nice it is to have error messages in code and translation support requires anything in the template. Looks like there is more need for communication.

@marcalj I know this way...but I need to have localized strings in my ts files.
@manklu I totally agree

I think this is a real showstopper, i18n is not ready to use until this is implemented.
E.g. I am not able to set translated validation messages in code

Yeah same here, I've just switched from ng2-translate to Angular2 i18n as I prefer to use OOTB modules, and it's much easier to extract translations (ng2-translate is more time consuming IMO)
At this stage I cannot translate error messages raised from my services. No workaround either.

If someone want to start a design spec, it would be helpful (ie on Google Docs).

It would need to list all the cases. Quickly thinking about it, I see a need for

_.('static text');
_.('with {{ parameter }}', {parameter: "parameter" });
_.plural(count, {'few': '...'});
_.select(on, {'value': '...'});

Hi guys, great feature request :+1:

Is there any suggested workaround to translate *.ts texts?

@fbobbio What I've been doing is create hidden elements in the template, ex.
<span class="translation" #trans-foo i18n>foo</span>.

Bind them using:
@ViewChild('trans-foo) transFoo : ElementRef;.

Then retrieve translated value
transFoo.nativeElement.textContent.

Looks retarded, but works for my needs.

As ng-xi18n already processes the entire TS-code, why not implement a decorator like @i18n() for (string-)properties? These could then be filled with the translated value, like @Input() is used with one-way data binding.
If the untranslated value cannot easily be extracted from code, just place it in the argument like so:

@i18n( {
  source : 'Untranslated value',
  description: 'Some details for the translator'
} )
public set translatedProperty( value : string ) {
   this._translatedProperty = value;
}

and feed the source into the property when there is no translation target.

This is an essential item in the whole internationalization cycle, IMO.

In my shop, we're accustomed to an "ng-xi18n - like" tool (an extension to xgettext) that crawls all types of source files looking for marked text to put into dictionary files for the translators.

I love the clean and easy i18n markup in the HTML templates, and I was expecting the same for Typescript code.

@vicb In addition to your use cases I'm thinking about possibility to support localization of interpolated string in TS code. However it's likely be needed to rewrite TS code to support such a scenario. Will it be a valid approach?

This is the primary feature stopping us from using the out-of-the-box approach to translation Angular 2 offers. We have many metadata driven components whose keys come from some metadata not stored in HTML. If we could translate via pipe or programmatically against the available TRANSLATIONS, we could get these components to show the proper strings.

In the mean time, we are limited to something like ng-translate because of this limitation.

The way I worked around this problem is by adding an empty 'lang' object in a service used throughout my application. I then apply a directive that reads all span in a div and store the value in that object. I place all my string in a template at the bottom of the page with an hidden property. The string is then reachable from the template or the component. It's ugly and you could easily overwrite an entry with the same id. But it's better then nothing.

SERVICE

@Injectable()
export class AppService {

    //Language string object.
    private _lang:Object = new Object();
    get lang():Object {
        return this._lang;
    }
}

DIRECTIVE

@Directive({
    selector: '[lang]'
})
export class LangDirective implements OnInit {

    constructor(
        public element: ElementRef,
        public app: AppService) {
    }

    ngOnInit() {
        let ele = this.element.nativeElement;
        for (var i = 0; i < ele.children.length; i++) {
            let id = ele.children[i].getAttribute('id');
            let value = ele.children[i].innerHTML;
            this.app.lang[id]=value;
        }
    }
}

TEMPLATE

<button>{{app.lang.myButtonText}}</button>
<div lang hidden >
    <span id="myButtonText" i18n="Test Button">Testing</span>
</div>

Hello @lvlbmeunier

Thank you for providing this suggestion for a workaround while we are waiting for the official implementation. I tried to implement your solution but I cannot seem to get the dynamic translation keys to get recognized. I was trying to do it like this:

<p *ngFor="let d of dataEntry">{{app.lang[d.name]}}</p>
<div lang hidden >
<span id="{{d.name}}" *ngFor="let d of dataEntry" i18n>{{d.name}}</span>
</div>

Those new keys don't show up in my xliff files. Is it possible to achieve this with your solution?

I never tried it but i'm almost certain that the build of the xliff file does not run code. Having dynamic value in i18n would be contrary to the concept. If you know for certain of all the name that would be in your list they should all be declared independently and not in a for loop.

Adding the keys manually works but it is impractical. In my case I get hundreds of text keys in need of translation from an API.

You can run your code and take the source and copy it to a file. The generation of the x18n file is based on static file.

With 4.0.0-beta you can assigning an id to i18n, for instance mainTitle:

<span i18n="title@@mainTitle">

With this we can, at least for the JIT compiler, create a dummy Component (it doesn't need to be added to the app html, just the app module) with all our "extra" translations.

For starters we'll add the providers not just to the compiler but to the app module as well:

// bootstrap.ts
getTranslationProviders().then(providers => {
    const options = { providers };
    // here we pass "options.providers" to "platformBrowserDynamic" as extra providers.
    // otherwise when we inject the token TRANSLATIONS it will be empty. The second argument of
   // "bootstrapModule" will assign the providers to the compiler and not our AppModule
    platformBrowserDynamic(<Provider[]>options.providers).bootstrapModule(AppModule, options);
});

Then we'll create a dummy component to house our extra translations, don't forget to add the component to declarations of AppModule. This is so that ng-xi18n can find the html (I think) and add it to the translation file.

//tmpI18N.ts
import {Component} from '@angular/core';

@Component({
    selector: 'tmpI18NComponent',
    moduleId: module.id,
    templateUrl: 'tmp.i18n.html'
})
export class TmpI18NComponent {
}

Add our translations to tmp.i18n.html:

<!-- tmp.i18n.html -->
<span i18n="test@@mainTitle">
    test {{something}}
</span>

Now we can create a service where we can fetch our translations:

import {Injectable, Inject, TRANSLATIONS} from '@angular/core';
import {I18NHtmlParser, HtmlParser, Xliff} from '@angular/compiler';

@Injectable()
export class I18NService {
    private _source: string;
    private _translations: {[name: string]: any};

    constructor(
        @Inject(TRANSLATIONS) source: string
    ) {
        let xliff = new Xliff();
        this._source = source;
        this._translations = xliff.load(this._source, '');
    }

    get(key: string, interpolation: any[] = []) {
        let parser = new I18NHtmlParser(new HtmlParser(), this._source);
        let placeholders = this._getPlaceholders(this._translations[key]);
        let parseTree = parser.parse(`<div i18n="@@${key}">content ${this._wrapPlaceholders(placeholders).join(' ')}</div>`, 'someI18NUrl');

        return this._interpolate(parseTree.rootNodes[0]['children'][0].value, this._interpolationWithName(placeholders, interpolation));
    }

    private _getPlaceholders(nodes: any[]): string[] {
        return nodes
            .filter((node) => node.hasOwnProperty('name'))
            .map((node) => `${node.name}`);
    }

    private _wrapPlaceholders(placeholders: string[]): string[] {
        return placeholders
            .map((node) => `{{${node}}}`);
    }

    private _interpolationWithName(placeholders: string[], interpolation: any[]): {[name: string]: any} {
        let asObj = {};

        placeholders.forEach((name, index) => {
            asObj[name] = interpolation[index];
        });

        return asObj;
    }

    private _interpolate(pattern: string, interpolation: {[name: string]: any}) {
        let compiled = '';
        compiled += pattern.replace(/{{(\w+)}}/g, function (match, key) {
            if (interpolation[key] && typeof interpolation[key] === 'string') {
                match = match.replace(`{{${key}}}`, interpolation[key]);
            }
            return match;
        });

        return compiled;
    }
}

Now we can do something like:

export class AppComponent {

    constructor(i18nService: I18NService) {
        // Here we pass value that should be interpolated in our tmp template as a array and 
        // not an object. This is due to the fact that interpolation in the translation files (xlf for instance)
        // are not named. They will have names such as `<x id="INTERPOLATION"/>
        // <x id="INTERPOLATION_1"/>`. And so on.
        console.log(i18nService.get('mainTitle', ['magic']));
    }
}

This is a hacky workaround. But at least we can leverage the translation file, get by key, interpolate and not have to have a hidden html in our application.

NOTE: the current @angular/compiler-cli NPM package of 4.0.0-beta has an incorrect dependency version of @angular/tsc-wrapped. It points to 0.4.2 it should be 0.5.0. @vicb is this easily fixed? Or should we wait for next release?

@fredrikredflag Awesome! And what about AOT?

@ghidoz AOT is another story. What we would like to do is to precompile all translations so we can get them by key. But since the ngc will replace all i18n with the correct translation we can't leverage it. Can't find any exposed options/properties that contains the parsed translations from ngc. We could use the same dynamic approach as for JIT by fetching the xlt file that way still provide it on the TRANSLATION token. However this goes against the purpose of AOT.

DON'T DO IT FOR ANY PRODUCTION APPS.

/// bootstrap.aot.ts
function fetchLocale() {
    const locale = 'sv';
    const noProviders: Object[] = [];

    const translationFile = `./assets/locale/messages.${locale}.xlf`;
    return window['fetch'](translationFile)
        .then(resp => resp.text())
        .then( (translations: string ) => [
            { provide: TRANSLATIONS, useValue: translations },
            { provide: TRANSLATIONS_FORMAT, useValue: 'xlf' },
            { provide: LOCALE_ID, useValue: locale }
        ])
        .catch(() => noProviders);
}

fetchLocale().then(providers => {
    const options = { providers };
    platformBrowser(<Provider[]>options.providers).bootstrapModuleFactory(AppModuleNgFactory);
});

Current thinking about how to implement this:

@Component()
class MyComp {
  // description, meaning and id are constants
  monday = __('Monday', {description?, meaning?, id?});
}
  • because we could not affect the DOM structure with such a construct, we could support runtime translations - there would be a single version of the binary and resolution would happen at runtime,
  • we can also support static translations as we do today for templates - strings would be replaced at compile time, better perf but one version of the app per locale,
  • we could probably support __(...) in templates at some later point,
  • this would be implemented via a TS transformer available from 2.3 - we could probably have a prototype before.

/cc @ocombe

I suppose that __() is for a method that has no name yet (and the method wouldn't really be __, right?)

It's a good idea for static translations that you might use in your classes, and pretty simple to implement I think.

No __() is the name, don't you like it :)

It is commonly used in translation frameworks - but you'll be able to import {__ as olivier} from '@angular/core' if you prefer an other name!

eeeeeeh it's not very self explaining :D
It looks like a very private function

I don't like the ___ method name either :) I had imagined it would be a service?
Either way, it's good to see progress 👍

I like __()! :D Very gettext-y and it's short.

In JS world, I only have experience with @ocombes ng2-translate, where marking all text with this._translateService.instant() makes the code somewhat harder to read compared to a shorter alternative, like proposed here.

nothing stops you from renaming the ng2 translate service __ you know :)

Actually, I have no idea how to wrap an DI service method in a simple function - anyway, never mind, that's beyond the scope of this issue:-) My comment was just a +1 for using __ as it's well known if you've ever used other translation frameworks, at least in the PHP world.

okay, maybe ___ is fine, better than "this.translationService.getTranslation()", much shorter.
And yes you can rename it if you like.

What about i18n(...) instead of __(...)?

Please stop the debate about the name, that's not the point. Thanks.

@vicb with monday = __('Monday', {description: 'First day of the week', id: 'firstDatOfWeek'}); will it be possible to resolve it by id, ie:

__('@@firstDatOfWeek') // Monday

Also would Monday be local to the class it's defined or would it be possible to resolve from anywhere? I'm thinking for common translations such as "close", "open" etc.

I was wondering what's the best workaround currently that you guys recommend to use with AOT until the official release supports it?

Regards,
Sean

Angular 2 Kitchen sink: http://ng2.javascriptninja.io
and source@ https://github.com/born2net/Angular-kitchen-sink

@vicb any news, par là ^

Until this feature is implemented I'm using the attribute translation feature.

import {Component, Input, OnInit} from '@angular/core';

@Component({
    selector: 'app-sandbox',
    templateUrl: 'sandbox.html'
})
export class SandboxComponent implements OnInit {
    @Input()
    public title: string;

    constructor() {
    }

    ngOnInit() {
        console.log('Translated title ', this.title);
    }
}

From the parent component template:

<app-sandbox i18n-title title="Sandbox"></app-sandbox>

It is a workaround, but still, I think it's the cleanest one so far. It gives you access to the translated title within ts.

I'm starting to work on this feature, expect a design doc soonish :-)

this will be the most anticipated feature

That s a good news. Thank you @ocombe .

The design doc is available here: https://goo.gl/jQ6tQf
Any feedback is appreciated, thanks.

sweet, will be reading asap!

Thank you @ocombe

I've read the document. Beautiful summary.

Being able to extract string from ts files sounds awesome.

I'm not sure when Typescript upgrade will be available for Angular4.

It means this feature that should already be available for months because pretty much everyone ran into this limitation won't be able to use this solution before at least 3/6 more months.

I might not be the only one right now who uses ngx-translate to manage translations in my controllers while I rely on angular i18n for the templates.

The goal to merge everything into i18n is obviously great.

But aside from the simplification in terms of understanding how to implement i18n in the angular framework, is this approach really going to boost performance compared to the current ngx-translate implementation ?

From my understand right now, the only bonus would be to be able to extract strings from controllers.

Also a big limitation right now is pluralization, please make sure it's working properly when you release the whole upgrade. I've read lots of i18n related tickets where xliff and xmb both wouldn't handle this correctly. This is one more case where I need to switch back to ngx-translate to deliver a working solution.

@ocombe Thank you for the detailed design doc, this looks like a really good solution for this feature request. The current design addresses the use-cases I am trying to resolve.

One question: The design talks of an I18N service for JIT mode, and a TS 2.2.x Transformer for AOT mode. Am I correct in assuming that the transformer will both replace the call to the service method with a static translation, and also remove all remaining references to the I18N service?

@Thommas Angular 4 will use TypeScript 2.1 (which means that you'll have to upgrade), but you can actually already use any more recent version of TypeScript (2.2.1 for example) with Angular 2 or 4.

In terms of performance gain over ngx-translate it depends if you use AOT or not. If you use AOT then it's a gain, but you probably won't see the difference. In JIT (no AOT) there won't be any gain (actually it depends if you use the get observable or the synchronous instant method, the observable costs more than a simple function call).
I'm talking about i18n in your code here. If we're talking about the templates then there is a real visible gain if you're using Angular i18n (in AOT and JIT) because it doesn't use bindings. The more translations you use, the more you gain.

I'll make sure that pluralization works exactly the same that it does for templates. Is there any issue right now with it in the templates?

@schmuli the only time that Angular can replace your code is when you use AOT. If you don't, then the service calls will remain the same in your code.
But if you use AOT then the service call will be replaced with a static translation (only the variables that you might use will remain dynamic). I'm not sure if we will remove references to the I18n service, because it could be used for other things in the future, I'll have to see what is possible with the transformer once I start writing the code. Are you thinking about a use case where this would be an issue?

@ocombe I can't think of a use-case where leaving the code would be an issue, unless we consider an extra DI injection which will not be used (is this a real issue?).

Considering what you wrote that the service might be used for other things in the future, and the potential for errors being introduced by changing the user's code, I would say that it is probably better to avoid removing code altogether.

Maybe it would be useful to use the service to get the current locale in AOT. Consider for example the case where you have a server that can return text in several languages, therefore you will need the current locale to include in the request. However you could always use a dummy translated string as a work around.

@diego0020 you can already get the locale, just inject LOCALE_ID from core

@ocombe Pluralization is not implemented with xliff and no one outside google has any clue how to use it with xmb either. See also: https://github.com/angular/angular/issues/13780

Anyway, nothing is blocking our i18n implementation right now ngx-translate with a pluralization pipe will do for now. Hopefully everything will look better in NG4 and the documentation will reflect the improvements. Thanks.

@Thommas ICU with xliff will be fixed soon: https://github.com/angular/angular/pull/15068 and if you want to know how to use, it's in the docs: https://angular.io/docs/ts/latest/cookbook/i18n.html#!#cardinality

Please apologize, Is there any ETA ? Coz i heard it will be available in ng4.0.0 release but no! Thanks

Cc @ocombe

We're still working on the design, there are a lot of unexpected things to take into account (like how libraries could ship their own translations), ...
Expect this for 4.2, but it's not guaranteed.

ok thanks @ocombe , I have two questions please:

  1. Actually can we translate text in template which include interpolation, like:
    <span i18n>This is {{myValue}}</span>
  1. I ask because I actually can't extract, I don't know why I get this error:
    Could not mark an element as translatable inside a translatable section

@istiti

  1. yes you can.
  2. you need to pay attention on the location of your i18n directive, it should be placed directly on the element that contains the text, not on any other siblings.
    for example:
    <div i18n><span i18n>my text</span></div> - this is not good
    <div><span i18n>my text</span></div> - this is good

@royiHalp
Thanks

  1. Ok how translate with interpolation?
  2. When extract ngc get me file and line and code snippet but i dont have i18n wrapped inside other i18n
    Thats really difficult to find which of my parent have accidently i18n attribut

@istiti

  1. you do it like any other element just add the i18n directive like you did:
    <span i18n>This is {{myValue}}</span>
    the result in the messages.xlf file will be:
    <source>This is <x id="INTERPOLATION"/></source>
    now when you translate it to different locales you should place the <x id="INTERPOLATION"/> in the correct place in the sentence, to help others understand your meaning you can add description to the i18n directive like this:
  1. from my experience the error Could not mark an element as translatable inside a translatable section is as i explained, i remember that if i read the error carefully i can notice in which file i have the issue.

@fredrikredflag Thanks a ton!

Your code is super useful! I just had to fix one issue because it seems that xliff.load returns a different object nowadays, so this._translations needs to be adjusted to:

const loaded = xliff.load(this._source, '');
this._translations = loaded['i18nNodesByMsgId'] ? loaded['i18nNodesByMsgId'] : {};

and a minor validation in the get method if we ask for a non-existing key:

const placeholders = this._getPlaceholders(this._translations[key] ? this._translations[key] : []);

Also, I think that the empty i18n component was tree shaked or something so I had to include the strings on the corresponding component templates with:

<span hidden i18n="@@MY_STRING_1">String 1</span>
<span hidden i18n="@@MY_STRING_2">String 2</span>

Luckly I just need a few amount of strings to my miniApp so it works great in production with AoT.
Thanks again!!!

P/S: the xliffmerge tool by Martin Roob is a must-use at the moment, and his TinyTranslator too B-)

I use AoT compilation and my project supports two languages: English and Russian. I have found a temporary solution for the issue by using the environment config.

The environments/environment.ts file contains:

import { messagesEn } from '../messages/messages-en';

export const environment = {
  production: false
};

export const messages = messagesEn;

Also there are two other files environment.prod-en.ts and environment.prod-ru.ts with the next content:

import { messagesEn } from '../messages/messages-en';

export const environment = {
  production: true
};

export const messages = messagesEn;

And for Russian:

import { messagesRu } from '../messages/messages-ru';

export const environment = {
  production: true
};

export const messages = messagesRu;

Each message file can contain something like this:

export const messages = {
  MessageKey: 'Translation',
  AnotherMessageKey: 'Translation',
  Group: {
    MessageKey: 'Translation',
    AnotherMessageKey: 'Translation',
  }
};

In my code (components, services, etc) I just import messages:

import { messages } from '../../environments/environment';

And use them:

alert(messages.MessageKey);

In .angular-cli.json I have specified the next environments for production:

"environments": {
  "dev": "environments/environment.ts",
  "prod-en": "environments/environment.prod-en.ts",
  "prod-ru": "environments/environment.prod-ru.ts"
}

It works!

@alex-chuev Have you tried this with JIT?

@ocombe any rough estimation when would this be available?

Great feature btw :)

It's on hold until changes to the compiler are made, so not for 4.2, I'm still hoping to make this for 4.3

anyone knows how to implement i18n in param values for example in [title] in the following example:
so in other words, add translation to the word HELLO

regards

Sean

If HELLO is just a string you type into the HTML template, you can do the following:
```html

````
The documentation has an example of this. Look here: https://angular.io/docs/ts/latest/cookbook/i18n.html#!#translate-attributes

If you have to bind to a string property in the component, you have to wait for this feature, or implement a custom way of translating (to my knowledge).

@ocombe

We're still working on the design, there are a lot of unexpected things to take into account (like how libraries could ship their own translations), ...

This's very important. I hope it's part of this feature too. Because at the moment I don't see the way how 3rd-party library can provide translations for you application. During the build you may specify only 1 XLIFF file, and it's seems you have to pre-build your library for you language to have ability to translate it.

Yeah, I'm waiting for that too! This is VERY important for projects intended for non-English speaking audiences!

It is definitively something that we want to support, the team is working on the refactoring of the compiler that will allow us to do that :-)

Is there an estimate for which version that will be targeted? I read somewhere that it might be around 4.3?

Because of the refactoring (and breaking changes) necessary, it won't be before v5 I'm afraid

Whaaaat 😳 Oh noo, Im waiting this feature for 4.2 you said and now it is for v5 ☹️ I was thinking for v5 will be dynamic language changing not release fir each version but anyway hope it will be one day even with 36k different build for each language. I just wonder for when is v5? Thanks

@ocombe

@istiti I said that it was not a guaranteed date ;-)
The release schedule is here: https://github.com/angular/angular/blob/master/docs/RELEASE_SCHEDULE.md
The final release for v5 is 2017‑09‑18, but there will be betas and RCs before

Is there any changes to create one locale file for each component? means, create chunks of messages.xlf, for example: messages.{component}.{locale}.xlf and for instance only messages.{component}.xlf for default language.

Not yet

Dammage it's strict minimu due to build time @ocombe

I am confused... Is this change request / change to come official or not?
Having same issue here, translations simply does not apply only to templates.
Having redux, custom treeview components etc... all generated from javascript objects IN CODE, not per template

It is official, and it's coming but we had to make other deep changes to the compiler first which is why it wasn't ready for 4.3

Is it known at what exact version this feature will be available / is planned? 4.3.1, 4.3.2 ... 4.3.X ?

There is no more major/minor version for the 4.x branch (only patches: https://github.com/angular/angular/blob/master/docs/RELEASE_SCHEDULE.md) which means that this will be for 5.x, not sure which one.

Is this still expected to get into 5.x? I can't see it in the betas. Thanks

Might be in v50.x not v5.x 😂

5.x yes, but it won't be in 5.0 most likely

Le jeu. 3 août 2017 à 12:56, vltr notifications@github.com a écrit :

Might be in v50.x not v5.x 😂


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/angular/angular/issues/11405#issuecomment-319936876,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAQMorXMbyI8l6K3QA4jmXEKawiEC46xks5sUad0gaJpZM4J2pkr
.

Hi @ocombe I am very interested in the process to get this to work and what about the way the compiler works currently makes this a tougher task. Do you have any clarity as to what the timeline looks like or is this still not likely 5.0 but some 5.x?

@arackow it will be in 5.x most likely, @vicb is working on the last changes to the compiler that will finally make this possible

@ocombe ... is there any design document that describes a concept of the solution for strings outside of templates? We are in a project development phase where it would be great to know it, so we would be able to prepare somehow our temporary solution and later it would be much more easier to switch to the final Angular syntax.

We had a design doc but it was based on the previous version of the compiler and it's probably not the implementation that we will end up doing. We will do a new public design doc once we have a better idea on the new implementation

@ocombe I have to +1 this feature. serious need for it right now lol :+1: Thank you guys for making amazing tools! :)

As I understand the current design doc, an author of an AOT library would need to provide the translations for all the languages required by the various applications that are using this library.
Have I understood this correctly?

This is a good point @gms1.
I tested this here.
As a library author you only have to add the i18n tags.
You should not compile your library to "ngfactories" (see Jason Aden's talk at ng-conf 2017), so the translation tag will not be replaced. This way when you run the ng xi18n command you will also get a translation in the xliff for the files in your node_modules folder.

So no you don't have to provide translations, but to add i18n tags with useful meanings.

quoted from the design doc:

For AOT the source code is transformed to replace the service calls by static translations. To do that we will use a TypeScript transformer.

So what would this mean if an angular (AOT) library will be published usually in transpiled form? I have not found any reference to .metadata.json in this document

You should not compile your library to the final code, but prepare it to be compiled by the developer using your code. See what @jasonaden mentions here. This also means that you can make your library translatable by adding the i18n tag.

@gms1 as I said:

We had a design doc but it was based on the previous version of the compiler and it's probably not the implementation that we will end up doing.

And the AOT compiler is being changed extensively for v5, it should be easier for libs to publish "aot-ready" code.
For i18n we want to make this as easy/flexible as possible as well, it will probably be something like: libraries can publish translation files that you can use, but that you can also override if you prefer, and you should be able to provide additional languages as well if the library doesn't support it by default

Thank you @ocombe for this clarification!

@ocombe Does this mean that features of ngx-translate will be available in the angular itself?

@montreal91 none of the parts of ngx-translate can be implemented as is into angular, my lib has a very naive approach, it works "mostly" but we want to be a lot more efficient with the framework.
That being said, angular will provide similar features (even if the final implementation will be different).

This is my approach (FLUX) while I'm working on a UI notification system based on (https://github.com/PointInside/ng2-toastr). The problem is that the notification content is defined in the service call instead of a template to be managed by angular i18n system (xi18n)

It's not what I'd like to, but it's a _working_ solution. You can _extract_ the idea from following code fragments. Hope it helps :)

anywhere

// this call in any other component, service... 
// "info" method parameter is the "id" of the HTML element in template NotificationComponent.html
this.notificationActionCreator.success("myMessage");

NotificationComponent.ts

@Component({
    selector: 'notification-cmp',
    templateUrl: '../../public/html/NotificationComponent.html'
})
export class NotificationComponent implements OnInit, OnDestroy, AfterViewInit {

    notificationMap: Map<string, any> = new Map();
    subscription: Subscription;

    constructor(private toastr: ToastsManager,
                private vcr: ViewContainerRef,
                private store: NotificationStore,
                private elementRef: ElementRef) {

        this.toastr.setRootViewContainerRef(vcr);
    }

    ngOnInit() {
        this.subscription = this.store.payload
            .subscribe((payload: NotificationStorePayload) => this.handleStorePayload(payload));
    }

    ngOnDestroy(): void {
        this.store.destroy();
        if (null != this.subscription) {
            this.subscription.unsubscribe();
        }
    }

    ngAfterViewInit(): void {
        this.elementRef.nativeElement.querySelectorAll("div[notification-title][notification-text]")
            .forEach(el => this.notificationMap.set(el.id, {
                    title: el.attributes["notification-title"].value,
                    text: el.attributes["notification-text"].value,
                })
            );
    }

    handleStorePayload(payload: NotificationStorePayload): void {

        if (null != payload.action) {
            let notification = this.notificationMap.get(payload.notification.id);

            switch (payload.notification.type) {
                case NotificationType.SUCCESS:
                    this.toastr.success(notification.text, notification.title);
                    break;
                case NotificationType.INFO:
                    this.toastr.info(notification.text, notification.title);
                    break;
                case NotificationType.WARNING:
                    this.toastr.warning(notification.text, notification.title);
                    break;
                case NotificationType.ERROR:
                    this.toastr.error(notification.text, notification.title);
                    break;
            }
        }
    }
}

NotificationComponent.html

<div hidden
     id="myMessage"
     i18n-notification-title="@@notificationTitleMyMessage"
     i18n-notification-text="@@notificationTextMyMessage"
     notification-title="Greeting"
     notification-text="Hello world"></div>

<div hidden
     id="error"
     i18n-notification-title="@@notificationTitleError"
     i18n-notification-text="@@notificationTextError"
     notification-title="Error"
     notification-text="Something went wrong!"></div>

Now that Angular 5 has been released, do we have any turn around on when will the changes on the i18n module be integrated?

It kinda looks like this feature is dependant on changes to the angular compiler, but the teams responsible for the compiler seems less interested. It doesn't look like theres a task or a PR or commits:-(. Sorry for venting, but this feature request which describes very basic functionality present in every other translation-framework is now more than a year old. I've given up hope that this essential feature will arrive. At least for 5.x...

Guys keep calm. It is not a silly thing to implement into a AOT compiler. Have a little trust, @ocombe said:

"it will be in 5.x most likely, @vicb is working on the last changes to the compiler that will finally make this possible"

Yes we are working on runtime i18n right now which is the first step.
Runtime i18n means: one bundle for all languages, AOT-compiled libraries can use i18n, and maybe later the ability to change the language at runtime. It also means that we can do translations at runtime, which is needed for translations inside of the code (because we need a runtime parser for translations, etc...).
One this is done, the next priority will be translations inside of the code.

But we couldn't do runtime i18n before we changed the compiler to use typescript transformers (which has been done for 5.0)

Any idea which 5.x version will have which i18n improvements? Anything being released with 5.0.0 itself?

5.0:

  • new i18n pipes (date/number/percent/currency) that don't use the intl API which fixed something like 20 bugs because now they work the same across all browsers + some other improvements (locale parameter, new additional formats, timezone parameter for the date pipe, ...)
  • new i18n api function for libraries and components: https://next.angular.io/api?query=getlocale
  • better integration with the cli

5.1 or 5.2 (if there is no unexpected blocker):

  • runtime i18n

5.x:

  • i18n code translations (not just template)

And other things that we'll add along the way but are not yet decided (you can follow https://github.com/angular/angular/issues/16477).

Just curious thoughts, although i am a little bit late, just by considering i18n support in a TS file, why don't we think about the annotation syntax? (I found someone has mentioned it above too)

Example would be:
@i18n (id='message1')
message1 = "This is a message need to be translated";

@i18n (id='dynamicMessage')
dynamicMessage = "This is a dynamic message with {0}";

With the annotation for dynamicMessage, we can inject a function through annotation to this string instance, which name is format, then we can use like below:
var finalMessage = dynamicMessage.format('value1')

Similar approach could be possible for taking care of named parameters etc.

I guess the question is not about how to make a good developer experience. The question is about compilation.

Also what happens if you change message1 to another string? All i18n variables need to be always constants then. I don't think using variables would work at all. E.g. Symfony has a translator-service with a translation-function which would work like this in JS syntax: let translated = this.translator.trans('Hello %name%', [{'%name%': nameVariable}]); which will result in: <source>Hello %name%</source> <target>Bonjour %name%</target>

@MickL the point of the current -aot translation is, make a minimal and efficient program. That's why an approach for dynamic translation doesn't exist yet.
I strongly believe that the current approach is really good, in the sense that we are not using dynamics service to translate strings. That means if you know that some text is static please translate in that way not using a service.

The problem with dynamic translation is simple, which is if you are going to translate a string every time you are showing it. Is not a really good way,
Why?

  1. Because you have a dictionary of several languages in memory when you are not using it.
  2. Every time that you are showing a dynamic text you need to go to the map and find a key and then find the language translation. EVERY TIME because you have a "main language".

From my point of view, the future solution should be:

  1. Backend gives you the text that you want to translate.
  2. Replace/(or download from backend which is the current approach) every text that you are showing in the current/main language. ( replace everything get time, but you don't need to find two keys every time you want to show a simple label ).
  3. Only truly dynamic text can be replaced and/or requested to the backend.

Think about it, if you really have dynamic text to show you can receive it from the backend, and if you really need a copy of that "dynamic text" you can always use the cache for that.

In my opinion there is no need to discuss this issue anymore. Actually there is a guy (Olivier Combe) working FULLTIME on i18n. AOT is very special and a lot of work had to be done before making this issue even close to possible. Soon we will have dynamic translations: No more need to build each language separate! When this is done we will have translations within code (this issue) later. He said the road until this issue is resolved hopefully takes half of a year from now on.

If you are interested checkout his speech on Angular Connect on 07.11.17 about present and future of i18n in Angular: https://youtu.be/DWet6RvhHWI?t=21m12s

This smells like a good use case for typescript tagged template strings...

This smells in general. I visit this thread once every month to check for progress and have stopped being disapointed because of lowered expectations. The only thing developers wants is to have the same options for string translation in code as in templates. My code is riddled with hidden translated template strings because of this lacking functionality. Whether this eventually is accomplished with a service or annotations is irrelevant and it doesn't matter if it takes a millisecond extra. I'm alredy watching something for changes when I'm picking a string from code. The only requirement is that the code is parsed and the relevant strings end up in the same files with the same format as my template based translations.

@eliasre you can try https://github.com/ngx-translate/i18n-polyfill if you're in a hurry for code translation
no promises that it'll be the same though, actually it'll probably be different (as in easier to use), there are limitations to what is possible right now... and it'll add ~80ko to your app to use that lib

You can use ngx-translate if you dont want to wait

@ocombe would you mind providing updates on the progress so that we can track and plan accordingly to development timelines when waiting decisions on what plugins or frameworks to use? it would be very helpful and appreciated

@eliasre I think google wants to make good solution at once. Without breaking changes in future. I agree it is taking a quite longer then we think.

Don't bother poor @ocombe he's working very VERY hard!
Keep it up! :)

I'm not the one working on this feature, and it's the holidays break, I'll let you know as soon as I know more

thanks @ocombe

thanks @ocombe, any updates would be greatly appreciated here. I have been desperately waiting for runtime i18n for the past few months, and the last build ETA was 5.2. Can you please let us know as soon as you have an updated ETA here?

Sorry for being so persistent, but building for 20 languages ends up being excruciatingly slow at the moment.

As Angular 5.2 is out and unfortunately i couldn't find anything i18n related (or did I overlooked something?) ... - @ocombe maybe you could update us on the release plan? Thx a lot & thx a lot for your effort on i18n!

@ocombe is not the guy we are waiting on here... check out the youtube link in this comment: https://github.com/angular/angular/issues/11405#issuecomment-343933617

The planning is constantly evolving. Whenever I give a probable release date it turns out that something else gets the priority or becomes required and it shifts the date once again. I know how disappointing it is for you all who are waiting for this feature, and I'm doing everything that I can to increase the priority.
We have a meeting tomorrow to discuss the i18n backlog/planning, I'll let you know if I get anything new.

@ocombe if you get i18n released this month I'll buy you a 24 pack of craft beer

I'll add 24 bottles of german beer and another 24 bottles if you ask what Angular Elements and library support is doing. Damn not getting informations what is going on is horrible.

@ocombe you have this rare once in a lifetime opportunity of acquiring 72 bottles of expensive craft beer, and make the development community happy. We will fulfill our promises if you accomplish these things my friend.

@MickL Angualr Elments, as I understand, it will allow us build non-SPA pages and use angular components as native elements (as self-widgets too)

Yes i know. This are 2 other features everyone is waiting for but no one knows what the status is and when to expect it...

If only someone promises to add the last 27 bottles of beer, we can start counting them down!
http://www.99-bottles-of-beer.net/language-javascript-1948.html

I'll add them, @ocombe : name your fav brand :D

Edit : someone create a service similar to eth bounty named beerbounty :p

As promised, a little update: we are still aiming to release runtime i18n in v6, which gives us 5 weeks. It's going to be short, it will probably be added in one of the last releases, and it will be behind a flag.
Runtime i18n should come with code translations, because that's how it'll work for templates as well anyway (it should use the same mechanism).
That being said, it's not certain that the service will be 100% ready, and we might postpone it to make sure that we won't break it right after releasing it, if we feel like it is the right choice. But it will follow runtime i18n in any case (that's our roadmap), and we don't need a major version to release it because it'll be behind a flag, and it shouldn't break anything anyway).

What could prevent us from releasing it in time:

  • if we stumble upon an unexpected issue (you know how development works)
  • if it breaks the internal google apps because we messed up somewhere
  • if something top priority comes along and we have to switch our focus
  • if one of the new things that it depends on is blocked or delayed (runtime i18n depends on some major internal changes that other members of the team are working on)
  • Senior angularJS developer, been using PascalPrecht's translate lib for 3 years.
  • Excited me. Angular 2 rookie. Naively browsing https://angular.io/guide/i18n
  • Successfully integrating i18n into my professional project
  • Spending a whole day messing with all the front-end hard-coded labels, porting it to a 100+kb xlf file
  • In the need of translating raw labels within ng services
  • Looking it up on google, be here
  • Find out there's no solution before Angular 6 ages.
  • me rn
    mfw

@Macadoshis you can use my polyfill lib for now: https://github.com/ngx-translate/i18n-polyfill

@Macadoshis now imagine what we've been through since alpha.46 ! ;)

Thank you for pointing i18n-polyfill to me @ocombe, but I'll go for ngx-translate as it supports async loading of the translations keys.
i18n-polyfill's factory for the TRANSLATIONS provider seems to only supports sync raw-loading of a known-before-bootstrap fixed locale.

// sync loading
return require(`raw-loader!../locale/messages.${locale}.xlf`);
// @ngx-translate/i18n-polyfill/esm5/ngx-translate-i18n-polyfill.js
Tokenizer.prototype._advance = function () {
    //...
    this._index++;
    // "this._input" needs to be a string and can't handle a Promise or an Observable
    this._peek = this._index >= this._length ? $EOF : this._input.charCodeAt(this._index);
    this._nextPeek = this._index + 1 >= this._length ? $EOF : this._input.charCodeAt(this._index + 1);
}

@ocombe how close is the polyfill you wrote to what is being released? Anything we should be aware of, or prepare for? Also, would you mind listing a list of the features with i18n that are being released in version 6, and how testing is going with i18n?

I don't have more details on the code translation service for now, we'll work on it next week probably (I'm traveling to mountain view). All I know is that behind the scene it'll be similar to goog.getMsg from the closure library (since it is what google is using internally for now): https://github.com/google/closure-library/blob/db9bc1a2e71d4b6ee8f57eebe37eb0c6494e9d7e/closure/goog/base.js#L2379-L2387 which is similar to my polyfill as well.

In v6 we will release runtime i18n: one bundle for all locale, translations resolved at runtime, and maybe code translations if we have the time (otherwise it'll come soon after). All of that will be behind a flag because it requires to use the new renderer (called ng-ivy) which will not be battle-tested.

Thx! @ocombe ... that sounds really nice!
Will there be breaking changes in syntax for template translation? I'm thinking about adding translations to our project now - but don't want to redo it completely in a few weeks.

No, the template syntax will be the same

Thanks @ocombe for the great news, this is extremely promising. What kind of production readiness can we expect from the ng-ivy renderer that would get released with v6? I understand it won't be battle-tested, but it would still be ready for production use and work on all major nearly current version browsers, right?

You can follow the progress here: https://github.com/angular/angular/issues/21706
I don't think that everything will be ready for v6, since it's under a flag we will keep progressing on this even after the v6 release (it's not a breaking change or anything)
Honestly I'm not sure what all those things are :D
I know that an hello world app from the cli is already working.
I think that some of those things are needed to benefit from the optimizations of the new renderer, but it can probably work without having everything checked
That being said, it also means that it won't be production ready, don't bet your product on it just yet. It will not be tested on internal google apps, and we might need to do some breaking changes to fix stuff. Using the new renderer is at your own discretion and at your own risk

Is any of this present in the v6-beta4 ?

No it's not. Check this roadmap, this feature is blocked by the i18n runtime.

updates @ocombe ?

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.

@ocombe i heart i18n features should already be here in v4.3 !! So many release but nothing on i18n. Can angular team manager allocate more works/developers on it i can understand you alone or two dev can’t go ahead quickly, this i 18n feature is the must have feature to pretend angular to be business app /large app with quick build of course these two feature are the most importants for large app don’t you think so? Thanks

I think the only thing we can say to @ocombe is thank him for all the effort he is putting in this feature. Im sure that if there are no more developers is not because he didnt ask for them. Just let him do, Im sure we wont regret when it gets released.

Just for reference...

I've got an alternative tool for importing and exporting strings, originally developed for use with Aurelia, which includes support for strings stored in json files - it's quite handy, as Webpack allows you to directly import json files into your ts files, giving your code easy access to those strings.

This tool works with Angular templates too, and has a bunch of other handy features - use it if you like, or feel free to borrow ideas from it to improve the Angular tooling. There's also a Webpack loader based on this - see the link in the docs.

https://www.npmjs.com/package/gulp-translate

We have a simpler problem and I'm unsure whether we need the upcoming feature (or the polyfill by @ocombe).

We define the structure of all reactive forms with constants, in which we set the properties of each form element. For example:

loginForm = [
  {
    type: 'email',
    placeholder: 'EMAILPLACEHOLDER' // "Your email"
  },
  {
    type: 'password,
    placeholder: 'PASSWORDPLACEHOLDER' // "Your password"
  }
];

Currently we use ngx-translate, so in the template we translate strings like so:

<input *ngFor="let ele of loginForm" [type]="ele.type" [placeholder]="ele.placeholder | translate">

We would now like to migrate to Angular i18n. However, since not all our strings are in a template, it seems that we cannot use Angular i18n out of the box.

Our situation looks simpler than the one in which translations need to be retrieved at runtime though. All our texts are pre-defined in constants, and could be replaced at compile time, just as x18n currently does for strings in templates.

For example, we could have a more complex string like the following one

placeholder: 'I18N:description|meaning@@PASSWORDPLACEHOLDER:Your password'

At compile time the string could be replaced to:

placeholder: 'Your password'

Is this something that might be considered for implementation?

you could definitely do that with runtime i18n, you could use the new i18n service in a pipe for example to transform your placeholders into their translations

I notice this thread has been opened for some time, it shows the major interest for developing multilanguage sites in Angular in a simple way, addressing both HTML and component-code texts closely.
ocombe please don't forget to send all subcribers an alert when that will be done - I understood is round the corner by May 2018. And thanks for your kind contributions!

@ocombe shall we use Ivy to be able to use this runtime services or it'll work with old engine too?

it will only be with ivy, which you cannot use for now since it's not finished

Thanks for all your hard work @ocombe !

Any word on how soon we can use these new features?

Ivy can be enabled in angular 6 with a compiler flag right now, in a pre-release state. Can these new 18n features be used using the Ivy pre-release at this time?

Not yet, but I'm making good progress (working full time on this right now). I'll post an update here once it's available in master to test

thanks so much @ocombe ! So many people are benefiting from your hard work. We all greatly appreciate it!

@ocombe Are static translations in ts still planned? I mean compile time expression replacement, likely with typescript transform.
If not, are we going to be able to plug custom ts transform into the ng build pipeline without extracting ?

I'm not sure to follow what you mean @TinyMan ? You mean being able to merge translations at build time (like it is right now), or being able to use translations in ts code (besides template translations that we already have)?

@ocombe I mean being able to use translation in ts, but not via a service or DI / runtime translation. I mean compile time translation but for typescript. Like C preprocessor. I thought that was planned as per vicb comment:

  • we can also support static translations as we do today for templates - strings would be replaced at compile time, better perf but one version of the app per locale,

For example, I want to be able to write:

export const Notifications = {
    newComment: i18n("New comment notification@@newComment", "New comment on your story !"),
    newStory: i18n("New story notification@@newStory", "{{0}} wrote a new story !"),
}

And then this gets transpiled to something like (in fr):

export const Notifications = {
    newComment: "Un nouveau commentaire a été ajouté a votre article !",
    newStory: "{{0}} a écrit un nouvel article !",
}

Currently for that purpose I use ngx-translate with a custom marker for the extraction (i18n in this example), and when I need to display the message I have to call this.translate.instant(Notifications.newStory, ["TinyMan"]) which does the translating + the interpolation. What I mean is that we'd need to be able to write Notifications.newComment without calling the translate service. And for string interpolation we could have an interpolation method which does only the ICU and interpolation (the template string would already be translated)

Notification.newStory // {{0}} a écrit un nouvel article, static string, no runtime translation
template(Notification.newStory, "TinyMan") // TinyMan a écrit un nouvel article !

Here we just get rid of the tranlations HTTP request and service overhead.

I hope it clarifies ?

What you describe is indeed on the roadmap for i18n.
For the moment, https://github.com/ngx-translate/i18n-polyfill will do the job.

@TinyMan I'm not sure yet if it'll be via a service or a global function. Both are possible, but a service also has a lot of advantages: you can mock it for tests, or replace it with your own, you can also change it's behavior for each module/component
I know that internally Google will use Closure i18n (via goog.getMsg) which is a global function, and the function will most likely be replaced by the service during compilation based on a global flag. I'll try to see if that flag can also be used externally, but I don't see why not. But if you use that, it'll be for templates and code translations

@ocombe I want to throw my thanks in here as well. I super appreciate the work you are doing on this that will be incredibly helpful for me at my job. One question: is there a way, or _will_ there be a way, to do AOT compilation that only makes one set of bundle files that, when looking for text, references the xlf at runtime to get the correct string?

so in essence you would need 1 xlf file per language plus the 5 or 6 bundle files. Right now, if we have 10 languages and AOT compilation its over 50 files: a set of bundle files per languages...

I don't know the effort or the complexity of something like that, but it would be nice to have aot compilation but with only 1 set of files.

yes, that's what we will do with ivy, runtime i18n
bundle your app once, and load the translations at runtime (you can lazy load the translations)

@ocombe that. is. incredible.

That makes me so unbelievable happy. THANK YOU!

Without ability to translate outside templates i18n feature is completely useless for me.

@ocombe Hello Olivier!
Any updates?

Thanks!

If its done it wouldnt matter since we have to wait for Ivy to be complete, which is scheduled for Angular 7 (sept/oct 2018)

By Angular 7 this issue will be two years old LOL.
In any case Ivy has been the thing that has delayed this new feature the most...

@ocombe , that's great to hear. We're currently stuck using a legacy i18n service we rolled from the ground up purely for this reason. Not being able to do it in JS makes it extremely hard to do some simple things for us:

  • Dynamic components
  • Integrating with 3rd party services where we have to provide some localized text to
  • Showing dynamic confirmation modals
  • Our api returns error types. In our context we need to dynamically localize these errors and a template driven approach would be kludgy.
  • We want to use the TitleService as the Angular team suggests, but there is no way to provide localized text!

I'm sort of curious how the Angular team deals with this currently...

Hi @vincentjames501,
At our company we tried to do the same you are doing now, but it was very troublesome, so we ended up using the i18n-polyfill mentioned before in this thread, and so far it's working great.
The only drawback we have is the size of the bundles of our app. We have three translation files, and each bundle contains all of them inside (we haven't figured out a way to generate the bundle just with the translation it uses). But all of this are just nuisances that we hope will be resolved when Ivy is out.
Cheers,

I've included a translation map and reexported it in each language's environment.ts file. It works without issues, is not included in all bundles, and doesn't require an external library. The only issue I have is since evey site is different I can't have the same service worker for all languages and if someone switch languages my site cannot tell if he's already registered for push notifications or not...

@ocombe any news in angular 6?

It will not be in Angular v6, as explained above. We depend on Ivy which is planned for v7.

@ocombe may I suggest that you lock this thread, so that only the team can give meaningful updates when appropriate and we avoid the repetitive questions that you keep answering ?

My hack for use with the current Angular is using an ng-templateand create the embedded view programatically:

<ng-template #messagetotranslate i18n>This message should be translated</ng-template>

and then in in the component ts file:

@ViewChild('messagetotranslate') messagetotranslate: TemplateRef<any>;

createTranslatedMessage(): string {
  return this.messagetotranslate.createEmbeddedView({}).rootNodes[0].textContent;
}

The createTranslatedMessage method returns the translated message. Although I'm using the template to declare the message which is not optimal, this makes it possible for the CLI translate tool to register it in the xlf files, and I have a way of getting the translated message programmatically for use outside templates.

Hopefully the api will work for the routes as well ! For example

export const routes: Routes = [
 {
  path: '',
  component: HomeComponent,
  data: {
   title: i18n('Home'),
   breadcrumb: i18n('Home')
  }
 }
]

If this feature is released, would there be a significant change from the current i18n api? Would you recommend I start my project with i18n now or should I wait for the new i18n Api when Angular 7 gets released?

Any update When we will get run-time and dynamic translation support?

Runtime translations are done and merged (with ivy), but the compiler side is not finished yet.
I'm working on ICU expressions right now.
I haven't started to code the dynamic translations yet (service) but it's probably the next thing I'll do after ICU expressions.

@vincentjames501 Can we use i18n-polyfills with ICU expression?i have get no solution how to implement that

@ocombe, can you please clarify the difference between runtime vs dynamic translations?

there's probably no official denomination, but the way I see it is:

  • runtime: translations are resolved at runtime, meaning not during build (like we do currently). It applies to all types of translations (templates, or in code)
  • dynamic means that you only know what you want to translate at runtime, and you couldn't do it at build time. It mostly applies to "in code" translations, using a service. I guess that you could consider ICU expressions dynamic in the sense that they depend on a variable, but you can still compute all of the possible translations at build time because the number of cases is finite.

I postponed adding i18n support to the current project we're developing but we're getting closer to the release, any idea if this will be final in angular 7? Or should I consider other ways of adding translations?

@andrei-tatar i18n is working perfectly since Angular 2. The only downsides:

  • No code translations (this issue) -> If you need it, use i18n-polyfill
  • If you build AOT(recommended), the app has to be built for each language separately
  • Since each app is built separately, when switching the language you have to reload the page

For the last two points you could use @ngx-translate instead. But it works differently from Angular's built in i18n so an later update could take some time. For the polyfill the update will be no time with probably no breaking changes.

@ocombe

How we can handle conditional operator using Angular i18n feature

*

@shobhit12345

We use ngSwitch to template all messages, so we can attach i18n-tags to them individually.

<div>
    <ng-container [ngSwitch]="paymentMethod.historyOpned">
        <ng-container *ngSwitchCase="true" i18n="@@hide">Hide</ng-container>
        <ng-container *ngSwitchCase="false" i18n="@@show">Show</ng-container>
    </ng-container>
    <ng-container>&nbsp;<ng-container>
    <ng-container i18n="@@history">History</ng-container>
</div>

We use this method a lot to get around the missing dynamic translations. Given a bit of thought a surprising amount of text can be expressed in this way in templates. A common pattern is to have an array of messages, and then use this with ngFor and ngSwitch to template the messages, something like this:

ts

messsages = [ 'this is the first message', 'this is the second message' ]
````

html



This is the first message
This is the second message


```

It's somewhat verboose - but it works!

Thanks a lot , I got the idea .

On Fri, 24 Aug 2018, 16:56 David Walther Birk Lauridsen, <
[email protected]> wrote:

@shobhit12345 https://github.com/shobhit12345

We use ngSwitch to template all messages, so we can attach i18n-tags to
them individually.

Hide Show   History

We use this method a lot to get around the missing dynamic translations.
Ggiven a bit of thought a surprising amount of text can be expressed in
this way in templates. A common pattern is to have an array of messages,
and then use this with ngFor and ngSwitch to template the messages,
something like this:

ts

messsages = [ 'this is the first message', 'this is the second message' ]

html


i18n="@@firstMesssage">This is the first message
i18n="@@secondMesssage">This is the second message

It's somewhat verboose - but it works!


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/angular/angular/issues/11405#issuecomment-415731284,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AGwM6jcWIpwtGxhkH1fwnVXNagRxmMnoks5uT-LxgaJpZM4J2pkr
.

If its done it wouldnt matter since we have to wait for Ivy to be complete, which is scheduled for Angular 7 (sept/oct 2018)

It's alraedy October. Do you think we'll have the new features by the end of October?

@lizzymendivil according to https://is-angular-ivy-ready.firebaseapp.com Ivy is 65% complete and it seems unlikely to be completed in only 30 days

Yes, ivy will not be finished in one month.

@ocombe can you please lock this down so only you can post updates. its a little annoying to get all the notifications of all the "when is it done?" comments

@ocombe it seems like Ivy is now at roughly 94%, do you think this may be ready by the end of the year?

I don't think so. Being feature ready and bug free (= usable) is very different. We're mostly working on fixing things right now.

@ocombe can we believe that i18n came before angular 8?

I don't think so. Being feature ready and bug free (= usable) is very different. We're mostly working on fixing things right now.

Ok, thanks for the prompt reply, much appreciated.

Any update @ocombe that by when we should expect ( Year End i see in last few comments ) the "Code Translation" part available along with "Template Translation" for I18N support with Angular? We thought to club ngx-translate-polyfill for the same along with Angular I18n feature but there also it seems xlf support is not available for merging existing xlf file by using https://github.com/biesbjerg/ngx-translate-extract CLI.

Thanks !

@Abekonge

We use ngSwitch to template all messages, so we can attach i18n-tags to them individually.
It's somewhat verboose - but it works!

Thanks for the suggestion! It seems the easiest to understand. My question is, do you still use this implementation in situations where the array has a "lot" of a values, like 10 or more? Seems like the code would get very bulky.

I dont know if we have any with that many, but why not. If it’s messages used several places we make small i18n components that are basically just the switch and i18n tags.

David

Den 23. jan. 2019 kl. 19.24 skrev Andrew Bissada notifications@github.com:

@Abekonge

We use ngSwitch to template all messages, so we can attach i18n-tags to them individually.
It's somewhat verboose - but it works!

Thanks for the suggestion! It seems the easiest to understand. My question is, do you still use this implementation in situations where the array has a "lot" of a values, like 10 or more? Seems like the code would get very bulky.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

How to user i18n-polifills in Validator classes. I a not able to user covert validator message through i18n service .

//In componenet
this.crForm= this.fb.group({
userName: [''],
ePassword: [''],
}, { validator: new AccValidator(this.i18n).validate()}
);

//Validator

import { AbstractControl, ValidationErrors, FormGroup, FormControl } from '@angular/forms';
import { I18n } from '@ngx-translate/i18n-polyfill';
export class AccValidator{
constructor(i18n:I18n)
{

}
validate() {       
    return (group: FormGroup): createAccountError => {
        if (group) {
            this.i18n("test");
}}}

getting below error

RROR Error: Uncaught (in promise): TypeError: i18n is not a function
TypeError: i18n is not a function
at FormGroup.eval [as validator] (acc-validator.ts:9)
at FormGroup.AbstractControl._runValidator (forms.js:3433)
at FormGroup.AbstractControl.updateValueAndValidity (forms.js:3387)
at new FormGroup (forms.js:4326)
at FormBuilder.group (forms.js:7864)

@ocombe

How to use i18n-polyfills with const in .ts files

export const glossayModel= () => [
{
title: 'test data',
active: true,
data: [

        [
            {
                title: 'test data',
                data: "test data."
            },
            {
                title: 'test data',
                data: "test data"
            },
            {
                title: 'test data',
                data: "test data"
            },
            {
                title: 'test data',
                data: "test data."
            },
            {
                title: 'Past Due',
                data: "test data."
            }

]]}];


{VAR_SELECT, select, Medical {Medical} Dental {Dental} Medical & Dental {Medical & Dental} Catastrophic & Dental {Catastrophic & Dental} Catastrophic {Catastrophic} }
{VAR_SELECT, select, Medical {Medical} Dental {Dental} Medical### & Dental {Medical y Dental}
Catastrophic ### & Dental{Catastrophic y Dental} Catastrophic {Catastrophic} }

When i am using ICU expression with special character , its not converting .

@ocombe How to handle breadcrumbs using i18n-polyfills?

export const routes: Routes = [
{
path: '',
component: HomeComponent,
data: {
title: 'Home',
breadcrumb: 'Home'
}
}
]

@ocombe

How to use i18n-polyfills with const in .ts files

export const glossayModel= () => [
{
title: 'test data',
active: true,
data: [

        [
            {
                title: 'test data',
                data: "test data."
            },
            {
                title: 'test data',
                data: "test data"
            },
            {
                title: 'test data',
                data: "test data"
            },
            {
                title: 'test data',
                data: "test data."
            },
            {
                title: 'Past Due',
                data: "test data."
            }

]]}];

You can use like this
{
data: 'test data',
title: this.i18n({ value: 'Name', id: 'name' }),
}
inject I18n polyfill in component constructor like this
private i18n: I18n
When using translations in ts file you need to use ngx-extractor and xliffmerge to get those ts files to translated. https://www.npmjs.com/package/ngx-i18nsupport

@JanneHarju
its not working , not getting the data in xlf file.

Here is my translation script:
"translate-i18n": "ng xi18n --output-path locale && ngx-extractor -i src/**/*.ts projects/**/*.ts -f xlf -o src/locale/messages.xlf && xliffmerge --profile xliffmerge.json fi en",

and here is xliffmerge.json

{
  "xliffmergeOptions": {
    "srcDir": "src/locale",
    "genDir": "src/locale",
    "i18nFile": "messages.xlf",
    "defaultLanguage": "en",
    "useSourceAsTarget": true
  }
}

@JanneHarju
Yes i m using same configuration , and able to extract .ts messages in xlf file .

But const i have in different .ts files

export const glossayModel= () => [
{
}

i am importing in component , when i m trying to use i18n with const , it is not extracting the values .

What if you use const values like provider.

{
    provide: glossayModel,
    useFactory: () => glossayModel()
}

code might be broken but you maybe get point.

how to translate breadcrumbs

const routes: Routes = [
{
path: './enroll-new.component',
component: EnrollNewComponent,
data: {
breadcrumb: 'Enrollment'
},
}
]

can you people stop using this thread as a support thread for i18n-polyfill or other issues? if you have a question regarding i18n-polyfill go raise an issue in that repo.
@ocombe maybe you can lock this topic? pretty much everything important should be covered.
whats the angular version you plan on releasing the i18n support mentioned in this topic?

Yes, locked for now.
We are working hard to have ivy available with v8 as an opt-in beta. Runtime i18n will be usable with google closure (what google uses internally) which will let us debug it (it's a very big change). For everyone else you will be able to test ivy with i18n but you won't be able to load translations. The app will run with the original language used to code the app.
The runtime service that is needed to actually translate currently returns the text untranslated. Since we're all working on fixing bugs with the existing code, new features will be limited to after the release. We'll work on it once v8 is out, it should be my first task.
I'll add an update and unlock this thread once we have some news.

For what it is worth the new $localize tag that we are working on can be used programatically.

E.g.

const name = 'World';
$localize `Hello ${name}:name!`;

And then will be able to provide translations for Hello {$name}! that can be replaced at compile-time (or run-time).

This is now available (if not documented).

Was this page helpful?
0 / 5 - 0 ratings