fix(ivy): i18n - start generated placeholder name at PH
(#32493)
Currently the expressions used in a template string are automatically named `PH_1`, `PH_2`, etc. Whereas interpolations used in i18n templates generate placeholders automatically named `INTERPOLATION`, `INTERPOLATION_1`, etc. This commit aligns the behaviors by starting the generated placeholder names for expressions at `PH`, then `PH_1`, etc. It also documents this behavior in the documentation of `$localize` as it was not mentioned before. PR Close #32493
This commit is contained in:
parent
4c168ed9ba
commit
f1b1de9a3d
@ -41,16 +41,26 @@ declare global {
|
|||||||
*
|
*
|
||||||
* **Naming placeholders**
|
* **Naming placeholders**
|
||||||
*
|
*
|
||||||
* If the template literal string contains expressions then you can optionally name the
|
* If the template literal string contains expressions, then the expressions will be automatically
|
||||||
* placeholder
|
* associated with placeholder names for you.
|
||||||
* associated with each expression. Do this by providing the placeholder name wrapped in `:`
|
|
||||||
* characters directly after the expression. These placeholder names are stripped out of the
|
|
||||||
* rendered localized string.
|
|
||||||
*
|
*
|
||||||
* For example, to name the `item.length` expression placeholder `itemCount` you write:
|
* For example:
|
||||||
*
|
*
|
||||||
* ```ts
|
* ```ts
|
||||||
* $localize `There are ${item.length}:itemCount: items`;
|
* $localize `Hi ${name}! There are ${items.length} items.`;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* will generate a message-source of `Hi {$PH}! There are {$PH_1} items`.
|
||||||
|
*
|
||||||
|
* The recommended practice is to name the placeholder associated with each expression though.
|
||||||
|
*
|
||||||
|
* Do this by providing the placeholder name wrapped in `:` characters directly after the
|
||||||
|
* expression. These placeholder names are stripped out of the rendered localized string.
|
||||||
|
*
|
||||||
|
* For example, to name the `items.length` expression placeholder `itemCount` you write:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* $localize `There are ${items.length}:itemCount: items`;
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* **Escaping colon markers**
|
* **Escaping colon markers**
|
||||||
|
@ -36,27 +36,65 @@ export interface TranslateFn {
|
|||||||
* $localize `some string to localize`
|
* $localize `some string to localize`
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* **Naming placeholders**
|
* **Providing meaning, description and id**
|
||||||
*
|
*
|
||||||
* If the template literal string contains expressions then you can optionally name the placeholder
|
* You can optionally specify one or more of `meaning`, `description` and `id` for a localized
|
||||||
* associated with each expression. Do this by providing the placeholder name wrapped in `:`
|
* string by pre-pending it with a colon delimited block of the form:
|
||||||
* characters directly after the expression. These placeholder names are stripped out of the
|
|
||||||
* rendered localized string.
|
|
||||||
*
|
|
||||||
* For example, to name the `item.length` expression placeholder `itemCount` you write:
|
|
||||||
*
|
*
|
||||||
* ```ts
|
* ```ts
|
||||||
* $localize `There are ${item.length}:itemCount: items`;
|
* $localize`:meaning|description@@id:source message text`;
|
||||||
|
*
|
||||||
|
* $localize`:meaning|:source message text`;
|
||||||
|
* $localize`:description:source message text`;
|
||||||
|
* $localize`:@@id:source message text`;
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* If you need to use a `:` character directly an expression you must either provide a name or you
|
* This format is the same as that used for `i18n` markers in Angular templates. See the
|
||||||
* can escape the `:` by preceding it with a backslash:
|
* [Angular 18n guide](guide/i18n#template-translations).
|
||||||
|
*
|
||||||
|
* **Naming placeholders**
|
||||||
|
*
|
||||||
|
* If the template literal string contains expressions, then the expressions will be automatically
|
||||||
|
* associated with placeholder names for you.
|
||||||
*
|
*
|
||||||
* For example:
|
* For example:
|
||||||
*
|
*
|
||||||
* ```ts
|
* ```ts
|
||||||
|
* $localize `Hi ${name}! There are ${items.length} items.`;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* will generate a message-source of `Hi {$PH}! There are {$PH_1} items`.
|
||||||
|
*
|
||||||
|
* The recommended practice is to name the placeholder associated with each expression though.
|
||||||
|
*
|
||||||
|
* Do this by providing the placeholder name wrapped in `:` characters directly after the
|
||||||
|
* expression. These placeholder names are stripped out of the rendered localized string.
|
||||||
|
*
|
||||||
|
* For example, to name the `items.length` expression placeholder `itemCount` you write:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* $localize `There are ${items.length}:itemCount: items`;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* **Escaping colon markers**
|
||||||
|
*
|
||||||
|
* If you need to use a `:` character directly at the start of a tagged string that has no
|
||||||
|
* metadata block, or directly after a substitution expression that has no name you must escape
|
||||||
|
* the `:` by preceding it with a backslash:
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* // message has a metadata block so no need to escape colon
|
||||||
|
* $localize `:some description::this message starts with a colon (:)`;
|
||||||
|
* // no metadata block so the colon must be escaped
|
||||||
|
* $localize `\:this message starts with a colon (:)`;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* // named substitution so no need to escape colon
|
||||||
* $localize `${label}:label:: ${}`
|
* $localize `${label}:label:: ${}`
|
||||||
* // or
|
* // anonymous substitution so colon must be escaped
|
||||||
* $localize `${label}\: ${}`
|
* $localize `${label}\: ${}`
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
@ -64,19 +102,18 @@ export interface TranslateFn {
|
|||||||
*
|
*
|
||||||
* There are three scenarios:
|
* There are three scenarios:
|
||||||
*
|
*
|
||||||
* * **compile-time inlining**: the `$localize` tag is transformed at compile time by a transpiler,
|
* * **compile-time inlining**: the `$localize` tag is transformed at compile time by a
|
||||||
* removing the tag and replacing the template literal string with a translated literal string
|
* transpiler, removing the tag and replacing the template literal string with a translated
|
||||||
* from a collection of translations provided to the transpilation tool.
|
* literal string from a collection of translations provided to the transpilation tool.
|
||||||
*
|
*
|
||||||
* * **run-time evaluation**: the `$localize` tag is a run-time function that replaces and reorders
|
* * **run-time evaluation**: the `$localize` tag is a run-time function that replaces and
|
||||||
* the parts (static strings and expressions) of the template literal string with strings from a
|
* reorders the parts (static strings and expressions) of the template literal string with strings
|
||||||
* collection of translations loaded at run-time.
|
* from a collection of translations loaded at run-time.
|
||||||
*
|
*
|
||||||
* * **pass-through evaluation**: the `$localize` tag is a run-time function that simply evaluates
|
* * **pass-through evaluation**: the `$localize` tag is a run-time function that simply evaluates
|
||||||
* the original template literal string without applying any translations to the parts. This version
|
* the original template literal string without applying any translations to the parts. This
|
||||||
* is used during development or where there is no need to translate the localized template
|
* version is used during development or where there is no need to translate the localized
|
||||||
* literals.
|
* template literals.
|
||||||
*
|
|
||||||
* @param messageParts a collection of the static parts of the template string.
|
* @param messageParts a collection of the static parts of the template string.
|
||||||
* @param expressions a collection of the values of each placeholder in the template string.
|
* @param expressions a collection of the values of each placeholder in the template string.
|
||||||
* @returns the translated string, with the `messageParts` and `expressions` interleaved together.
|
* @returns the translated string, with the `messageParts` and `expressions` interleaved together.
|
||||||
|
@ -82,7 +82,7 @@ export function parseMessage(
|
|||||||
const metadata = parseMetadata(messageParts[0], messageParts.raw[0]);
|
const metadata = parseMetadata(messageParts[0], messageParts.raw[0]);
|
||||||
let messageString = metadata.text;
|
let messageString = metadata.text;
|
||||||
for (let i = 1; i < messageParts.length; i++) {
|
for (let i = 1; i < messageParts.length; i++) {
|
||||||
const {text: messagePart, block: placeholderName = `ph_${i}`} =
|
const {text: messagePart, block: placeholderName = computePlaceholderName(i)} =
|
||||||
splitBlock(messageParts[i], messageParts.raw[i]);
|
splitBlock(messageParts[i], messageParts.raw[i]);
|
||||||
messageString += `{$${placeholderName}}${messagePart}`;
|
messageString += `{$${placeholderName}}${messagePart}`;
|
||||||
if (expressions !== undefined) {
|
if (expressions !== undefined) {
|
||||||
@ -186,3 +186,7 @@ export function splitBlock(cooked: string, raw: string): {text: string, block?:
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function computePlaceholderName(index: number) {
|
||||||
|
return index === 1 ? 'PH' : `PH_${index - 1}`;
|
||||||
|
}
|
||||||
|
@ -16,10 +16,10 @@ describe('$localize tag with translations', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
loadTranslations(computeIds({
|
loadTranslations(computeIds({
|
||||||
'abc': 'abc',
|
'abc': 'abc',
|
||||||
'abc{$ph_1}': 'abc{$ph_1}',
|
'abc{$PH}': 'abc{$PH}',
|
||||||
'abc{$ph_1}def': 'abc{$ph_1}def',
|
'abc{$PH}def': 'abc{$PH}def',
|
||||||
'abc{$ph_1}def{$ph_2}': 'abc{$ph_1}def{$ph_2}',
|
'abc{$PH}def{$PH_1}': 'abc{$PH}def{$PH_1}',
|
||||||
'Hello, {$ph_1}!': 'Hello, {$ph_1}!',
|
'Hello, {$PH}!': 'Hello, {$PH}!',
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
afterEach(() => { clearTranslations(); });
|
afterEach(() => { clearTranslations(); });
|
||||||
@ -38,10 +38,10 @@ describe('$localize tag with translations', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
loadTranslations(computeIds({
|
loadTranslations(computeIds({
|
||||||
'abc': 'ABC',
|
'abc': 'ABC',
|
||||||
'abc{$ph_1}': 'ABC{$ph_1}',
|
'abc{$PH}': 'ABC{$PH}',
|
||||||
'abc{$ph_1}def': 'ABC{$ph_1}DEF',
|
'abc{$PH}def': 'ABC{$PH}DEF',
|
||||||
'abc{$ph_1}def{$ph_2}': 'ABC{$ph_1}DEF{$ph_2}',
|
'abc{$PH}def{$PH_1}': 'ABC{$PH}DEF{$PH_1}',
|
||||||
'Hello, {$ph_1}!': 'HELLO, {$ph_1}!',
|
'Hello, {$PH}!': 'HELLO, {$PH}!',
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
afterEach(() => { clearTranslations(); });
|
afterEach(() => { clearTranslations(); });
|
||||||
@ -59,7 +59,7 @@ describe('$localize tag with translations', () => {
|
|||||||
describe('to reverse expressions', () => {
|
describe('to reverse expressions', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
loadTranslations(computeIds({
|
loadTranslations(computeIds({
|
||||||
'abc{$ph_1}def{$ph_2} - Hello, {$ph_3}!': 'abc{$ph_3}def{$ph_2} - Hello, {$ph_1}!',
|
'abc{$PH}def{$PH_1} - Hello, {$PH_2}!': 'abc{$PH_2}def{$PH_1} - Hello, {$PH}!',
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
afterEach(() => { clearTranslations(); });
|
afterEach(() => { clearTranslations(); });
|
||||||
@ -74,7 +74,7 @@ describe('$localize tag with translations', () => {
|
|||||||
describe('to remove expressions', () => {
|
describe('to remove expressions', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
loadTranslations(computeIds({
|
loadTranslations(computeIds({
|
||||||
'abc{$ph_1}def{$ph_2} - Hello, {$ph_3}!': 'abc{$ph_1} - Hello, {$ph_3}!',
|
'abc{$PH}def{$PH_1} - Hello, {$PH_2}!': 'abc{$PH} - Hello, {$PH_2}!',
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
afterEach(() => { clearTranslations(); });
|
afterEach(() => { clearTranslations(); });
|
||||||
|
@ -44,13 +44,13 @@ describe('messages utils', () => {
|
|||||||
|
|
||||||
it('should compute the translation key, inferring placeholder names if not given', () => {
|
it('should compute the translation key, inferring placeholder names if not given', () => {
|
||||||
const message = parseMessage(makeTemplateObject(['a', 'b', 'c'], ['a', 'b', 'c']), [1, 2]);
|
const message = parseMessage(makeTemplateObject(['a', 'b', 'c'], ['a', 'b', 'c']), [1, 2]);
|
||||||
expect(message.messageId).toEqual('3269094494609300850');
|
expect(message.messageId).toEqual('8107531564991075946');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should compute the translation key, ignoring escaped placeholder names', () => {
|
it('should compute the translation key, ignoring escaped placeholder names', () => {
|
||||||
const message = parseMessage(
|
const message = parseMessage(
|
||||||
makeTemplateObject(['a', ':one:b', ':two:c'], ['a', '\\:one:b', '\\:two:c']), [1, 2]);
|
makeTemplateObject(['a', ':one:b', ':two:c'], ['a', '\\:one:b', '\\:two:c']), [1, 2]);
|
||||||
expect(message.messageId).toEqual('529036009514785949');
|
expect(message.messageId).toEqual('2623373088949454037');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should compute the translation key, handling empty raw values', () => {
|
it('should compute the translation key, handling empty raw values', () => {
|
||||||
@ -67,7 +67,7 @@ describe('messages utils', () => {
|
|||||||
|
|
||||||
it('should build a map of implied placeholders to expressions', () => {
|
it('should build a map of implied placeholders to expressions', () => {
|
||||||
const message = parseMessage(makeTemplateObject(['a', 'b', 'c'], ['a', 'b', 'c']), [1, 2]);
|
const message = parseMessage(makeTemplateObject(['a', 'b', 'c'], ['a', 'b', 'c']), [1, 2]);
|
||||||
expect(message.substitutions).toEqual({ph_1: 1, ph_2: 2});
|
expect(message.substitutions).toEqual({PH: 1, PH_1: 2});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -83,10 +83,10 @@ describe('utils', () => {
|
|||||||
it('(with identity translations) should render template literals as-is', () => {
|
it('(with identity translations) should render template literals as-is', () => {
|
||||||
const translations = {
|
const translations = {
|
||||||
'abc': 'abc',
|
'abc': 'abc',
|
||||||
'abc{$ph_1}': 'abc{$ph_1}',
|
'abc{$PH}': 'abc{$PH}',
|
||||||
'abc{$ph_1}def': 'abc{$ph_1}def',
|
'abc{$PH}def': 'abc{$PH}def',
|
||||||
'abc{$ph_1}def{$ph_2}': 'abc{$ph_1}def{$ph_2}',
|
'abc{$PH}def{$PH_1}': 'abc{$PH}def{$PH_1}',
|
||||||
'Hello, {$ph_1}!': 'Hello, {$ph_1}!',
|
'Hello, {$PH}!': 'Hello, {$PH}!',
|
||||||
};
|
};
|
||||||
expect(doTranslate(translations, parts `abc`)).toEqual(parts `abc`);
|
expect(doTranslate(translations, parts `abc`)).toEqual(parts `abc`);
|
||||||
expect(doTranslate(translations, parts `abc${1 + 2 + 3}`)).toEqual(parts `abc${1 + 2 + 3}`);
|
expect(doTranslate(translations, parts `abc${1 + 2 + 3}`)).toEqual(parts `abc${1 + 2 + 3}`);
|
||||||
@ -103,10 +103,10 @@ describe('utils', () => {
|
|||||||
() => {
|
() => {
|
||||||
const translations = {
|
const translations = {
|
||||||
'abc': 'ABC',
|
'abc': 'ABC',
|
||||||
'abc{$ph_1}': 'ABC{$ph_1}',
|
'abc{$PH}': 'ABC{$PH}',
|
||||||
'abc{$ph_1}def': 'ABC{$ph_1}DEF',
|
'abc{$PH}def': 'ABC{$PH}DEF',
|
||||||
'abc{$ph_1}def{$ph_2}': 'ABC{$ph_1}DEF{$ph_2}',
|
'abc{$PH}def{$PH_1}': 'ABC{$PH}DEF{$PH_1}',
|
||||||
'Hello, {$ph_1}!': 'HELLO, {$ph_1}!',
|
'Hello, {$PH}!': 'HELLO, {$PH}!',
|
||||||
};
|
};
|
||||||
expect(doTranslate(translations, parts `abc`)).toEqual(parts `ABC`);
|
expect(doTranslate(translations, parts `abc`)).toEqual(parts `ABC`);
|
||||||
expect(doTranslate(translations, parts `abc${1 + 2 + 3}`))
|
expect(doTranslate(translations, parts `abc${1 + 2 + 3}`))
|
||||||
@ -123,7 +123,7 @@ describe('utils', () => {
|
|||||||
it('(with translations to reverse expressions) should render template literals with expressions reversed',
|
it('(with translations to reverse expressions) should render template literals with expressions reversed',
|
||||||
() => {
|
() => {
|
||||||
const translations = {
|
const translations = {
|
||||||
'abc{$ph_1}def{$ph_2} - Hello, {$ph_3}!': 'abc{$ph_3}def{$ph_2} - Hello, {$ph_1}!',
|
'abc{$PH}def{$PH_1} - Hello, {$PH_2}!': 'abc{$PH_2}def{$PH_1} - Hello, {$PH}!',
|
||||||
};
|
};
|
||||||
const getName = () => 'World';
|
const getName = () => 'World';
|
||||||
expect(doTranslate(
|
expect(doTranslate(
|
||||||
@ -134,7 +134,7 @@ describe('utils', () => {
|
|||||||
it('(with translations to remove expressions) should render template literals with expressions removed',
|
it('(with translations to remove expressions) should render template literals with expressions removed',
|
||||||
() => {
|
() => {
|
||||||
const translations = {
|
const translations = {
|
||||||
'abc{$ph_1}def{$ph_2} - Hello, {$ph_3}!': 'abc{$ph_1} - Hello, {$ph_3}!',
|
'abc{$PH}def{$PH_1} - Hello, {$PH_2}!': 'abc{$PH} - Hello, {$PH_2}!',
|
||||||
};
|
};
|
||||||
const getName = () => 'World';
|
const getName = () => 'World';
|
||||||
expect(doTranslate(
|
expect(doTranslate(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user