diff --git a/packages/compiler/src/i18n/serializers/xml_helper.ts b/packages/compiler/src/i18n/serializers/xml_helper.ts
index 805592f7f5..27a8d10c5a 100644
--- a/packages/compiler/src/i18n/serializers/xml_helper.ts
+++ b/packages/compiler/src/i18n/serializers/xml_helper.ts
@@ -54,7 +54,7 @@ export class Declaration implements Node {
constructor(unescapedAttrs: {[k: string]: string}) {
Object.keys(unescapedAttrs).forEach((k: string) => {
- this.attrs[k] = _escapeXml(unescapedAttrs[k]);
+ this.attrs[k] = escapeXml(unescapedAttrs[k]);
});
}
@@ -74,7 +74,7 @@ export class Tag implements Node {
public name: string, unescapedAttrs: {[k: string]: string} = {},
public children: Node[] = []) {
Object.keys(unescapedAttrs).forEach((k: string) => {
- this.attrs[k] = _escapeXml(unescapedAttrs[k]);
+ this.attrs[k] = escapeXml(unescapedAttrs[k]);
});
}
@@ -83,7 +83,7 @@ export class Tag implements Node {
export class Text implements Node {
value: string;
- constructor(unescapedValue: string) { this.value = _escapeXml(unescapedValue); }
+ constructor(unescapedValue: string) { this.value = escapeXml(unescapedValue); }
visit(visitor: IVisitor): any { return visitor.visitText(this); }
}
@@ -100,7 +100,8 @@ const _ESCAPED_CHARS: [RegExp, string][] = [
[/>/g, '>'],
];
-function _escapeXml(text: string): string {
+// Escape `_ESCAPED_CHARS` characters in the given text with encoded entities
+export function escapeXml(text: string): string {
return _ESCAPED_CHARS.reduce(
(text: string, entry: [RegExp, string]) => text.replace(entry[0], entry[1]), text);
}
diff --git a/packages/compiler/src/i18n/translation_bundle.ts b/packages/compiler/src/i18n/translation_bundle.ts
index c1c5daf68c..242ff966d8 100644
--- a/packages/compiler/src/i18n/translation_bundle.ts
+++ b/packages/compiler/src/i18n/translation_bundle.ts
@@ -14,6 +14,7 @@ import {Console} from '../util';
import * as i18n from './i18n_ast';
import {I18nError} from './parse_util';
import {PlaceholderMapper, Serializer} from './serializers/serializer';
+import {escapeXml} from './serializers/xml_helper';
/**
@@ -88,7 +89,11 @@ class I18nToHtmlVisitor implements i18n.Visitor {
};
}
- visitText(text: i18n.Text, context?: any): string { return text.value; }
+ visitText(text: i18n.Text, context?: any): string {
+ // `convert()` uses an `HtmlParser` to return `html.Node`s
+ // we should then make sure that any special characters are escaped
+ return escapeXml(text.value);
+ }
visitContainer(container: i18n.Container, context?: any): any {
return container.children.map(n => n.visit(this)).join('');
diff --git a/packages/compiler/test/i18n/integration_common.ts b/packages/compiler/test/i18n/integration_common.ts
index 400365f9ad..11027cbf75 100644
--- a/packages/compiler/test/i18n/integration_common.ts
+++ b/packages/compiler/test/i18n/integration_common.ts
@@ -47,7 +47,8 @@ export function validateHtml(
expectHtml(el, '#i18n-3b')
.toBe(
'
avec des espaces réservés
');
- expectHtml(el, '#i18n-4').toBe('');
+ expectHtml(el, '#i18n-4')
+ .toBe('');
expectHtml(el, '#i18n-5').toBe('');
expectHtml(el, '#i18n-6').toBe('');
@@ -117,7 +118,7 @@ export const HTML = `
diff --git a/packages/compiler/test/i18n/integration_xliff2_spec.ts b/packages/compiler/test/i18n/integration_xliff2_spec.ts
index 657f8141f5..e7f82d09cc 100644
--- a/packages/compiler/test/i18n/integration_xliff2_spec.ts
+++ b/packages/compiler/test/i18n/integration_xliff2_spec.ts
@@ -95,6 +95,12 @@ const XLIFF2_TOMERGE = `
sur des balises non traductibles
+
+
+
+ <b>gras</b>
+
+
@@ -267,6 +273,14 @@ const XLIFF2_EXTRACTED = `
+
+
+ file.ts:14
+
+
+
+
+
file.ts:15
diff --git a/packages/compiler/test/i18n/integration_xliff_spec.ts b/packages/compiler/test/i18n/integration_xliff_spec.ts
index ba347cc1bb..5c8daf4984 100644
--- a/packages/compiler/test/i18n/integration_xliff_spec.ts
+++ b/packages/compiler/test/i18n/integration_xliff_spec.ts
@@ -85,6 +85,10 @@ const XLIFF_TOMERGE = `
sur des balises non traductibles
+
+
+ <b>gras</b>
+
sur des balises traductibles
@@ -215,6 +219,13 @@ const XLIFF_EXTRACTED = `
14
+
+
+
+ file.ts
+ 14
+
+
diff --git a/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts b/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts
index 33f9543da4..51b074700f 100644
--- a/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts
+++ b/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts
@@ -63,6 +63,7 @@ const XTB = `
avec des espaces réservés
<div>avec <div>des espaces réservés</div> imbriqués</div>
sur des balises non traductibles
+ <b>gras</b>
sur des balises traductibles
{VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {beaucoup}}
@@ -93,6 +94,7 @@ const XMB = `i18n attribu
<i>with placeholders</i>
<div>with <div>nested</div> placeholders</div>
on not translatable node
+ <b>bold</b>
on translatable node
{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>} }
diff --git a/packages/compiler/test/i18n/translation_bundle_spec.ts b/packages/compiler/test/i18n/translation_bundle_spec.ts
index 6da0e99456..cac903ecca 100644
--- a/packages/compiler/test/i18n/translation_bundle_spec.ts
+++ b/packages/compiler/test/i18n/translation_bundle_spec.ts
@@ -10,8 +10,10 @@ import {MissingTranslationStrategy} from '@angular/core';
import * as i18n from '../../src/i18n/i18n_ast';
import {TranslationBundle} from '../../src/i18n/translation_bundle';
+import * as html from '../../src/ml_parser/ast';
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../src/parse_util';
import {serializeNodes} from '../ml_parser/ast_serializer_spec';
+
import {_extractMessages} from './i18n_parser_spec';
{
@@ -22,13 +24,24 @@ import {_extractMessages} from './i18n_parser_spec';
const span = new ParseSourceSpan(startLocation, endLocation);
const srcNode = new i18n.Text('src', span);
- it('should translate a plain message', () => {
+ it('should translate a plain text', () => {
const msgMap = {foo: [new i18n.Text('bar', null !)]};
const tb = new TranslationBundle(msgMap, null, (_) => 'foo');
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
expect(serializeNodes(tb.get(msg))).toEqual(['bar']);
});
+ it('should translate html-like plain text', () => {
+ const msgMap = {foo: [new i18n.Text('bar
', null !)]};
+ const tb = new TranslationBundle(msgMap, null, (_) => 'foo');
+ const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
+ const nodes = tb.get(msg);
+ expect(nodes.length).toEqual(1);
+ const textNode: html.Text = nodes[0] as any;
+ expect(textNode instanceof html.Text).toEqual(true);
+ expect(textNode.value).toBe('bar
');
+ });
+
it('should translate a message with placeholder', () => {
const msgMap = {
foo: [