-
Notifications
You must be signed in to change notification settings - Fork 24.8k
/
xmb_translation_serializer.ts
123 lines (116 loc) · 4.49 KB
/
xmb_translation_serializer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/**
* @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 {AbsoluteFsPath, FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
import {ɵParsedMessage, ɵSourceLocation} from '@angular/localize';
import {extractIcuPlaceholders} from './icu_parsing';
import {TranslationSerializer} from './translation_serializer';
import {XmlFile} from './xml_file';
/**
* A translation serializer that can write files in XMB format.
*
* http://cldr.unicode.org/development/development-process/design-proposals/xmb
*
* @see XmbTranslationParser
* @publicApi used by CLI
*/
export class XmbTranslationSerializer implements TranslationSerializer {
constructor(
private basePath: AbsoluteFsPath, private useLegacyIds: boolean,
private fs: FileSystem = getFileSystem()) {}
serialize(messages: ɵParsedMessage[]): string {
const ids = new Set<string>();
const xml = new XmlFile();
xml.rawText(
`<!DOCTYPE messagebundle [\n` +
`<!ELEMENT messagebundle (msg)*>\n` +
`<!ATTLIST messagebundle class CDATA #IMPLIED>\n` +
`\n` +
`<!ELEMENT msg (#PCDATA|ph|source)*>\n` +
`<!ATTLIST msg id CDATA #IMPLIED>\n` +
`<!ATTLIST msg seq CDATA #IMPLIED>\n` +
`<!ATTLIST msg name CDATA #IMPLIED>\n` +
`<!ATTLIST msg desc CDATA #IMPLIED>\n` +
`<!ATTLIST msg meaning CDATA #IMPLIED>\n` +
`<!ATTLIST msg obsolete (obsolete) #IMPLIED>\n` +
`<!ATTLIST msg xml:space (default|preserve) "default">\n` +
`<!ATTLIST msg is_hidden CDATA #IMPLIED>\n` +
`\n` +
`<!ELEMENT source (#PCDATA)>\n` +
`\n` +
`<!ELEMENT ph (#PCDATA|ex)*>\n` +
`<!ATTLIST ph name CDATA #REQUIRED>\n` +
`\n` +
`<!ELEMENT ex (#PCDATA)>\n` +
`]>\n`);
xml.startTag('messagebundle');
for (const message of messages) {
const id = this.getMessageId(message);
if (ids.has(id)) {
// Do not render the same message more than once
continue;
}
ids.add(id);
xml.startTag(
'msg', {id, desc: message.description, meaning: message.meaning},
{preserveWhitespace: true});
if (message.location) {
this.serializeLocation(xml, message.location);
}
this.serializeMessage(xml, message);
xml.endTag('msg', {preserveWhitespace: false});
}
xml.endTag('messagebundle');
return xml.toString();
}
private serializeLocation(xml: XmlFile, location: ɵSourceLocation): void {
xml.startTag('source');
const endLineString = location.end !== undefined && location.end.line !== location.start.line ?
`,${location.end.line + 1}` :
'';
xml.text(
`${this.fs.relative(this.basePath, location.file)}:${location.start.line}${endLineString}`);
xml.endTag('source');
}
private serializeMessage(xml: XmlFile, message: ɵParsedMessage): void {
const length = message.messageParts.length - 1;
for (let i = 0; i < length; i++) {
this.serializeTextPart(xml, message.messageParts[i]);
xml.startTag('ph', {name: message.placeholderNames[i]}, {selfClosing: true});
}
this.serializeTextPart(xml, message.messageParts[length]);
}
private serializeTextPart(xml: XmlFile, text: string): void {
const pieces = extractIcuPlaceholders(text);
const length = pieces.length - 1;
for (let i = 0; i < length; i += 2) {
xml.text(pieces[i]);
xml.startTag('ph', {name: pieces[i + 1]}, {selfClosing: true});
}
xml.text(pieces[length]);
}
/**
* 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
* from the list of legacy ids that were extracted.
*
* Otherwise return the canonical message id.
*
* An XMB legacy message id is a 64 bit number encoded as a decimal string, which will have
* at most 20 digits, since 2^65-1 = 36,893,488,147,419,103,231. This digest is based on:
* https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/GoogleJsMessageIdGenerator.java
*/
private getMessageId(message: ɵParsedMessage): string {
return message.customId ||
this.useLegacyIds && message.legacyIds !== undefined &&
message.legacyIds.find(id => id.length <= 20 && !/[^0-9]/.test(id)) ||
message.id;
}
}