-
Notifications
You must be signed in to change notification settings - Fork 24.8k
/
xliff1_translation_parser.ts
99 lines (87 loc) 路 3.5 KB
/
xliff1_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
/**
* @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 {BaseVisitor} from '../base_visitor';
import {TranslationParseError} from '../translation_parse_error';
import {ParsedTranslationBundle, TranslationParser} from '../translation_parser';
import {getAttrOrThrow, getAttribute, parseInnerRange} from '../translation_utils';
import {Xliff1MessageSerializer} from './xliff1_message_serializer';
const XLIFF_1_2_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:1.2"/;
/**
* A translation parser that can load XLIFF 1.2 files.
*
* http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html
* http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
*
*/
export class Xliff1TranslationParser implements TranslationParser {
canParse(filePath: string, contents: string): boolean {
return (extname(filePath) === '.xlf') && XLIFF_1_2_NS_REGEX.test(contents);
}
parse(filePath: string, contents: string): ParsedTranslationBundle {
const xmlParser = new XmlParser();
const xml = xmlParser.parse(contents, filePath);
const bundle = XliffFileElementVisitor.extractBundle(xml.rootNodes);
if (bundle === undefined) {
throw new Error(`Unable to parse "${filePath}" as XLIFF 1.2 format.`);
}
return bundle;
}
}
class XliffFileElementVisitor extends BaseVisitor {
private bundle: ParsedTranslationBundle|undefined;
static extractBundle(xliff: Node[]): ParsedTranslationBundle|undefined {
const visitor = new this();
visitAll(visitor, xliff);
return visitor.bundle;
}
visitElement(element: Element): any {
if (element.name === 'file') {
this.bundle = {
locale: getAttribute(element, 'target-language'),
translations: XliffTranslationVisitor.extractTranslations(element)
};
} else {
return visitAll(this, element.children);
}
}
}
class XliffTranslationVisitor 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): any {
if (element.name === 'trans-unit') {
const id = getAttrOrThrow(element, 'id');
if (this.translations[id] !== undefined) {
throw new TranslationParseError(
element.sourceSpan, `Duplicated translations for message "${id}"`);
}
const targetMessage = element.children.find(isTargetElement);
if (targetMessage === undefined) {
throw new TranslationParseError(element.sourceSpan, 'Missing required <target> element');
}
this.translations[id] = serializeTargetMessage(targetMessage);
} else {
return visitAll(this, element.children);
}
}
}
function serializeTargetMessage(source: Element): 傻ParsedTranslation {
const serializer = new Xliff1MessageSerializer(new TargetMessageRenderer());
return serializer.serialize(parseInnerRange(source));
}
function isTargetElement(node: Node): node is Element {
return node instanceof Element && node.name === 'target';
}