fix(ivy): i18n - support colons in $localize metadata (#32867)
Metadata blocks are delimited by colons. Previously the code naively just looked for the next colon in the string as the end marker. This commit supports escaping colons within the metadata content. The Angular compiler has been updated to add escaping as required. PR Close #32867
This commit is contained in:

committed by
atscott

parent
9b15588188
commit
d24ade91b8
@ -179,12 +179,13 @@ export function parseI18nMeta(meta?: string): I18nMeta {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()`.
|
||||
* Serialize the given `meta` and `messagePart` a string that can be used in a `$localize`
|
||||
* tagged string. The format of the metadata is the same as that parsed by `parseI18nMeta()`.
|
||||
*
|
||||
* @param meta The metadata to serialize
|
||||
* @param messagePart The first part of the tagged string
|
||||
*/
|
||||
export function serializeI18nMetaBlock(meta: I18nMeta): string {
|
||||
export function serializeI18nHead(meta: I18nMeta, messagePart: string): string {
|
||||
let metaBlock = meta.description || '';
|
||||
if (meta.meaning) {
|
||||
metaBlock = `${meta.meaning}|${metaBlock}`;
|
||||
@ -192,22 +193,28 @@ export function serializeI18nMetaBlock(meta: I18nMeta): string {
|
||||
if (meta.id) {
|
||||
metaBlock = `${metaBlock}@@${meta.id}`;
|
||||
}
|
||||
return metaBlock !== '' ? `:${metaBlock}:` : '';
|
||||
if (metaBlock === '') {
|
||||
// There is no metaBlock, so we must ensure that any starting colon is escaped.
|
||||
return escapeStartingColon(messagePart);
|
||||
} else {
|
||||
return `:${escapeColons(metaBlock)}:${messagePart}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a placeholder into marked block for rendering.
|
||||
*
|
||||
* We want our tagged literals to include placeholder name information to aid runtime translation.
|
||||
*
|
||||
* The expressions are marked with placeholder names by postfixing the expression with
|
||||
* `:placeHolderName:`. To achieve this, we actually "prefix" the message part that follows the
|
||||
* expression.
|
||||
* Serialize the given `placeholderName` and `messagePart` into strings that can be used in a
|
||||
* `$localize` tagged string.
|
||||
*
|
||||
* @param placeholderName The placeholder name to serialize
|
||||
* @param messagePart The following message string after this placeholder
|
||||
*/
|
||||
export function serializeI18nPlaceholderBlock(placeholderName: string): string {
|
||||
return placeholderName !== '' ? `:${placeholderName}:` : '';
|
||||
export function serializeI18nTemplatePart(placeholderName: string, messagePart: string): string {
|
||||
if (placeholderName === '') {
|
||||
// There is no placeholder name block, so we must ensure that any starting colon is escaped.
|
||||
return escapeStartingColon(messagePart);
|
||||
} else {
|
||||
return `:${placeholderName}:${messagePart}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Converts i18n meta information for a message (id, description, meaning)
|
||||
@ -222,3 +229,11 @@ export function i18nMetaToDocStmt(meta: I18nMeta): o.JSDocCommentStmt|null {
|
||||
}
|
||||
return tags.length == 0 ? null : new o.JSDocCommentStmt(tags);
|
||||
}
|
||||
|
||||
export function escapeStartingColon(str: string): string {
|
||||
return str.replace(/^:/, '\\:');
|
||||
}
|
||||
|
||||
export function escapeColons(str: string): string {
|
||||
return str.replace(/:/g, '\\:');
|
||||
}
|
@ -15,7 +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, serializeI18nMetaBlock, serializeI18nPlaceholderBlock} from '../../../src/render3/view/i18n/meta';
|
||||
import {I18nMeta, parseI18nMeta, serializeI18nHead, serializeI18nTemplatePart} from '../../../src/render3/view/i18n/meta';
|
||||
import {formatI18nPlaceholderName} from '../../../src/render3/view/i18n/util';
|
||||
|
||||
import {parseR3 as parse} from './util';
|
||||
@ -209,18 +209,36 @@ describe('Utils', () => {
|
||||
expect(parseI18nMeta('@@id')).toEqual(meta('id', '', ''));
|
||||
});
|
||||
|
||||
it('serializeI18nMetaBlock()', () => {
|
||||
expect(serializeI18nMetaBlock(meta())).toEqual('');
|
||||
expect(serializeI18nMetaBlock(meta('', '', 'desc'))).toEqual(':desc:');
|
||||
expect(serializeI18nMetaBlock(meta('id', '', 'desc'))).toEqual(':desc@@id:');
|
||||
expect(serializeI18nMetaBlock(meta('', 'meaning', 'desc'))).toEqual(':meaning|desc:');
|
||||
expect(serializeI18nMetaBlock(meta('id', 'meaning', 'desc'))).toEqual(':meaning|desc@@id:');
|
||||
expect(serializeI18nMetaBlock(meta('id', '', ''))).toEqual(':@@id:');
|
||||
it('serializeI18nHead()', () => {
|
||||
expect(serializeI18nHead(meta(), '')).toEqual('');
|
||||
expect(serializeI18nHead(meta('', '', 'desc'), '')).toEqual(':desc:');
|
||||
expect(serializeI18nHead(meta('id', '', 'desc'), '')).toEqual(':desc@@id:');
|
||||
expect(serializeI18nHead(meta('', 'meaning', 'desc'), '')).toEqual(':meaning|desc:');
|
||||
expect(serializeI18nHead(meta('id', 'meaning', 'desc'), '')).toEqual(':meaning|desc@@id:');
|
||||
expect(serializeI18nHead(meta('id', '', ''), '')).toEqual(':@@id:');
|
||||
|
||||
// Escaping colons (block markers)
|
||||
expect(serializeI18nHead(meta('id:sub_id', 'meaning', 'desc'), ''))
|
||||
.toEqual(':meaning|desc@@id\\:sub_id:');
|
||||
expect(serializeI18nHead(meta('id', 'meaning:sub_meaning', 'desc'), ''))
|
||||
.toEqual(':meaning\\:sub_meaning|desc@@id:');
|
||||
expect(serializeI18nHead(meta('id', 'meaning', 'desc:sub_desc'), ''))
|
||||
.toEqual(':meaning|desc\\:sub_desc@@id:');
|
||||
expect(serializeI18nHead(meta('id', 'meaning', 'desc'), 'message source'))
|
||||
.toEqual(':meaning|desc@@id:message source');
|
||||
expect(serializeI18nHead(meta('id', 'meaning', 'desc'), ':message source'))
|
||||
.toEqual(':meaning|desc@@id::message source');
|
||||
expect(serializeI18nHead(meta('', '', ''), 'message source')).toEqual('message source');
|
||||
expect(serializeI18nHead(meta('', '', ''), ':message source')).toEqual('\\:message source');
|
||||
});
|
||||
|
||||
it('serializeI18nPlaceholderBlock()', () => {
|
||||
expect(serializeI18nPlaceholderBlock('')).toEqual('');
|
||||
expect(serializeI18nPlaceholderBlock('abc')).toEqual(':abc:');
|
||||
expect(serializeI18nTemplatePart('', '')).toEqual('');
|
||||
expect(serializeI18nTemplatePart('abc', '')).toEqual(':abc:');
|
||||
expect(serializeI18nTemplatePart('', 'message')).toEqual('message');
|
||||
expect(serializeI18nTemplatePart('abc', 'message')).toEqual(':abc:message');
|
||||
expect(serializeI18nTemplatePart('', ':message')).toEqual('\\:message');
|
||||
expect(serializeI18nTemplatePart('abc', ':message')).toEqual(':abc::message');
|
||||
});
|
||||
|
||||
function meta(id?: string, meaning?: string, description?: string): I18nMeta {
|
||||
|
Reference in New Issue
Block a user