fix(ivy): i18n - update the compiler to output MessageId
s (#32594)
Now that the `$localize` translations are `MessageId` based the compiler must render `MessageId`s in its generated `$localize` code. This is because the `MessageId` used by the compiler is computed from information that does not get passed through to the `$localize` tagged string. For example, the generated code for the following template ```html <div id="static" i18n-title="m|d" title="introduction"></div> ``` will contain these localization statements ```ts if (ngI18nClosureMode) { /** * @desc d * @meaning m */ const MSG_EXTERNAL_8809028065680254561$$APP_SPEC_TS_1 = goog.getMsg("introduction"); I18N_1 = MSG_EXTERNAL_8809028065680254561$$APP_SPEC_TS_1; } else { I18N_1 = $localize \`:m|d@@8809028065680254561:introduction\`; } ``` Since `$localize` is not able to accurately regenerate the source-message (and so the `MessageId`) from the generated code, it must rely upon the `MessageId` being provided explicitly in the generated code. The compiler now prepends all localized messages with a "metadata block" containing the id (and the meaning and description if defined). Note that this metadata block will also allow translation file extraction from the compiled code - rather than relying on the legacy ViewEngine extraction code. (This will be implemented post-v9). Although these metadata blocks add to the initial code size, compile-time inlining will completely remove these strings and so will not impact on production bundle size. PR Close #32594
This commit is contained in:

committed by
Andrew Kushnir

parent
357aa4a097
commit
b741a1c3e7
@ -9,6 +9,7 @@ import * as i18n from '../../../i18n/i18n_ast';
|
||||
import * as o from '../../../output/output_ast';
|
||||
|
||||
import {serializeIcuNode} from './icu_serializer';
|
||||
import {metaFromI18nMessage, serializeI18nMeta} from './meta';
|
||||
import {formatI18nPlaceholderName} from './util';
|
||||
|
||||
export function createLocalizeStatements(
|
||||
@ -16,15 +17,13 @@ export function createLocalizeStatements(
|
||||
params: {[name: string]: o.Expression}): o.Statement[] {
|
||||
const statements = [];
|
||||
|
||||
// TODO: re-enable these comments when we have a plan on how to make them work so that Closure
|
||||
// compiler doesn't complain about the JSDOC comments.
|
||||
|
||||
// const jsdocComment = i18nMetaToDocStmt(metaFromI18nMessage(message));
|
||||
// if (jsdocComment !== null) {
|
||||
// statements.push(jsdocComment);
|
||||
// }
|
||||
const metaBlock = serializeI18nMeta(metaFromI18nMessage(message));
|
||||
|
||||
const {messageParts, placeHolders} = serializeI18nMessageForLocalize(message);
|
||||
|
||||
// Update first message part with metadata
|
||||
messageParts[0] = `:${metaBlock}:${messageParts[0]}`;
|
||||
|
||||
statements.push(new o.ExpressionStatement(variable.set(
|
||||
o.localizedString(messageParts, placeHolders, placeHolders.map(ph => params[ph])))));
|
||||
|
||||
@ -132,4 +131,4 @@ function processMessagePieces(pieces: MessagePiece[]):
|
||||
messageParts.push('');
|
||||
}
|
||||
return {messageParts, placeHolders};
|
||||
}
|
||||
}
|
||||
|
@ -178,6 +178,23 @@ export function parseI18nMeta(meta?: string): I18nMeta {
|
||||
return {id, meaning, description};
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the given `meta` into a string that can be used in a `$localize` tagged string metadata
|
||||
* block. The format is the same as that parsed by `parseI18nMeta()`.
|
||||
*
|
||||
* @param meta The metadata to serialize
|
||||
*/
|
||||
export function serializeI18nMeta(meta: I18nMeta): string {
|
||||
let metaBlock = meta.description || '';
|
||||
if (meta.meaning) {
|
||||
metaBlock = `${meta.meaning}|${metaBlock}`;
|
||||
}
|
||||
if (meta.id) {
|
||||
metaBlock = `${metaBlock}@@${meta.id}`;
|
||||
}
|
||||
return metaBlock;
|
||||
}
|
||||
|
||||
// Converts i18n meta information for a message (id, description, meaning)
|
||||
// to a JsDoc statement formatted as expected by the Closure compiler.
|
||||
export function i18nMetaToDocStmt(meta: I18nMeta): o.JSDocCommentStmt|null {
|
||||
|
@ -5,8 +5,6 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {I18nMeta, parseI18nMeta} from '@angular/compiler/src/render3/view/i18n/meta';
|
||||
|
||||
import {AST} from '../../../src/expression_parser/ast';
|
||||
import {Lexer} from '../../../src/expression_parser/lexer';
|
||||
import {Parser} from '../../../src/expression_parser/parser';
|
||||
@ -17,6 +15,7 @@ import {I18nContext} from '../../../src/render3/view/i18n/context';
|
||||
import {serializeI18nMessageForGetMsg} from '../../../src/render3/view/i18n/get_msg_utils';
|
||||
import {serializeIcuNode} from '../../../src/render3/view/i18n/icu_serializer';
|
||||
import {serializeI18nMessageForLocalize} from '../../../src/render3/view/i18n/localize_utils';
|
||||
import {I18nMeta, parseI18nMeta, serializeI18nMeta} from '../../../src/render3/view/i18n/meta';
|
||||
import {formatI18nPlaceholderName} from '../../../src/render3/view/i18n/util';
|
||||
|
||||
import {parseR3 as parse} from './util';
|
||||
@ -200,10 +199,8 @@ describe('Utils', () => {
|
||||
([input, output]) => { expect(formatI18nPlaceholderName(input)).toEqual(output); });
|
||||
});
|
||||
|
||||
it('parseI18nMeta', () => {
|
||||
const meta = (id?: string, meaning?: string, description?: string) =>
|
||||
({id, meaning, description});
|
||||
const cases = [
|
||||
describe('metadata serialization', () => {
|
||||
const metadataCases: [string, I18nMeta][] = [
|
||||
['', meta()],
|
||||
['desc', meta('', '', 'desc')],
|
||||
['desc@@id', meta('id', '', 'desc')],
|
||||
@ -211,9 +208,20 @@ describe('Utils', () => {
|
||||
['meaning|desc@@id', meta('id', 'meaning', 'desc')],
|
||||
['@@id', meta('id', '', '')],
|
||||
];
|
||||
cases.forEach(([input, output]) => {
|
||||
expect(parseI18nMeta(input as string)).toEqual(output as I18nMeta, input);
|
||||
|
||||
it('parseI18nMeta()', () => {
|
||||
metadataCases.forEach(
|
||||
([input, output]) => { expect(parseI18nMeta(input)).toEqual(output, input); });
|
||||
});
|
||||
|
||||
it('serializeI18nMeta()', () => {
|
||||
metadataCases.forEach(
|
||||
([output, input]) => { expect(serializeI18nMeta(input)).toEqual(output, input); });
|
||||
});
|
||||
|
||||
function meta(id?: string, meaning?: string, description?: string): I18nMeta {
|
||||
return {id, meaning, description};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user