fix(core): handle spaces after `select` and `plural` ICU keywords (#37866)

Currently when the `plural` or `select` keywords in an ICU contain trailing spaces (e.g. `{count, select , ...}`), these spaces are also included into the key names in ICU vars (e.g. "VAR_SELECT "). These trailing spaces are not desirable, since they will later be converted into `_` symbols while normalizing placeholder names, thus causing mismatches at runtime (i.e. placeholder will not be replaced with the correct value). This commit updates the code to trim these spaces while generating an object with placeholders, to make sure the runtime logic can replace these placeholders with the right values.

PR Close #37866
This commit is contained in:
Andrew Kushnir 2020-06-29 17:50:21 -07:00 committed by Alex Rickabaugh
parent d148fdccf2
commit aed6b131bb
3 changed files with 62 additions and 1 deletions

View File

@ -3661,6 +3661,39 @@ describe('i18n support in the template compiler', () => {
verify(input, output);
});
it('should produce proper messages when `select` or `plural` keywords have spaces after them',
() => {
const input = `
<div i18n>
{count, select , 1 {one} other {more than one}}
{count, plural , =1 {one} other {more than one}}
</div>
`;
const output = String.raw`
var $I18N_1$;
if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) {
const $MSG_EXTERNAL_199763560911211963$$APP_SPEC_TS_2$ = goog.getMsg("{VAR_SELECT , select , 1 {one} other {more than one}}");
$I18N_1$ = $MSG_EXTERNAL_199763560911211963$$APP_SPEC_TS_2$;
}
else {
$I18N_1$ = $localize \`{VAR_SELECT , select , 1 {one} other {more than one}}\`;
}
$I18N_1$ = i0.ɵɵi18nPostprocess($I18N_1$, { "VAR_SELECT": "\uFFFD0\uFFFD" });
var $I18N_3$;
if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) {
const $MSG_EXTERNAL_3383986062053865025$$APP_SPEC_TS_4$ = goog.getMsg("{VAR_PLURAL , plural , =1 {one} other {more than one}}");
$I18N_3$ = $MSG_EXTERNAL_3383986062053865025$$APP_SPEC_TS_4$;
}
else {
$I18N_3$ = $localize \`{VAR_PLURAL , plural , =1 {one} other {more than one}}\`;
}
$I18N_3$ = i0.ɵɵi18nPostprocess($I18N_3$, { "VAR_PLURAL": "\uFFFD1\uFFFD" });
`;
verify(input, output);
});
});
describe('$localize legacy message ids', () => {

View File

@ -273,10 +273,20 @@ class HtmlAstToIvyAst implements html.Visitor {
const value = message.placeholders[key];
if (key.startsWith(I18N_ICU_VAR_PREFIX)) {
const config = this.bindingParser.interpolationConfig;
// ICU expression is a plain string, not wrapped into start
// and end tags, so we wrap it before passing to binding parser
const wrapped = `${config.start}${value}${config.end}`;
vars[key] = this._visitTextWithInterpolation(wrapped, expansion.sourceSpan) as t.BoundText;
// Currently when the `plural` or `select` keywords in an ICU contain trailing spaces (e.g.
// `{count, select , ...}`), these spaces are also included into the key names in ICU vars
// (e.g. "VAR_SELECT "). These trailing spaces are not desirable, since they will later be
// converted into `_` symbols while normalizing placeholder names, which might lead to
// mismatches at runtime (i.e. placeholder will not be replaced with the correct value).
const formattedKey = key.trim();
vars[formattedKey] =
this._visitTextWithInterpolation(wrapped, expansion.sourceSpan) as t.BoundText;
} else {
placeholders[key] = this._visitTextWithInterpolation(value, expansion.sourceSpan);
}

View File

@ -627,6 +627,24 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
expect(element.textContent).toContain('ICU start --> Autre <-- ICU end');
});
it('when `select` or `plural` keywords have spaces after them', () => {
loadTranslations({
[computeMsgId('{VAR_SELECT , select , 10 {ten} 20 {twenty} other {other}}')]:
'{VAR_SELECT , select , 10 {dix} 20 {vingt} other {autre}}',
[computeMsgId('{VAR_PLURAL , plural , =0 {zero} =1 {one} other {other}}')]:
'{VAR_PLURAL , plural , =0 {zéro} =1 {une} other {autre}}'
});
const fixture = initWithTemplate(AppComp, `
<div i18n>
{count, select , 10 {ten} 20 {twenty} other {other}} -
{count, plural , =0 {zero} =1 {one} other {other}}
</div>
`);
const element = fixture.nativeElement;
expect(element.textContent).toContain('autre - zéro');
});
it('with no root node and text and DOM nodes surrounding ICU', () => {
loadTranslations({
[computeMsgId('{VAR_SELECT, select, 10 {Ten} 20 {Twenty} other {Other}}')]: