diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index 3b315da5f7d9b..7b6c8508fe0d4 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -38,7 +38,7 @@ export * from './version'; export {CompilerConfig, preserveWhitespacesDefault} from './config'; export * from './resource_loader'; export {ConstantPool} from './constant_pool'; -export {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/interpolation_config'; +export {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/defaults'; export * from './schema/element_schema_registry'; export * from './i18n/index'; export * from './expression_parser/ast'; @@ -47,7 +47,6 @@ export * from './expression_parser/parser'; export * from './ml_parser/ast'; export * from './ml_parser/html_parser'; export * from './ml_parser/html_tags'; -export * from './ml_parser/interpolation_config'; export * from './ml_parser/tags'; export {ParseTreeResult, TreeError} from './ml_parser/parser'; export {LexerRange} from './ml_parser/lexer'; diff --git a/packages/compiler/src/expression_parser/parser.ts b/packages/compiler/src/expression_parser/parser.ts index a83db2e26b4ce..a60c08efd84cf 100644 --- a/packages/compiler/src/expression_parser/parser.ts +++ b/packages/compiler/src/expression_parser/parser.ts @@ -7,7 +7,7 @@ */ import * as chars from '../chars'; -import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config'; +import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/defaults'; import {InterpolatedAttributeToken, InterpolatedTextToken, TokenType as MlParserTokenType} from '../ml_parser/tokens'; import {AbsoluteSourceSpan, AST, ASTWithSource, Binary, BindingPipe, Call, Chain, Conditional, EmptyExpr, ExpressionBinding, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralMapKey, LiteralPrimitive, NonNullAssert, ParserError, ParseSpan, PrefixNot, PropertyRead, PropertyWrite, RecursiveAstVisitor, SafeCall, SafeKeyedRead, SafePropertyRead, TemplateBinding, TemplateBindingIdentifier, ThisReceiver, Unary, VariableBinding} from './ast'; diff --git a/packages/compiler/src/i18n/digest.ts b/packages/compiler/src/i18n/digest.ts index 276515c609844..68b8c18e4e0bd 100644 --- a/packages/compiler/src/i18n/digest.ts +++ b/packages/compiler/src/i18n/digest.ts @@ -81,6 +81,11 @@ class _SerializerVisitor implements i18n.Visitor { visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { return `${ph.value.visit(this)}`; } + + visitBlockPlaceholder(ph: i18n.BlockPlaceholder, context: any): any { + return `${ + ph.children.map(child => child.visit(this)).join(', ')}`; + } } const serializerVisitor = new _SerializerVisitor(); diff --git a/packages/compiler/src/i18n/extractor_merger.ts b/packages/compiler/src/i18n/extractor_merger.ts index 264427b7827c1..33f951c66963c 100644 --- a/packages/compiler/src/i18n/extractor_merger.ts +++ b/packages/compiler/src/i18n/extractor_merger.ts @@ -7,7 +7,7 @@ */ import * as html from '../ml_parser/ast'; -import {InterpolationConfig} from '../ml_parser/interpolation_config'; +import {DEFAULT_CONTAINER_BLOCKS, InterpolationConfig} from '../ml_parser/defaults'; import {ParseTreeResult} from '../ml_parser/parser'; import * as i18n from './i18n_ast'; @@ -308,7 +308,8 @@ class _Visitor implements html.Visitor { this._errors = []; this._messages = []; this._inImplicitNode = false; - this._createI18nMessage = createI18nMessageFactory(interpolationConfig); + this._createI18nMessage = + createI18nMessageFactory(interpolationConfig, DEFAULT_CONTAINER_BLOCKS); } // looks for translatable attributes diff --git a/packages/compiler/src/i18n/i18n_ast.ts b/packages/compiler/src/i18n/i18n_ast.ts index a8b5f77d16d36..94d86128d78d8 100644 --- a/packages/compiler/src/i18n/i18n_ast.ts +++ b/packages/compiler/src/i18n/i18n_ast.ts @@ -129,6 +129,17 @@ export class IcuPlaceholder implements Node { } } +export class BlockPlaceholder implements Node { + constructor( + public name: string, public parameters: string[], public startName: string, + public closeName: string, public children: Node[], public sourceSpan: ParseSourceSpan, + public startSourceSpan: ParseSourceSpan|null, public endSourceSpan: ParseSourceSpan|null) {} + + visit(visitor: Visitor, context?: any): any { + return visitor.visitBlockPlaceholder(this, context); + } +} + /** * Each HTML node that is affect by an i18n tag will also have an `i18n` property that is of type * `I18nMeta`. @@ -144,6 +155,7 @@ export interface Visitor { visitTagPlaceholder(ph: TagPlaceholder, context?: any): any; visitPlaceholder(ph: Placeholder, context?: any): any; visitIcuPlaceholder(ph: IcuPlaceholder, context?: any): any; + visitBlockPlaceholder(ph: BlockPlaceholder, context?: any): any; } // Clone the AST @@ -178,6 +190,13 @@ export class CloneVisitor implements Visitor { visitIcuPlaceholder(ph: IcuPlaceholder, context?: any): IcuPlaceholder { return new IcuPlaceholder(ph.value, ph.name, ph.sourceSpan); } + + visitBlockPlaceholder(ph: BlockPlaceholder, context?: any): BlockPlaceholder { + const children = ph.children.map(n => n.visit(this, context)); + return new BlockPlaceholder( + ph.name, ph.parameters, ph.startName, ph.closeName, children, ph.sourceSpan, + ph.startSourceSpan, ph.endSourceSpan); + } } // Visit all the nodes recursively @@ -201,6 +220,10 @@ export class RecurseVisitor implements Visitor { visitPlaceholder(ph: Placeholder, context?: any): any {} visitIcuPlaceholder(ph: IcuPlaceholder, context?: any): any {} + + visitBlockPlaceholder(ph: BlockPlaceholder, context?: any): any { + ph.children.forEach(child => child.visit(this)); + } } @@ -240,4 +263,9 @@ class LocalizeMessageStringVisitor implements Visitor { visitIcuPlaceholder(ph: IcuPlaceholder): any { return `{$${ph.name}}`; } + + visitBlockPlaceholder(ph: BlockPlaceholder): any { + const children = ph.children.map(child => child.visit(this)).join(''); + return `{$${ph.startName}}${children}{$${ph.closeName}}`; + } } diff --git a/packages/compiler/src/i18n/i18n_html_parser.ts b/packages/compiler/src/i18n/i18n_html_parser.ts index 41029bfb8e937..cf54abd543918 100644 --- a/packages/compiler/src/i18n/i18n_html_parser.ts +++ b/packages/compiler/src/i18n/i18n_html_parser.ts @@ -7,8 +7,8 @@ */ import {MissingTranslationStrategy} from '../core'; +import {DEFAULT_INTERPOLATION_CONFIG} from '../ml_parser/defaults'; import {HtmlParser} from '../ml_parser/html_parser'; -import {DEFAULT_INTERPOLATION_CONFIG} from '../ml_parser/interpolation_config'; import {TokenizeOptions} from '../ml_parser/lexer'; import {ParseTreeResult} from '../ml_parser/parser'; import {Console} from '../util'; diff --git a/packages/compiler/src/i18n/i18n_parser.ts b/packages/compiler/src/i18n/i18n_parser.ts index c5ef54aa94459..ee2977751ec26 100644 --- a/packages/compiler/src/i18n/i18n_parser.ts +++ b/packages/compiler/src/i18n/i18n_parser.ts @@ -9,8 +9,8 @@ import {Lexer as ExpressionLexer} from '../expression_parser/lexer'; import {Parser as ExpressionParser} from '../expression_parser/parser'; import * as html from '../ml_parser/ast'; +import {InterpolationConfig} from '../ml_parser/defaults'; import {getHtmlTagDefinition} from '../ml_parser/html_tags'; -import {InterpolationConfig} from '../ml_parser/interpolation_config'; import {InterpolatedAttributeToken, InterpolatedTextToken, TokenType} from '../ml_parser/tokens'; import {ParseSourceSpan} from '../parse_util'; @@ -29,9 +29,9 @@ export interface I18nMessageFactory { /** * Returns a function converting html nodes to an i18n Message given an interpolationConfig */ -export function createI18nMessageFactory(interpolationConfig: InterpolationConfig): - I18nMessageFactory { - const visitor = new _I18nVisitor(_expParser, interpolationConfig); +export function createI18nMessageFactory( + interpolationConfig: InterpolationConfig, containerBlocks: Set): I18nMessageFactory { + const visitor = new _I18nVisitor(_expParser, interpolationConfig, containerBlocks); return (nodes, meaning, description, customId, visitNodeFn) => visitor.toI18nMessage(nodes, meaning, description, customId, visitNodeFn); } @@ -52,7 +52,9 @@ function noopVisitNodeFn(_html: html.Node, i18n: i18n.Node): i18n.Node { class _I18nVisitor implements html.Visitor { constructor( private _expressionParser: ExpressionParser, - private _interpolationConfig: InterpolationConfig) {} + private _interpolationConfig: InterpolationConfig, + private _containerBlocks: Set, + ) {} public toI18nMessage( nodes: html.Node[], meaning = '', description = '', customId = '', @@ -164,11 +166,35 @@ class _I18nVisitor implements html.Visitor { visitBlock(block: html.Block, context: I18nMessageVisitorContext) { const children = html.visitAll(this, block.children, context); - const node = new i18n.Container(children, block.sourceSpan); + + if (this._containerBlocks.has(block.name)) { + return new i18n.Container(children, block.sourceSpan); + } + + const parameters = block.parameters.map(param => param.expression); + const startPhName = + context.placeholderRegistry.getStartBlockPlaceholderName(block.name, parameters); + const closePhName = context.placeholderRegistry.getCloseBlockPlaceholderName(block.name); + + context.placeholderToContent[startPhName] = { + text: block.startSourceSpan.toString(), + sourceSpan: block.startSourceSpan, + }; + + context.placeholderToContent[closePhName] = { + text: block.endSourceSpan ? block.endSourceSpan.toString() : '}', + sourceSpan: block.endSourceSpan ?? block.sourceSpan, + }; + + const node = new i18n.BlockPlaceholder( + block.name, parameters, startPhName, closePhName, children, block.sourceSpan, + block.startSourceSpan, block.endSourceSpan); return context.visitNodeFn(block, node); } - visitBlockParameter(_parameter: html.BlockParameter, _context: I18nMessageVisitorContext) {} + visitBlockParameter(_parameter: html.BlockParameter, _context: I18nMessageVisitorContext) { + throw new Error('Unreachable code'); + } /** * Convert, text and interpolated tokens up into text and placeholder pieces. diff --git a/packages/compiler/src/i18n/message_bundle.ts b/packages/compiler/src/i18n/message_bundle.ts index 3bf6d90a98493..070452cd8282a 100644 --- a/packages/compiler/src/i18n/message_bundle.ts +++ b/packages/compiler/src/i18n/message_bundle.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ +import {InterpolationConfig} from '../ml_parser/defaults'; import {HtmlParser} from '../ml_parser/html_parser'; -import {InterpolationConfig} from '../ml_parser/interpolation_config'; import {ParseError} from '../parse_util'; import {extractMessages} from './extractor_merger'; @@ -99,6 +99,16 @@ class MapPlaceholderNames extends i18n.CloneVisitor { ph.startSourceSpan, ph.endSourceSpan); } + override visitBlockPlaceholder(ph: i18n.BlockPlaceholder, mapper: PlaceholderMapper): + i18n.BlockPlaceholder { + const startName = mapper.toPublicName(ph.startName)!; + const closeName = ph.closeName ? mapper.toPublicName(ph.closeName)! : ph.closeName; + const children = ph.children.map(n => n.visit(this, mapper)); + return new i18n.BlockPlaceholder( + ph.name, ph.parameters, startName, closeName, children, ph.sourceSpan, ph.startSourceSpan, + ph.endSourceSpan); + } + override visitPlaceholder(ph: i18n.Placeholder, mapper: PlaceholderMapper): i18n.Placeholder { return new i18n.Placeholder(ph.value, mapper.toPublicName(ph.name)!, ph.sourceSpan); } diff --git a/packages/compiler/src/i18n/serializers/placeholder.ts b/packages/compiler/src/i18n/serializers/placeholder.ts index 17f4b72dbf67c..55831fbf6f3e9 100644 --- a/packages/compiler/src/i18n/serializers/placeholder.ts +++ b/packages/compiler/src/i18n/serializers/placeholder.ts @@ -97,6 +97,29 @@ export class PlaceholderRegistry { return this._generateUniqueName(name.toUpperCase()); } + getStartBlockPlaceholderName(name: string, parameters: string[]): string { + const signature = this._hashBlock(name, parameters); + if (this._signatureToName[signature]) { + return this._signatureToName[signature]; + } + + const placeholder = this._generateUniqueName(`START_BLOCK_${this._toSnakeCase(name)}`); + this._signatureToName[signature] = placeholder; + return placeholder; + } + + getCloseBlockPlaceholderName(name: string): string { + const signature = this._hashClosingBlock(name); + if (this._signatureToName[signature]) { + return this._signatureToName[signature]; + } + + const placeholder = this._generateUniqueName(`CLOSE_BLOCK_${this._toSnakeCase(name)}`); + this._signatureToName[signature] = placeholder; + return placeholder; + } + + // Generate a hash for a tag - does not take attribute order into account private _hashTag(tag: string, attrs: {[k: string]: string}, isVoid: boolean): string { const start = `<${tag}`; @@ -110,6 +133,19 @@ export class PlaceholderRegistry { return this._hashTag(`/${tag}`, {}, false); } + private _hashBlock(name: string, parameters: string[]): string { + const params = parameters.length === 0 ? '' : ` (${parameters.sort().join('; ')})`; + return `@${name}${params} {}`; + } + + private _hashClosingBlock(name: string): string { + return this._hashBlock(`close_${name}`, []); + } + + private _toSnakeCase(name: string) { + return name.toUpperCase().replace(/[^A-Z0-9]/g, '_'); + } + private _generateUniqueName(base: string): string { const seen = this._placeHolderNameCounts.hasOwnProperty(base); if (!seen) { diff --git a/packages/compiler/src/i18n/serializers/serializer.ts b/packages/compiler/src/i18n/serializers/serializer.ts index effcfa6091f4f..e7a4090545d8a 100644 --- a/packages/compiler/src/i18n/serializers/serializer.ts +++ b/packages/compiler/src/i18n/serializers/serializer.ts @@ -77,6 +77,12 @@ export class SimplePlaceholderMapper extends i18n.RecurseVisitor implements Plac this.visitPlaceholderName(ph.name); } + override visitBlockPlaceholder(ph: i18n.BlockPlaceholder, context?: any): any { + this.visitPlaceholderName(ph.startName); + super.visitBlockPlaceholder(ph, context); + this.visitPlaceholderName(ph.closeName); + } + override visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { this.visitPlaceholderName(ph.name); } diff --git a/packages/compiler/src/i18n/serializers/xliff.ts b/packages/compiler/src/i18n/serializers/xliff.ts index 7c9b5a1a5dc38..54aabbd252c45 100644 --- a/packages/compiler/src/i18n/serializers/xliff.ts +++ b/packages/compiler/src/i18n/serializers/xliff.ts @@ -164,6 +164,15 @@ class _WriteVisitor implements i18n.Visitor { return [new xml.Tag(_PLACEHOLDER_TAG, {id: ph.name, 'equiv-text': `{{${ph.value}}}`})]; } + visitBlockPlaceholder(ph: i18n.BlockPlaceholder, context?: any): xml.Node[] { + const ctype = `x-${ph.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}`; + const startTagPh = + new xml.Tag(_PLACEHOLDER_TAG, {id: ph.startName, ctype, 'equiv-text': `@${ph.name}`}); + const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {id: ph.closeName, ctype, 'equiv-text': `}`}); + + return [startTagPh, ...this.serialize(ph.children), closeTagPh]; + } + visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] { const equivText = `{${ph.value.expression}, ${ph.value.type}, ${ Object.keys(ph.value.cases).map((value: string) => value + ' {...}').join(' ')}}`; diff --git a/packages/compiler/src/i18n/serializers/xliff2.ts b/packages/compiler/src/i18n/serializers/xliff2.ts index 370a055c8663a..c6ba2f12cd5de 100644 --- a/packages/compiler/src/i18n/serializers/xliff2.ts +++ b/packages/compiler/src/i18n/serializers/xliff2.ts @@ -178,6 +178,25 @@ class _WriteVisitor implements i18n.Visitor { })]; } + visitBlockPlaceholder(ph: i18n.BlockPlaceholder, context?: any): xml.Node[] { + const tagPc = new xml.Tag(_PLACEHOLDER_SPANNING_TAG, { + id: (this._nextPlaceholderId++).toString(), + equivStart: ph.startName, + equivEnd: ph.closeName, + type: 'other', + dispStart: `@${ph.name}`, + dispEnd: `}`, + }); + const nodes: xml.Node[] = [].concat(...ph.children.map(node => node.visit(this))); + if (nodes.length) { + nodes.forEach((node: xml.Node) => tagPc.children.push(node)); + } else { + tagPc.children.push(new xml.Text('')); + } + + return [tagPc]; + } + visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] { const cases = Object.keys(ph.value.cases).map((value: string) => value + ' {...}').join(' '); const idStr = (this._nextPlaceholderId++).toString(); diff --git a/packages/compiler/src/i18n/serializers/xmb.ts b/packages/compiler/src/i18n/serializers/xmb.ts index 3688454d308d4..28bb0974716e4 100644 --- a/packages/compiler/src/i18n/serializers/xmb.ts +++ b/packages/compiler/src/i18n/serializers/xmb.ts @@ -148,6 +148,20 @@ class _Visitor implements i18n.Visitor { ]; } + visitBlockPlaceholder(ph: i18n.BlockPlaceholder, context?: any): xml.Node[] { + const startAsText = new xml.Text(`@${ph.name}`); + const startEx = new xml.Tag(_EXAMPLE_TAG, {}, [startAsText]); + // 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, startAsText]); + + const closeAsText = new xml.Text(`}`); + const closeEx = new xml.Tag(_EXAMPLE_TAG, {}, [closeAsText]); + // 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, closeAsText]); + + return [startTagPh, ...this.serialize(ph.children), closeTagPh]; + } + visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] { const icuExpression = ph.value.expression; const icuType = ph.value.type; diff --git a/packages/compiler/src/i18n/translation_bundle.ts b/packages/compiler/src/i18n/translation_bundle.ts index 6d5b19be4b9af..f6cd6e305a391 100644 --- a/packages/compiler/src/i18n/translation_bundle.ts +++ b/packages/compiler/src/i18n/translation_bundle.ts @@ -149,6 +149,12 @@ class I18nToHtmlVisitor implements i18n.Visitor { return this._convertToText(this._srcMsg.placeholderToMessage[ph.name]); } + visitBlockPlaceholder(ph: i18n.BlockPlaceholder, context?: any): string { + const params = ph.parameters.length === 0 ? '' : ` (${ph.parameters.join('; ')})`; + const children = ph.children.map((c: i18n.Node) => c.visit(this)).join(''); + return `@${ph.name}${params} {${children}}`; + } + /** * Convert a source message to a translated text string: * - text nodes are replaced with their translation, diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index 42c6559bc1047..fb4dd7b02c7be 100644 --- a/packages/compiler/src/jit_compiler_facade.ts +++ b/packages/compiler/src/jit_compiler_facade.ts @@ -10,7 +10,7 @@ import {CompilerFacade, CoreEnvironment, ExportedCompilerFacade, InputMap, Input import {ConstantPool} from './constant_pool'; import {ChangeDetectionStrategy, HostBinding, HostListener, Input, Output, ViewEncapsulation} from './core'; import {compileInjectable} from './injectable_compiler_2'; -import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/interpolation_config'; +import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/defaults'; import {DeclareVarStmt, Expression, literal, LiteralExpr, Statement, StmtModifier, WrappedNodeExpr} from './output/output_ast'; import {JitEvaluator} from './output/output_jit'; import {ParseError, ParseSourceSpan, r3JitTypeSourceSpan} from './parse_util'; diff --git a/packages/compiler/src/ml_parser/ast.ts b/packages/compiler/src/ml_parser/ast.ts index 1d608a28861a4..b509728e408a5 100644 --- a/packages/compiler/src/ml_parser/ast.ts +++ b/packages/compiler/src/ml_parser/ast.ts @@ -86,13 +86,16 @@ export class Comment implements BaseNode { } } -export class Block implements BaseNode { +export class Block extends NodeWithI18n { constructor( public name: string, public parameters: BlockParameter[], public children: Node[], - public sourceSpan: ParseSourceSpan, public nameSpan: ParseSourceSpan, - public startSourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan|null = null) {} + sourceSpan: ParseSourceSpan, public nameSpan: ParseSourceSpan, + public startSourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan|null = null, + i18n?: I18nMeta) { + super(sourceSpan, i18n); + } - visit(visitor: Visitor, context: any) { + override visit(visitor: Visitor, context: any) { return visitor.visitBlock(this, context); } } diff --git a/packages/compiler/src/ml_parser/interpolation_config.ts b/packages/compiler/src/ml_parser/defaults.ts similarity index 92% rename from packages/compiler/src/ml_parser/interpolation_config.ts rename to packages/compiler/src/ml_parser/defaults.ts index 70854b9f8de0e..90078a26a8e45 100644 --- a/packages/compiler/src/ml_parser/interpolation_config.ts +++ b/packages/compiler/src/ml_parser/defaults.ts @@ -23,3 +23,5 @@ export class InterpolationConfig { export const DEFAULT_INTERPOLATION_CONFIG: InterpolationConfig = new InterpolationConfig('{{', '}}'); + +export const DEFAULT_CONTAINER_BLOCKS = new Set(['switch']); diff --git a/packages/compiler/src/ml_parser/lexer.ts b/packages/compiler/src/ml_parser/lexer.ts index f2842341809ff..74ef678dea6b1 100644 --- a/packages/compiler/src/ml_parser/lexer.ts +++ b/packages/compiler/src/ml_parser/lexer.ts @@ -9,8 +9,8 @@ import * as chars from '../chars'; import {ParseError, ParseLocation, ParseSourceFile, ParseSourceSpan} from '../parse_util'; +import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './defaults'; import {NAMED_ENTITIES} from './entities'; -import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config'; import {TagContentType, TagDefinition} from './tags'; import {IncompleteTagOpenToken, TagOpenStartToken, Token, TokenType} from './tokens'; diff --git a/packages/compiler/src/render3/partial/component.ts b/packages/compiler/src/render3/partial/component.ts index 23913ed141eb6..c1f89aef9a80f 100644 --- a/packages/compiler/src/render3/partial/component.ts +++ b/packages/compiler/src/render3/partial/component.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import * as core from '../../core'; -import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config'; +import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/defaults'; import * as o from '../../output/output_ast'; import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../parse_util'; import {RecursiveVisitor, visitAll} from '../r3_ast'; diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts index 74363df18e8d7..103eb707f7067 100644 --- a/packages/compiler/src/render3/view/api.ts +++ b/packages/compiler/src/render3/view/api.ts @@ -7,7 +7,7 @@ */ import {ChangeDetectionStrategy, ViewEncapsulation} from '../../core'; -import {InterpolationConfig} from '../../ml_parser/interpolation_config'; +import {InterpolationConfig} from '../../ml_parser/defaults'; import * as o from '../../output/output_ast'; import {ParseSourceSpan} from '../../parse_util'; import * as t from '../r3_ast'; diff --git a/packages/compiler/src/render3/view/i18n/get_msg_utils.ts b/packages/compiler/src/render3/view/i18n/get_msg_utils.ts index f17b5ac0fbf79..2a38d2eaa7f57 100644 --- a/packages/compiler/src/render3/view/i18n/get_msg_utils.ts +++ b/packages/compiler/src/render3/view/i18n/get_msg_utils.ts @@ -128,6 +128,11 @@ class GetMsgSerializerVisitor implements i18n.Visitor { return this.formatPh(ph.name); } + visitBlockPlaceholder(ph: i18n.BlockPlaceholder): any { + return `${this.formatPh(ph.startName)}${ph.children.map(child => child.visit(this)).join('')}${ + this.formatPh(ph.closeName)}`; + } + visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { return this.formatPh(ph.name); } diff --git a/packages/compiler/src/render3/view/i18n/icu_serializer.ts b/packages/compiler/src/render3/view/i18n/icu_serializer.ts index 2466c7bfb393b..0454c145e74ec 100644 --- a/packages/compiler/src/render3/view/i18n/icu_serializer.ts +++ b/packages/compiler/src/render3/view/i18n/icu_serializer.ts @@ -37,6 +37,11 @@ class IcuSerializerVisitor implements i18n.Visitor { return this.formatPh(ph.name); } + visitBlockPlaceholder(ph: i18n.BlockPlaceholder): any { + return `${this.formatPh(ph.startName)}${ph.children.map(child => child.visit(this)).join('')}${ + this.formatPh(ph.closeName)}`; + } + visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { return this.formatPh(ph.name); } diff --git a/packages/compiler/src/render3/view/i18n/localize_utils.ts b/packages/compiler/src/render3/view/i18n/localize_utils.ts index 436e353dd57ff..70006561ba808 100644 --- a/packages/compiler/src/render3/view/i18n/localize_utils.ts +++ b/packages/compiler/src/render3/view/i18n/localize_utils.ts @@ -68,6 +68,13 @@ class LocalizeSerializerVisitor implements i18n.Visitor { this.pieces.push(this.createPlaceholderPiece(ph.name, ph.sourceSpan)); } + visitBlockPlaceholder(ph: i18n.BlockPlaceholder): any { + this.pieces.push( + this.createPlaceholderPiece(ph.startName, ph.startSourceSpan ?? ph.sourceSpan)); + ph.children.forEach(child => child.visit(this)); + this.pieces.push(this.createPlaceholderPiece(ph.closeName, ph.endSourceSpan ?? ph.sourceSpan)); + } + visitIcuPlaceholder(ph: i18n.IcuPlaceholder): any { this.pieces.push( this.createPlaceholderPiece(ph.name, ph.sourceSpan, this.placeholderToMessage[ph.name])); diff --git a/packages/compiler/src/render3/view/i18n/meta.ts b/packages/compiler/src/render3/view/i18n/meta.ts index f70bb498cd8a8..30c4943545253 100644 --- a/packages/compiler/src/render3/view/i18n/meta.ts +++ b/packages/compiler/src/render3/view/i18n/meta.ts @@ -8,10 +8,10 @@ import {computeDecimalDigest, computeDigest, decimalDigest} from '../../../i18n/digest'; import * as i18n from '../../../i18n/i18n_ast'; -import {createI18nMessageFactory, I18nMessageFactory, VisitNodeFn} from '../../../i18n/i18n_parser'; +import {createI18nMessageFactory, VisitNodeFn} from '../../../i18n/i18n_parser'; import {I18nError} from '../../../i18n/parse_util'; import * as html from '../../../ml_parser/ast'; -import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../ml_parser/interpolation_config'; +import {DEFAULT_CONTAINER_BLOCKS, DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../ml_parser/defaults'; import {ParseTreeResult} from '../../../ml_parser/parser'; import * as o from '../../../output/output_ast'; import {isTrustedTypesSink} from '../../../schema/trusted_types_sinks'; @@ -53,13 +53,15 @@ export class I18nMetaVisitor implements html.Visitor { constructor( private interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG, - private keepI18nAttrs = false, private enableI18nLegacyMessageIdFormat = false) {} + private keepI18nAttrs = false, private enableI18nLegacyMessageIdFormat = false, + private containerBlocks: Set = DEFAULT_CONTAINER_BLOCKS) {} private _generateI18nMessage( nodes: html.Node[], meta: string|i18n.I18nMeta = '', visitNodeFn?: VisitNodeFn): i18n.Message { const {meaning, description, customId} = this._parseMetadata(meta); - const createI18nMessage = createI18nMessageFactory(this.interpolationConfig); + const createI18nMessage = + createI18nMessageFactory(this.interpolationConfig, this.containerBlocks); const message = createI18nMessage(nodes, meaning, description, customId, visitNodeFn); this._setMessageId(message, meta); this._setLegacyIds(message, meta); diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 3854cadb8329a..c320f471759d6 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -14,9 +14,9 @@ import {Lexer} from '../../expression_parser/lexer'; import {Parser} from '../../expression_parser/parser'; import * as i18n from '../../i18n/i18n_ast'; import * as html from '../../ml_parser/ast'; +import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../ml_parser/defaults'; import {HtmlParser} from '../../ml_parser/html_parser'; import {WhitespaceVisitor} from '../../ml_parser/html_whitespaces'; -import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../ml_parser/interpolation_config'; import {LexerRange} from '../../ml_parser/lexer'; import {isNgContainer as checkIsNgContainer, splitNsName} from '../../ml_parser/tags'; import {mapLiteral} from '../../output/map_util'; diff --git a/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts b/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts index 595660908f3e9..d9fdfa474087c 100644 --- a/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts +++ b/packages/compiler/src/template/pipeline/src/phases/resolve_i18n_icu_placeholders.ts @@ -50,11 +50,9 @@ class ResolveIcuPlaceholdersVisitor extends i18n.RecurseVisitor { super(); } - override visitTagPlaceholder(placeholder: i18n.TagPlaceholder) { - super.visitTagPlaceholder(placeholder); - - // Add the start and end source span for tag placeholders. These need to be recorded for - // elements inside ICUs. The slots for the elements were recorded separately under the i18n + private visitContainerPlaceholder(placeholder: i18n.TagPlaceholder|i18n.BlockPlaceholder) { + // Add the start and end source span for container placeholders. These need to be recorded for + // elements inside ICUs. The slots for the nodes were recorded separately under the i18n // block's context as part of the `resolveI18nElementPlaceholders` phase. if (placeholder.startName && placeholder.startSourceSpan && !this.params.has(placeholder.startName)) { @@ -73,4 +71,14 @@ class ResolveIcuPlaceholdersVisitor extends i18n.RecurseVisitor { }]); } } + + override visitTagPlaceholder(placeholder: i18n.TagPlaceholder) { + super.visitTagPlaceholder(placeholder); + this.visitContainerPlaceholder(placeholder); + } + + override visitBlockPlaceholder(placeholder: i18n.BlockPlaceholder) { + super.visitBlockPlaceholder(placeholder); + this.visitContainerPlaceholder(placeholder); + } } diff --git a/packages/compiler/src/template_parser/binding_parser.ts b/packages/compiler/src/template_parser/binding_parser.ts index d640a9cf8eea3..d905e24cdd3ba 100644 --- a/packages/compiler/src/template_parser/binding_parser.ts +++ b/packages/compiler/src/template_parser/binding_parser.ts @@ -9,10 +9,10 @@ import {SecurityContext} from '../core'; import {AbsoluteSourceSpan, ASTWithSource, BindingPipe, BindingType, BoundElementProperty, EmptyExpr, ParsedEvent, ParsedEventType, ParsedProperty, ParsedPropertyType, ParsedVariable, ParserError, RecursiveAstVisitor, TemplateBinding, VariableBinding} from '../expression_parser/ast'; import {Parser} from '../expression_parser/parser'; -import {InterpolationConfig} from '../ml_parser/interpolation_config'; +import {InterpolationConfig} from '../ml_parser/defaults'; import {mergeNsAndName} from '../ml_parser/tags'; import {InterpolatedAttributeToken, InterpolatedTextToken} from '../ml_parser/tokens'; -import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceSpan} from '../parse_util'; +import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util'; import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {CssSelector} from '../selector'; import {splitAtColon, splitAtPeriod} from '../util'; diff --git a/packages/compiler/test/expression_parser/utils/unparser.ts b/packages/compiler/test/expression_parser/utils/unparser.ts index d4488498089f1..4cf474595f3dc 100644 --- a/packages/compiler/test/expression_parser/utils/unparser.ts +++ b/packages/compiler/test/expression_parser/utils/unparser.ts @@ -7,7 +7,7 @@ */ import {AST, AstVisitor, ASTWithSource, Binary, BindingPipe, Call, Chain, Conditional, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, NonNullAssert, PrefixNot, PropertyRead, PropertyWrite, RecursiveAstVisitor, SafeCall, SafeKeyedRead, SafePropertyRead, ThisReceiver, Unary} from '../../../src/expression_parser/ast'; -import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../src/ml_parser/interpolation_config'; +import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../src/ml_parser/defaults'; class Unparser implements AstVisitor { private static _quoteRegExp = /"/g; diff --git a/packages/compiler/test/i18n/extractor_merger_spec.ts b/packages/compiler/test/i18n/extractor_merger_spec.ts index 3d0fa6df06101..27b7071c936fb 100644 --- a/packages/compiler/test/i18n/extractor_merger_spec.ts +++ b/packages/compiler/test/i18n/extractor_merger_spec.ts @@ -168,7 +168,13 @@ describe('Extractor', () => { it('should handle blocks inside of translated elements', () => { expect(extract('@if (cond) {main content} @else {else content}`')) - .toEqual([[['[main content]', ' ', '[else content]'], 'a', 'b|c', '']]); + .toEqual([[ + [ + 'main content', ' ', + 'else content' + ], + 'a', 'b|c', '' + ]]); }); }); diff --git a/packages/compiler/test/i18n/i18n_ast_spec.ts b/packages/compiler/test/i18n/i18n_ast_spec.ts index 0ef344da2a2c8..59933948afdb4 100644 --- a/packages/compiler/test/i18n/i18n_ast_spec.ts +++ b/packages/compiler/test/i18n/i18n_ast_spec.ts @@ -8,11 +8,12 @@ import {createI18nMessageFactory} from '../../src/i18n/i18n_parser'; import {Node} from '../../src/ml_parser/ast'; +import {DEFAULT_CONTAINER_BLOCKS, DEFAULT_INTERPOLATION_CONFIG} from '../../src/ml_parser/defaults'; import {HtmlParser} from '../../src/ml_parser/html_parser'; -import {DEFAULT_INTERPOLATION_CONFIG} from '../../src/ml_parser/interpolation_config'; describe('Message', () => { - const messageFactory = createI18nMessageFactory(DEFAULT_INTERPOLATION_CONFIG); + const messageFactory = + createI18nMessageFactory(DEFAULT_INTERPOLATION_CONFIG, DEFAULT_CONTAINER_BLOCKS); describe('messageText()', () => { it('should serialize simple text', () => { const message = messageFactory(parseHtml('abc\ndef'), '', '', ''); @@ -58,6 +59,15 @@ describe('Message', () => { .toEqual( `{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}`); }); + + it('should serialize blocks', () => { + const message = messageFactory( + parseHtml('abc @if (foo) {foo} @else if (bar) {bar} @else {baz} def'), '', '', ''); + + expect(message.messageString) + .toEqual( + 'abc {$START_BLOCK_IF}foo{$CLOSE_BLOCK_IF} {$START_BLOCK_ELSE_IF}bar{$CLOSE_BLOCK_ELSE_IF} {$START_BLOCK_ELSE}baz{$CLOSE_BLOCK_ELSE} def'); + }); }); }); diff --git a/packages/compiler/test/i18n/i18n_parser_spec.ts b/packages/compiler/test/i18n/i18n_parser_spec.ts index 0e2f29fb8f07f..881b9bcbaf32b 100644 --- a/packages/compiler/test/i18n/i18n_parser_spec.ts +++ b/packages/compiler/test/i18n/i18n_parser_spec.ts @@ -9,8 +9,8 @@ import {digest, serializeNodes} from '@angular/compiler/src/i18n/digest'; import {extractMessages} from '@angular/compiler/src/i18n/extractor_merger'; import {Message} from '@angular/compiler/src/i18n/i18n_ast'; +import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/defaults'; import {HtmlParser} from '@angular/compiler/src/ml_parser/html_parser'; -import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/interpolation_config'; describe('I18nParser', () => { describe('elements', () => { diff --git a/packages/compiler/test/i18n/integration_common.ts b/packages/compiler/test/i18n/integration_common.ts index 53073f1162c28..4411ebb6ca261 100644 --- a/packages/compiler/test/i18n/integration_common.ts +++ b/packages/compiler/test/i18n/integration_common.ts @@ -9,8 +9,8 @@ import {NgLocalization} from '@angular/common'; import {Serializer} from '@angular/compiler/src/i18n'; import {MessageBundle} from '@angular/compiler/src/i18n/message_bundle'; +import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/defaults'; import {HtmlParser} from '@angular/compiler/src/ml_parser/html_parser'; -import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/interpolation_config'; import {ResourceLoader} from '@angular/compiler/src/resource_loader'; import {Component, DebugElement, TRANSLATIONS, TRANSLATIONS_FORMAT} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; diff --git a/packages/compiler/test/i18n/message_bundle_spec.ts b/packages/compiler/test/i18n/message_bundle_spec.ts index ddaa1b63b8386..926cf340ec7ff 100644 --- a/packages/compiler/test/i18n/message_bundle_spec.ts +++ b/packages/compiler/test/i18n/message_bundle_spec.ts @@ -10,8 +10,8 @@ import {serializeNodes} from '../../src/i18n/digest'; import * as i18n from '../../src/i18n/i18n_ast'; import {MessageBundle} from '../../src/i18n/message_bundle'; import {Serializer} from '../../src/i18n/serializers/serializer'; +import {DEFAULT_INTERPOLATION_CONFIG} from '../../src/ml_parser/defaults'; import {HtmlParser} from '../../src/ml_parser/html_parser'; -import {DEFAULT_INTERPOLATION_CONFIG} from '../../src/ml_parser/interpolation_config'; describe('MessageBundle', () => { describe('Messages', () => { diff --git a/packages/compiler/test/i18n/serializers/placeholder_spec.ts b/packages/compiler/test/i18n/serializers/placeholder_spec.ts index 5ca889f613c24..de40a0dcc414c 100644 --- a/packages/compiler/test/i18n/serializers/placeholder_spec.ts +++ b/packages/compiler/test/i18n/serializers/placeholder_spec.ts @@ -86,4 +86,16 @@ describe('PlaceholderRegistry', () => { expect(reg.getPlaceholderName('name2', 'content')).toEqual('NAME2'); }); }); + + describe('block placeholders', () => { + it('should generate placeholders for a plain block', () => { + expect(reg.getStartBlockPlaceholderName('if', [])).toBe('START_BLOCK_IF'); + expect(reg.getCloseBlockPlaceholderName('if')).toBe('CLOSE_BLOCK_IF'); + }); + + it('should generate placeholders for a block with spaces in its name', () => { + expect(reg.getStartBlockPlaceholderName('else if', [])).toBe('START_BLOCK_ELSE_IF'); + expect(reg.getCloseBlockPlaceholderName('else if')).toBe('CLOSE_BLOCK_ELSE_IF'); + }); + }); }); diff --git a/packages/compiler/test/i18n/serializers/xliff2_spec.ts b/packages/compiler/test/i18n/serializers/xliff2_spec.ts index f89a416d50385..edd1d54f6a761 100644 --- a/packages/compiler/test/i18n/serializers/xliff2_spec.ts +++ b/packages/compiler/test/i18n/serializers/xliff2_spec.ts @@ -11,8 +11,8 @@ import {escapeRegExp} from '@angular/compiler/src/util'; import {serializeNodes} from '../../../src/i18n/digest'; import {MessageBundle} from '../../../src/i18n/message_bundle'; import {Xliff2} from '../../../src/i18n/serializers/xliff2'; +import {DEFAULT_INTERPOLATION_CONFIG} from '../../../src/ml_parser/defaults'; import {HtmlParser} from '../../../src/ml_parser/html_parser'; -import {DEFAULT_INTERPOLATION_CONFIG} from '../../../src/ml_parser/interpolation_config'; const HTML = `

not translatable

@@ -26,6 +26,7 @@ const HTML = `

Test: { count, plural, =0 { { sex, select, other {

deeply nested

}} } =other {a lot}}

multi lines

+

translatable element @if (foo) {with} @else if (bar) {blocks}

`; const WRITE_XLIFF = ` @@ -125,6 +126,14 @@ const WRITE_XLIFF = ` lines + + + file.ts:13 + + + translatable element with blocks + + `; @@ -238,6 +247,15 @@ lines lignes + + + file.ts:13 + + + translatable element with blocks + élément traduisible avec des blocs + + Please check the translation for 'namespace'. On also can use 'espace de nom',but I think most technical manuals use the English term. @@ -260,26 +278,25 @@ lignes `; -(function() { -const serializer = new Xliff2(); +describe('XLIFF 2.0 serializer', () => { + const serializer = new Xliff2(); -function toXliff(html: string, locale: string|null = null): string { - const catalog = new MessageBundle(new HtmlParser, [], {}, locale); - catalog.updateFromTemplate(html, 'file.ts', DEFAULT_INTERPOLATION_CONFIG); - return catalog.write(serializer); -} + function toXliff(html: string, locale: string|null = null): string { + const catalog = new MessageBundle(new HtmlParser, [], {}, locale); + catalog.updateFromTemplate(html, 'file.ts', DEFAULT_INTERPOLATION_CONFIG); + return catalog.write(serializer); + } -function loadAsMap(xliff: string): {[id: string]: string} { - const {i18nNodesByMsgId} = serializer.load(xliff, 'url'); + function loadAsMap(xliff: string): {[id: string]: string} { + const {i18nNodesByMsgId} = serializer.load(xliff, 'url'); - const msgMap: {[id: string]: string} = {}; - Object.keys(i18nNodesByMsgId) - .forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join('')); + const msgMap: {[id: string]: string} = {}; + Object.keys(i18nNodesByMsgId) + .forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join('')); - return msgMap; -} + return msgMap; + } -describe('XLIFF 2.0 serializer', () => { describe('write', () => { it('should write a valid xliff 2.0 file', () => { expect(toXliff(HTML)).toEqual(WRITE_XLIFF); @@ -309,6 +326,8 @@ describe('XLIFF 2.0 serializer', () => { ' name="START_PARAGRAPH"/>, profondément imbriqué, ]}}, ]}, =other {[beaucoup]}}', '2340165783990709777': `multi lignes`, + '6618832065070552029': + 'élément traduisible avec des blocs', 'mrk-test': 'Vous pouvez utiliser votre propre namespace.', 'mrk-test2': 'Vous pouvez utiliser votre propre namespace.' }); @@ -439,4 +458,3 @@ lignes`, }); }); }); -})(); diff --git a/packages/compiler/test/i18n/serializers/xliff_spec.ts b/packages/compiler/test/i18n/serializers/xliff_spec.ts index 9acf397aa8686..dbc4c470a658d 100644 --- a/packages/compiler/test/i18n/serializers/xliff_spec.ts +++ b/packages/compiler/test/i18n/serializers/xliff_spec.ts @@ -11,8 +11,8 @@ import {escapeRegExp} from '@angular/compiler/src/util'; import {serializeNodes} from '../../../src/i18n/digest'; import {MessageBundle} from '../../../src/i18n/message_bundle'; import {Xliff} from '../../../src/i18n/serializers/xliff'; +import {DEFAULT_INTERPOLATION_CONFIG} from '../../../src/ml_parser/defaults'; import {HtmlParser} from '../../../src/ml_parser/html_parser'; -import {DEFAULT_INTERPOLATION_CONFIG} from '../../../src/ml_parser/interpolation_config'; const HTML = `

not translatable

@@ -27,6 +27,7 @@ const HTML = `

Test: { count, plural, =0 { { sex, select, other {

deeply nested

}} } =other {a lot}}

multi lines

+

translatable element @if (foo) {with} @else if (bar) {blocks}

`; const WRITE_XLIFF = ` @@ -120,6 +121,13 @@ lines 12 + + translatable element with blocks + + file.ts + 14 + + @@ -221,6 +229,14 @@ lignes 12 + + translatable element with blocks + élément traduisible avec des blocs + + file.ts + 14 + + First sentence. @@ -240,26 +256,25 @@ lignes `; -(function() { -const serializer = new Xliff(); +describe('XLIFF serializer', () => { + const serializer = new Xliff(); -function toXliff(html: string, locale: string|null = null): string { - const catalog = new MessageBundle(new HtmlParser, [], {}, locale); - catalog.updateFromTemplate(html, 'file.ts', DEFAULT_INTERPOLATION_CONFIG); - return catalog.write(serializer); -} + function toXliff(html: string, locale: string|null = null): string { + const catalog = new MessageBundle(new HtmlParser, [], {}, locale); + catalog.updateFromTemplate(html, 'file.ts', DEFAULT_INTERPOLATION_CONFIG); + return catalog.write(serializer); + } -function loadAsMap(xliff: string): {[id: string]: string} { - const {i18nNodesByMsgId} = serializer.load(xliff, 'url'); + function loadAsMap(xliff: string): {[id: string]: string} { + const {i18nNodesByMsgId} = serializer.load(xliff, 'url'); - const msgMap: {[id: string]: string} = {}; - Object.keys(i18nNodesByMsgId) - .forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join('')); + const msgMap: {[id: string]: string} = {}; + Object.keys(i18nNodesByMsgId) + .forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join('')); - return msgMap; -} + return msgMap; + } -describe('XLIFF serializer', () => { describe('write', () => { it('should write a valid xliff file', () => { expect(toXliff(HTML)).toEqual(WRITE_XLIFF); @@ -291,6 +306,8 @@ describe('XLIFF serializer', () => { ' name="START_PARAGRAPH"/>, profondément imbriqué, ]}}, ]}, =other {[beaucoup]}}', 'fcfa109b0e152d4c217dbc02530be0bcb8123ad1': `multi lignes`, + '3e17847a6823c7777ca57c7338167badca0f4d19': + 'élément traduisible avec des blocs', 'mrk-test': 'Translated first sentence.', 'mrk-test2': 'Translated first sentence.' }); @@ -426,4 +443,3 @@ lignes`, }); }); }); -})(); diff --git a/packages/compiler/test/i18n/serializers/xmb_spec.ts b/packages/compiler/test/i18n/serializers/xmb_spec.ts index 384a82d99c993..c9c580ab9112a 100644 --- a/packages/compiler/test/i18n/serializers/xmb_spec.ts +++ b/packages/compiler/test/i18n/serializers/xmb_spec.ts @@ -8,8 +8,8 @@ import {MessageBundle} from '@angular/compiler/src/i18n/message_bundle'; import {Xmb} from '@angular/compiler/src/i18n/serializers/xmb'; +import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/defaults'; import {HtmlParser} from '@angular/compiler/src/ml_parser/html_parser'; -import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/interpolation_config'; describe('XMB serializer', () => { const HTML = ` @@ -22,7 +22,8 @@ describe('XMB serializer', () => {

{ count, plural, =0 { { sex, select, other {

deeply nested

}} }}

Test: { count, plural, =0 { { sex, select, other {

deeply nested

}} } =other {a lot}}

multi -lines

`; +lines

+

translatable element @if (foo) {with} @else if (bar) {blocks}

`; const XMB = ` `; 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 + file.ts:12translatable element @if@ifwith}} @else if@else ifblocks}} `; diff --git a/packages/compiler/test/render3/view/util.ts b/packages/compiler/test/render3/view/util.ts index 1de9ea2e03f56..afd5e95d1ae1e 100644 --- a/packages/compiler/test/render3/view/util.ts +++ b/packages/compiler/test/render3/view/util.ts @@ -10,9 +10,9 @@ import * as e from '../../../src/expression_parser/ast'; import {Lexer} from '../../../src/expression_parser/lexer'; import {Parser} from '../../../src/expression_parser/parser'; import * as html from '../../../src/ml_parser/ast'; +import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../src/ml_parser/defaults'; import {HtmlParser} from '../../../src/ml_parser/html_parser'; import {WhitespaceVisitor} from '../../../src/ml_parser/html_whitespaces'; -import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../src/ml_parser/interpolation_config'; import {ParseTreeResult} from '../../../src/ml_parser/parser'; import * as a from '../../../src/render3/r3_ast'; import {htmlAstToRender3Ast, Render3ParseResult} from '../../../src/render3/r3_template_transform';