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

[ ] bug report => search github for a similar issue or PR before submitting
[x] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

I am logging a feature request to support more dynamic i18n lookups.

Current behavior

From what I can tell the current i18n approach can only target elements with a static reference in the markup.

(e.g. <div i18n="meaning|description">English Text</div>)

Expected behavior

One of the challenges I see with this is building a data driven ui with translations. If you come from the ng-translate world it's not uncommon to loop over a list of language keys and do lookups to resolve translated values.

It's not clear to me how to replicate this behavior using i18n in angular.

It would be nice if i18n allowed us to specify the keys based on data binding instead of hard coded references in the markup.

Example:

<div *ngFor="let key of keys">
<div i18n="@@{{key}}"></div>
</div>

It looks like the ability to set your own @@keys was added in 4.0. Thinking this key is perfect for the lookup, but it would need to be generated based on bindings.

This would also assume that we are allowed to manually create the xlf file instead of relying on the i18n compiler cli tool to generate it. The list of keys could potentially come from an api and would not be a fixed list. There would be no way for the generator to know the set until runtime.

I still think this can be made compatible with AoT. It would require one more level of indirection in the translation lookup though.

Instead of

var currVal_2 = inlineInterpolate(1, ‘Welcome ', this.parentView.context.firstNameName, '');

You could maybe have

var currVal_2 = inlineInterpolate(1, lookupKey('@@Welcome'), this.parentView.context.firstNameName, '');

What is the motivation / use case for changing the behavior?

The motivation is a more flexible translation experience.

Angular version: 2.0.X

Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]

Language: [all | TypeScript X.X | ES6/7 | ES5]

Node (for AoT issues): node --version =

Issue 2:
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