Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i18n: Able to use translation strings outside a template #11405

Closed
marcalj opened this issue Sep 7, 2016 · 204 comments
Closed

i18n: Able to use translation strings outside a template #11405

marcalj opened this issue Sep 7, 2016 · 204 comments

Comments

@marcalj
Copy link

@marcalj marcalj commented Sep 7, 2016

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

[x] feature request

Current behavior
#9104 (comment)

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

@marcalj marcalj mentioned this issue Sep 7, 2016
13 of 21 tasks complete
@Mattes83
Copy link

@Mattes83 Mattes83 commented Sep 20, 2016

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

@marcalj
Copy link
Author

@marcalj marcalj commented Sep 20, 2016

@Mattes83 Did you try this way?

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

@manklu manklu commented Sep 20, 2016

😕 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.

@Mattes83
Copy link

@Mattes83 Mattes83 commented Sep 21, 2016

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

@rolandoldengarm
Copy link

@rolandoldengarm rolandoldengarm commented Sep 30, 2016

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.

@vicb
Copy link
Contributor

@vicb vicb commented Sep 30, 2016

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': '...'});
@fbobbio
Copy link

@fbobbio fbobbio commented Oct 14, 2016

Hi guys, great feature request 👍

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

@mstawick
Copy link

@mstawick mstawick commented Oct 17, 2016

@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.

@db6edr
Copy link

@db6edr db6edr commented Oct 20, 2016

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.

@pharapiak
Copy link

@pharapiak pharapiak commented Oct 20, 2016

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.

@quanterion
Copy link

@quanterion quanterion commented Nov 3, 2016

@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?

@aaronleesmith
Copy link

@aaronleesmith aaronleesmith commented Nov 4, 2016

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.

@lvlbmeunier
Copy link

@lvlbmeunier lvlbmeunier commented Nov 7, 2016

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>
@sknoth
Copy link

@sknoth sknoth commented Dec 6, 2016

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?

@lvlbmeunier
Copy link

@lvlbmeunier lvlbmeunier commented Dec 6, 2016

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.

@sknoth
Copy link

@sknoth sknoth commented Dec 6, 2016

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.

@lvlbmeunier
Copy link

@lvlbmeunier lvlbmeunier commented Dec 6, 2016

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.

@fredrikredflag
Copy link

@fredrikredflag fredrikredflag commented Dec 20, 2016

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?

@ghidoz
Copy link

@ghidoz ghidoz commented Dec 20, 2016

@fredrikredflag Awesome! And what about AOT?

@fredrikredflag
Copy link

@fredrikredflag fredrikredflag commented Dec 20, 2016

@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);
});
@PowerKiKi
Copy link
Contributor

@PowerKiKi PowerKiKi commented Oct 1, 2018

@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

@ocombe
Copy link
Contributor

@ocombe ocombe commented Oct 1, 2018

Yes, ivy will not be finished in one month.

@JanEggers
Copy link

@JanEggers JanEggers commented Oct 1, 2018

@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

@asultan001
Copy link

@asultan001 asultan001 commented Dec 5, 2018

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

@ocombe
Copy link
Contributor

@ocombe ocombe commented Dec 5, 2018

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

@NiZelooer
Copy link

@NiZelooer NiZelooer commented Dec 5, 2018

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

@asultan001
Copy link

@asultan001 asultan001 commented Dec 5, 2018

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.

@animbalk
Copy link

@animbalk animbalk commented Jan 2, 2019

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 !

@andrewbissada
Copy link

@andrewbissada andrewbissada commented Jan 23, 2019

@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.

@Abekonge
Copy link

@Abekonge Abekonge commented Jan 23, 2019

@shobhit12345
Copy link

@shobhit12345 shobhit12345 commented Feb 1, 2019

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)

@shobhit12345
Copy link

@shobhit12345 shobhit12345 commented Feb 6, 2019

@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."
            }

]]}];

@shobhit12345
Copy link

@shobhit12345 shobhit12345 commented Feb 7, 2019

{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 .

@shobhit12345
Copy link

@shobhit12345 shobhit12345 commented Feb 15, 2019

@ocombe How to handle breadcrumbs using i18n-polyfills?

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

@JanneHarju
Copy link

@JanneHarju JanneHarju commented Feb 27, 2019

@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

@shobhit12345
Copy link

@shobhit12345 shobhit12345 commented Feb 27, 2019

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

@JanneHarju
Copy link

@JanneHarju JanneHarju commented Feb 27, 2019

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
  }
}

@shobhit12345
Copy link

@shobhit12345 shobhit12345 commented Feb 27, 2019

@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 .

@JanneHarju
Copy link

@JanneHarju JanneHarju commented Mar 7, 2019

What if you use const values like provider.

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

code might be broken but you maybe get point.

@shobhit12345
Copy link

@shobhit12345 shobhit12345 commented Mar 19, 2019

how to translate breadcrumbs

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

@ngehlert
Copy link

@ngehlert ngehlert commented Mar 25, 2019

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?

@angular angular locked and limited conversation to collaborators Mar 25, 2019
@ocombe
Copy link
Contributor

@ocombe ocombe commented Mar 25, 2019

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.

@petebacondarwin
Copy link
Member

@petebacondarwin petebacondarwin commented Aug 8, 2019

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).

@ocombe ocombe removed their assignment Dec 2, 2019
@petebacondarwin petebacondarwin modified the milestones: Backlog, i18n-ideas Feb 12, 2020
@petebacondarwin
Copy link
Member

@petebacondarwin petebacondarwin commented Sep 25, 2020

This is now available (if not documented).

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
Core Roadmap
i18n / l10n
Linked pull requests

Successfully merging a pull request may close this issue.

None yet