refactor(compiler): add original_code to goog.getMsg() options (#45606)

This links back each placeholder in a message to the original Angular template span which defines its expression. This is useful for understanding where each placeholder comes from in the context of the full message.

PR Close #45606
This commit is contained in:
Doug Parker 2022-04-11 16:28:35 -07:00 committed by Dylan Hunn
parent 8c83f12daa
commit 1fe255c76f
68 changed files with 363 additions and 132 deletions

View file

@ -0,0 +1,47 @@
/****************************************************************************************************
* PARTIAL FILE: repeated_placeholder.js
****************************************************************************************************/
import { Component, NgModule } from '@angular/core';
import * as i0 from "@angular/core";
export class MyComponent {
}
MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, selector: "my-component", ngImport: i0, template: `
<div i18n>Hello, {{ placeholder }}! You are a very good {{ placeholder }}.</div>
<div i18n>Hello, {{ placeholder // i18n(ph = "ph") }}! Hello again {{ placeholder // i18n(ph = "ph") }}.</div>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
type: Component,
args: [{
selector: 'my-component',
template: `
<div i18n>Hello, {{ placeholder }}! You are a very good {{ placeholder }}.</div>
<div i18n>Hello, {{ placeholder // i18n(ph = "ph") }}! Hello again {{ placeholder // i18n(ph = "ph") }}.</div>
`,
}]
}] });
export class MyModule {
}
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyComponent] });
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, decorators: [{
type: NgModule,
args: [{ declarations: [MyComponent] }]
}] });
/****************************************************************************************************
* PARTIAL FILE: repeated_placeholder.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyComponent {
placeholder: any;
static ɵfac: i0.ɵɵFactoryDeclaration<MyComponent, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never>;
}
export declare class MyModule {
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyComponent], never, never>;
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
}

View file

@ -0,0 +1,19 @@
{
"$schema": "../test_case_schema.json",
"cases": [
{
"description": "should allow repeating the same placeholder",
"inputFiles": [
"repeated_placeholder.ts"
],
"expectations": [
{
"extraChecks": [
"verifyPlaceholdersIntegrity",
"verifyUniqueConsts"
]
}
]
}
]
}

View file

@ -1,6 +1,6 @@
consts: function() {
__i18nMsg__('Element title', [], {meaning: 'm', desc: 'd'})
__i18nMsg__('Some content', [], {})
__i18nMsg__('Element title', [], {}, {meaning: 'm', desc: 'd'})
__i18nMsg__('Some content', [], {}, {})
return [
["title", $i18n_0$],
$i18n_1$
@ -12,4 +12,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵi18n(1, 1);
$r3$.ɵɵelementEnd();
}
}
}

View file

@ -1,11 +1,11 @@
decls: 5,
vars: 8,
consts: function() {
__i18nMsg__('static text', [], {})
__i18nMsg__('intro {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {meaning: 'm', desc: 'd'})
__i18nMsg__('{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {meaning: 'm1', desc: 'd1'})
__i18nMsg__('{$interpolation} and {$interpolation_1} and again {$interpolation_2}', [['interpolation', String.raw`\uFFFD0\uFFFD`],['interpolation_1', String.raw`\uFFFD1\uFFFD`],['interpolation_2', String.raw`\uFFFD2\uFFFD`]], {meaning: 'm2', desc: 'd2'})
__i18nMsg__('{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
__i18nMsg__('static text', [], {}, {})
__i18nMsg__('intro {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ valueA | uppercase }}'}}, {meaning: 'm', desc: 'd'})
__i18nMsg__('{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ valueB }}'}}, {meaning: 'm1', desc: 'd1'})
__i18nMsg__('{$interpolation} and {$interpolation_1} and again {$interpolation_2}', [['interpolation', String.raw`\uFFFD0\uFFFD`],['interpolation_1', String.raw`\uFFFD1\uFFFD`],['interpolation_2', String.raw`\uFFFD2\uFFFD`]], {original_code: {'interpolation': '{{ valueA }}', 'interpolation_1': '{{ valueB }}', 'interpolation_2': '{{ valueA + valueB }}'}}, {meaning: 'm2', desc: 'd2'})
__i18nMsg__('{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ valueC }}'}}, {})
return [
["id", "dynamic-1", "aria-roledescription", $i18n_0$, __AttributeMarker.I18n__,
"title", "aria-label"],

View file

@ -1,7 +1,7 @@
decls: 2,
vars: 1,
consts: function() {
__i18nMsg__('{$interpolation} title', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
__i18nMsg__('{$interpolation} title', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{valueA.getRawValue()?.getTitle()}}'}}, {})
return [
[__AttributeMarker.I18n__, "title"],
["title", $i18n_0$]

View file

@ -1,5 +1,5 @@
consts: function() {
__i18nMsg__('intro {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {meaning: 'm', desc: 'd'})
__i18nMsg__('intro {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{% valueA | uppercase %}'}}, {meaning: 'm', desc: 'd'})
return [
[__AttributeMarker.I18n__, "title"],
["title", $i18n_0$]

View file

@ -16,7 +16,7 @@ function MyComponent_div_0_Template(rf, ctx) {
decls: 1,
vars: 1,
consts: function() {
__i18nMsg__('different scope {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {meaning: 'm', desc: 'd'})
__i18nMsg__('different scope {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ outer | uppercase }}'}}, {meaning: 'm', desc: 'd'})
return [
[__AttributeMarker.Template__, "ngFor", "ngForOf"],
[__AttributeMarker.I18n__, "title"],

View file

@ -1,11 +1,11 @@
consts:
function() {
__i18nMsg__('Content A', [], {id: 'idA', meaning: 'meaningA', desc: 'descA'})
__i18nMsg__('Title B', [], {id: 'idB', meaning: 'meaningB', desc: 'descB'})
__i18nMsg__('Title C', [], {meaning: 'meaningC'})
__i18nMsg__('Title D', [], {meaning: 'meaningD', desc: 'descD'})
__i18nMsg__('Title E', [], {id: 'idE', desc: 'meaningE'})
__i18nMsg__('Title F', [], {id: 'idF'})
__i18nMsg__('Content A', [], {}, {id: 'idA', meaning: 'meaningA', desc: 'descA'})
__i18nMsg__('Title B', [], {}, {id: 'idB', meaning: 'meaningB', desc: 'descB'})
__i18nMsg__('Title C', [], {}, {meaning: 'meaningC'})
__i18nMsg__('Title D', [], {}, {meaning: 'meaningD', desc: 'descD'})
__i18nMsg__('Title E', [], {}, {id: 'idE', desc: 'meaningE'})
__i18nMsg__('Title F', [], {}, {id: 'idF'})
// NOTE: Keeping this block as a raw string, since it checks escaping of special chars.
let $i18n_23$;
@ -64,4 +64,4 @@ consts:
$r3$.ɵɵi18n(15, 7);
$r3$.ɵɵelementEnd();
}
}
}

View file

@ -1,5 +1,5 @@
consts: function () {
__i18nMsg__('Hello', [], {})
__i18nMsg__('Hello', [], {}, {})
return [
["title", $i18n_0$]
];

View file

@ -1,5 +1,5 @@
consts: function() {
__i18nMsg__('Hello {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
__i18nMsg__('Hello {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ name }}'}}, {})
return [
[__AttributeMarker.Bindings__, "title"],
["title", $i18n_0$]

View file

@ -11,7 +11,7 @@ function MyComponent_0_Template(rf, ctx) {
}
consts: function() {
__i18nMsg__('Hello {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
__i18nMsg__('Hello {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ name }}'}}, {})
return [
[__AttributeMarker.Template__, "ngIf"],
[__AttributeMarker.Bindings__, "title"],

View file

@ -10,7 +10,7 @@ function MyComponent_0_Template(rf, ctx) {
}
consts: function() {
__i18nMsg__('Hello', [], {})
__i18nMsg__('Hello', [], {}, {})
return [
// NOTE: AttributeMarker.Template = 4
[4, "ngIf"],

View file

@ -1,5 +1,5 @@
consts: function() {
__i18nMsg__('introduction', [], {meaning: 'm', desc: 'd'})
__i18nMsg__('introduction', [], {}, {meaning: 'm', desc: 'd'})
return [
["id", "static", "title", $i18n_0$]
];

View file

@ -5,7 +5,7 @@ function MyComponent_div_0_Template(rf, ctx) {
}
consts: function() {
__i18nMsg__('introduction', [], {meaning: 'm', desc: 'd'})
__i18nMsg__('introduction', [], {}, {meaning: 'm', desc: 'd'})
return [
["id", "static", "title", $i18n_0$, __AttributeMarker.Template__, "ngIf"],
["id", "static", "title", $i18n_0$]

View file

@ -33,8 +33,8 @@ function $MyComponent_div_3_Template$(rf, ctx) {
decls: 4,
vars: 3,
consts: function() {
__i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]]) __i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
__i18nIcuMsg__('{VAR_SELECT, select, 0 {no emails} 1 {one email} other {{INTERPOLATION} emails}}', [ ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`]])
__i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {}) __i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
__i18nIcuMsg__('{VAR_SELECT, select, 0 {no emails} 1 {one email} other {{INTERPOLATION} emails}}', [ ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`]], {})
return [
$i18n_0$,
["title", "icu only", __AttributeMarker.Template__, "ngIf"],

View file

@ -1,5 +1,5 @@
consts: function() {
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {{INTERPOLATION}}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`]])
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {{INTERPOLATION}}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`]], {})
return [
$i18n_0$
];

View file

@ -15,9 +15,9 @@ function MyComponent_span_2_Template(rf, ctx) {
decls: 3,
vars: 2,
consts: function() {
__i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0:1\uFFFD`]])
__i18nMsg__(' {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}', [['startTagSpan', String.raw`\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD`], ['closeTagSpan', String.raw`\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD`], ['icu', '$i18n_0$', '7670372064920373295'], ['icu_1', '$i18n_1$', '4590395557003415341']], {})
__i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0:1\uFFFD`]], {})
__i18nMsg__(' {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}', [['startTagSpan', String.raw`\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD`], ['closeTagSpan', String.raw`\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD`], ['icu', '$i18n_0$', '7670372064920373295'], ['icu_1', '$i18n_1$', '4590395557003415341']], {original_code: {'startTagSpan': '<span *ngIf="ageVisible">', 'closeTagSpan': '</span>', 'icu': '{gender, select, male {male} female {female} other {other}}', 'icu_1': '{age, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}'}}, {})
return [
$i18n_2$,
[__AttributeMarker.Template__, "ngIf"]

View file

@ -1,7 +1,7 @@
decls: 2,
vars: 2,
consts: function() {
__i18nIcuMsg__('{VAR_SELECT, select, male {male of age: {INTERPOLATION}} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`],])
__i18nIcuMsg__('{VAR_SELECT, select, male {male of age: {INTERPOLATION}} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`]], {})
return [
$i18n_0$
];

View file

@ -1,8 +1,8 @@
decls: 5,
vars: 1,
consts: function() {
__i18nIcuMsg__('{VAR_SELECT, select, male {male - {START_BOLD_TEXT}male{CLOSE_BOLD_TEXT}} female {female {START_BOLD_TEXT}female{CLOSE_BOLD_TEXT}} other {{START_TAG_DIV}{START_ITALIC_TEXT}other{CLOSE_ITALIC_TEXT}{CLOSE_TAG_DIV}}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['START_BOLD_TEXT', '<b>'], ['CLOSE_BOLD_TEXT', '</b>'], ['START_ITALIC_TEXT', '<i>'], ['CLOSE_ITALIC_TEXT', '</i>'], ['START_TAG_DIV', '<div class=\\"other\\">'], ['CLOSE_TAG_DIV', '</div>'],])
__i18nMsg__(' {$icu} {$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}', [['startBoldText', String.raw`\uFFFD#2\uFFFD`], ['closeBoldText', String.raw`\uFFFD/#2\uFFFD`], ['startTagDiv', String.raw`\uFFFD#3\uFFFD`], ['startItalicText', String.raw`\uFFFD#4\uFFFD`], ['closeItalicText', String.raw`\uFFFD/#4\uFFFD`], ['closeTagDiv', String.raw`\uFFFD/#3\uFFFD`], ['icu', '$I18N_1$', '4731057199984078679']], {})
__i18nIcuMsg__('{VAR_SELECT, select, male {male - {START_BOLD_TEXT}male{CLOSE_BOLD_TEXT}} female {female {START_BOLD_TEXT}female{CLOSE_BOLD_TEXT}} other {{START_TAG_DIV}{START_ITALIC_TEXT}other{CLOSE_ITALIC_TEXT}{CLOSE_TAG_DIV}}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['START_BOLD_TEXT', '<b>'], ['CLOSE_BOLD_TEXT', '</b>'], ['START_ITALIC_TEXT', '<i>'], ['CLOSE_ITALIC_TEXT', '</i>'], ['START_TAG_DIV', '<div class="other">'], ['CLOSE_TAG_DIV', '</div>']], {})
__i18nMsg__(' {$icu} {$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}', [['startBoldText', String.raw`\uFFFD#2\uFFFD`], ['closeBoldText', String.raw`\uFFFD/#2\uFFFD`], ['startTagDiv', String.raw`\uFFFD#3\uFFFD`], ['startItalicText', String.raw`\uFFFD#4\uFFFD`], ['closeItalicText', String.raw`\uFFFD/#4\uFFFD`], ['closeTagDiv', String.raw`\uFFFD/#3\uFFFD`], ['icu', '$I18N_1$', '4731057199984078679']], {original_code: {'startBoldText': '<b>', 'closeBoldText': '</b>', 'startTagDiv': '<div class="other">', 'startItalicText': '<i>', 'closeItalicText': '</i>', 'closeTagDiv': '</div>', 'icu': '{gender, select, male {male - <b>male</b>} female {female <b>female</b>} other {<div class="other"><i>other</i></div>}}'}}, {})
return [
$i18n_1$,
[__AttributeMarker.Classes__, "other"]

View file

@ -1,7 +1,7 @@
decls: 1,
vars: 1,
consts: function() {
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
return [
$i18n_0$
];

View file

@ -15,9 +15,9 @@ function MyComponent_span_2_Template(rf, ctx) {
decls: 3,
vars: 4,
consts: function() {
__i18nIcuMsg__('{VAR_SELECT, select, male {male {INTERPOLATION}} female {female {INTERPOLATION_1}} other {other}}',[ ['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`], ['INTERPOLATION_1', String.raw`\uFFFD2\uFFFD`]])
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {INTERPOLATION}}}', [['VAR_SELECT', String.raw`\uFFFD0:1\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1:1\uFFFD`]])
__i18nMsg__(' {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}', [['startTagSpan', String.raw`\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD`], ['closeTagSpan', String.raw`\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD`], ['icu', '$i18n_0$', '567200399523107034'], ['icu_1', '$i18n_1$', '5762277079421427850']], {})
__i18nIcuMsg__('{VAR_SELECT, select, male {male {INTERPOLATION}} female {female {INTERPOLATION_1}} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1\uFFFD`], ['INTERPOLATION_1', String.raw`\uFFFD2\uFFFD`]], {})
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {INTERPOLATION}}}', [['VAR_SELECT', String.raw`\uFFFD0:1\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD1:1\uFFFD`]], {})
__i18nMsg__(' {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}', [['startTagSpan', String.raw`\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD`], ['closeTagSpan', String.raw`\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD`], ['icu', '$i18n_0$', '567200399523107034'], ['icu_1', '$i18n_1$', '5762277079421427850']], {original_code: {'startTagSpan': '<span *ngIf="ageVisible">', 'closeTagSpan': '</span>', 'icu': '{gender, select, male {male {{ weight }}} female {female {{ height }}} other {other}}', 'icu_1': '{age, select, 10 {ten} 20 {twenty} 30 {thirty} other {other: {{ otherAge }}}}'}}, {})
return [
$i18n_2$,
[__AttributeMarker.Template__, "ngIf"]

View file

@ -1,3 +1,3 @@
__i18nIcuMsg__('{VAR_SELECT , select , 1 {one} other {more than one}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
__i18nIcuMsg__('{VAR_PLURAL , plural , =1 {one} other {more than one}}', [['VAR_PLURAL', String.raw`\uFFFD1\uFFFD`]])
__i18nIcuMsg__('{VAR_SELECT , select , 1 {one} other {more than one}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
__i18nIcuMsg__('{VAR_PLURAL , plural , =1 {one} other {more than one}}', [['VAR_PLURAL', String.raw`\uFFFD1\uFFFD`]], {})

View file

@ -1 +1 @@
__i18nMsgWithPostprocess__('{VAR_SELECT, select, 1 {one} other {more than one}}', [], {meaning: 'meaningA', desc: 'descA', id: 'idA'}, [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
__i18nMsgWithPostprocess__('{VAR_SELECT, select, 1 {one} other {more than one}}', [], {}, {meaning: 'meaningA', desc: 'descA', id: 'idA'}, [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])

View file

@ -1,9 +1,9 @@
decls: 2,
vars: 2,
consts: function() {
__i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD1\uFFFD`]])
__i18nMsg__(' {$icu} {$icu_1} ', [['icu', '$i18n_0$', '7670372064920373295'], ['icu_1', '$i18n_1$', '4590395557003415341']], {})
__i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD1\uFFFD`]], {})
__i18nMsg__(' {$icu} {$icu_1} ', [['icu', '$i18n_0$', '7670372064920373295'], ['icu_1', '$i18n_1$', '4590395557003415341']], {original_code: {'icu': '{gender, select, male {male} female {female} other {other}}', 'icu_1': '{age, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}'}}, {})
return [
$i18n_2$
];

View file

@ -1,7 +1,7 @@
decls: 2,
vars: 4,
consts: function() {
__i18nIcuMsg__('{VAR_SELECT, select, male {male {PH_A}} female {female {PH_B}} other {other {PH_WITH_SPACES}}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['PH_A', String.raw`\uFFFD1\uFFFD`], ['PH_B', String.raw`\uFFFD2\uFFFD`], ['PH_WITH_SPACES', String.raw`\uFFFD3\uFFFD`]])
__i18nIcuMsg__('{VAR_SELECT, select, male {male {PH_A}} female {female {PH_B}} other {other {PH_WITH_SPACES}}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['PH_A', String.raw`\uFFFD1\uFFFD`], ['PH_B', String.raw`\uFFFD2\uFFFD`], ['PH_WITH_SPACES', String.raw`\uFFFD3\uFFFD`]], {})
return [
$i18n_0$
];

View file

@ -1,7 +1,7 @@
decls: 2,
vars: 3,
consts: function() {
__i18nIcuMsg__('{VAR_PLURAL, plural, =0 {zero} =2 {{INTERPOLATION} {VAR_SELECT, select, cat {cats} dog {dogs} other {animals}} !} other {other - {INTERPOLATION}}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['VAR_PLURAL', String.raw`\uFFFD1\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD2\uFFFD`]])
__i18nIcuMsg__('{VAR_PLURAL, plural, =0 {zero} =2 {{INTERPOLATION} {VAR_SELECT, select, cat {cats} dog {dogs} other {animals}} !} other {other - {INTERPOLATION}}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['VAR_PLURAL', String.raw`\uFFFD1\uFFFD`], ['INTERPOLATION', String.raw`\uFFFD2\uFFFD`]], {})
return [
$i18n_0$
];

View file

@ -1,8 +1,8 @@
decls: 2,
vars: 2,
consts: function() {
__i18nIcuMsg__('{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['VAR_SELECT_1', String.raw`\uFFFD1\uFFFD`]])
__i18nMsg__(' {$icu} ', [['icu', '$i18n_0$', '2960440207608193372']], {})
__i18nIcuMsg__('{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`], ['VAR_SELECT_1', String.raw`\uFFFD1\uFFFD`]], {})
__i18nMsg__(' {$icu} ', [['icu', '$i18n_0$', '2960440207608193372']], {original_code: {'icu': '{gender, select,\\n male {male of age: {age, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}}\\n female {female}\\n other {other}\\n }'}}, {})
return [
$i18n_1$
];

View file

@ -71,6 +71,13 @@ consts: function() {
"closeTagDiv": "[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]",
"startTagDiv_1": "\uFFFD*3:1\uFFFD\uFFFD#1:1\uFFFD",
"icu": "\uFFFDI18N_EXP_ICU\uFFFD"
}, {
original_code: {
"startTagDiv": "<div>",
"closeTagDiv": "</div>",
"startTagDiv_1": "<div *ngIf=\"visible\">",
"icu": "{gender, select, male {male} female {female} other {other}}"
}
});
$I18N_0$ = $MSG_APP_SPEC_TS_0$;
}

View file

@ -1,8 +1,8 @@
decls: 2,
vars: 1,
consts: function() {
__i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}',[['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
__i18nMsg__('before {$icu} after', [['icu', '$i18n_0$', '7670372064920373295']], {})
__i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
__i18nMsg__('before {$icu} after', [['icu', '$i18n_0$', '7670372064920373295']], {original_code: {'icu': '{gender, select, male {male} female {female} other {other}}'}}, {})
return [$i18n_1$];
},
template: function MyComponent_Template(rf, ctx) {

View file

@ -10,6 +10,13 @@ consts: function() {
"startTagXhtmlSpan": "\uFFFD#4\uFFFD",
"closeTagXhtmlSpan": "\uFFFD/#4\uFFFD",
"closeTagXhtmlDiv": "\uFFFD/#3\uFFFD"
}, {
original_code: {
"startTagXhtmlDiv": "<xhtml:div xmlns=\"http://www.w3.org/1999/xhtml\">",
"startTagXhtmlSpan": "<span>",
"closeTagXhtmlSpan": "</span>",
"closeTagXhtmlDiv": "</xhtml:div>"
}
});
$I18N_0$ = $MSG_EXTERNAL_7128002169381370313$$APP_SPEC_TS_1$;
}

View file

@ -7,6 +7,11 @@ consts: function() {
const $MSG_EXTERNAL_7428861019045796010$$APP_SPEC_TS_1$ = goog.getMsg(" Count: {$startTagXhtmlSpan}5{$closeTagXhtmlSpan}", {
"startTagXhtmlSpan": "\uFFFD#4\uFFFD",
"closeTagXhtmlSpan": "\uFFFD/#4\uFFFD"
}, {
original_code: {
"startTagXhtmlSpan": "<span>",
"closeTagXhtmlSpan": "</span>"
}
});
$I18N_0$ = $MSG_EXTERNAL_7428861019045796010$$APP_SPEC_TS_1$;
}

View file

@ -4,7 +4,7 @@ if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) {
/**
* @suppress {msgDescriptions}
*/
const $MSG_APP_SPEC_TS_1$ = goog.getMsg("`{$interpolation}`", { "interpolation": "\uFFFD0\uFFFD" });
const $MSG_APP_SPEC_TS_1$ = goog.getMsg("`{$interpolation}`", { "interpolation": "\uFFFD0\uFFFD" }, { original_code: { "interpolation": "{{ count }}" } });
$I18N_0$ = $MSG_APP_SPEC_TS_1$;
}
else {

View file

@ -1,9 +1,9 @@
decls: 7,
vars: 5,
consts: function() {
__i18nMsg__('My i18n block #{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
__i18nMsg__('My i18n block #{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
__i18nMsg__('My i18n block #{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
__i18nMsg__('My i18n block #{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ one }}'}}, {})
__i18nMsg__('My i18n block #{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ two | uppercase }}'}}, {})
__i18nMsg__('My i18n block #{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ three + four + five }}'}}, {})
return [
$i18n_0$,
$i18n_1$,

View file

@ -1 +1 @@
__i18nMsg__('Some text', [], {})
__i18nMsg__('Some text', [], {}, {})

View file

@ -17,7 +17,7 @@ function MyComponent_div_0_Template(rf, ctx) {
decls: 1,
vars: 1,
consts: function() {
__i18nMsg__('Some other content {$startTagSpan}{$interpolation}{$closeTagSpan}', [['startTagSpan', String.raw`\uFFFD#2\uFFFD`], ['interpolation', String.raw`\uFFFD0\uFFFD`], ['closeTagSpan', String.raw`\uFFFD/#2\uFFFD`]], {})
__i18nMsg__('Some other content {$startTagSpan}{$interpolation}{$closeTagSpan}', [['startTagSpan', String.raw`\uFFFD#2\uFFFD`], ['interpolation', String.raw`\uFFFD0\uFFFD`], ['closeTagSpan', String.raw`\uFFFD/#2\uFFFD`]], {original_code: {'startTagSpan': '<span>', 'interpolation': '{{ valueA }}', 'closeTagSpan': '</span>'}}, {})
return [
[__AttributeMarker.Template__, "ngIf"],
$i18n_0$
@ -30,4 +30,4 @@ template: function MyComponent_Template(rf, ctx) {
if (rf & 2) {
$r3$.ɵɵproperty("ngIf", ctx.visible);
}
}
}

View file

@ -1,5 +1,5 @@
consts: function() {
__i18nMsg__('Hello', [], {})
__i18nMsg__('Hello', [], {}, {})
return [
[__AttributeMarker.Bindings__, "click"],
$i18n_0$
@ -12,4 +12,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵi18n(1, 1);
$r3$.ɵɵelementEnd();
}
}
}

View file

@ -1,5 +1,5 @@
consts: function() {
__i18nMsg__(' {$interpolation} {$interpolation_1} {$interpolation_2} ', [['interpolation', String.raw`\uFFFD0\uFFFD`], ['interpolation_1', String.raw`\uFFFD1\uFFFD`], ['interpolation_2', String.raw`\uFFFD2\uFFFD`]], {})
__i18nMsg__(' {$interpolation} {$interpolation_1} {$interpolation_2} ', [['interpolation', String.raw`\uFFFD0\uFFFD`], ['interpolation_1', String.raw`\uFFFD1\uFFFD`], ['interpolation_2', String.raw`\uFFFD2\uFFFD`]], {original_code: {'interpolation': '{{ valueA | async }}', 'interpolation_1': '{{ valueA?.a?.b }}', 'interpolation_2': '{{ valueA.getRawValue()?.getTitle() }}'}}, {})
return [
$i18n_0$
];

View file

@ -1,5 +1,5 @@
consts: function() {
__i18nMsg__('{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
__i18nMsg__('{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{% valueA %}'}}, {})
return [
$i18n_0$
];
@ -15,4 +15,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵi18nExp(ctx.valueA);
$r3$.ɵɵi18nApply(1);
}
}
}

View file

@ -10,6 +10,11 @@ consts: function() {
const $MSG_EXTERNAL_7597881511811528589$$APP_SPEC_TS_0$ = goog.getMsg(" Named interpolation: {$phA} Named interpolation with spaces: {$phB} ", {
"phA": "\uFFFD0\uFFFD",
"phB": "\uFFFD1\uFFFD"
}, {
original_code: {
"phA": "{{ valueA // i18n(ph=\"PH_A\") }}",
"phB": "{{ valueB // i18n(ph=\"PH B\") }}"
}
});
$I18N_0$ = $MSG_EXTERNAL_7597881511811528589$$APP_SPEC_TS_0$;
}

View file

@ -1,8 +1,8 @@
decls: 9,
vars: 5,
consts: function() {
__i18nMsg__(' My i18n block #{$interpolation} {$startTagSpan}Plain text in nested element{$closeTagSpan}', [['interpolation', String.raw`\uFFFD0\uFFFD`], ['startTagSpan', String.raw`\uFFFD#2\uFFFD`], ['closeTagSpan', String.raw`\uFFFD/#2\uFFFD`]], {})
__i18nMsgWithPostprocess__(' My i18n block #{$interpolation} {$startTagDiv}{$startTagDiv}{$startTagSpan} More bindings in more nested element: {$interpolation_1} {$closeTagSpan}{$closeTagDiv}{$closeTagDiv}', [['interpolation', String.raw`\uFFFD0\uFFFD`], ['startTagDiv', String.raw`[\uFFFD#6\uFFFD|\uFFFD#7\uFFFD]`], ['startTagSpan', String.raw`\uFFFD#8\uFFFD`], ['interpolation_1', String.raw`\uFFFD1\uFFFD`], ['closeTagSpan', String.raw`\uFFFD/#8\uFFFD`], ['closeTagDiv', String.raw`[\uFFFD/#7\uFFFD|\uFFFD/#6\uFFFD]`]], {}, [])
__i18nMsg__(' My i18n block #{$interpolation} {$startTagSpan}Plain text in nested element{$closeTagSpan}', [['interpolation', String.raw`\uFFFD0\uFFFD`], ['startTagSpan', String.raw`\uFFFD#2\uFFFD`], ['closeTagSpan', String.raw`\uFFFD/#2\uFFFD`]], {original_code: {'interpolation': '{{ one }}', 'startTagSpan': '<span>', 'closeTagSpan': '</span>'}}, {})
__i18nMsgWithPostprocess__(' My i18n block #{$interpolation} {$startTagDiv}{$startTagDiv}{$startTagSpan} More bindings in more nested element: {$interpolation_1} {$closeTagSpan}{$closeTagDiv}{$closeTagDiv}', [['interpolation', String.raw`\uFFFD0\uFFFD`], ['startTagDiv', String.raw`[\uFFFD#6\uFFFD|\uFFFD#7\uFFFD]`], ['startTagSpan', String.raw`\uFFFD#8\uFFFD`], ['interpolation_1', String.raw`\uFFFD1\uFFFD`], ['closeTagSpan', String.raw`\uFFFD/#8\uFFFD`], ['closeTagDiv', String.raw`[\uFFFD/#7\uFFFD|\uFFFD/#6\uFFFD]`]], {original_code: {'interpolation': '{{ two | uppercase }}', 'startTagDiv': '<div>', 'startTagSpan': '<span>', 'interpolation_1': '{{ nestedInBlockTwo }}', 'closeTagSpan': '</span>', 'closeTagDiv': '</div>'}}, {}, [])
return [
$i18n_0$,
$i18n_1$
@ -32,4 +32,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(5, 3, ctx.two))(ctx.nestedInBlockTwo);
$r3$.ɵɵi18nApply(4);
}
}
}

View file

@ -1,10 +1,10 @@
decls: 9,
vars: 7,
consts: function() {
__i18nMsg__('Span title {$interpolation} and {$interpolation_1}', [['interpolation', String.raw`\uFFFD0\uFFFD`], ['interpolation_1', String.raw`\uFFFD1\uFFFD`]], {})
__i18nMsg__(' My i18n block #1 with value: {$interpolation} {$startTagSpan} Plain text in nested element (block #1) {$closeTagSpan}',[['interpolation', String.raw`\uFFFD0\uFFFD`], ['startTagSpan', String.raw`\uFFFD#2\uFFFD`], ['closeTagSpan', String.raw`\uFFFD/#2\uFFFD`]], {})
__i18nMsg__('Span title {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
__i18nMsg__(' My i18n block #2 with value {$interpolation} {$startTagSpan} Plain text in nested element (block #2) {$closeTagSpan}',[ ['interpolation', String.raw`\uFFFD0\uFFFD`], ['startTagSpan', String.raw`\uFFFD#7\uFFFD`], ['closeTagSpan', String.raw`\uFFFD/#7\uFFFD`]], {})
__i18nMsg__('Span title {$interpolation} and {$interpolation_1}', [['interpolation', String.raw`\uFFFD0\uFFFD`], ['interpolation_1', String.raw`\uFFFD1\uFFFD`]], {original_code: {'interpolation': '{{ valueB }}', 'interpolation_1': '{{ valueC }}'}}, {})
__i18nMsg__(' My i18n block #1 with value: {$interpolation} {$startTagSpan} Plain text in nested element (block #1) {$closeTagSpan}',[['interpolation', String.raw`\uFFFD0\uFFFD`], ['startTagSpan', String.raw`\uFFFD#2\uFFFD`], ['closeTagSpan', String.raw`\uFFFD/#2\uFFFD`]], {original_code: {'interpolation': '{{ valueA }}', 'startTagSpan': '<span i18n-title title="Span title {{ valueB }} and {{ valueC }}">', 'closeTagSpan': '</span>'}}, {})
__i18nMsg__('Span title {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ valueE }}'}}, {})
__i18nMsg__(' My i18n block #2 with value {$interpolation} {$startTagSpan} Plain text in nested element (block #2) {$closeTagSpan}',[ ['interpolation', String.raw`\uFFFD0\uFFFD`], ['startTagSpan', String.raw`\uFFFD#7\uFFFD`], ['closeTagSpan', String.raw`\uFFFD/#7\uFFFD`]], {original_code: {'interpolation': '{{ valueD | uppercase }}', 'startTagSpan': '<span i18n-title title="Span title {{ valueE }}">', 'closeTagSpan': '</span>'}}, {})
return [
$i18n_0$,
[__AttributeMarker.I18n__, "title"],
@ -45,4 +45,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(6, 5, ctx.valueD));
$r3$.ɵɵi18nApply(5);
}
}
}

View file

@ -18,7 +18,7 @@ function MyComponent_div_2_Template(rf, ctx) {
decls: 3,
vars: 1,
consts: function() {
__i18nMsg__(' Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$closeTagDiv}', [['interpolation', String.raw`\uFFFD0\uFFFD`], ['startTagDiv', String.raw`\uFFFD#3\uFFFD`], ['interpolation_1', String.raw`\uFFFD1\uFFFD`], ['closeTagDiv', String.raw`\uFFFD/#3\uFFFD`]], {})
__i18nMsg__(' Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$closeTagDiv}', [['interpolation', String.raw`\uFFFD0\uFFFD`], ['startTagDiv', String.raw`\uFFFD#3\uFFFD`], ['interpolation_1', String.raw`\uFFFD1\uFFFD`], ['closeTagDiv', String.raw`\uFFFD/#3\uFFFD`]], {original_code: {'interpolation': '{{ valueA }}', 'startTagDiv': '<div>', 'interpolation_1': '{{ valueB | uppercase }}', 'closeTagDiv': '</div>'}}, {})
return [
[__AttributeMarker.Template__, "ngIf"],
$i18n_0$

View file

@ -51,7 +51,7 @@ function MyComponent_div_3_Template(rf, ctx) {
decls: 4,
vars: 2,
consts: function() {
__i18nMsgWithPostprocess__(' Some content {$startTagDiv_2} Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$startTagDiv_1} Content inside sub-template {$interpolation_2} {$startTagDiv} Bottom level element {$interpolation_3} {$closeTagDiv}{$closeTagDiv}{$closeTagDiv}{$closeTagDiv}{$startTagDiv_3} Some other content {$interpolation_4} {$startTagDiv} More nested levels with bindings {$interpolation_5} {$closeTagDiv}{$closeTagDiv}', [['startTagDiv_2', String.raw`\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD`], [ 'closeTagDiv', String.raw`[\uFFFD/#2:2\uFFFD|\uFFFD/#1:2\uFFFD\uFFFD/*4:2\uFFFD|\uFFFD/#2:1\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD|\uFFFD/#2:3\uFFFD|\uFFFD/#1:3\uFFFD\uFFFD/*3:3\uFFFD]`], ['startTagDiv_3', String.raw`\uFFFD*3:3\uFFFD\uFFFD#1:3\uFFFD`], ['interpolation', String.raw`\uFFFD0:1\uFFFD`], ['startTagDiv', String.raw`[\uFFFD#2:1\uFFFD|\uFFFD#2:2\uFFFD|\uFFFD#2:3\uFFFD]`], ['interpolation_1', String.raw`\uFFFD1:1\uFFFD`], ['startTagDiv_1', String.raw`\uFFFD*4:2\uFFFD\uFFFD#1:2\uFFFD`], ['interpolation_2', String.raw`\uFFFD0:2\uFFFD`], ['interpolation_3', String.raw`\uFFFD1:2\uFFFD`], ['interpolation_4', String.raw`\uFFFD0:3\uFFFD`], ['interpolation_5', String.raw`\uFFFD1:3\uFFFD`]], {}, [])
__i18nMsgWithPostprocess__(' Some content {$startTagDiv_2} Some other content {$interpolation} {$startTagDiv} More nested levels with bindings {$interpolation_1} {$startTagDiv_1} Content inside sub-template {$interpolation_2} {$startTagDiv} Bottom level element {$interpolation_3} {$closeTagDiv}{$closeTagDiv}{$closeTagDiv}{$closeTagDiv}{$startTagDiv_3} Some other content {$interpolation_4} {$startTagDiv} More nested levels with bindings {$interpolation_5} {$closeTagDiv}{$closeTagDiv}', [['startTagDiv_2', String.raw`\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD`], [ 'closeTagDiv', String.raw`[\uFFFD/#2:2\uFFFD|\uFFFD/#1:2\uFFFD\uFFFD/*4:2\uFFFD|\uFFFD/#2:1\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD|\uFFFD/#2:3\uFFFD|\uFFFD/#1:3\uFFFD\uFFFD/*3:3\uFFFD]`], ['startTagDiv_3', String.raw`\uFFFD*3:3\uFFFD\uFFFD#1:3\uFFFD`], ['interpolation', String.raw`\uFFFD0:1\uFFFD`], ['startTagDiv', String.raw`[\uFFFD#2:1\uFFFD|\uFFFD#2:2\uFFFD|\uFFFD#2:3\uFFFD]`], ['interpolation_1', String.raw`\uFFFD1:1\uFFFD`], ['startTagDiv_1', String.raw`\uFFFD*4:2\uFFFD\uFFFD#1:2\uFFFD`], ['interpolation_2', String.raw`\uFFFD0:2\uFFFD`], ['interpolation_3', String.raw`\uFFFD1:2\uFFFD`], ['interpolation_4', String.raw`\uFFFD0:3\uFFFD`], ['interpolation_5', String.raw`\uFFFD1:3\uFFFD`]], {original_code: {'startTagDiv_2': '<div *ngIf="visible">', 'closeTagDiv': '</div>', 'startTagDiv_3': '<div *ngIf="!visible">', 'interpolation': '{{ valueA }}', 'startTagDiv': '<div>', 'interpolation_1': '{{ valueB | uppercase }}', 'startTagDiv_1': '<div *ngIf="exists">', 'interpolation_2': '{{ valueC }}', 'interpolation_3': '{{ valueD }}', 'interpolation_4': '{{ valueE + valueF }}', 'interpolation_5': '{{ valueG | uppercase }}'}}, {}, [])
return [
$i18n_0$,
[__AttributeMarker.Template__, "ngIf"]
@ -72,4 +72,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵadvance(1);
$r3$.ɵɵproperty("ngIf", !ctx.visible);
}
}
}

View file

@ -1,7 +1,7 @@
consts: function() {
__i18nMsg__('My i18n block #1', [], {})
__i18nMsg__('My i18n block #2', [], {})
__i18nMsg__('My i18n block #3', [], {})
__i18nMsg__('My i18n block #1', [], {}, {})
__i18nMsg__('My i18n block #2', [], {}, {})
__i18nMsg__('My i18n block #3', [], {}, {})
return [
$i18n_0$,
$i18n_1$,
@ -26,4 +26,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵi18n(9, 2);
$r3$.ɵɵelementEnd();
}
}
}

View file

@ -20,7 +20,7 @@ function MyComponent_img_2_Template(rf, ctx) {
decls: 3,
vars: 2,
consts: function() {
__i18nMsg__('App logo #{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
__i18nMsg__('App logo #{$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ id }}'}}, {})
return [
["src", "logo.png"],
["src", "logo.png", __AttributeMarker.Template__, "ngIf"],
@ -42,4 +42,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵadvance(1);
$r3$.ɵɵproperty("ngIf", ctx.visible);
}
}
}

View file

@ -12,8 +12,8 @@ function MyComponent_ng_template_0_Template(rf, ctx) {
decls: 3,
vars: 1,
consts: function() {
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
__i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
__i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
return [
$i18n_0$,
$i18n_1$

View file

@ -14,7 +14,7 @@ function MyComponent_ng_template_2_Template(rf, ctx) {
decls: 5,
vars: 3,
consts: function() {
__i18nMsg__('{$startTagNgTemplate}Template content: {$interpolation}{$closeTagNgTemplate}{$startTagNgContainer}Container content: {$interpolation_1}{$closeTagNgContainer}', [['startTagNgTemplate', String.raw`\uFFFD*2:1\uFFFD`], ['closeTagNgTemplate', String.raw`\uFFFD/*2:1\uFFFD`], ['startTagNgContainer', String.raw`\uFFFD#3\uFFFD`], ['interpolation_1', String.raw`\uFFFD0\uFFFD`], ['closeTagNgContainer', String.raw`\uFFFD/#3\uFFFD`], ['interpolation', String.raw`\uFFFD0:1\uFFFD`]], {})
__i18nMsg__('{$startTagNgTemplate}Template content: {$interpolation}{$closeTagNgTemplate}{$startTagNgContainer}Container content: {$interpolation_1}{$closeTagNgContainer}', [['startTagNgTemplate', String.raw`\uFFFD*2:1\uFFFD`], ['closeTagNgTemplate', String.raw`\uFFFD/*2:1\uFFFD`], ['startTagNgContainer', String.raw`\uFFFD#3\uFFFD`], ['interpolation_1', String.raw`\uFFFD0\uFFFD`], ['closeTagNgContainer', String.raw`\uFFFD/#3\uFFFD`], ['interpolation', String.raw`\uFFFD0:1\uFFFD`]], {original_code: {'startTagNgTemplate': '<ng-template>', 'closeTagNgTemplate': '</ng-template>', 'startTagNgContainer': '<ng-container>', 'interpolation_1': '{{ valueB | uppercase }}', 'closeTagNgContainer': '</ng-container>', 'interpolation': '{{ valueA | uppercase }}'}}, {})
return [
$i18n_0$
];

View file

@ -1,4 +1,4 @@
// NOTE: TODO(FW-635): currently we generate unique consts for each i18n block even though it might contain the same content. This should be optimized by translation statements caching, that can be implemented in the future.
__i18nMsg__('Test', [], {})
__i18nMsg__('Test', [], {})
__i18nMsg__('Test', [], {}, {})
__i18nMsg__('Test', [], {}, {})

View file

@ -12,8 +12,8 @@ function MyComponent_ng_template_2_Template(rf, ctx) {
decls: 3,
vars: 1,
consts: function() {
__i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
__i18nIcuMsg__('{VAR_SELECT, select, male {male} female {female} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
return [
$i18n_0$,
$i18n_1$

View file

@ -40,7 +40,7 @@ function MyComponent_ng_template_2_Template(rf, ctx) {
decls: 3,
vars: 0,
consts: function() {
__i18nMsgWithPostprocess__('{$startTagNgTemplate} Template A: {$interpolation} {$startTagNgTemplate} Template B: {$interpolation_1} {$startTagNgTemplate} Template C: {$interpolation_2} {$closeTagNgTemplate}{$closeTagNgTemplate}{$closeTagNgTemplate}', [['startTagNgTemplate', String.raw`[\uFFFD*2:1\uFFFD|\uFFFD*2:2\uFFFD|\uFFFD*1:3\uFFFD]`], ['closeTagNgTemplate', String.raw`[\uFFFD/*1:3\uFFFD|\uFFFD/*2:2\uFFFD|\uFFFD/*2:1\uFFFD]`], ['interpolation', String.raw`\uFFFD0:1\uFFFD`], ['interpolation_1', String.raw`\uFFFD0:2\uFFFD`], ['interpolation_2', String.raw`\uFFFD0:3\uFFFD`]], {}, [])
__i18nMsgWithPostprocess__('{$startTagNgTemplate} Template A: {$interpolation} {$startTagNgTemplate} Template B: {$interpolation_1} {$startTagNgTemplate} Template C: {$interpolation_2} {$closeTagNgTemplate}{$closeTagNgTemplate}{$closeTagNgTemplate}', [['startTagNgTemplate', String.raw`[\uFFFD*2:1\uFFFD|\uFFFD*2:2\uFFFD|\uFFFD*1:3\uFFFD]`], ['closeTagNgTemplate', String.raw`[\uFFFD/*1:3\uFFFD|\uFFFD/*2:2\uFFFD|\uFFFD/*2:1\uFFFD]`], ['interpolation', String.raw`\uFFFD0:1\uFFFD`], ['interpolation_1', String.raw`\uFFFD0:2\uFFFD`], ['interpolation_2', String.raw`\uFFFD0:3\uFFFD`]], {original_code: {'startTagNgTemplate': '<ng-template>', 'closeTagNgTemplate': '</ng-template>', 'interpolation': '{{ valueA | uppercase }}', 'interpolation_1': '{{ valueB }}', 'interpolation_2': '{{ valueC }}'}}, {}, [])
return [
$i18n_0$
];

View file

@ -1,7 +1,7 @@
decls: 4,
vars: 0,
consts: function() {
__i18nMsg__(' Hello {$startTagNgContainer}there {$startTagStrong}!{$closeTagStrong}{$closeTagNgContainer}', [['startTagNgContainer', String.raw`\uFFFD#2\uFFFD`], ['startTagStrong', String.raw`\uFFFD#3\uFFFD`], ['closeTagStrong', String.raw`\uFFFD/#3\uFFFD`], ['closeTagNgContainer', String.raw`\uFFFD/#2\uFFFD`]], {})
__i18nMsg__(' Hello {$startTagNgContainer}there {$startTagStrong}!{$closeTagStrong}{$closeTagNgContainer}', [['startTagNgContainer', String.raw`\uFFFD#2\uFFFD`], ['startTagStrong', String.raw`\uFFFD#3\uFFFD`], ['closeTagStrong', String.raw`\uFFFD/#3\uFFFD`], ['closeTagNgContainer', String.raw`\uFFFD/#2\uFFFD`]], {original_code: {'startTagNgContainer': '<ng-container>', 'startTagStrong': '<strong>', 'closeTagStrong': '</strong>', 'closeTagNgContainer': '</ng-container>'}}, {})
return [
$i18n_0$
];

View file

@ -1,7 +1,7 @@
decls: 3,
vars: 0,
consts: function() {
__i18nMsg__(' Hello {$startTagNgContainer}there{$closeTagNgContainer}', [['startTagNgContainer', String.raw`\uFFFD#2\uFFFD`], ['closeTagNgContainer', String.raw`\uFFFD/#2\uFFFD`]], {})
__i18nMsg__(' Hello {$startTagNgContainer}there{$closeTagNgContainer}', [['startTagNgContainer', String.raw`\uFFFD#2\uFFFD`], ['closeTagNgContainer', String.raw`\uFFFD/#2\uFFFD`]], {original_code: {'startTagNgContainer': '<ng-container>', 'closeTagNgContainer': '</ng-container>'}}, {})
return [
$i18n_0$
];

View file

@ -7,8 +7,8 @@ function MyComponent_ng_template_3_Template(rf, ctx) {
}
consts: function() {
__i18nMsg__('{$tagImg} is my logo #1 ', [['tagImg', String.raw`\uFFFD#2\uFFFD\uFFFD/#2\uFFFD`]], {})
__i18nMsg__('{$tagImg} is my logo #2 ', [['tagImg', String.raw`\uFFFD#1\uFFFD\uFFFD/#1\uFFFD`]], {})
__i18nMsg__('{$tagImg} is my logo #1 ', [['tagImg', String.raw`\uFFFD#2\uFFFD\uFFFD/#2\uFFFD`]], {original_code: {tagImg: '<img src="logo.png" title="Logo" />'}}, {})
__i18nMsg__('{$tagImg} is my logo #2 ', [['tagImg', String.raw`\uFFFD#1\uFFFD\uFFFD/#1\uFFFD`]], {original_code: {tagImg: '<img src="logo.png" title="Logo" />'}}, {})
return [
$i18n_0$,
["src", "logo.png", "title", "Logo"],

View file

@ -1,7 +1,7 @@
decls: 3,
vars: 3,
consts: function() {
__i18nMsg__('Some content: {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
__i18nMsg__('Some content: {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ valueA | uppercase }}'}}, {})
return [
$i18n_0$
];

View file

@ -12,7 +12,7 @@ function MyComponent_ng_template_0_Template(rf, ctx) {
}
decls: 1, vars: 0, consts: function() {
__i18nMsg__('Some content: {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {})
__i18nMsg__('Some content: {$interpolation}', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{ valueA | uppercase }}'}}, {})
return [$i18n_0$];
},
template: function MyComponent_Template(rf, ctx) {

View file

@ -21,8 +21,8 @@ function MyComponent_ng_container_1_Template(rf, ctx) {
decls: 2,
vars: 2,
consts: function() {
__i18nMsg__('Content A', [], {})
__i18nMsg__('Content B', [], {})
__i18nMsg__('Content A', [], {}, {})
__i18nMsg__('Content B', [], {}, {})
return [
[__AttributeMarker.Template__, "ngIf"],
$i18n_0$,

View file

@ -0,0 +1,3 @@
consts: function () {
__i18nMsgWithPostprocess__('Hello, {$interpolation}! You are a very good {$interpolation}.', [['interpolation', String.raw`[\uFFFD0\uFFFD|\uFFFD1\uFFFD]`]], {original_code: {'interpolation': '{{ placeholder }}'}}, {}, [])
__i18nMsgWithPostprocess__('Hello, {$ph}! Hello again {$ph}.', [['ph', String.raw`[\uFFFD0\uFFFD|\uFFFD1\uFFFD]`]], {original_code: {'ph': '{{ placeholder // i18n(ph = "ph") }}'}}, {}, [])

View file

@ -0,0 +1,16 @@
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: `
<div i18n>Hello, {{ placeholder }}! You are a very good {{ placeholder }}.</div>
<div i18n>Hello, {{ placeholder // i18n(ph = "ph") }}! Hello again {{ placeholder // i18n(ph = "ph") }}.</div>
`,
})
export class MyComponent {
placeholder: any;
}
@NgModule({declarations: [MyComponent]})
export class MyModule {
}

View file

@ -1,7 +1,7 @@
decls: 2,
vars: 1,
consts: function() {
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]])
__i18nIcuMsg__('{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}', [['VAR_SELECT', String.raw`\uFFFD0\uFFFD`]], {})
return [
$i18n_0$
];
@ -17,4 +17,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵi18nExp(ctx.age);
$r3$.ɵɵi18nApply(1);
}
}
}

View file

@ -5,8 +5,8 @@ function MyComponent_ng_template_0_Template(rf, ctx) {
}
consts: function() {
__i18nMsg__('My i18n block #2', [], {})
__i18nMsg__('My i18n block #1', [], {})
__i18nMsg__('My i18n block #2', [], {}, {})
__i18nMsg__('My i18n block #1', [], {}, {})
return [
$i18n_0$,
$i18n_1$
@ -19,4 +19,4 @@ template: function MyComponent_Template(rf, ctx) {
$r3$.ɵɵi18n(2, 0);
$r3$.ɵɵelementContainerEnd();
}
}
}

View file

@ -1,8 +1,8 @@
decls: 4,
vars: 0,
consts: function() {
__i18nMsg__('Text #1', [] , {})
__i18nMsg__('Text #2', [] , {})
__i18nMsg__('Text #1', [], {}, {})
__i18nMsg__('Text #2', [], {}, {})
return [
[__AttributeMarker.Classes__, "myClass"],
$i18n_0$,

View file

@ -1,5 +1,5 @@
consts: function() {
__i18nMsg__('My i18n block #1', [] , {})
__i18nMsg__('My i18n block #1', [], {}, {})
return [
$i18n_0$
];

View file

@ -8,6 +8,11 @@ consts: function() {
const $MSG_EXTERNAL_963542717423364282$$APP_SPEC_TS_0$ = goog.getMsg("\n Some text\n {$startTagSpan}Text inside span{$closeTagSpan}\n ", {
"startTagSpan": "\uFFFD#3\uFFFD",
"closeTagSpan": "\uFFFD/#3\uFFFD"
}, {
original_code: {
"startTagSpan": "<span>",
"closeTagSpan": "</span>"
}
});
$I18N_0$ = $MSG_EXTERNAL_963542717423364282$$APP_SPEC_TS_0$;
}

View file

@ -7,26 +7,32 @@
*/
import {AttributeMarker, SelectorFlags} from '@angular/compiler/src/core';
import {QueryFlags} from '@angular/compiler/src/render3/view/compiler';
import {i18nIcuMsg, i18nMsg, i18nMsgWithPostprocess, Placeholder, resetMessageIndex} from './i18n_helpers';
import {i18nIcuMsg, i18nMsg, i18nMsgWithPostprocess, Options, Placeholder, resetMessageIndex} from './i18n_helpers';
const EXPECTED_FILE_MACROS: [RegExp, (...args: string[]) => string][] = [
[
// E.g. `__i18nMsg__('message string', [ ['placeholder', 'pair'] ], { meta: 'properties'})`
macroFn(/__i18nMsg__/, stringParam(), arrayParam(), objectParam()),
(_match, message, placeholders, meta) =>
i18nMsg(message, parsePlaceholders(placeholders), parseMetaProperties(meta)),
// E.g. `__i18nMsg__('message string', [ ['placeholder', 'pair']
// ], {original_code: {'placeholder': '{{ foo }}'}}, {meta: 'properties'})`
macroFn(/__i18nMsg__/, stringParam(), arrayParam(), objectParam(), objectParam()),
(_match, message, placeholders, options, meta) => i18nMsg(
message, parsePlaceholders(placeholders), parseOptions(options), parseMetaProperties(meta)),
],
[
// E.g. `__i18nMsgWithPostprocess__('message', [ ['placeholder', 'pair'] ], { meta: 'props'})`
macroFn(/__i18nMsgWithPostprocess__/, stringParam(), arrayParam(), objectParam(), arrayParam()),
(_match, message, placeholders, meta, postProcessPlaceholders) => i18nMsgWithPostprocess(
message, parsePlaceholders(placeholders), parseMetaProperties(meta),
parsePlaceholders(postProcessPlaceholders)),
macroFn(
/__i18nMsgWithPostprocess__/, stringParam(), arrayParam(), objectParam(), objectParam(),
arrayParam()),
(_match, message, placeholders, options, meta, postProcessPlaceholders) =>
i18nMsgWithPostprocess(
message, parsePlaceholders(placeholders), parseOptions(options),
parseMetaProperties(meta), parsePlaceholders(postProcessPlaceholders)),
],
[
// E.g. `__i18nIcuMsg__('message string', [ ['placeholder', 'pair'] ])`
macroFn(/__i18nIcuMsg__/, stringParam(), arrayParam()),
(_match, message, placeholders) => i18nIcuMsg(message, parsePlaceholders(placeholders)),
macroFn(/__i18nIcuMsg__/, stringParam(), arrayParam(), objectParam()),
(_match, message, placeholders, options) =>
i18nIcuMsg(message, parsePlaceholders(placeholders), parseOptions(options)),
],
[
// E.g. `__AttributeMarker.Bindings__`
@ -68,6 +74,38 @@ function parsePlaceholders(str: string): Placeholder[] {
return placeholders;
}
function parseOptions(str: string): Options {
const inputObj = eval(`(${str})`) as unknown;
if (typeof inputObj !== 'object') {
throw new Error(`Expected an object of properties but got:\n\n${str}.`);
}
const obj = inputObj as Record<string, unknown>;
// Verify the object does not have any unexpected properties, as this is likely a sign that it was
// authored incorrectly.
const unexpectedKeys = Object.keys(obj).filter((key) => key !== 'original_code');
if (unexpectedKeys.length > 0) {
throw new Error(`Expected an i18n options object with \`original_code\`, but got ${
unexpectedKeys.join(', ')}`);
}
// Validate `original_code`.
const original = obj?.original_code;
if (typeof original !== 'undefined' && typeof original !== 'object') {
throw new Error(
`Expected an i18n options object with \`original_code\`, as a nested object, but got ${
JSON.stringify(obj, null, 4)}`);
}
for (const [key, value] of Object.entries(original ?? {})) {
if (typeof value !== 'string') {
throw new Error(`Expected an object whose values are strings, but property ${key} has type ${
typeof value}, when parsing:\n\n${str}`);
}
}
return obj;
}
function parseMetaProperties(str: string): Record<string, string> {
const obj = eval(`(${str})`);
if (typeof obj !== 'object') {
@ -137,7 +175,14 @@ function arrayParam() {
return /(\[.*?\])/;
}
function objectParam() {
return /(\{[^}]*\})/;
// Matches a JavaScript object literal with up to 6 levels of indentation. Regular expressions
// cannot use recursion so it is impossible to match an arbitrarily deep object. While it looks
// complicated it is really just the same pattern nested inside itself n times:
// (?:\{[^{}]*RECURSE_HERE\}[^{}]*)
//
// Each nested level uses (?:) for a non-matching group so the whole expression is the only match
// and avoids generating multiple macro arguments for each nested level.
return /(\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}[^{}]*)*\}[^{}]*)*\}[^{}]*)*\}[^{}]*)*\})/;
}
function macroFn(fnName: RegExp, ...args: RegExp[]): RegExp {

View file

@ -19,15 +19,18 @@ export function resetMessageIndex(): void {
/**
* Generate a string that represents expected i18n block content for a simple message.
*/
export function i18nMsg(message: string, placeholders: Placeholder[], meta: Meta): string {
export function i18nMsg(
message: string, placeholders: Placeholder[], options: Options, meta: Meta): string {
const varName = `$I18N_${msgIndex++}$`;
const closurePlaceholders = i18nPlaceholdersToString(placeholders);
const closureOptions = i18nOptionsToString(options);
const locMessageWithPlaceholders = i18nMsgInsertLocalizePlaceholders(message, placeholders);
return `
let ${varName};
if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) {
${i18nMsgClosureMeta(meta)}
const $MSG_EXTERNAL_${msgIndex}$ = goog.getMsg("${message}"${closurePlaceholders});
const $MSG_EXTERNAL_${msgIndex}$ = goog.getMsg("${message}"${closurePlaceholders}${
closureOptions});
${varName} = $MSG_EXTERNAL_${msgIndex}$;
}
else {
@ -35,17 +38,22 @@ export function i18nMsg(message: string, placeholders: Placeholder[], meta: Meta
}`;
}
/** Describes options bag passed to `goog.getMsg()`. */
export interface Options {
original_code?: Record<string /* placeholderName */, string /* original */>;
}
/**
* Generate a string that represents expected i18n block content for a message that requires
* post-processing.
*/
export function i18nMsgWithPostprocess(
message: string, placeholders: Placeholder[], meta: Meta,
message: string, placeholders: Placeholder[], options: Options, meta: Meta,
postprocessPlaceholders: Placeholder[]): string {
const varName = `$I18N_${msgIndex}$`;
const ppPlaceholders = i18nPlaceholdersToString(postprocessPlaceholders);
return String.raw`
${i18nMsg(message, placeholders, meta)}
${i18nMsg(message, placeholders, options, meta)}
${varName} = $r3$.ɵɵi18nPostprocess($${varName}$${ppPlaceholders});
`;
}
@ -53,8 +61,8 @@ export function i18nMsgWithPostprocess(
/**
* Generates a string that represents expected i18n block content for an ICU.
*/
export function i18nIcuMsg(message: string, placeholders: Placeholder[]): string {
return i18nMsgWithPostprocess(message, [], {}, placeholders);
export function i18nIcuMsg(message: string, placeholders: Placeholder[], options: Options): string {
return i18nMsgWithPostprocess(message, [], options, {}, placeholders);
}
/**
@ -86,6 +94,13 @@ function i18nPlaceholdersToString(placeholders: Placeholder[]): string {
return `, { ${result.join(',')} }`;
}
/** Convert an object of `goog.getMsg()` options to the expected string. */
function i18nOptionsToString({original_code: originals = {}}: Options = {}): string {
if (Object.keys(originals).length === 0) return '';
const result = Object.entries(originals).map(([key, value]) => `"${key}": ${quotedValue(value)}`);
return `, { original_code: {\n${result.join(',\n')}\n} }`;
}
/**
* Transform a message in a Closure format to a $localize version.
*/
@ -135,5 +150,5 @@ function i18nMsgLocalizeMeta(meta?: Meta): string {
* placeholder value. Such special cases should not be wrapped in quotes.
*/
function quotedValue(value: string): string {
return value.startsWith('$') ? value : `"${value}"`;
return value.startsWith('$') ? value : `"${value.replace(/"/g, '\\"')}"`;
}

View file

@ -11,18 +11,77 @@ import * as o from '../../../output/output_ast';
import {serializeIcuNode} from './icu_serializer';
import {i18nMetaToJSDoc} from './meta';
import {formatI18nPlaceholderName} from './util';
import {formatI18nPlaceholderName, formatI18nPlaceholderNamesInMap} from './util';
/** Closure uses `goog.getMsg(message)` to lookup translations */
const GOOG_GET_MSG = 'goog.getMsg';
/**
* Generates a `goog.getMsg()` statement and reassignment. The template:
*
* ```html
* <div i18n>Sent from {{ sender }} to <span class="receiver">{{ receiver }}</span></div>
* ```
*
* Generates:
*
* ```typescript
* const MSG_FOO = goog.getMsg(
* // Message template.
* 'Sent from {$interpolation} to {$startTagSpan}{$interpolation_1}{$closeTagSpan}.',
* // Placeholder values, set to magic strings which get replaced by the Angular runtime.
* {
* 'interpolation': '\uFFFD0\uFFFD',
* 'startTagSpan': '\uFFFD1\uFFFD',
* 'interpolation_1': '\uFFFD2\uFFFD',
* 'closeTagSpan': '\uFFFD3\uFFFD',
* },
* // Options bag.
* {
* // Maps each placeholder to the original Angular source code which generates it's value.
* original_code: {
* 'interpolation': '{{ sender }}',
* 'startTagSpan': '<span class="receiver">',
* 'interploation_1': '{{ receiver }}',
* 'closeTagSpan': '</span>',
* },
* },
* );
* const I18N_0 = MSG_FOO;
* ```
*/
export function createGoogleGetMsgStatements(
variable: o.ReadVarExpr, message: i18n.Message, closureVar: o.ReadVarExpr,
params: {[name: string]: o.Expression}): o.Statement[] {
placeholderValues: {[name: string]: o.Expression}): o.Statement[] {
const messageString = serializeI18nMessageForGetMsg(message);
const args = [o.literal(messageString) as o.Expression];
if (Object.keys(params).length) {
args.push(mapLiteral(params, true));
if (Object.keys(placeholderValues).length) {
// Message template parameters containing the magic strings replaced by the Angular runtime with
// real data, e.g. `{'interpolation': '\uFFFD0\uFFFD'}`.
args.push(mapLiteral(
formatI18nPlaceholderNamesInMap(placeholderValues, true /* useCamelCase */),
true /* quoted */));
// Message options object, which contains original source code for placeholders (as they are
// present in a template, e.g.
// `{original_code: {'interpolation': '{{ name }}', 'startTagSpan': '<span>'}}`.
args.push(mapLiteral({
original_code:
o.literalMap(Object.keys(placeholderValues)
.map((param) => ({
key: formatI18nPlaceholderName(param),
quoted: true,
value: message.placeholders[param] ?
// Get source span for typical placeholder if it exists.
o.literal(message.placeholders[param].sourceSpan.toString()) :
// Otherwise must be an ICU expression, get it's source span.
o.literal(
message.placeholderToMessage[param]
.nodes.map((node) => node.sourceSpan.toString())
.join(''),
),
}))),
}));
}
// /**

View file

@ -123,7 +123,7 @@ export function assembleBoundTextPlaceholders(
* @param useCamelCase whether to camelCase the placeholder name when formatting.
* @returns A new map of formatted placeholder names to expressions.
*/
export function i18nFormatPlaceholderNames(
export function formatI18nPlaceholderNamesInMap(
params: {[name: string]: o.Expression} = {}, useCamelCase: boolean) {
const _params: {[key: string]: o.Expression} = {};
if (params && Object.keys(params).length) {

View file

@ -36,7 +36,7 @@ import {I18nContext} from './i18n/context';
import {createGoogleGetMsgStatements} from './i18n/get_msg_utils';
import {createLocalizeStatements} from './i18n/localize_utils';
import {I18nMetaVisitor} from './i18n/meta';
import {assembleBoundTextPlaceholders, assembleI18nBoundString, declareI18nVariable, getTranslationConstPrefix, hasI18nMeta, I18N_ICU_MAPPING_PREFIX, i18nFormatPlaceholderNames, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, placeholdersToParams, TRANSLATION_VAR_PREFIX, wrapI18nPlaceholder} from './i18n/util';
import {assembleBoundTextPlaceholders, assembleI18nBoundString, declareI18nVariable, formatI18nPlaceholderNamesInMap, getTranslationConstPrefix, hasI18nMeta, I18N_ICU_MAPPING_PREFIX, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, placeholdersToParams, TRANSLATION_VAR_PREFIX, wrapI18nPlaceholder} from './i18n/util';
import {StylingBuilder, StylingInstruction} from './styling_builder';
import {asLiteral, CONTEXT_NAME, getInstructionStatements, getInterpolationArgsLength, IMPLICIT_REFERENCE, Instruction, InstructionParams, invalid, invokeInstruction, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, RESTORED_VIEW_CONTEXT_NAME, trimTrailingNulls} from './util';
@ -1035,7 +1035,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// - all ICU vars (such as `VAR_SELECT` or `VAR_PLURAL`) are replaced with correct values
const transformFn = (raw: o.ReadVarExpr) => {
const params = {...vars, ...placeholders};
const formatted = i18nFormatPlaceholderNames(params, /* useCamelCase */ false);
const formatted = formatI18nPlaceholderNamesInMap(params, /* useCamelCase */ false);
return invokeInstruction(null, R3.i18nPostprocess, [raw, mapLiteral(formatted, true)]);
};
@ -2269,11 +2269,9 @@ export function getTranslationDeclStmts(
declareI18nVariable(variable),
o.ifStmt(
createClosureModeGuard(),
createGoogleGetMsgStatements(
variable, message, closureVar,
i18nFormatPlaceholderNames(params, /* useCamelCase */ true)),
createGoogleGetMsgStatements(variable, message, closureVar, params),
createLocalizeStatements(
variable, message, i18nFormatPlaceholderNames(params, /* useCamelCase */ false))),
variable, message, formatI18nPlaceholderNamesInMap(params, /* useCamelCase */ false))),
];
if (transformFn) {