diff --git a/packages/localize/src/translate.ts b/packages/localize/src/translate.ts index 1db3e5885e..12c438ae80 100644 --- a/packages/localize/src/translate.ts +++ b/packages/localize/src/translate.ts @@ -6,7 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ import {LocalizeFn} from './localize'; -import {ParsedTranslation, TargetMessage, TranslationKey, parseTranslation, translate as _translate} from './utils/translations'; +import {MessageId, TargetMessage} from './utils/messages'; +import {ParsedTranslation, parseTranslation, translate as _translate} from './utils/translations'; /** * We augment the `$localize` object to also store the translations. @@ -24,7 +25,7 @@ declare const $localize: LocalizeFn&{TRANSLATIONS: Record) { +export function loadTranslations(translations: Record) { // Ensure the translate function exists if (!$localize.translate) { $localize.translate = translate; diff --git a/packages/localize/src/utils/constants.ts b/packages/localize/src/utils/constants.ts index efcd0d7ab2..1bb88a14b4 100644 --- a/packages/localize/src/utils/constants.ts +++ b/packages/localize/src/utils/constants.ts @@ -7,13 +7,15 @@ */ /** - * The character used to mark the start and end of a placeholder name in a `$localize` tagged - * string. + * The character used to mark the start and end of a "block" in a `$localize` tagged string. + * A block can indicate metadata about the message or specify a name of a placeholder for a + * substitution expressions. * * For example: * - * ``` + * ```ts * $localize`Hello, ${title}:title:!`; + * $localize`:meaning|description@@id:source message text`; * ``` */ -export const PLACEHOLDER_NAME_MARKER = ':'; +export const BLOCK_MARKER = ':'; diff --git a/packages/localize/src/utils/messages.ts b/packages/localize/src/utils/messages.ts index 9eed8dca4f..c0182f87e1 100644 --- a/packages/localize/src/utils/messages.ts +++ b/packages/localize/src/utils/messages.ts @@ -5,8 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {PLACEHOLDER_NAME_MARKER} from './constants'; -import {TranslationKey} from './translations'; +import {BLOCK_MARKER} from './constants'; /** * A string containing a translation source message. @@ -17,6 +16,20 @@ import {TranslationKey} from './translations'; */ export type SourceMessage = string; +/** + * A string containing a translation target message. + * + * I.E. the message that indicates what will be translated to. + * + * Uses `{$placeholder-name}` to indicate a placeholder. + */ +export type TargetMessage = string; + +/** + * A string that uniquely identifies a message, to be used for matching translations. + */ +export type MessageId = string; + /** * Information parsed from a `$localize` tagged string that is used to translate it. * @@ -31,7 +44,7 @@ export type SourceMessage = string; * * ``` * { - * translationKey: 'Hello {$title}!', + * messageId: '6998194507597730591', * substitutions: { title: 'Jo Bloggs' }, * } * ``` @@ -40,7 +53,7 @@ export interface ParsedMessage { /** * The key used to look up the appropriate translation target. */ - translationKey: TranslationKey; + messageId: MessageId; /** * A mapping of placeholder names to substitution values. */ @@ -55,7 +68,7 @@ export interface ParsedMessage { export function parseMessage( messageParts: TemplateStringsArray, expressions: readonly any[]): ParsedMessage { const replacements: {[placeholderName: string]: any} = {}; - let translationKey = messageParts[0]; + let messageId = messageParts[0]; for (let i = 1; i < messageParts.length; i++) { const messagePart = messageParts[i]; const expression = expressions[i - 1]; @@ -66,16 +79,16 @@ export function parseMessage( // This should be OK because synthesized nodes only come from the template compiler and they // will always contain placeholder name information. // So there will be no escaped placeholder marker character (`:`) directly after a substitution. - if ((messageParts.raw[i] || messagePart).charAt(0) === PLACEHOLDER_NAME_MARKER) { - const endOfPlaceholderName = messagePart.indexOf(PLACEHOLDER_NAME_MARKER, 1); + if ((messageParts.raw[i] || messagePart).charAt(0) === BLOCK_MARKER) { + const endOfPlaceholderName = messagePart.indexOf(BLOCK_MARKER, 1); const placeholderName = messagePart.substring(1, endOfPlaceholderName); - translationKey += `{$${placeholderName}}${messagePart.substring(endOfPlaceholderName + 1)}`; + messageId += `{$${placeholderName}}${messagePart.substring(endOfPlaceholderName + 1)}`; replacements[placeholderName] = expression; } else { const placeholderName = `ph_${i}`; - translationKey += `{$${placeholderName}}${messagePart}`; + messageId += `{$${placeholderName}}${messagePart}`; replacements[placeholderName] = expression; } } - return {translationKey, substitutions: replacements}; + return {messageId: messageId, substitutions: replacements}; } diff --git a/packages/localize/src/utils/translations.ts b/packages/localize/src/utils/translations.ts index 8345929989..fb054dabc4 100644 --- a/packages/localize/src/utils/translations.ts +++ b/packages/localize/src/utils/translations.ts @@ -5,22 +5,8 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {PLACEHOLDER_NAME_MARKER} from './constants'; -import {SourceMessage, parseMessage} from './messages'; - -/** - * A key used to lookup a `TargetMessage` in a hash map. - */ -export type TranslationKey = SourceMessage; - -/** - * A string containing a translation target message. - * - * I.E. the message that indicates what will be translated to. - * - * Uses `{$placeholder-name}` to indicate a placeholder. - */ -export type TargetMessage = string; +import {BLOCK_MARKER} from './constants'; +import {MessageId, TargetMessage, parseMessage} from './messages'; /** * A translation message that has been processed to extract the message parts and placeholders. @@ -33,14 +19,14 @@ export interface ParsedTranslation { /** * The internal structure used by the runtime localization to translate messages. */ -export type ParsedTranslations = Record; +export type ParsedTranslations = Record; /** * Translate the text of the `$localize` tagged-string (i.e. `messageParts` and * `substitutions`) using the given `translations`. * - * The tagged-string is parsed to extract its `translationKey` which is used to find an appropriate + * The tagged-string is parsed to extract its `messageId` which is used to find an appropriate * `ParsedTranslation`. * * If one is found then it is used to translate the message into a new set of `messageParts` and @@ -53,7 +39,7 @@ export function translate( translations: Record, messageParts: TemplateStringsArray, substitutions: readonly any[]): [TemplateStringsArray, readonly any[]] { const message = parseMessage(messageParts, substitutions); - const translation = translations[message.translationKey]; + const translation = translations[message.messageId]; if (translation !== undefined) { return [ translation.messageParts, @@ -81,7 +67,7 @@ export function parseTranslation(message: TargetMessage): ParsedTranslation { messageParts.push(`${parts[i + 1]}`); } const rawMessageParts = - messageParts.map(part => part.charAt(0) === PLACEHOLDER_NAME_MARKER ? '\\' + part : part); + messageParts.map(part => part.charAt(0) === BLOCK_MARKER ? '\\' + part : part); return {messageParts: makeTemplateObject(messageParts, rawMessageParts), placeholderNames}; } diff --git a/packages/localize/test/utils/messages_spec.ts b/packages/localize/test/utils/messages_spec.ts index 9eb8cdcd3f..632c11b1ad 100644 --- a/packages/localize/test/utils/messages_spec.ts +++ b/packages/localize/test/utils/messages_spec.ts @@ -13,24 +13,24 @@ describe('messages utils', () => { it('should compute the translation key', () => { const message = parseMessage( makeTemplateObject(['a', ':one:b', ':two:c'], ['a', ':one:b', ':two:c']), [1, 2]); - expect(message.translationKey).toEqual('a{$one}b{$two}c'); + expect(message.messageId).toEqual('a{$one}b{$two}c'); }); it('should compute the translation key, inferring placeholder names if not given', () => { const message = parseMessage(makeTemplateObject(['a', 'b', 'c'], ['a', 'b', 'c']), [1, 2]); - expect(message.translationKey).toEqual('a{$ph_1}b{$ph_2}c'); + expect(message.messageId).toEqual('a{$ph_1}b{$ph_2}c'); }); it('should compute the translation key, ignoring escaped placeholder names', () => { const message = parseMessage( makeTemplateObject(['a', ':one:b', ':two:c'], ['a', '\\:one:b', '\\:two:c']), [1, 2]); - expect(message.translationKey).toEqual('a{$ph_1}:one:b{$ph_2}:two:c'); + expect(message.messageId).toEqual('a{$ph_1}:one:b{$ph_2}:two:c'); }); it('should compute the translation key, handling empty raw values', () => { const message = parseMessage(makeTemplateObject(['a', ':one:b', ':two:c'], ['', '', '']), [1, 2]); - expect(message.translationKey).toEqual('a{$one}b{$two}c'); + expect(message.messageId).toEqual('a{$one}b{$two}c'); }); it('should build a map of named placeholders to expressions', () => { diff --git a/packages/localize/test/utils/translations_spec.ts b/packages/localize/test/utils/translations_spec.ts index a9158f0c15..da8ab2c3c4 100644 --- a/packages/localize/test/utils/translations_spec.ts +++ b/packages/localize/test/utils/translations_spec.ts @@ -5,7 +5,8 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {ParsedTranslation, TargetMessage, TranslationKey, makeTemplateObject, parseTranslation, translate} from '../../src/utils/translations'; +import {TargetMessage} from '@angular/localize/src/utils/messages'; +import {ParsedTranslation, makeTemplateObject, parseTranslation, translate} from '../../src/utils/translations'; describe('utils', () => { describe('makeTemplateObject', () => { @@ -146,7 +147,7 @@ describe('utils', () => { return [messageParts, substitutions]; } - function parseTranslations(translations: Record): + function parseTranslations(translations: Record): Record { const parsedTranslations: Record = {}; Object.keys(translations).forEach(key => { @@ -156,7 +157,7 @@ describe('utils', () => { } function doTranslate( - translations: Record, + translations: Record, message: [TemplateStringsArray, any[]]): [TemplateStringsArray, readonly any[]] { return translate(parseTranslations(translations), message[0], message[1]); }