fix(ivy): i18n - do not render message ids unnecessarily (#32867)

In an attempt to be compatible with previous translation files
the Angular compiler was generating instructions that always
included the message id. This was because it was not possible
to accurately re-generate the id from the calls to `$localize()` alone.

In line with https://hackmd.io/EQF4_-atSXK4XWg8eAha2g this
commit changes the compiler so that it only renders ids if they are
"custom" ones provided by the template author.

NOTE:

When translating messages generated by the Angular compiler
from i18n tags in templates, the `$localize.translate()` function
will compute message ids, if no custom id is provided, using a
common digest function that only relies upon the information
available in the `$localize()` calls.

This computed message id will not be the same as the message
ids stored in legacy translation files. Such files will need to be
migrated to use the new common digest function.

This only affects developers who have been trialling `$localize`, have
been calling `loadTranslations()`, and are not exclusively using custom
ids in their templates.

PR Close #32867
This commit is contained in:
Pete Bacon Darwin
2019-10-02 18:17:56 +01:00
committed by atscott
parent d24ade91b8
commit 9188751adc
11 changed files with 439 additions and 261 deletions

View File

@ -157,7 +157,7 @@ export function fingerprint(str: string): [number, number] {
return [hi, lo];
}
export function computeMsgId(msg: string, meaning: string): string {
export function computeMsgId(msg: string, meaning: string = ''): string {
let [hi, lo] = fingerprint(msg);
if (meaning) {

View File

@ -10,6 +10,7 @@ import {ParseSourceSpan} from '../parse_util';
export class Message {
sources: MessageSpan[];
id: string = this.customId;
/**
* @param nodes message AST
@ -22,7 +23,7 @@ export class Message {
constructor(
public nodes: Node[], public placeholders: {[phName: string]: string},
public placeholderToMessage: {[phName: string]: Message}, public meaning: string,
public description: string, public id: string) {
public description: string, public customId: string) {
if (nodes.length) {
this.sources = [{
filePath: nodes[0].sourceSpan.start.file.url,

View File

@ -18,6 +18,7 @@ import {I18N_ATTR, I18N_ATTR_PREFIX, hasI18nAttrs, icuFromI18nMessage} from './u
export type I18nMeta = {
id?: string,
customId?: string,
description?: string,
meaning?: string
};
@ -47,7 +48,7 @@ export class I18nMetaVisitor implements html.Visitor {
const parsed: I18nMeta =
typeof meta === 'string' ? parseI18nMeta(meta) : metaFromI18nMessage(meta as i18n.Message);
const message = this._createI18nMessage(
nodes, parsed.meaning || '', parsed.description || '', parsed.id || '', visitNodeFn);
nodes, parsed.meaning || '', parsed.description || '', parsed.customId || '', visitNodeFn);
if (!message.id) {
// generate (or restore) message id if not specified in template
message.id = typeof meta !== 'string' && (meta as i18n.Message).id || decimalDigest(message);
@ -140,6 +141,7 @@ export function processI18nMeta(
export function metaFromI18nMessage(message: i18n.Message, id: string | null = null): I18nMeta {
return {
id: typeof id === 'string' ? id : message.id || '',
customId: message.customId,
meaning: message.meaning || '',
description: message.description || ''
};
@ -160,7 +162,7 @@ const I18N_ID_SEPARATOR = '@@';
* @returns Object with id, meaning and description fields
*/
export function parseI18nMeta(meta?: string): I18nMeta {
let id: string|undefined;
let customId: string|undefined;
let meaning: string|undefined;
let description: string|undefined;
@ -168,14 +170,14 @@ export function parseI18nMeta(meta?: string): I18nMeta {
const idIndex = meta.indexOf(I18N_ID_SEPARATOR);
const descIndex = meta.indexOf(I18N_MEANING_SEPARATOR);
let meaningAndDesc: string;
[meaningAndDesc, id] =
[meaningAndDesc, customId] =
(idIndex > -1) ? [meta.slice(0, idIndex), meta.slice(idIndex + 2)] : [meta, ''];
[meaning, description] = (descIndex > -1) ?
[meaningAndDesc.slice(0, descIndex), meaningAndDesc.slice(descIndex + 1)] :
['', meaningAndDesc];
}
return {id, meaning, description};
return {customId, meaning, description};
}
/**
@ -190,8 +192,8 @@ export function serializeI18nHead(meta: I18nMeta, messagePart: string): string {
if (meta.meaning) {
metaBlock = `${meta.meaning}|${metaBlock}`;
}
if (meta.id) {
metaBlock = `${metaBlock}@@${meta.id}`;
if (meta.customId) {
metaBlock = `${metaBlock}@@${meta.customId}`;
}
if (metaBlock === '') {
// There is no metaBlock, so we must ensure that any starting colon is escaped.

View File

@ -20,6 +20,7 @@ import {computeMsgId, digest, sha1} from '../../src/i18n/digest';
meaning: '',
description: '',
sources: [],
customId: 'i',
})).toEqual('i');
});
});

View File

@ -241,8 +241,8 @@ describe('Utils', () => {
expect(serializeI18nTemplatePart('abc', ':message')).toEqual(':abc::message');
});
function meta(id?: string, meaning?: string, description?: string): I18nMeta {
return {id, meaning, description};
function meta(customId?: string, meaning?: string, description?: string): I18nMeta {
return {customId, meaning, description};
}
});
});