refactor(ivy): i18n - share `MessageSerializer` across `TranslationParsers` (#33444)

Each of the XML based `TranslationParsers` was providing its own
`MessageSerializer`, but they are all very similar. So these have been
consolidated into a single more generic `MessageSerializer.

As a result of this, the extra layers of folders in the project seemed
unnecessary, so they have been flattened.

PR Close #33444
This commit is contained in:
Pete Bacon Darwin 2019-10-28 12:51:17 +00:00 committed by Andrew Kushnir
parent e483acaa17
commit c2f13a1e3a
12 changed files with 67 additions and 93 deletions

View File

@ -15,9 +15,9 @@ import {getOutputPathFn, OutputPathFn} from './output_path';
import {SourceFileTranslationHandler} from './source_files/source_file_translation_handler'; import {SourceFileTranslationHandler} from './source_files/source_file_translation_handler';
import {MissingTranslationStrategy} from './source_files/source_file_utils'; import {MissingTranslationStrategy} from './source_files/source_file_utils';
import {TranslationLoader} from './translation_files/translation_loader'; import {TranslationLoader} from './translation_files/translation_loader';
import {SimpleJsonTranslationParser} from './translation_files/translation_parsers/simple_json/simple_json_translation_parser'; import {SimpleJsonTranslationParser} from './translation_files/translation_parsers/simple_json_translation_parser';
import {Xliff1TranslationParser} from './translation_files/translation_parsers/xliff1/xliff1_translation_parser'; import {Xliff1TranslationParser} from './translation_files/translation_parsers/xliff1_translation_parser';
import {Xliff2TranslationParser} from './translation_files/translation_parsers/xliff2/xliff2_translation_parser'; import {Xliff2TranslationParser} from './translation_files/translation_parsers/xliff2_translation_parser';
import {Translator} from './translator'; import {Translator} from './translator';
import {Diagnostics} from '../diagnostics'; import {Diagnostics} from '../diagnostics';

View File

@ -6,15 +6,28 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Element, Expansion, ExpansionCase, Node, Text, visitAll} from '@angular/compiler'; import {Element, Expansion, ExpansionCase, Node, Text, visitAll} from '@angular/compiler';
import {MessageRenderer} from '../../../message_renderers/message_renderer';
import {BaseVisitor} from '../base_visitor'; import {BaseVisitor} from '../base_visitor';
import {TranslationParseError} from '../translation_parse_error'; import {TranslationParseError} from '../translation_parsers/translation_parse_error';
import {getAttrOrThrow, getAttribute} from '../translation_utils'; import {getAttrOrThrow, getAttribute} from '../translation_parsers/translation_utils';
const INLINE_ELEMENTS = ['cp', 'sc', 'ec', 'mrk', 'sm', 'em']; import {MessageRenderer} from './message_renderer';
export class Xliff2MessageSerializer<T> extends BaseVisitor { interface MessageSerializerConfig {
constructor(private renderer: MessageRenderer<T>) { super(); } inlineElements: string[];
placeholder?: {elementName: string; nameAttribute: string; bodyAttribute?: string;};
placeholderContainer?: {elementName: string; startAttribute: string; endAttribute: string;};
}
/**
* This visitor will walk over a set of XML nodes, which represent an i18n message, and serialize
* them into a message object of type `T`.
* The type of the serialized message is controlled by the
*/
export class MessageSerializer<T> extends BaseVisitor {
constructor(private renderer: MessageRenderer<T>, private config: MessageSerializerConfig) {
super();
}
serialize(nodes: Node[]): T { serialize(nodes: Node[]): T {
this.renderer.startRender(); this.renderer.startRender();
@ -24,13 +37,18 @@ export class Xliff2MessageSerializer<T> extends BaseVisitor {
} }
visitElement(element: Element): void { visitElement(element: Element): void {
if (element.name === 'ph') { if (this.config.placeholder && element.name === this.config.placeholder.elementName) {
this.visitPlaceholder(getAttrOrThrow(element, 'equiv'), getAttribute(element, 'disp')); const name = getAttrOrThrow(element, this.config.placeholder.nameAttribute);
} else if (element.name === 'pc') { const body = this.config.placeholder.bodyAttribute &&
this.visitPlaceholderContainer( getAttribute(element, this.config.placeholder.bodyAttribute);
getAttrOrThrow(element, 'equivStart'), element.children, this.visitPlaceholder(name, body);
getAttrOrThrow(element, 'equivEnd')); } else if (
} else if (INLINE_ELEMENTS.indexOf(element.name) !== -1) { this.config.placeholderContainer &&
element.name === this.config.placeholderContainer.elementName) {
const start = getAttrOrThrow(element, this.config.placeholderContainer.startAttribute);
const end = getAttrOrThrow(element, this.config.placeholderContainer.endAttribute);
this.visitPlaceholderContainer(start, element.children, end);
} else if (this.config.inlineElements.indexOf(element.name) !== -1) {
visitAll(this, element.children); visitAll(this, element.children);
} else { } else {
throw new TranslationParseError(element.sourceSpan, `Invalid element found in message.`); throw new TranslationParseError(element.sourceSpan, `Invalid element found in message.`);
@ -58,11 +76,11 @@ export class Xliff2MessageSerializer<T> extends BaseVisitor {
const length = nodes.length; const length = nodes.length;
let index = 0; let index = 0;
while (index < length) { while (index < length) {
if (!isPlaceholderContainer(nodes[index])) { if (!this.isPlaceholderContainer(nodes[index])) {
const startOfContainedNodes = index; const startOfContainedNodes = index;
while (index < length - 1) { while (index < length - 1) {
index++; index++;
if (isPlaceholderContainer(nodes[index])) { if (this.isPlaceholderContainer(nodes[index])) {
break; break;
} }
} }
@ -89,8 +107,8 @@ export class Xliff2MessageSerializer<T> extends BaseVisitor {
this.visitContainedNodes(children); this.visitContainedNodes(children);
this.renderer.closePlaceholder(closeName); this.renderer.closePlaceholder(closeName);
} }
}
function isPlaceholderContainer(node: Node): boolean { private isPlaceholderContainer(node: Node): boolean {
return node instanceof Element && node.name === 'pc'; return node instanceof Element && node.name === this.config.placeholderContainer !.elementName;
}
} }

View File

@ -7,7 +7,7 @@
*/ */
import {ɵMessageId, ɵParsedTranslation, ɵparseTranslation} from '@angular/localize'; import {ɵMessageId, ɵParsedTranslation, ɵparseTranslation} from '@angular/localize';
import {extname} from 'path'; import {extname} from 'path';
import {ParsedTranslationBundle, TranslationParser} from '../translation_parser'; import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
/** /**
* A translation parser that can parse JSON that has the form: * A translation parser that can parse JSON that has the form:

View File

@ -1,56 +0,0 @@
/**
* @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, Expansion, ExpansionCase, Node, Text, visitAll} from '@angular/compiler';
import {MessageRenderer} from '../../../message_renderers/message_renderer';
import {BaseVisitor} from '../base_visitor';
import {TranslationParseError} from '../translation_parse_error';
import {getAttrOrThrow} from '../translation_utils';
const INLINE_ELEMENTS = ['g', 'bx', 'ex', 'bpt', 'ept', 'ph', 'it', 'mrk'];
export class Xliff1MessageSerializer<T> extends BaseVisitor {
constructor(private renderer: MessageRenderer<T>) { super(); }
serialize(nodes: Node[]): T {
this.renderer.startRender();
visitAll(this, nodes);
this.renderer.endRender();
return this.renderer.message;
}
visitElement(element: Element): void {
if (element.name === 'x') {
this.visitPlaceholder(getAttrOrThrow(element, 'id'), '');
} else if (INLINE_ELEMENTS.indexOf(element.name) !== -1) {
visitAll(this, element.children);
} else {
throw new TranslationParseError(element.sourceSpan, `Invalid element found in message.`);
}
}
visitText(text: Text): void { this.renderer.text(text.value); }
visitExpansion(expansion: Expansion): void {
this.renderer.startIcu();
this.renderer.text(`${expansion.switchValue}, ${expansion.type},`);
visitAll(this, expansion.cases);
this.renderer.endIcu();
}
visitExpansionCase(expansionCase: ExpansionCase): void {
this.renderer.text(` ${expansionCase.value} {`);
this.renderer.startContainer();
visitAll(this, expansionCase.expression);
this.renderer.closeContainer();
this.renderer.text(`}`);
}
visitPlaceholder(name: string, body: string|undefined): void {
this.renderer.placeholder(name, body);
}
}

View File

@ -8,12 +8,14 @@
import {Element, Node, XmlParser, visitAll} from '@angular/compiler'; import {Element, Node, XmlParser, visitAll} from '@angular/compiler';
import {ɵMessageId, ɵParsedTranslation} from '@angular/localize'; import {ɵMessageId, ɵParsedTranslation} from '@angular/localize';
import {extname} from 'path'; import {extname} from 'path';
import {TargetMessageRenderer} from '../../../message_renderers/target_message_renderer';
import {BaseVisitor} from '../base_visitor'; import {BaseVisitor} from '../base_visitor';
import {TranslationParseError} from '../translation_parse_error'; import {MessageSerializer} from '../message_serialization/message_serializer';
import {ParsedTranslationBundle, TranslationParser} from '../translation_parser'; import {TargetMessageRenderer} from '../message_serialization/target_message_renderer';
import {getAttrOrThrow, getAttribute, parseInnerRange} from '../translation_utils';
import {Xliff1MessageSerializer} from './xliff1_message_serializer'; import {TranslationParseError} from './translation_parse_error';
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
import {getAttrOrThrow, getAttribute, parseInnerRange} from './translation_utils';
const XLIFF_1_2_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:1.2"/; const XLIFF_1_2_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:1.2"/;
@ -90,7 +92,10 @@ class XliffTranslationVisitor extends BaseVisitor {
} }
function serializeTargetMessage(source: Element): ɵParsedTranslation { function serializeTargetMessage(source: Element): ɵParsedTranslation {
const serializer = new Xliff1MessageSerializer(new TargetMessageRenderer()); const serializer = new MessageSerializer(new TargetMessageRenderer(), {
inlineElements: ['g', 'bx', 'ex', 'bpt', 'ept', 'ph', 'it', 'mrk'],
placeholder: {elementName: 'x', nameAttribute: 'id'}
});
return serializer.serialize(parseInnerRange(source)); return serializer.serialize(parseInnerRange(source));
} }

View File

@ -8,12 +8,14 @@
import {Element, Node, XmlParser, visitAll} from '@angular/compiler'; import {Element, Node, XmlParser, visitAll} from '@angular/compiler';
import {ɵMessageId, ɵParsedTranslation} from '@angular/localize'; import {ɵMessageId, ɵParsedTranslation} from '@angular/localize';
import {extname} from 'path'; import {extname} from 'path';
import {TargetMessageRenderer} from '../../../message_renderers/target_message_renderer';
import {BaseVisitor} from '../base_visitor'; import {BaseVisitor} from '../base_visitor';
import {TranslationParseError} from '../translation_parse_error'; import {MessageSerializer} from '../message_serialization/message_serializer';
import {ParsedTranslationBundle, TranslationParser} from '../translation_parser'; import {TargetMessageRenderer} from '../message_serialization/target_message_renderer';
import {getAttrOrThrow, getAttribute, parseInnerRange} from '../translation_utils';
import {Xliff2MessageSerializer} from './xliff2_message_serializer'; import {TranslationParseError} from './translation_parse_error';
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
import {getAttrOrThrow, getAttribute, parseInnerRange} from './translation_utils';
const XLIFF_2_0_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:2.0"/; const XLIFF_2_0_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:2.0"/;
@ -105,7 +107,12 @@ function assertTranslationUnit(segment: Element, context: any) {
} }
function serializeTargetMessage(source: Element): ɵParsedTranslation { function serializeTargetMessage(source: Element): ɵParsedTranslation {
const serializer = new Xliff2MessageSerializer(new TargetMessageRenderer()); const serializer = new MessageSerializer(new TargetMessageRenderer(), {
inlineElements: ['cp', 'sc', 'ec', 'mrk', 'sm', 'em'],
placeholder: {elementName: 'ph', nameAttribute: 'equiv', bodyAttribute: 'disp'},
placeholderContainer:
{elementName: 'pc', startAttribute: 'equivStart', endAttribute: 'equivEnd'}
});
return serializer.serialize(parseInnerRange(source)); return serializer.serialize(parseInnerRange(source));
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ɵmakeTemplateObject} from '@angular/localize'; import {ɵmakeTemplateObject} from '@angular/localize';
import {SimpleJsonTranslationParser} from '../../../../../src/translate/translation_files/translation_parsers/simple_json/simple_json_translation_parser'; import {SimpleJsonTranslationParser} from '../../../../src/translate/translation_files/translation_parsers/simple_json_translation_parser';
describe('SimpleJsonTranslationParser', () => { describe('SimpleJsonTranslationParser', () => {
describe('canParse()', () => { describe('canParse()', () => {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ɵcomputeMsgId, ɵmakeParsedTranslation} from '@angular/localize'; import {ɵcomputeMsgId, ɵmakeParsedTranslation} from '@angular/localize';
import {Xliff1TranslationParser} from '../../../../../src/translate/translation_files/translation_parsers/xliff1/xliff1_translation_parser'; import {Xliff1TranslationParser} from '../../../../src/translate/translation_files/translation_parsers/xliff1_translation_parser';
describe('Xliff1TranslationParser', () => { describe('Xliff1TranslationParser', () => {
describe('canParse()', () => { describe('canParse()', () => {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ɵcomputeMsgId, ɵmakeParsedTranslation} from '@angular/localize'; import {ɵcomputeMsgId, ɵmakeParsedTranslation} from '@angular/localize';
import {Xliff2TranslationParser} from '../../../../../src/translate/translation_files/translation_parsers/xliff2/xliff2_translation_parser'; import {Xliff2TranslationParser} from '../../../../src/translate/translation_files/translation_parsers/xliff2_translation_parser';
describe('Xliff2TranslationParser', () => { describe('Xliff2TranslationParser', () => {
describe('canParse()', () => { describe('canParse()', () => {