forked from angular/angular
/
xliff2_translation_parser.ts
115 lines (103 loc) · 4.23 KB
/
xliff2_translation_parser.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
/**
* @license
* Copyright Google Inc. 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 {Element, Node, XmlParser, visitAll} from '@angular/compiler';
import {ɵMessageId, ɵParsedTranslation} from '@angular/localize';
import {extname} from 'path';
import {TargetMessageRenderer} from '../../../message_renderers/target_message_renderer';
import {TranslationBundle} from '../../../translator';
import {BaseVisitor} from '../base_visitor';
import {TranslationParseError} from '../translation_parse_error';
import {TranslationParser} from '../translation_parser';
import {getAttrOrThrow, parseInnerRange} from '../translation_utils';
import {Xliff2MessageSerializer} from './xliff2_message_serializer';
const XLIFF_2_0_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:2.0"/;
/**
* A translation parser that can load translations from XLIFF 2 files.
*
* http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html
*
*/
export class Xliff2TranslationParser implements TranslationParser {
canParse(filePath: string, contents: string): boolean {
return (extname(filePath) === '.xlf') && XLIFF_2_0_NS_REGEX.test(contents);
}
parse(filePath: string, contents: string, locale: string|undefined): TranslationBundle {
const xmlParser = new XmlParser();
const xml = xmlParser.parse(contents, filePath);
const bundle = Xliff2TranslationBundleVisitor.extractBundle(xml.rootNodes, locale);
if (bundle === undefined) {
throw new Error(`Unable to parse "${filePath}" as XLIFF 2.0 format.`);
}
if (locale !== undefined) {
bundle.locale = locale;
}
return bundle;
}
}
class Xliff2TranslationBundleVisitor extends BaseVisitor {
private bundle: TranslationBundle|undefined;
constructor(private locale: string|undefined) { super(); }
static extractBundle(xliff: Node[], locale: string|undefined): TranslationBundle|undefined {
const visitor = new this(locale);
visitAll(visitor, xliff);
return visitor.bundle;
}
visitElement(element: Element): any {
if (element.name === 'xliff') {
this.locale = this.locale || getAttrOrThrow(element, 'trgLang');
return visitAll(this, element.children);
} else if (element.name === 'file') {
this.bundle = {
locale: this.locale !,
translations: Xliff2TranslationVisitor.extractTranslations(element)
};
} else {
return visitAll(this, element.children);
}
}
}
class Xliff2TranslationVisitor extends BaseVisitor {
private translations: Record<ɵMessageId, ɵParsedTranslation> = {};
static extractTranslations(file: Element): Record<string, ɵParsedTranslation> {
const visitor = new this();
visitAll(visitor, file.children);
return visitor.translations;
}
visitElement(element: Element, context: any): any {
if (element.name === 'unit') {
const externalId = getAttrOrThrow(element, 'id');
if (this.translations[externalId] !== undefined) {
throw new TranslationParseError(
element.sourceSpan, `Duplicated translations for message "${externalId}"`);
}
visitAll(this, element.children, {unit: externalId});
} else if (element.name === 'segment') {
assertTranslationUnit(element, context);
const targetMessage = element.children.find(isTargetElement);
if (targetMessage === undefined) {
throw new TranslationParseError(element.sourceSpan, 'Missing required <target> element');
}
this.translations[context.unit] = serializeTargetMessage(targetMessage);
} else {
return visitAll(this, element.children);
}
}
}
function assertTranslationUnit(segment: Element, context: any) {
if (context === undefined || context.unit === undefined) {
throw new TranslationParseError(
segment.sourceSpan, 'Invalid <segment> element: should be a child of a <unit> element.');
}
}
function serializeTargetMessage(source: Element): ɵParsedTranslation {
const serializer = new Xliff2MessageSerializer(new TargetMessageRenderer());
return serializer.serialize(parseInnerRange(source));
}
function isTargetElement(node: Node): node is Element {
return node instanceof Element && node.name === 'target';
}