fix(localize): extract the correct message ids (#38498)

Previously, if `useLegacyIds` was enabled, the message extractor
was always rendering the legacy message ids in translation
files even if an explicit "custom message id" had been provided
in the original message.

PR Close #38498
This commit is contained in:
Pete Bacon Darwin 2020-08-17 12:29:18 +01:00 committed by Misko Hevery
parent f245c6bb15
commit ac461e1efd
13 changed files with 273 additions and 161 deletions

View File

@ -107,6 +107,8 @@ export class Xliff1TranslationSerializer implements TranslationSerializer {
/** /**
* Get the id for the given `message`. * Get the id for the given `message`.
* *
* If there was a custom id provided, use that.
*
* If we have requested legacy message ids, then try to return the appropriate id * If we have requested legacy message ids, then try to return the appropriate id
* from the list of legacy ids that were extracted. * from the list of legacy ids that were extracted.
* *
@ -116,7 +118,8 @@ export class Xliff1TranslationSerializer implements TranslationSerializer {
* https://csrc.nist.gov/csrc/media/publications/fips/180/4/final/documents/fips180-4-draft-aug2014.pdf * https://csrc.nist.gov/csrc/media/publications/fips/180/4/final/documents/fips180-4-draft-aug2014.pdf
*/ */
private getMessageId(message: ɵParsedMessage): string { private getMessageId(message: ɵParsedMessage): string {
return this.useLegacyIds && message.legacyIds !== undefined && return message.customId ||
this.useLegacyIds && message.legacyIds !== undefined &&
message.legacyIds.find(id => id.length === LEGACY_XLIFF_MESSAGE_LENGTH) || message.legacyIds.find(id => id.length === LEGACY_XLIFF_MESSAGE_LENGTH) ||
message.id; message.id;
} }

View File

@ -120,6 +120,8 @@ export class Xliff2TranslationSerializer implements TranslationSerializer {
/** /**
* Get the id for the given `message`. * Get the id for the given `message`.
* *
* If there was a custom id provided, use that.
*
* If we have requested legacy message ids, then try to return the appropriate id * If we have requested legacy message ids, then try to return the appropriate id
* from the list of legacy ids that were extracted. * from the list of legacy ids that were extracted.
* *
@ -130,7 +132,8 @@ export class Xliff2TranslationSerializer implements TranslationSerializer {
* https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/GoogleJsMessageIdGenerator.java * https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/GoogleJsMessageIdGenerator.java
*/ */
private getMessageId(message: ɵParsedMessage): string { private getMessageId(message: ɵParsedMessage): string {
return this.useLegacyIds && message.legacyIds !== undefined && return message.customId ||
this.useLegacyIds && message.legacyIds !== undefined &&
message.legacyIds.find( message.legacyIds.find(
id => id.length <= MAX_LEGACY_XLIFF_2_MESSAGE_LENGTH && !/[^0-9]/.test(id)) || id => id.length <= MAX_LEGACY_XLIFF_2_MESSAGE_LENGTH && !/[^0-9]/.test(id)) ||
message.id; message.id;

View File

@ -99,6 +99,8 @@ export class XmbTranslationSerializer implements TranslationSerializer {
/** /**
* Get the id for the given `message`. * Get the id for the given `message`.
* *
* If there was a custom id provided, use that.
*
* If we have requested legacy message ids, then try to return the appropriate id * If we have requested legacy message ids, then try to return the appropriate id
* from the list of legacy ids that were extracted. * from the list of legacy ids that were extracted.
* *
@ -109,7 +111,8 @@ export class XmbTranslationSerializer implements TranslationSerializer {
* https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/GoogleJsMessageIdGenerator.java * https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/GoogleJsMessageIdGenerator.java
*/ */
private getMessageId(message: ɵParsedMessage): string { private getMessageId(message: ɵParsedMessage): string {
return this.useLegacyIds && message.legacyIds !== undefined && return message.customId ||
this.useLegacyIds && message.legacyIds !== undefined &&
message.legacyIds.find(id => id.length <= 20 && !/[^0-9]/.test(id)) || message.legacyIds.find(id => id.length <= 20 && !/[^0-9]/.test(id)) ||
message.id; message.id;
} }

View File

@ -23,14 +23,16 @@ runInEachFileSystem(() => {
fs.ensureDir(absoluteFrom('/root/path/relative')); fs.ensureDir(absoluteFrom('/root/path/relative'));
fs.writeFile(file, [ fs.writeFile(file, [
'$localize`:meaning|description:a${1}b${2}c`;', '$localize`:meaning|description:a${1}b${2}c`;',
'$localize(__makeTemplateObject(["a", ":custom-placeholder:b", "c"], ["a", ":custom-placeholder:b", "c"]), 1, 2);' '$localize(__makeTemplateObject(["a", ":custom-placeholder:b", "c"], ["a", ":custom-placeholder:b", "c"]), 1, 2);',
'$localize`:@@custom-id:a${1}b${2}c`;',
].join('\n')); ].join('\n'));
const messages = extractor.extractMessages(filename); const messages = extractor.extractMessages(filename);
expect(messages.length).toEqual(2); expect(messages.length).toEqual(3);
expect(messages[0]).toEqual({ expect(messages[0]).toEqual({
id: '2714330828844000684', id: '2714330828844000684',
customId: undefined,
description: 'description', description: 'description',
meaning: 'meaning', meaning: 'meaning',
messageParts: ['a', 'b', 'c'], messageParts: ['a', 'b', 'c'],
@ -43,6 +45,7 @@ runInEachFileSystem(() => {
expect(messages[1]).toEqual({ expect(messages[1]).toEqual({
id: '5692770902395945649', id: '5692770902395945649',
customId: undefined,
description: '', description: '',
meaning: '', meaning: '',
messageParts: ['a', 'b', 'c'], messageParts: ['a', 'b', 'c'],
@ -52,6 +55,19 @@ runInEachFileSystem(() => {
legacyIds: [], legacyIds: [],
location: {start: {line: 1, column: 0}, end: {line: 1, column: 111}, file}, location: {start: {line: 1, column: 0}, end: {line: 1, column: 111}, file},
}); });
expect(messages[2]).toEqual({
id: 'custom-id',
customId: 'custom-id',
description: '',
meaning: '',
messageParts: ['a', 'b', 'c'],
text: 'a{$PH}b{$PH_1}c',
placeholderNames: ['PH', 'PH_1'],
substitutions: jasmine.any(Object),
legacyIds: [],
location: {start: {line: 2, column: 9}, end: {line: 2, column: 35}, file},
});
}); });
}); });
}); });

View File

@ -55,138 +55,189 @@ runInEachFileSystem(() => {
].join('\n')); ].join('\n'));
}); });
it('should extract translations from source code, and write as JSON format', () => { for (const useLegacyIds of [true, false]) {
extractTranslations({ describe(useLegacyIds ? '[using legacy ids]' : '', () => {
rootPath, it('should extract translations from source code, and write as JSON format', () => {
sourceLocale: 'en-GB', extractTranslations({
sourceFilePaths: [sourceFilePath], rootPath,
format: 'json', sourceLocale: 'en-GB',
outputPath, sourceFilePaths: [sourceFilePath],
logger, format: 'json',
useSourceMaps: false, outputPath,
useLegacyIds: false, logger,
duplicateMessageHandling: 'ignore', useSourceMaps: false,
}); useLegacyIds,
expect(fs.readFile(outputPath)).toEqual([ duplicateMessageHandling: 'ignore',
`{`, });
` "locale": "en-GB",`, expect(fs.readFile(outputPath)).toEqual([
` "translations": {`, `{`,
` "3291030485717846467": "Hello, {$PH}!",`, ` "locale": "en-GB",`,
` "8669027859022295761": "try{$PH}me"`, ` "translations": {`,
` }`, ` "3291030485717846467": "Hello, {$PH}!",`,
`}`, ` "8669027859022295761": "try{$PH}me",`,
].join('\n')); ` "custom-id": "Custom id message",`,
}); ` "273296103957933077": "Legacy id message",`,
` "custom-id-2": "Custom and legacy message"`,
` }`,
`}`,
].join('\n'));
});
it('should extract translations from source code, and write as xmb format', () => { it('should extract translations from source code, and write as xmb format', () => {
extractTranslations({ extractTranslations({
rootPath, rootPath,
sourceLocale: 'en', sourceLocale: 'en',
sourceFilePaths: [sourceFilePath], sourceFilePaths: [sourceFilePath],
format: 'xmb', format: 'xmb',
outputPath, outputPath,
logger, logger,
useSourceMaps: false, useSourceMaps: false,
useLegacyIds: false, useLegacyIds,
duplicateMessageHandling: 'ignore', duplicateMessageHandling: 'ignore',
}); });
expect(fs.readFile(outputPath)).toEqual([ expect(fs.readFile(outputPath)).toEqual([
`<?xml version="1.0" encoding="UTF-8" ?>`, `<?xml version="1.0" encoding="UTF-8" ?>`,
`<!DOCTYPE messagebundle [`, `<!DOCTYPE messagebundle [`,
`<!ELEMENT messagebundle (msg)*>`, `<!ELEMENT messagebundle (msg)*>`,
`<!ATTLIST messagebundle class CDATA #IMPLIED>`, `<!ATTLIST messagebundle class CDATA #IMPLIED>`,
``, ``,
`<!ELEMENT msg (#PCDATA|ph|source)*>`, `<!ELEMENT msg (#PCDATA|ph|source)*>`,
`<!ATTLIST msg id CDATA #IMPLIED>`, `<!ATTLIST msg id CDATA #IMPLIED>`,
`<!ATTLIST msg seq CDATA #IMPLIED>`, `<!ATTLIST msg seq CDATA #IMPLIED>`,
`<!ATTLIST msg name CDATA #IMPLIED>`, `<!ATTLIST msg name CDATA #IMPLIED>`,
`<!ATTLIST msg desc CDATA #IMPLIED>`, `<!ATTLIST msg desc CDATA #IMPLIED>`,
`<!ATTLIST msg meaning CDATA #IMPLIED>`, `<!ATTLIST msg meaning CDATA #IMPLIED>`,
`<!ATTLIST msg obsolete (obsolete) #IMPLIED>`, `<!ATTLIST msg obsolete (obsolete) #IMPLIED>`,
`<!ATTLIST msg xml:space (default|preserve) "default">`, `<!ATTLIST msg xml:space (default|preserve) "default">`,
`<!ATTLIST msg is_hidden CDATA #IMPLIED>`, `<!ATTLIST msg is_hidden CDATA #IMPLIED>`,
``, ``,
`<!ELEMENT source (#PCDATA)>`, `<!ELEMENT source (#PCDATA)>`,
``, ``,
`<!ELEMENT ph (#PCDATA|ex)*>`, `<!ELEMENT ph (#PCDATA|ex)*>`,
`<!ATTLIST ph name CDATA #REQUIRED>`, `<!ATTLIST ph name CDATA #REQUIRED>`,
``, ``,
`<!ELEMENT ex (#PCDATA)>`, `<!ELEMENT ex (#PCDATA)>`,
`]>`, `]>`,
`<messagebundle>`, `<messagebundle>`,
` <msg id="3291030485717846467"><source>test_files/test.js:1</source>Hello, <ph name="PH"/>!</msg>`, ` <msg id="3291030485717846467"><source>test_files/test.js:1</source>Hello, <ph name="PH"/>!</msg>`,
` <msg id="8669027859022295761"><source>test_files/test.js:2</source>try<ph name="PH"/>me</msg>`, ` <msg id="8669027859022295761"><source>test_files/test.js:2</source>try<ph name="PH"/>me</msg>`,
`</messagebundle>\n`, ` <msg id="custom-id"><source>test_files/test.js:3</source>Custom id message</msg>`,
].join('\n')); ` <msg id="${
}); useLegacyIds ?
'12345678901234567890' :
'273296103957933077'}"><source>test_files/test.js:5</source>Legacy id message</msg>`,
` <msg id="custom-id-2"><source>test_files/test.js:7</source>Custom and legacy message</msg>`,
`</messagebundle>\n`,
].join('\n'));
});
it('should extract translations from source code, and write as XLIFF 1.2 format', () => { it('should extract translations from source code, and write as XLIFF 1.2 format', () => {
extractTranslations({ extractTranslations({
rootPath, rootPath,
sourceLocale: 'en-CA', sourceLocale: 'en-CA',
sourceFilePaths: [sourceFilePath], sourceFilePaths: [sourceFilePath],
format: 'xliff', format: 'xliff',
outputPath, outputPath,
logger, logger,
useSourceMaps: false, useSourceMaps: false,
useLegacyIds: false, useLegacyIds,
duplicateMessageHandling: 'ignore', duplicateMessageHandling: 'ignore',
}); });
expect(fs.readFile(outputPath)).toEqual([ expect(fs.readFile(outputPath)).toEqual([
`<?xml version="1.0" encoding="UTF-8" ?>`, `<?xml version="1.0" encoding="UTF-8" ?>`,
`<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">`, `<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">`,
` <file source-language="en-CA" datatype="plaintext">`, ` <file source-language="en-CA" datatype="plaintext">`,
` <body>`, ` <body>`,
` <trans-unit id="3291030485717846467" datatype="html">`, ` <trans-unit id="3291030485717846467" datatype="html">`,
` <source>Hello, <x id="PH"/>!</source>`, ` <source>Hello, <x id="PH"/>!</source>`,
` <context-group purpose="location">`, ` <context-group purpose="location">`,
` <context context-type="sourcefile">test_files/test.js</context>`, ` <context context-type="sourcefile">test_files/test.js</context>`,
` <context context-type="linenumber">2</context>`, ` <context context-type="linenumber">2</context>`,
` </context-group>`, ` </context-group>`,
` </trans-unit>`, ` </trans-unit>`,
` <trans-unit id="8669027859022295761" datatype="html">`, ` <trans-unit id="8669027859022295761" datatype="html">`,
` <source>try<x id="PH"/>me</source>`, ` <source>try<x id="PH"/>me</source>`,
` <context-group purpose="location">`, ` <context-group purpose="location">`,
` <context context-type="sourcefile">test_files/test.js</context>`, ` <context context-type="sourcefile">test_files/test.js</context>`,
` <context context-type="linenumber">3</context>`, ` <context context-type="linenumber">3</context>`,
` </context-group>`, ` </context-group>`,
` </trans-unit>`, ` </trans-unit>`,
` </body>`, ` <trans-unit id="custom-id" datatype="html">`,
` </file>`, ` <source>Custom id message</source>`,
`</xliff>\n`, ` <context-group purpose="location">`,
].join('\n')); ` <context context-type="sourcefile">test_files/test.js</context>`,
}); ` <context context-type="linenumber">4</context>`,
` </context-group>`,
` </trans-unit>`,
` <trans-unit id="${
useLegacyIds ? '1234567890123456789012345678901234567890' :
'273296103957933077'}" datatype="html">`,
` <source>Legacy id message</source>`,
` <context-group purpose="location">`,
` <context context-type="sourcefile">test_files/test.js</context>`,
` <context context-type="linenumber">6</context>`,
` </context-group>`,
` </trans-unit>`,
` <trans-unit id="custom-id-2" datatype="html">`,
` <source>Custom and legacy message</source>`,
` <context-group purpose="location">`,
` <context context-type="sourcefile">test_files/test.js</context>`,
` <context context-type="linenumber">8</context>`,
` </context-group>`,
` </trans-unit>`,
` </body>`,
` </file>`,
`</xliff>\n`,
].join('\n'));
});
it('should extract translations from source code, and write as XLIFF 2 format', () => { it('should extract translations from source code, and write as XLIFF 2 format', () => {
extractTranslations({ extractTranslations({
rootPath, rootPath,
sourceLocale: 'en-AU', sourceLocale: 'en-AU',
sourceFilePaths: [sourceFilePath], sourceFilePaths: [sourceFilePath],
format: 'xliff2', format: 'xliff2',
outputPath, outputPath,
logger, logger,
useSourceMaps: false, useSourceMaps: false,
useLegacyIds: false, useLegacyIds,
duplicateMessageHandling: 'ignore', duplicateMessageHandling: 'ignore',
});
expect(fs.readFile(outputPath)).toEqual([
`<?xml version="1.0" encoding="UTF-8" ?>`,
`<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en-AU">`,
` <file>`,
` <unit id="3291030485717846467">`,
` <segment>`,
` <source>Hello, <ph id="0" equiv="PH"/>!</source>`,
` </segment>`,
` </unit>`,
` <unit id="8669027859022295761">`,
` <segment>`,
` <source>try<ph id="0" equiv="PH"/>me</source>`,
` </segment>`,
` </unit>`,
` <unit id="custom-id">`,
` <segment>`,
` <source>Custom id message</source>`,
` </segment>`,
` </unit>`,
` <unit id="${useLegacyIds ? '12345678901234567890' : '273296103957933077'}">`,
` <segment>`,
` <source>Legacy id message</source>`,
` </segment>`,
` </unit>`,
` <unit id="custom-id-2">`,
` <segment>`,
` <source>Custom and legacy message</source>`,
` </segment>`,
` </unit>`,
` </file>`,
`</xliff>\n`,
].join('\n'));
});
}); });
expect(fs.readFile(outputPath)).toEqual([ }
`<?xml version="1.0" encoding="UTF-8" ?>`,
`<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en-AU">`,
` <file>`,
` <unit id="3291030485717846467">`,
` <segment>`,
` <source>Hello, <ph id="0" equiv="PH"/>!</source>`,
` </segment>`,
` </unit>`,
` <unit id="8669027859022295761">`,
` <segment>`,
` <source>try<ph id="0" equiv="PH"/>me</source>`,
` </segment>`,
` </unit>`,
` </file>`,
`</xliff>\n`,
].join('\n'));
});
for (const target of ['es2015', 'es5']) { for (const target of ['es2015', 'es5']) {
it(`should render the original location of translations, when processing an ${ it(`should render the original location of translations, when processing an ${

View File

@ -1,3 +1,8 @@
var name = 'World'; var name = 'World';
var message = $localize`Hello, ${name}!`; var message = $localize`Hello, ${name}!`;
var other = $localize(__makeTemplateObject(['try', 'me'], ['try', 'me']), 40 + 2); var other = $localize(__makeTemplateObject(['try', 'me'], ['try', 'me']), 40 + 2);
var customMessage = $localize`:@@custom-id:Custom id message`;
var legacyMessage =
$localize`:␟1234567890123456789012345678901234567890␟12345678901234567890:Legacy id message`;
var customAndLegacyMessage =
$localize`:@@custom-id-2␟1234567890123456789012345678901234567890␟12345678901234567890:Custom and legacy message`;

View File

@ -16,6 +16,9 @@ describe('JsonTranslationSerializer', () => {
it('should convert a set of parsed messages into a JSON string', () => { it('should convert a set of parsed messages into a JSON string', () => {
const messages: ɵParsedMessage[] = [ const messages: ɵParsedMessage[] = [
mockMessage('12345', ['a', 'b', 'c'], ['PH', 'PH_1'], {meaning: 'some meaning'}), mockMessage('12345', ['a', 'b', 'c'], ['PH', 'PH_1'], {meaning: 'some meaning'}),
mockMessage('54321', ['a', 'b', 'c'], ['PH', 'PH_1'], {
customId: 'someId',
}),
mockMessage( mockMessage(
'67890', ['a', '', 'c'], ['START_TAG_SPAN', 'CLOSE_TAG_SPAN'], '67890', ['a', '', 'c'], ['START_TAG_SPAN', 'CLOSE_TAG_SPAN'],
{description: 'some description'}), {description: 'some description'}),
@ -49,7 +52,8 @@ describe('JsonTranslationSerializer', () => {
` "80808": "multi\\nlines",`, ` "80808": "multi\\nlines",`,
` "90000": "<escape{$double-quotes-\\"}me>",`, ` "90000": "<escape{$double-quotes-\\"}me>",`,
` "100000": "pre-ICU {VAR_SELECT, select, a {a} b {{INTERPOLATION}} c {pre {INTERPOLATION_1} post}} post-ICU",`, ` "100000": "pre-ICU {VAR_SELECT, select, a {a} b {{INTERPOLATION}} c {pre {INTERPOLATION_1} post}} post-ICU",`,
` "100001": "{VAR_PLURAL, plural, one {{START_BOLD_TEXT}something bold{CLOSE_BOLD_TEXT}} other {pre {START_TAG_SPAN}middle{CLOSE_TAG_SPAN} post}}"`, ` "100001": "{VAR_PLURAL, plural, one {{START_BOLD_TEXT}something bold{CLOSE_BOLD_TEXT}} other {pre {START_TAG_SPAN}middle{CLOSE_TAG_SPAN} post}}",`,
` "someId": "a{$PH}b{$PH_1}c"`,
` }`, ` }`,
`}`, `}`,
].join('\n')); ].join('\n'));

View File

@ -9,28 +9,31 @@ import {ɵParsedMessage} from '@angular/localize';
import {SourceLocation} from '@angular/localize/src/utils'; import {SourceLocation} from '@angular/localize/src/utils';
export interface MockMessageOptions { export interface MockMessageOptions {
customId?: string;
meaning?: string; meaning?: string;
description?: string; description?: string;
location?: SourceLocation; location?: SourceLocation;
legacyIds?: string[]; legacyIds?: string[];
} }
/** /**
* This helper is used to create `ParsedMessage` objects to be rendered in the * This helper is used to create `ParsedMessage` objects to be rendered in the
* `TranslationSerializer` tests. * `TranslationSerializer` tests.
*/ */
export function mockMessage( export function mockMessage(
id: string, messageParts: string[], placeholderNames: string[], id: string, messageParts: string[], placeholderNames: string[],
{meaning = '', description = '', location, legacyIds = []}: MockMessageOptions): {customId, meaning = '', description = '', location, legacyIds = []}: MockMessageOptions):
ɵParsedMessage { ɵParsedMessage {
let text = messageParts[0]; let text = messageParts[0];
for (let i = 1; i < messageParts.length; i++) { for (let i = 1; i < messageParts.length; i++) {
text += `{$${placeholderNames[i - 1]}}${messageParts[i]}`; text += `{$${placeholderNames[i - 1]}}${messageParts[i]}`;
} }
return { return {
id, id: customId || id, // customId trumps id
text, text,
messageParts, messageParts,
placeholderNames, placeholderNames,
customId,
description, description,
meaning, meaning,
substitutions: [], substitutions: [],

View File

@ -28,6 +28,10 @@ runInEachFileSystem(() => {
}, },
legacyIds: ['1234567890ABCDEF1234567890ABCDEF12345678', '615790887472569365'], legacyIds: ['1234567890ABCDEF1234567890ABCDEF12345678', '615790887472569365'],
}), }),
mockMessage('54321', ['a', 'b', 'c'], ['PH', 'PH_1'], {
customId: 'someId',
legacyIds: ['87654321FEDCBA0987654321FEDCBA0987654321', '563965274788097516'],
}),
mockMessage( mockMessage(
'67890', ['a', '', 'c'], ['START_TAG_SPAN', 'CLOSE_TAG_SPAN'], '67890', ['a', '', 'c'], ['START_TAG_SPAN', 'CLOSE_TAG_SPAN'],
{description: 'some description'}), {description: 'some description'}),
@ -66,6 +70,9 @@ runInEachFileSystem(() => {
` </context-group>`, ` </context-group>`,
` <note priority="1" from="meaning">some meaning</note>`, ` <note priority="1" from="meaning">some meaning</note>`,
` </trans-unit>`, ` </trans-unit>`,
` <trans-unit id="someId" datatype="html">`,
` <source>a<x id="PH"/>b<x id="PH_1"/>c</source>`,
` </trans-unit>`,
` <trans-unit id="67890" datatype="html">`, ` <trans-unit id="67890" datatype="html">`,
` <source>a<x id="START_TAG_SPAN"/><x id="CLOSE_TAG_SPAN"/>c</source>`, ` <source>a<x id="START_TAG_SPAN"/><x id="CLOSE_TAG_SPAN"/>c</source>`,
` <note priority="1" from="description">some description</note>`, ` <note priority="1" from="description">some description</note>`,

View File

@ -29,6 +29,10 @@ runInEachFileSystem(() => {
}, },
legacyIds: ['1234567890ABCDEF1234567890ABCDEF12345678', '615790887472569365'], legacyIds: ['1234567890ABCDEF1234567890ABCDEF12345678', '615790887472569365'],
}), }),
mockMessage('54321', ['a', 'b', 'c'], ['PH', 'PH_1'], {
customId: 'someId',
legacyIds: ['87654321FEDCBA0987654321FEDCBA0987654321', '563965274788097516'],
}),
mockMessage('67890', ['a', '', 'c'], ['START_TAG_SPAN', 'CLOSE_TAG_SPAN'], { mockMessage('67890', ['a', '', 'c'], ['START_TAG_SPAN', 'CLOSE_TAG_SPAN'], {
description: 'some description', description: 'some description',
location: { location: {
@ -70,6 +74,11 @@ runInEachFileSystem(() => {
` <source>a<ph id="0" equiv="PH"/>b<ph id="1" equiv="PH_1"/>c</source>`, ` <source>a<ph id="0" equiv="PH"/>b<ph id="1" equiv="PH_1"/>c</source>`,
` </segment>`, ` </segment>`,
` </unit>`, ` </unit>`,
` <unit id="someId">`,
` <segment>`,
` <source>a<ph id="0" equiv="PH"/>b<ph id="1" equiv="PH_1"/>c</source>`,
` </segment>`,
` </unit>`,
` <unit id="67890">`, ` <unit id="67890">`,
` <notes>`, ` <notes>`,
` <note category="location">file.ts:3,4</note>`, ` <note category="location">file.ts:3,4</note>`,

View File

@ -23,6 +23,10 @@ runInEachFileSystem(() => {
meaning: 'some meaning', meaning: 'some meaning',
legacyIds: ['1234567890ABCDEF1234567890ABCDEF12345678', '615790887472569365'], legacyIds: ['1234567890ABCDEF1234567890ABCDEF12345678', '615790887472569365'],
}), }),
mockMessage('54321', ['a', 'b', 'c'], ['PH', 'PH_1'], {
customId: 'someId',
legacyIds: ['87654321FEDCBA0987654321FEDCBA0987654321', '563965274788097516'],
}),
mockMessage( mockMessage(
'67890', ['a', '', 'c'], ['START_TAG_SPAN', 'CLOSE_TAG_SPAN'], '67890', ['a', '', 'c'], ['START_TAG_SPAN', 'CLOSE_TAG_SPAN'],
{description: 'some description'}), {description: 'some description'}),
@ -51,6 +55,7 @@ runInEachFileSystem(() => {
useLegacyIds ? useLegacyIds ?
'615790887472569365' : '615790887472569365' :
'12345'}" meaning="some meaning">a<ph name="PH"/>b<ph name="PH_1"/>c</msg>`, '12345'}" meaning="some meaning">a<ph name="PH"/>b<ph name="PH_1"/>c</msg>`,
` <msg id="someId">a<ph name="PH"/>b<ph name="PH_1"/>c</msg>`,
` <msg id="67890" desc="some description">a<ph name="START_TAG_SPAN"/><ph name="CLOSE_TAG_SPAN"/>c</msg>`, ` <msg id="67890" desc="some description">a<ph name="START_TAG_SPAN"/><ph name="CLOSE_TAG_SPAN"/>c</msg>`,
` <msg id="13579"><ph name="START_BOLD_TEXT"/>b<ph name="CLOSE_BOLD_TEXT"/></msg>`, ` <msg id="13579"><ph name="START_BOLD_TEXT"/>b<ph name="CLOSE_BOLD_TEXT"/></msg>`,
` <msg id="24680" desc="and description" meaning="meaning">a</msg>`, ` <msg id="24680" desc="and description" meaning="meaning">a</msg>`,

View File

@ -58,10 +58,6 @@ export interface MessageMetadata {
* A human readable rendering of the message * A human readable rendering of the message
*/ */
text: string; text: string;
/**
* A unique identifier for this message.
*/
id?: MessageId;
/** /**
* Legacy message ids, if provided. * Legacy message ids, if provided.
* *
@ -73,6 +69,12 @@ export interface MessageMetadata {
* of translation if the translations are encoded using the legacy message id. * of translation if the translations are encoded using the legacy message id.
*/ */
legacyIds?: string[]; legacyIds?: string[];
/**
* The id of the `message` if a custom one was specified explicitly.
*
* This id overrides any computed or legacy ids.
*/
customId?: string;
/** /**
* The meaning of the `message`, used to distinguish identical `messageString`s. * The meaning of the `message`, used to distinguish identical `messageString`s.
*/ */
@ -110,8 +112,6 @@ export interface MessageMetadata {
export interface ParsedMessage extends MessageMetadata { export interface ParsedMessage extends MessageMetadata {
/** /**
* The key used to look up the appropriate translation target. * The key used to look up the appropriate translation target.
*
* In `ParsedMessage` this is a required field, whereas it is optional in `MessageMetadata`.
*/ */
id: MessageId; id: MessageId;
/** /**
@ -129,7 +129,8 @@ export interface ParsedMessage extends MessageMetadata {
} }
/** /**
* Parse a `$localize` tagged string into a structure that can be used for translation. * Parse a `$localize` tagged string into a structure that can be used for translation or
* extraction.
* *
* See `ParsedMessage` for an example. * See `ParsedMessage` for an example.
*/ */
@ -151,13 +152,14 @@ export function parseMessage(
placeholderNames.push(placeholderName); placeholderNames.push(placeholderName);
cleanedMessageParts.push(messagePart); cleanedMessageParts.push(messagePart);
} }
const messageId = metadata.id || computeMsgId(messageString, metadata.meaning || ''); const messageId = metadata.customId || computeMsgId(messageString, metadata.meaning || '');
const legacyIds = metadata.legacyIds ? metadata.legacyIds.filter(id => id !== messageId) : []; const legacyIds = metadata.legacyIds ? metadata.legacyIds.filter(id => id !== messageId) : [];
return { return {
id: messageId, id: messageId,
legacyIds, legacyIds,
substitutions, substitutions,
text: messageString, text: messageString,
customId: metadata.customId,
meaning: metadata.meaning || '', meaning: metadata.meaning || '',
description: metadata.description || '', description: metadata.description || '',
messageParts: cleanedMessageParts, messageParts: cleanedMessageParts,
@ -198,7 +200,7 @@ export function parseMetadata(cooked: string, raw: string): MessageMetadata {
return {text: messageString}; return {text: messageString};
} else { } else {
const [meaningDescAndId, ...legacyIds] = block.split(LEGACY_ID_INDICATOR); const [meaningDescAndId, ...legacyIds] = block.split(LEGACY_ID_INDICATOR);
const [meaningAndDesc, id] = meaningDescAndId.split(ID_SEPARATOR, 2); const [meaningAndDesc, customId] = meaningDescAndId.split(ID_SEPARATOR, 2);
let [meaning, description]: (string|undefined)[] = meaningAndDesc.split(MEANING_SEPARATOR, 2); let [meaning, description]: (string|undefined)[] = meaningAndDesc.split(MEANING_SEPARATOR, 2);
if (description === undefined) { if (description === undefined) {
description = meaning; description = meaning;
@ -207,7 +209,7 @@ export function parseMetadata(cooked: string, raw: string): MessageMetadata {
if (description === '') { if (description === '') {
description = undefined; description = undefined;
} }
return {text: messageString, meaning, description, id, legacyIds}; return {text: messageString, meaning, description, customId, legacyIds};
} }
} }

View File

@ -9,13 +9,14 @@ import {findEndOfBlock, makeTemplateObject, parseMessage, parseMetadata, splitBl
describe('messages utils', () => { describe('messages utils', () => {
describe('parseMessage', () => { describe('parseMessage', () => {
it('should use the custom id parsed from the metadata if available', () => { it('should use the custom id parsed from the metadata for the message id, if available', () => {
const message = parseMessage( const message = parseMessage(
makeTemplateObject( makeTemplateObject(
[':@@custom-message-id:a', ':one:b', ':two:c'], [':@@custom-message-id:a', ':one:b', ':two:c'],
[':@@custom-message-id:a', ':one:b', ':two:c']), [':@@custom-message-id:a', ':one:b', ':two:c']),
[1, 2]); [1, 2]);
expect(message.id).toEqual('custom-message-id'); expect(message.customId).toEqual('custom-message-id');
expect(message.id).toEqual(message.customId!);
}); });
it('should compute the translation key if no metadata', () => { it('should compute the translation key if no metadata', () => {
@ -24,7 +25,7 @@ describe('messages utils', () => {
expect(message.id).toEqual('8865273085679272414'); expect(message.id).toEqual('8865273085679272414');
}); });
it('should compute the translation key if no id in the metadata', () => { it('should compute the translation key if no custom id in the metadata', () => {
const message = parseMessage( const message = parseMessage(
makeTemplateObject( makeTemplateObject(
[':description:a', ':one:b', ':two:c'], [':description:a', ':one:b', ':two:c']), [':description:a', ':one:b', ':two:c'], [':description:a', ':one:b', ':two:c']),
@ -181,21 +182,21 @@ describe('messages utils', () => {
text: 'abc def', text: 'abc def',
description: 'description', description: 'description',
meaning: undefined, meaning: undefined,
id: undefined, customId: undefined,
legacyIds: [] legacyIds: []
}); });
expect(parseMetadata(':meaning|:abc def', ':meaning|:abc def')).toEqual({ expect(parseMetadata(':meaning|:abc def', ':meaning|:abc def')).toEqual({
text: 'abc def', text: 'abc def',
description: undefined, description: undefined,
meaning: 'meaning', meaning: 'meaning',
id: undefined, customId: undefined,
legacyIds: [] legacyIds: []
}); });
expect(parseMetadata(':@@message-id:abc def', ':@@message-id:abc def')).toEqual({ expect(parseMetadata(':@@message-id:abc def', ':@@message-id:abc def')).toEqual({
text: 'abc def', text: 'abc def',
description: undefined, description: undefined,
meaning: undefined, meaning: undefined,
id: 'message-id', customId: 'message-id',
legacyIds: [] legacyIds: []
}); });
expect(parseMetadata(':meaning|description:abc def', ':meaning|description:abc def')) expect(parseMetadata(':meaning|description:abc def', ':meaning|description:abc def'))
@ -203,7 +204,7 @@ describe('messages utils', () => {
text: 'abc def', text: 'abc def',
description: 'description', description: 'description',
meaning: 'meaning', meaning: 'meaning',
id: undefined, customId: undefined,
legacyIds: [] legacyIds: []
}); });
expect(parseMetadata(':description@@message-id:abc def', ':description@@message-id:abc def')) expect(parseMetadata(':description@@message-id:abc def', ':description@@message-id:abc def'))
@ -211,7 +212,7 @@ describe('messages utils', () => {
text: 'abc def', text: 'abc def',
description: 'description', description: 'description',
meaning: undefined, meaning: undefined,
id: 'message-id', customId: 'message-id',
legacyIds: [] legacyIds: []
}); });
expect(parseMetadata(':meaning|@@message-id:abc def', ':meaning|@@message-id:abc def')) expect(parseMetadata(':meaning|@@message-id:abc def', ':meaning|@@message-id:abc def'))
@ -219,7 +220,7 @@ describe('messages utils', () => {
text: 'abc def', text: 'abc def',
description: undefined, description: undefined,
meaning: 'meaning', meaning: 'meaning',
id: 'message-id', customId: 'message-id',
legacyIds: [] legacyIds: []
}); });
expect(parseMetadata( expect(parseMetadata(
@ -229,7 +230,7 @@ describe('messages utils', () => {
text: 'abc def', text: 'abc def',
description: 'description', description: 'description',
meaning: undefined, meaning: undefined,
id: 'message-id', customId: 'message-id',
legacyIds: ['legacy-1', 'legacy-2', 'legacy-3'] legacyIds: ['legacy-1', 'legacy-2', 'legacy-3']
}); });
expect(parseMetadata( expect(parseMetadata(
@ -239,7 +240,7 @@ describe('messages utils', () => {
text: 'abc def', text: 'abc def',
description: undefined, description: undefined,
meaning: 'meaning', meaning: 'meaning',
id: 'message-id', customId: 'message-id',
legacyIds: ['legacy-message-id'] legacyIds: ['legacy-message-id']
}); });
expect(parseMetadata( expect(parseMetadata(
@ -248,7 +249,7 @@ describe('messages utils', () => {
text: 'abc def', text: 'abc def',
description: undefined, description: undefined,
meaning: 'meaning', meaning: 'meaning',
id: undefined, customId: undefined,
legacyIds: ['legacy-message-id'] legacyIds: ['legacy-message-id']
}); });
@ -256,7 +257,7 @@ describe('messages utils', () => {
text: 'abc def', text: 'abc def',
description: undefined, description: undefined,
meaning: undefined, meaning: undefined,
id: undefined, customId: undefined,
legacyIds: ['legacy-message-id'] legacyIds: ['legacy-message-id']
}); });
}); });
@ -266,7 +267,7 @@ describe('messages utils', () => {
text: 'abc def', text: 'abc def',
meaning: undefined, meaning: undefined,
description: undefined, description: undefined,
id: undefined, customId: undefined,
legacyIds: [] legacyIds: []
}); });
}); });