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:

committed by
atscott

parent
d24ade91b8
commit
9188751adc
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -20,6 +20,7 @@ import {computeMsgId, digest, sha1} from '../../src/i18n/digest';
|
||||
meaning: '',
|
||||
description: '',
|
||||
sources: [],
|
||||
customId: 'i',
|
||||
})).toEqual('i');
|
||||
});
|
||||
});
|
||||
|
@ -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};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user