diff --git a/packages/compiler-cli/test/BUILD.bazel b/packages/compiler-cli/test/BUILD.bazel index f0e842f65b..d2364a8002 100644 --- a/packages/compiler-cli/test/BUILD.bazel +++ b/packages/compiler-cli/test/BUILD.bazel @@ -43,6 +43,7 @@ jasmine_node_test( ], deps = [ ":extract_i18n_lib", + "//packages/common:npm_package", "//packages/core", "//tools/testing:node", ], diff --git a/packages/compiler-cli/test/extract_i18n_spec.ts b/packages/compiler-cli/test/extract_i18n_spec.ts index 8e44da3e22..3a03392b11 100644 --- a/packages/compiler-cli/test/extract_i18n_spec.ts +++ b/packages/compiler-cli/test/extract_i18n_spec.ts @@ -46,6 +46,15 @@ const EXPECTED_XMB = ` src/basic.html:1src/comp2.ts:1src/basic.html:1translate me src/basic.html:3,4src/comp2.ts:3,4src/comp2.ts:2,3src/basic.html:3,4 Welcome + src/icu.html:1,3src/icu.html:5{VAR_PLURAL, plural, =1 {book} other {books} } + src/icu.html:4,6 + foo { count, plural, =1 {...} other {...}}{ count, plural, =1 {...} other {...}} + + src/placeholders.html:1Name: <b><b>{{ + name // i18n(ph="name") + }}{{ + name // i18n(ph="name") + }}</b></b> `; @@ -90,6 +99,41 @@ const EXPECTED_XLIFF = ` 3 + + {VAR_PLURAL, plural, =1 {book} other {books} } + + src/icu.html + 1 + + with ICU + + + + foo + + + src/icu.html + 4 + + with ICU and other things + + + {VAR_PLURAL, plural, =1 {book} other {books} } + + src/icu.html + 5 + + + + Name: + + src/placeholders.html + 1 + + with placeholders + @@ -122,6 +166,38 @@ const EXPECTED_XLIFF2 = ` Welcome + + + with ICU + src/icu.html:1,3 + src/icu.html:5 + + + {VAR_PLURAL, plural, =1 {book} other {books} } + + + + + with ICU and other things + src/icu.html:4,6 + + + + foo + + + + + + with placeholders + src/placeholders.html:1 + + + Name: + + `; @@ -132,7 +208,7 @@ describe('extract_i18n command line', () => { let write: (fileName: string, content: string) => void; let errorSpy: jasmine.Spy&((s: string) => void); - function writeConfig(tsconfig: string = '{"extends": "./tsconfig-base.json"}') { + function writeConfig(tsconfig = '{"extends": "./tsconfig-base.json"}') { write('tsconfig.json', tsconfig); } @@ -147,7 +223,7 @@ describe('extract_i18n command line', () => { basePath = makeTempDir(); write = (fileName: string, content: string) => { const dir = path.dirname(fileName); - if (dir != '.') { + if (dir !== '.') { const newDir = path.join(basePath, dir); if (!fs.existsSync(newDir)) fs.mkdirSync(newDir); } @@ -223,14 +299,54 @@ describe('extract_i18n command line', () => { }) export class BasicCmp3 {}`); + write('src/placeholders.html', `
Name: {{ + name // i18n(ph="name") + }}
`); + + write('src/placeholder_cmp.ts', ` + import {Component} from '@angular/core'; + + @Component({ + selector: 'placeholders', + templateUrl: './placeholders.html', + }) + export class PlaceholderCmp { name = 'whatever'; }`); + + write('src/icu.html', `
{ + count, plural, =1 {book} other {books} + }
+
+ foo { count, plural, =1 {book} other {books} } +
`); + + write('src/icu_cmp.ts', ` + import {Component} from '@angular/core'; + + @Component({ + selector: 'icu', + templateUrl: './icu.html', + }) + export class IcuCmp { count = 3; }`); + write('src/module.ts', ` import {NgModule} from '@angular/core'; + import {CommonModule} from '@angular/common'; import {BasicCmp1} from './comp1'; import {BasicCmp2, BasicCmp4} from './comp2'; import {BasicCmp3} from './comp3'; + import {PlaceholderCmp} from './placeholder_cmp'; + import {IcuCmp} from './icu_cmp'; @NgModule({ - declarations: [BasicCmp1, BasicCmp2, BasicCmp3, BasicCmp4] + declarations: [ + BasicCmp1, + BasicCmp2, + BasicCmp3, + BasicCmp4, + PlaceholderCmp, + IcuCmp, + ], + imports: [CommonModule], }) export class I18nModule {} `); diff --git a/packages/compiler/src/i18n/serializers/xmb.ts b/packages/compiler/src/i18n/serializers/xmb.ts index dde588f7aa..eeddddc869 100644 --- a/packages/compiler/src/i18n/serializers/xmb.ts +++ b/packages/compiler/src/i18n/serializers/xmb.ts @@ -15,7 +15,7 @@ import * as xml from './xml_helper'; const _MESSAGES_TAG = 'messagebundle'; const _MESSAGE_TAG = 'msg'; const _PLACEHOLDER_TAG = 'ph'; -const _EXEMPLE_TAG = 'ex'; +const _EXAMPLE_TAG = 'ex'; const _SOURCE_TAG = 'source'; const _DOCTYPE = ` @@ -115,30 +115,45 @@ class _Visitor implements i18n.Visitor { } visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): xml.Node[] { - const startEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`<${ph.tag}>`)]); - const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.startName}, [startEx]); + const startTagAsText = new xml.Text(`<${ph.tag}>`); + const startEx = new xml.Tag(_EXAMPLE_TAG, {}, [startTagAsText]); + // TC requires PH to have a non empty EX, and uses the text node to show the "original" value. + const startTagPh = + new xml.Tag(_PLACEHOLDER_TAG, {name: ph.startName}, [startEx, startTagAsText]); if (ph.isVoid) { // void tags have no children nor closing tags return [startTagPh]; } - const closeEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(``)]); - const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.closeName}, [closeEx]); + const closeTagAsText = new xml.Text(``); + const closeEx = new xml.Tag(_EXAMPLE_TAG, {}, [closeTagAsText]); + // TC requires PH to have a non empty EX, and uses the text node to show the "original" value. + const closeTagPh = + new xml.Tag(_PLACEHOLDER_TAG, {name: ph.closeName}, [closeEx, closeTagAsText]); return [startTagPh, ...this.serialize(ph.children), closeTagPh]; } visitPlaceholder(ph: i18n.Placeholder, context?: any): xml.Node[] { - const exTag = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`{{${ph.value}}}`)]); - return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name}, [exTag])]; + const interpolationAsText = new xml.Text(`{{${ph.value}}}`); + // Example tag needs to be not-empty for TC. + const exTag = new xml.Tag(_EXAMPLE_TAG, {}, [interpolationAsText]); + return [ + // TC requires PH to have a non empty EX, and uses the text node to show the "original" value. + new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name}, [exTag, interpolationAsText]) + ]; } visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] { - const exTag = new xml.Tag(_EXEMPLE_TAG, {}, [ - new xml.Text( - `{${ph.value.expression}, ${ph.value.type}, ${Object.keys(ph.value.cases).map((value: string) => value + ' {...}').join(' ')}}`) - ]); - return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name}, [exTag])]; + const icuExpression = ph.value.expression; + const icuType = ph.value.type; + const icuCases = Object.keys(ph.value.cases).map((value: string) => value + ' {...}').join(' '); + const icuAsText = new xml.Text(`{${icuExpression}, ${icuType}, ${icuCases}}`); + const exTag = new xml.Tag(_EXAMPLE_TAG, {}, [icuAsText]); + return [ + // TC requires PH to have a non empty EX, and uses the text node to show the "original" value. + new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name}, [exTag, icuAsText]) + ]; } serialize(nodes: i18n.Node[]): xml.Node[] { @@ -161,7 +176,7 @@ class ExampleVisitor implements xml.IVisitor { if (tag.name === _PLACEHOLDER_TAG) { if (!tag.children || tag.children.length == 0) { const exText = new xml.Text(tag.attrs['name'] || '...'); - tag.children = [new xml.Tag(_EXEMPLE_TAG, {}, [exText])]; + tag.children = [new xml.Tag(_EXAMPLE_TAG, {}, [exText])]; } } else if (tag.children) { tag.children.forEach(node => node.visit(this)); diff --git a/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts b/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts index 51b074700f..59853bb233 100644 --- a/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts +++ b/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts @@ -91,32 +91,32 @@ const XTB = ` const XMB = `file.ts:3i18n attribute on tags file.ts:5nested file.ts:7nested - file.ts:9file.ts:10<i>with placeholders</i> - file.ts:11<div>with <div>nested</div> placeholders</div> + file.ts:9file.ts:10<i><i>with placeholders</i></i> + file.ts:11<div><div>with <div><div>nested</div></div> placeholders</div></div> file.ts:14on not translatable node file.ts:14<b>bold</b> file.ts:15on translatable node - file.ts:20file.ts:37{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>} } + file.ts:20file.ts:37{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<b><b>many</b></b>} } file.ts:22,24 - {sex, select, male {...} female {...} other {...}} + {sex, select, male {...} female {...} other {...}}{sex, select, male {...} female {...} other {...}} file.ts:23{VAR_SELECT, select, male {m} female {f} other {other} } file.ts:25,27 - {sexB, select, male {...} female {...}} + {sexB, select, male {...} female {...}}{sexB, select, male {...} female {...}} file.ts:26{VAR_SELECT, select, male {m} female {f} } - file.ts:29{{ "count = " + count }} - file.ts:30sex = {{ sex }} - file.ts:31{{ "custom name" //i18n(ph="CUSTOM_NAME") }} + file.ts:29{{ "count = " + count }}{{ "count = " + count }} + file.ts:30sex = {{ sex }}{{ sex }} + file.ts:31{{ "custom name" //i18n(ph="CUSTOM_NAME") }}{{ "custom name" //i18n(ph="CUSTOM_NAME") }} file.ts:36file.ts:54in a translatable section file.ts:34,38 - <h1>Markers in html comments</h1> - <div></div> - <div>{count, plural, =0 {...} =1 {...} =2 {...} other {...}}</div> + <h1><h1>Markers in html comments</h1></h1> + <div><div></div></div> + <div><div>{count, plural, =0 {...} =1 {...} =2 {...} other {...}}{count, plural, =0 {...} =1 {...} =2 {...} other {...}}</div></div> - file.ts:40it <b>should</b> work + file.ts:40it <b><b>should</b></b> work file.ts:42with an explicit ID - file.ts:43{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>} } - file.ts:46,52{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found {{response.getItemsList().length}} results} } - file.ts:54foo<a>bar</a> - file.ts:56{{ 'test' //i18n(ph="map name") }}`; + file.ts:43{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<b><b>many</b></b>} } + file.ts:46,52{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found {{response.getItemsList().length}}{{response.getItemsList().length}} results} } + file.ts:54foo<a><a>bar</a></a> + file.ts:56{{ 'test' //i18n(ph="map name") }}{{ 'test' //i18n(ph="map name") }}`; diff --git a/packages/compiler/test/i18n/serializers/xmb_spec.ts b/packages/compiler/test/i18n/serializers/xmb_spec.ts index 45d0bc6450..68a8f47db1 100644 --- a/packages/compiler/test/i18n/serializers/xmb_spec.ts +++ b/packages/compiler/test/i18n/serializers/xmb_spec.ts @@ -48,14 +48,14 @@ lines

`; ]> - file.ts:3translatable element <b>with placeholders</b> {{ interpolation}} - file.ts:4{VAR_PLURAL, plural, =0 {<p>test</p>} } + file.ts:3translatable element <b><b>with placeholders</b></b> {{ interpolation}}{{ interpolation}} + file.ts:4{VAR_PLURAL, plural, =0 {<p><p>test</p></p>} } file.ts:5foo file.ts:6foo file.ts:7foo - file.ts:8{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<p>deeply nested</p>} } } } - file.ts:9Test: { count, plural, =0 {...} =other {...}} - file.ts:9{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<p>deeply nested</p>} } } =other {a lot} } + file.ts:8{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<p><p>deeply nested</p></p>} } } } + file.ts:9Test: { count, plural, =0 {...} =other {...}}{ count, plural, =0 {...} =other {...}} + file.ts:9{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<p><p>deeply nested</p></p>} } } =other {a lot} } file.ts:10,11multi lines diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index e686f5de62..acd8a803c6 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -1737,7 +1737,7 @@ "name": "_ESCAPED_CHARS" }, { - "name": "_EXEMPLE_TAG" + "name": "_EXAMPLE_TAG" }, { "name": "_EmittedLine" @@ -3839,4 +3839,4 @@ { "name": "wtfLeave" } -] \ No newline at end of file +]