feat(ivy): i18n - add syntax support for $localize
metadata block (#32594)
This commit documents and extends the basic `$localize` implementation to support adding a metadata block to the start of a tagged message. All the "pass-though" version does is to strip this block out, similar to what it does to placeholder name blocks. PR Close #32594
This commit is contained in:

committed by
Andrew Kushnir

parent
fd62ed66e3
commit
c7abb7d196
@ -6,8 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
const PLACEHOLDER_NAME_MARKER = ':';
|
||||
|
||||
export interface LocalizeFn {
|
||||
(messageParts: TemplateStringsArray, ...expressions: readonly any[]): string;
|
||||
|
||||
@ -91,37 +89,39 @@ export const $localize: LocalizeFn = function(
|
||||
messageParts = translation[0];
|
||||
expressions = translation[1];
|
||||
}
|
||||
let message = messageParts[0];
|
||||
let message = stripBlock(messageParts[0], messageParts.raw[0]);
|
||||
for (let i = 1; i < messageParts.length; i++) {
|
||||
message += expressions[i - 1] + stripPlaceholderName(messageParts[i], messageParts.raw[i]);
|
||||
message += expressions[i - 1] + stripBlock(messageParts[i], messageParts.raw[i]);
|
||||
}
|
||||
return message;
|
||||
};
|
||||
|
||||
const BLOCK_MARKER = ':';
|
||||
|
||||
/**
|
||||
* Strip the placeholder name from the start of the `messagePart`, if it is found.
|
||||
* Strip a delimited "block" from the start of the `messagePart`, if it is found.
|
||||
*
|
||||
* Placeholder marker characters (:) may appear after a substitution that does not provide an
|
||||
* explicit placeholder name. In this case the character must be escaped with a backslash, `\:`.
|
||||
* We can check for this by looking at the `raw` messagePart, which should still contain the
|
||||
* backslash.
|
||||
* If a marker character (:) actually appears in the content at the start of a tagged string or
|
||||
* after a substitution expression, where a block has not been provided the character must be
|
||||
* escaped with a backslash, `\:`. This function checks for this by looking at the `raw`
|
||||
* messagePart, which should still contain the backslash.
|
||||
*
|
||||
* If the template literal was synthesized then its raw array will only contain empty strings.
|
||||
* This is because TS needs the original source code to find the raw text and in the case of
|
||||
* synthesize AST nodes, there is no source code.
|
||||
* If the template literal was synthesized, rather than appearing in original source code, then its
|
||||
* raw array will only contain empty strings. This is because the current TypeScript compiler use
|
||||
* the original source code to find the raw text and in the case of synthesized AST nodes, there is
|
||||
* no source code to draw upon.
|
||||
*
|
||||
* The workaround is to assume that the template literal did not contain an escaped placeholder
|
||||
* name, and fall back on checking the cooked array instead.
|
||||
*
|
||||
* This should be OK because synthesized nodes (from the Angular template compiler) will always
|
||||
* provide explicit placeholder names and so will never need to escape placeholder name markers.
|
||||
* The workaround in this function is to assume that the template literal did not contain an escaped
|
||||
* placeholder name, and fall back on checking the cooked array instead. This should be OK because
|
||||
* synthesized nodes (from the Angular template compiler) will always provide explicit delimited
|
||||
* blocks and so will never need to escape placeholder name markers.
|
||||
*
|
||||
* @param messagePart The cooked message part to process.
|
||||
* @param rawMessagePart The raw message part to check.
|
||||
* @returns the message part with the placeholder name stripped, if found.
|
||||
*/
|
||||
function stripPlaceholderName(messagePart: string, rawMessagePart: string) {
|
||||
return (rawMessagePart || messagePart).charAt(0) === PLACEHOLDER_NAME_MARKER ?
|
||||
messagePart.substring(messagePart.indexOf(PLACEHOLDER_NAME_MARKER, 1) + 1) :
|
||||
function stripBlock(messagePart: string, rawMessagePart: string) {
|
||||
return (rawMessagePart || messagePart).charAt(0) === BLOCK_MARKER ?
|
||||
messagePart.substring(messagePart.indexOf(BLOCK_MARKER, 1) + 1) :
|
||||
messagePart;
|
||||
}
|
||||
|
@ -19,6 +19,16 @@ describe('$localize tag', () => {
|
||||
expect($localize `Hello, ${getName()}!`).toEqual('Hello, World!');
|
||||
});
|
||||
|
||||
it('should strip metadata block from message parts', () => {
|
||||
expect($localize.translate).toBeUndefined();
|
||||
expect($localize `:meaning|description@@custom-id:abcdef`).toEqual('abcdef');
|
||||
});
|
||||
|
||||
it('should ignore escaped metadata block marker', () => {
|
||||
expect($localize.translate).toBeUndefined();
|
||||
expect($localize `\:abc:def`).toEqual(':abc:def');
|
||||
});
|
||||
|
||||
it('should strip placeholder names from message parts', () => {
|
||||
expect($localize.translate).toBeUndefined();
|
||||
expect($localize `abc${1 + 2 + 3}:ph1:def${4 + 5 + 6}:ph2:`).toEqual('abc6def15');
|
||||
|
Reference in New Issue
Block a user