angular/packages/localize/tools/test/extract/extractor_spec.ts
Pete Bacon Darwin d3cf222a81 feat(localize): support "associated message ids" for placeholders (#43534)
When extracting i18n messages, the Angular compiler needs to split ICU expressions
into their own message.  Currently there is no guaranteed way to re-associate
the ICU message with the original message where the ICU was found.

This change adds support in the localize tooling so that associated ids can be
stored as metadata in the `$localize` tagged strings. These ids can then be
used in generated translation files to provide a link between the two messages.

The XLIFF 1.2 and 2.0 formats have been updated to render these relationships,
via the `xid` and `subFlows` attributes respectively.

PR Close #43534
2021-10-18 09:23:59 -07:00

150 lines
5.1 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* 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 {absoluteFrom, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
import {MockLogger} from '@angular/compiler-cli/src/ngtsc/logging/testing';
import {MessageExtractor} from '../../src/extract/extraction';
import {runInNativeFileSystem} from '../helpers';
runInNativeFileSystem(() => {
describe('extractMessages', () => {
it('should extract a message for each $localize template tag', () => {
const fs = getFileSystem();
const logger = new MockLogger();
const basePath = absoluteFrom('/root/path/');
const filename = 'relative/path.js';
const file = fs.resolve(basePath, filename);
const extractor = new MessageExtractor(fs, logger, {basePath});
fs.ensureDir(fs.dirname(file));
fs.writeFile(file, [
'$localize`:meaning|description:a${1}b${2}:ICU@@associated-id:c`;',
'$localize(__makeTemplateObject(["a", ":custom-placeholder:b", "c"], ["a", ":custom-placeholder:b", "c"]), 1, 2);',
'$localize`:@@custom-id:a${1}b${2}c`;',
].join('\n'));
const messages = extractor.extractMessages(filename);
expect(messages.length).toEqual(3);
expect(messages[0]).toEqual({
id: '8550342801935228331',
customId: undefined,
description: 'description',
meaning: 'meaning',
messageParts: ['a', 'b', 'c'],
messagePartLocations: [
{
start: {line: 0, column: 10},
end: {line: 0, column: 32},
file,
text: ':meaning|description:a',
},
{
start: {line: 0, column: 36},
end: {line: 0, column: 37},
file,
text: 'b',
},
{
start: {line: 0, column: 41},
end: {line: 0, column: 62},
file,
text: ':ICU@@associated-id:c',
}
],
text: 'a{$PH}b{$ICU}c',
placeholderNames: ['PH', 'ICU'],
associatedMessageIds: {ICU: 'associated-id'},
substitutions: jasmine.any(Object),
substitutionLocations: {
PH: {start: {line: 0, column: 34}, end: {line: 0, column: 35}, file, text: '1'},
ICU: {start: {line: 0, column: 39}, end: {line: 0, column: 40}, file, text: '2'}
},
legacyIds: [],
location: {
start: {line: 0, column: 9},
end: {line: 0, column: 63},
file,
text: '`:meaning|description:a${1}b${2}:ICU@@associated-id:c`',
},
});
expect(messages[1]).toEqual({
id: '5692770902395945649',
customId: undefined,
description: '',
meaning: '',
messageParts: ['a', 'b', 'c'],
messagePartLocations: [
{
start: {line: 1, column: 69},
end: {line: 1, column: 72},
file,
text: '"a"',
},
{
start: {line: 1, column: 74},
end: {line: 1, column: 97},
file,
text: '":custom-placeholder:b"',
},
{
start: {line: 1, column: 99},
end: {line: 1, column: 102},
file,
text: '"c"',
}
],
text: 'a{$custom-placeholder}b{$PH_1}c',
placeholderNames: ['custom-placeholder', 'PH_1'],
associatedMessageIds: {},
substitutions: jasmine.any(Object),
substitutionLocations: {
'custom-placeholder':
{start: {line: 1, column: 106}, end: {line: 1, column: 107}, file, text: '1'},
PH_1: {start: {line: 1, column: 109}, end: {line: 1, column: 110}, file, text: '2'}
},
legacyIds: [],
location: {
start: {line: 1, column: 10},
end: {line: 1, column: 107},
file,
text:
'__makeTemplateObject(["a", ":custom-placeholder:b", "c"], ["a", ":custom-placeholder:b", "c"])',
},
});
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'],
associatedMessageIds: {},
substitutions: jasmine.any(Object),
substitutionLocations: {
PH: {start: {line: 2, column: 26}, end: {line: 2, column: 27}, file, text: '1'},
PH_1: {start: {line: 2, column: 31}, end: {line: 2, column: 32}, file, text: '2'}
},
messagePartLocations: [
{start: {line: 2, column: 10}, end: {line: 2, column: 24}, file, text: ':@@custom-id:a'},
{start: {line: 2, column: 28}, end: {line: 2, column: 29}, file, text: 'b'},
{start: {line: 2, column: 33}, end: {line: 2, column: 34}, file, text: 'c'}
],
legacyIds: [],
location: {
start: {line: 2, column: 9},
end: {line: 2, column: 35},
file,
text: '`:@@custom-id:a${1}b${2}c`'
},
});
});
});
});