fix(core): move generated i18n statements to the consts
field of ComponentDef (#38404)
This commit updates the code to move generated i18n statements into the `consts` field of ComponentDef to avoid invoking `$localize` function before component initialization (to better support runtime translations) and also avoid problems with lazy-loading when i18n defs may not be present in a chunk where it's referenced. Prior to this change the i18n statements were generated at the top leve: ``` var I18N_0; if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { var MSG_X = goog.getMsg(“…”); I18N_0 = MSG_X; } else { I18N_0 = $localize('...'); } defineComponent({ // ... template: function App_Template(rf, ctx) { i0.ɵɵi18n(2, I18N_0); } }); ``` This commit updates the logic to generate the following code instead: ``` defineComponent({ // ... consts: function() { var I18N_0; if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { var MSG_X = goog.getMsg(“…”); I18N_0 = MSG_X; } else { I18N_0 = $localize('...'); } return [ I18N_0 ]; }, template: function App_Template(rf, ctx) { i0.ɵɵi18n(2, 0); } }); ``` Also note that i18n template instructions now refer to the `consts` array using an index (similar to other template instructions). PR Close #38404
This commit is contained in:

committed by
Andrew Scott

parent
5f90b64328
commit
cb05c0102f
@ -12,6 +12,7 @@ import {getTranslationForTemplate} from '@angular/core/src/render3/i18n/i18n_par
|
||||
import {noop} from '../../../compiler/src/render3/view/util';
|
||||
import {setDelayProjection, ɵɵelementEnd, ɵɵelementStart} from '../../src/render3/instructions/all';
|
||||
import {I18nUpdateOpCodes, TI18n, TIcu} from '../../src/render3/interfaces/i18n';
|
||||
import {TConstants} from '../../src/render3/interfaces/node';
|
||||
import {HEADER_OFFSET, LView, TVIEW} from '../../src/render3/interfaces/view';
|
||||
import {getNativeByIndex} from '../../src/render3/util/view_utils';
|
||||
|
||||
@ -57,26 +58,29 @@ describe('Runtime i18n', () => {
|
||||
});
|
||||
|
||||
function prepareFixture(
|
||||
createTemplate: () => void, updateTemplate: (() => void)|null, nbConsts = 0,
|
||||
nbVars = 0): TemplateFixture {
|
||||
return new TemplateFixture(createTemplate, updateTemplate || noop, nbConsts, nbVars);
|
||||
createTemplate: () => void, updateTemplate: (() => void)|null, nbConsts = 0, nbVars = 0,
|
||||
consts: TConstants = []): TemplateFixture {
|
||||
return new TemplateFixture(
|
||||
createTemplate, updateTemplate || noop, nbConsts, nbVars, null, null, null, undefined,
|
||||
consts);
|
||||
}
|
||||
|
||||
function getOpCodes(
|
||||
createTemplate: () => void, updateTemplate: (() => void)|null, nbConsts: number,
|
||||
index: number): TI18n|I18nUpdateOpCodes {
|
||||
const fixture = prepareFixture(createTemplate, updateTemplate, nbConsts);
|
||||
messageOrAtrs: string|string[], createTemplate: () => void, updateTemplate: (() => void)|null,
|
||||
nbConsts: number, index: number): TI18n|I18nUpdateOpCodes {
|
||||
const fixture =
|
||||
prepareFixture(createTemplate, updateTemplate, nbConsts, undefined, [messageOrAtrs]);
|
||||
const tView = fixture.hostView[TVIEW];
|
||||
return tView.data[index + HEADER_OFFSET] as TI18n;
|
||||
}
|
||||
|
||||
describe('i18nStart', () => {
|
||||
it('for text', () => {
|
||||
const MSG_DIV = `simple text`;
|
||||
const message = 'simple text';
|
||||
const nbConsts = 1;
|
||||
const index = 0;
|
||||
const opCodes = getOpCodes(() => {
|
||||
ɵɵi18nStart(index, MSG_DIV);
|
||||
const opCodes = getOpCodes(message, () => {
|
||||
ɵɵi18nStart(index, 0);
|
||||
}, null, nbConsts, index) as TI18n;
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
@ -91,13 +95,13 @@ describe('Runtime i18n', () => {
|
||||
});
|
||||
|
||||
it('for elements', () => {
|
||||
const MSG_DIV = `Hello <20>#2<>world<6C>/#2<> and <20>#3<>universe<73>/#3<>!`;
|
||||
const message = `Hello <20>#2<>world<6C>/#2<> and <20>#3<>universe<73>/#3<>!`;
|
||||
// Template: `<div>Hello <div>world</div> and <span>universe</span>!`
|
||||
// 3 consts for the 2 divs and 1 span + 1 const for `i18nStart` = 4 consts
|
||||
const nbConsts = 4;
|
||||
const index = 1;
|
||||
const opCodes = getOpCodes(() => {
|
||||
ɵɵi18nStart(index, MSG_DIV);
|
||||
const opCodes = getOpCodes(message, () => {
|
||||
ɵɵi18nStart(index, 0);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
@ -124,11 +128,11 @@ describe('Runtime i18n', () => {
|
||||
});
|
||||
|
||||
it('for simple bindings', () => {
|
||||
const MSG_DIV = `Hello <20>0<EFBFBD>!`;
|
||||
const message = `Hello <20>0<EFBFBD>!`;
|
||||
const nbConsts = 2;
|
||||
const index = 1;
|
||||
const opCodes = getOpCodes(() => {
|
||||
ɵɵi18nStart(index, MSG_DIV);
|
||||
const opCodes = getOpCodes(message, () => {
|
||||
ɵɵi18nStart(index, 0);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
expect((opCodes as any).update.debug).toEqual([
|
||||
@ -148,11 +152,11 @@ describe('Runtime i18n', () => {
|
||||
});
|
||||
|
||||
it('for multiple bindings', () => {
|
||||
const MSG_DIV = `Hello <20>0<EFBFBD> and <20>1<EFBFBD>, again <20>0<EFBFBD>!`;
|
||||
const message = `Hello <20>0<EFBFBD> and <20>1<EFBFBD>, again <20>0<EFBFBD>!`;
|
||||
const nbConsts = 2;
|
||||
const index = 1;
|
||||
const opCodes = getOpCodes(() => {
|
||||
ɵɵi18nStart(index, MSG_DIV);
|
||||
const opCodes = getOpCodes(message, () => {
|
||||
ɵɵi18nStart(index, 0);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
@ -176,17 +180,15 @@ describe('Runtime i18n', () => {
|
||||
// </span>
|
||||
// !
|
||||
// </div>
|
||||
const MSG_DIV =
|
||||
const message =
|
||||
`<EFBFBD>0<EFBFBD> is rendered as: <20>*2:1<><31>#1:1<>before<72>*2:2<><32>#1:2<>middle<6C>/#1:2<><32>/*2:2<>after<65>/#1:1<><31>/*2:1<>!`;
|
||||
|
||||
/**** Root template ****/
|
||||
// <20>0<EFBFBD> is rendered as: <20>*2:1<><31>/*2:1<>!
|
||||
let nbConsts = 3;
|
||||
let index = 1;
|
||||
const firstTextNode = 3;
|
||||
const rootTemplate = 2;
|
||||
let opCodes = getOpCodes(() => {
|
||||
ɵɵi18nStart(index, MSG_DIV);
|
||||
let opCodes = getOpCodes(message, () => {
|
||||
ɵɵi18nStart(index, 0);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
@ -207,10 +209,8 @@ describe('Runtime i18n', () => {
|
||||
// <20>#1:1<>before<72>*2:2<>middle<6C>/*2:2<>after<65>/#1:1<>
|
||||
nbConsts = 3;
|
||||
index = 0;
|
||||
const spanElement = 1;
|
||||
const bElementSubTemplate = 2;
|
||||
opCodes = getOpCodes(() => {
|
||||
ɵɵi18nStart(index, MSG_DIV, 1);
|
||||
opCodes = getOpCodes(message, () => {
|
||||
ɵɵi18nStart(index, 0, 1);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
@ -233,9 +233,8 @@ describe('Runtime i18n', () => {
|
||||
// middle
|
||||
nbConsts = 2;
|
||||
index = 0;
|
||||
const bElement = 1;
|
||||
opCodes = getOpCodes(() => {
|
||||
ɵɵi18nStart(index, MSG_DIV, 2);
|
||||
opCodes = getOpCodes(message, () => {
|
||||
ɵɵi18nStart(index, 0, 2);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
@ -252,15 +251,15 @@ describe('Runtime i18n', () => {
|
||||
});
|
||||
|
||||
it('for ICU expressions', () => {
|
||||
const MSG_DIV = `{<7B>0<EFBFBD>, plural,
|
||||
const message = `{<7B>0<EFBFBD>, plural,
|
||||
=0 {no <b title="none">emails</b>!}
|
||||
=1 {one <i>email</i>}
|
||||
other {<7B>0<EFBFBD> <span title="<22>1<EFBFBD>">emails</span>}
|
||||
}`;
|
||||
const nbConsts = 1;
|
||||
const index = 0;
|
||||
const opCodes = getOpCodes(() => {
|
||||
ɵɵi18nStart(index, MSG_DIV);
|
||||
const opCodes = getOpCodes(message, () => {
|
||||
ɵɵi18nStart(index, 0);
|
||||
}, null, nbConsts, index) as TI18n;
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
@ -337,7 +336,7 @@ describe('Runtime i18n', () => {
|
||||
});
|
||||
|
||||
it('for nested ICU expressions', () => {
|
||||
const MSG_DIV = `{<7B>0<EFBFBD>, plural,
|
||||
const message = `{<7B>0<EFBFBD>, plural,
|
||||
=0 {zero}
|
||||
other {<7B>0<EFBFBD> {<7B>1<EFBFBD>, select,
|
||||
cat {cats}
|
||||
@ -347,16 +346,9 @@ describe('Runtime i18n', () => {
|
||||
}`;
|
||||
const nbConsts = 1;
|
||||
const index = 0;
|
||||
const opCodes = getOpCodes(() => {
|
||||
ɵɵi18nStart(index, MSG_DIV);
|
||||
const opCodes = getOpCodes(message, () => {
|
||||
ɵɵi18nStart(index, 0);
|
||||
}, null, nbConsts, index);
|
||||
const icuCommentNodeIndex = index + 1;
|
||||
const firstTextNodeIndex = index + 2;
|
||||
const nestedIcuCommentNodeIndex = index + 3;
|
||||
const lastTextNodeIndex = index + 4;
|
||||
const nestedTextNodeIndex = index + 5;
|
||||
const tIcuIndex = 1;
|
||||
const nestedTIcuIndex = 0;
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
vars: 9,
|
||||
@ -443,31 +435,31 @@ describe('Runtime i18n', () => {
|
||||
|
||||
describe(`i18nAttribute`, () => {
|
||||
it('for text', () => {
|
||||
const MSG_title = `Hello world!`;
|
||||
const MSG_div_attr = ['title', MSG_title];
|
||||
const message = `Hello world!`;
|
||||
const attrs = ['title', message];
|
||||
const nbConsts = 2;
|
||||
const index = 1;
|
||||
const fixture = prepareFixture(() => {
|
||||
ɵɵelementStart(0, 'div');
|
||||
ɵɵi18nAttributes(index, MSG_div_attr);
|
||||
ɵɵi18nAttributes(index, 0);
|
||||
ɵɵelementEnd();
|
||||
}, null, nbConsts, index);
|
||||
}, null, nbConsts, index, [attrs]);
|
||||
const tView = fixture.hostView[TVIEW];
|
||||
const opCodes = tView.data[index + HEADER_OFFSET] as I18nUpdateOpCodes;
|
||||
|
||||
expect(opCodes).toEqual([]);
|
||||
expect(
|
||||
(getNativeByIndex(0, fixture.hostView as LView) as any as Element).getAttribute('title'))
|
||||
.toEqual(MSG_title);
|
||||
.toEqual(message);
|
||||
});
|
||||
|
||||
it('for simple bindings', () => {
|
||||
const MSG_title = `Hello <20>0<EFBFBD>!`;
|
||||
const MSG_div_attr = ['title', MSG_title];
|
||||
const message = `Hello <20>0<EFBFBD>!`;
|
||||
const attrs = ['title', message];
|
||||
const nbConsts = 2;
|
||||
const index = 1;
|
||||
const opCodes = getOpCodes(() => {
|
||||
ɵɵi18nAttributes(index, MSG_div_attr);
|
||||
const opCodes = getOpCodes(attrs, () => {
|
||||
ɵɵi18nAttributes(index, 0);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
expect(opCodes).toEqual(debugMatch([
|
||||
@ -476,12 +468,12 @@ describe('Runtime i18n', () => {
|
||||
});
|
||||
|
||||
it('for multiple bindings', () => {
|
||||
const MSG_title = `Hello <20>0<EFBFBD> and <20>1<EFBFBD>, again <20>0<EFBFBD>!`;
|
||||
const MSG_div_attr = ['title', MSG_title];
|
||||
const message = `Hello <20>0<EFBFBD> and <20>1<EFBFBD>, again <20>0<EFBFBD>!`;
|
||||
const attrs = ['title', message];
|
||||
const nbConsts = 2;
|
||||
const index = 1;
|
||||
const opCodes = getOpCodes(() => {
|
||||
ɵɵi18nAttributes(index, MSG_div_attr);
|
||||
const opCodes = getOpCodes(attrs, () => {
|
||||
ɵɵi18nAttributes(index, 0);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
expect(opCodes).toEqual(debugMatch([
|
||||
@ -490,12 +482,12 @@ describe('Runtime i18n', () => {
|
||||
});
|
||||
|
||||
it('for multiple attributes', () => {
|
||||
const MSG_title = `Hello <20>0<EFBFBD>!`;
|
||||
const MSG_div_attr = ['title', MSG_title, 'aria-label', MSG_title];
|
||||
const message = `Hello <20>0<EFBFBD>!`;
|
||||
const attrs = ['title', message, 'aria-label', message];
|
||||
const nbConsts = 2;
|
||||
const index = 1;
|
||||
const opCodes = getOpCodes(() => {
|
||||
ɵɵi18nAttributes(index, MSG_div_attr);
|
||||
const opCodes = getOpCodes(attrs, () => {
|
||||
ɵɵi18nAttributes(index, 0);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
expect(opCodes).toEqual(debugMatch([
|
||||
@ -643,4 +635,4 @@ describe('Runtime i18n', () => {
|
||||
.toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user