The original design doc for $localize
avoids message-ids for Angular v9 since the template compiler does not need to generate messages based on ids.
Therefore the original implementation for $localize
relies upon the source-message as the "key" for looking up translations. For example give then following template:
<head>
<div i18n>
Hello, {{title}}!
</div>
</head>
The Angular compiler generates the following source-message, which is then used as a translation-key.
" Hello, {$interpolation}! "
It is possible for there to be more than one translation for a source message depending upon the context, requiring a custom/manual id:
"right" (correct) => vrai
"right" (opposite of left) => droit
"right" (fair) => juste
In the curent version of Angular the message "meaning" is combined with the source-message to compute the id.
In order to support consistent rendering of expandable ICU messages (containing markup), the source-message has its whitespace removed, if preserveWhitespace: false
. This means that the source-message in the template is different to that extracted into translation files. In the example given in Background the source-message in the template is:
" Hello, {$interpolation}! "
while in the translation file it is:
"\n Hello, {$interpolation}!\n "
This prevents accurate translation lookup based on the source-message string.
Extend the format of localized strings to allow "meaning", "description" and "message-id" to be provided. This metadata is provided at the start of the string marked by colons. Each of these is optional. For example:
$localize`:meaning|description@@message-id:source message text`;
$localize`:meaning|:source message text`;
$localize`:description:source message text`;
$localize`:@@message-id:source message text`;
The delimiters within the colons are chosen to match those already used in Angular templates. For example:
<h1
i18n="site header|An introduction header@@introductionHeader">
Hello i18n!
</h1>
If a source-message actually starts with a colon then it must be escaped with a backslash. For example:
$localize`\:this message actually starts with a colon (:)`;
This approach is similar to that already implemented for named placeholders: the substitution is post-fixed by a colon delimited placeholder name. For example:
$localize`Hello ${item.name}:title:`; // placeholder name is 'title'
$localize`${hours}\:${mins}\:${secs}`; // colons are part of the message
The template source-message strings are not guaranteed to be identical to those in translation files. See Whitespace removal) above. Therefore we must provide the computed id when generating $localize
tagged strings in template generation. For example:
$localize `:@@123456789: Hello, {$interpolation}! `;
This ensures that translation of this message is based on the message-id and not the source-message.
Use the message-id as the key when looking up translations rather than the source-message.
If a source-message does not provide a custom message-id then compute one.
Computed message-ids are generated using the same algorithm as XLIFF2 and XMB/XTB formats. This is implemented in the decimalDigest()
function.
The $localize()
function passes the messageParts
and expressions
to the $localize.translate()
run-time translation function. The current implementation computes the source-message and uses that as the translation-key.
function translate(messageParts: TemplateStringsArray,
expressions: readonly any[]): [TemplateStringsArray, readonly any[]];
Modify this function to compute the message-id instead and use that as the translation-key.
Internally each translation is stored as a ParsedTranslation
object, which is kept in a lookup table.
export interface ParsedTranslation {
messageParts: TemplateStringsArray;
placeholderNames: string[];
}
type SourceMessage = string;
type InternalTranslations = Record<SourceMessage, ParsedTranslation>;
Modify this lookup table so that the key is the message-id.
type MessageId = string;
type InternalTranslations = Record<MessageId, ParsedTranslation>;
The loadTranslations()
function accepts a translations
argument:
export type Translations = Record<SourceMessage, TargetMessage>;
export function loadTranslations(translations: Translations): void;
Change this function to accept a translations
argument that is a map of message-ids instead of source-messages:
export type Translations = Record<MessageId, TargetMessage>;
export function loadTranslations(translations: Translations): void;
When calling loadTranslations()
, the caller is now responsible for providing the message-id of each translation: either custom message-ids or computed via decimalDigest()
.
If the translations are loaded from a formatted translation file that uses the same algorithm as decimalDigest()
, e.g. XLIFF2 or XTB, then the loader can just use the message-ids directly.
If the translations are loaded from a formatted translation file uses a different digest algorithm, e.g. XLIFF 1.2, then the loader must convert the given message-id before calling loadTranslations()
. This is done as follows:
decimalDigest()
algorithm.