feat(i18n): add an HtmlParser decorator (#10645)
* fix(i18n): merge retains attributes w/o value * feat(i18n): allow attributes on ng-container (i.e. i18n) * feat(i18n): add an HtmlParser decorator * style: clang format
This commit is contained in:
parent
7606c96c80
commit
50345b8c36
|
@ -132,7 +132,7 @@ export class CodeGenerator {
|
|||
const reflectorHost = new ReflectorHost(program, compilerHost, options, reflectorHostContext);
|
||||
const staticReflector = new StaticReflector(reflectorHost);
|
||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||
const htmlParser = new HtmlParser();
|
||||
const htmlParser = new compiler.i18n.HtmlParser(new HtmlParser());
|
||||
const config = new compiler.CompilerConfig({
|
||||
genDebugInfo: options.debug === true,
|
||||
defaultEncapsulation: ViewEncapsulation.Emulated,
|
||||
|
|
|
@ -142,7 +142,7 @@ export class Extractor {
|
|||
const reflectorHost = new ReflectorHost(program, compilerHost, options, reflectorHostContext);
|
||||
const staticReflector = new StaticReflector(reflectorHost);
|
||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||
const htmlParser = new HtmlParser();
|
||||
const htmlParser = new compiler.i18n.HtmlParser(new HtmlParser());
|
||||
|
||||
const config = new compiler.CompilerConfig({
|
||||
genDebugInfo: options.debug === true,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Component, Inject, Injectable, PLATFORM_DIRECTIVES, PLATFORM_INITIALIZER, PLATFORM_PIPES, PlatformRef, ReflectiveInjector, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Component, Inject, Injectable, OptionalMetadata, PLATFORM_DIRECTIVES, PLATFORM_INITIALIZER, PLATFORM_PIPES, PlatformRef, Provider, ReflectiveInjector, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||
|
||||
export * from './template_parser/template_ast';
|
||||
export {TEMPLATE_TRANSFORMS} from './template_parser/template_parser';
|
||||
|
@ -42,6 +42,7 @@ import {PipeResolver} from './pipe_resolver';
|
|||
import {NgModuleResolver} from './ng_module_resolver';
|
||||
import {Console, Reflector, reflector, ReflectorReader, ReflectionCapabilities} from '../core_private';
|
||||
import {XHR} from './xhr';
|
||||
import * as i18n from './i18n/index';
|
||||
|
||||
const _NO_XHR: XHR = {
|
||||
get(url: string): Promise<string>{
|
||||
|
@ -60,6 +61,12 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
|
|||
Lexer,
|
||||
Parser,
|
||||
HtmlParser,
|
||||
{
|
||||
provide: i18n.HtmlParser,
|
||||
useFactory: (parser: HtmlParser, translations: string) =>
|
||||
new i18n.HtmlParser(parser, translations),
|
||||
deps: [HtmlParser, [new OptionalMetadata(), new Inject(i18n.TRANSLATIONS)]]
|
||||
},
|
||||
TemplateParser,
|
||||
DirectiveNormalizer,
|
||||
CompileMetadataResolver,
|
||||
|
@ -78,7 +85,6 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
|
|||
NgModuleResolver
|
||||
];
|
||||
|
||||
|
||||
export function analyzeAppProvidersForDeprecatedConfiguration(appProviders: any[] = []): {
|
||||
compilerOptions: CompilerOptions,
|
||||
moduleDeclarations: Type<any>[],
|
||||
|
|
|
@ -225,11 +225,11 @@ class TemplatePreparseVisitor implements html.Visitor {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
visitComment(ast: html.Comment, context: any): any { return null; }
|
||||
visitAttribute(ast: html.Attribute, context: any): any { return null; }
|
||||
visitText(ast: html.Text, context: any): any { return null; }
|
||||
visitExpansion(ast: html.Expansion, context: any): any { return null; }
|
||||
|
||||
visitExpansionCase(ast: html.ExpansionCase, context: any): any { return null; }
|
||||
}
|
||||
|
||||
|
|
|
@ -111,6 +111,7 @@ class _Visitor implements html.Visitor {
|
|||
|
||||
const translatedNode = wrapper.visit(this, null);
|
||||
|
||||
// TODO(vicb): return MergeResult with errors
|
||||
if (this._inI18nBlock) {
|
||||
this._reportError(nodes[nodes.length - 1], 'Unclosed block');
|
||||
}
|
||||
|
@ -184,7 +185,9 @@ class _Visitor implements html.Visitor {
|
|||
this._closeTranslatableSection(comment, this._blockChildren);
|
||||
this._inI18nBlock = false;
|
||||
const message = this._addMessage(this._blockChildren, this._blockMeaningAndDesc);
|
||||
return this._translateMessage(comment, message);
|
||||
// merge attributes in sections
|
||||
const nodes = this._translateMessage(comment, message);
|
||||
return html.visitAll(this, nodes);
|
||||
} else {
|
||||
this._reportError(comment, 'I18N blocks should not cross element boundaries');
|
||||
return;
|
||||
|
@ -341,7 +344,8 @@ class _Visitor implements html.Visitor {
|
|||
return message;
|
||||
}
|
||||
|
||||
// translate the given message given the `TranslationBundle`
|
||||
// Translates the given message given the `TranslationBundle`
|
||||
// no-op when called in extraction mode (returns [])
|
||||
private _translateMessage(el: html.Node, message: i18n.Message): html.Node[] {
|
||||
if (message && this._mode === _VisitorMode.Merge) {
|
||||
const id = digestMessage(message);
|
||||
|
@ -377,7 +381,7 @@ class _Visitor implements html.Visitor {
|
|||
return;
|
||||
}
|
||||
|
||||
if (i18nAttributeMeanings.hasOwnProperty(attr.name)) {
|
||||
if (attr.value && attr.value != '' && i18nAttributeMeanings.hasOwnProperty(attr.name)) {
|
||||
const meaning = i18nAttributeMeanings[attr.name];
|
||||
const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
|
||||
const id = digestMessage(message);
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {HtmlParser as BaseHtmlParser} from '../ml_parser/html_parser';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {ParseTreeResult} from '../ml_parser/parser';
|
||||
|
||||
import {mergeTranslations} from './extractor_merger';
|
||||
import {MessageBundle} from './message_bundle';
|
||||
import {Xtb} from './serializers/xtb';
|
||||
import {TranslationBundle} from './translation_bundle';
|
||||
|
||||
export class HtmlParser implements BaseHtmlParser {
|
||||
// @override
|
||||
public getTagDefinition: any;
|
||||
|
||||
// TODO(vicb): transB.load() should not need a msgB & add transB.resolve(msgB,
|
||||
// interpolationConfig)
|
||||
// TODO(vicb): remove the interpolationConfig from the Xtb serializer
|
||||
constructor(private _htmlParser: BaseHtmlParser, private _translations?: string) {}
|
||||
|
||||
parse(
|
||||
source: string, url: string, parseExpansionForms: boolean = false,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ParseTreeResult {
|
||||
const parseResult =
|
||||
this._htmlParser.parse(source, url, parseExpansionForms, interpolationConfig);
|
||||
|
||||
if (!this._translations || this._translations === '') {
|
||||
// Do not enable i18n when no translation bundle is provided
|
||||
return parseResult;
|
||||
}
|
||||
|
||||
// TODO(vicb): add support for implicit tags / attributes
|
||||
const messageBundle = new MessageBundle(this._htmlParser, [], {});
|
||||
const errors = messageBundle.updateFromTemplate(source, url, interpolationConfig);
|
||||
|
||||
if (errors && errors.length) {
|
||||
return new ParseTreeResult(parseResult.rootNodes, parseResult.errors.concat(errors));
|
||||
}
|
||||
|
||||
const xtb = new Xtb(this._htmlParser, interpolationConfig);
|
||||
const translationBundle = TranslationBundle.load(this._translations, url, messageBundle, xtb);
|
||||
|
||||
const translatedNodes =
|
||||
mergeTranslations(parseResult.rootNodes, translationBundle, interpolationConfig, [], {});
|
||||
|
||||
return new ParseTreeResult(translatedNodes, []);
|
||||
}
|
||||
}
|
|
@ -6,7 +6,12 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {OpaqueToken} from '@angular/core';
|
||||
|
||||
export {HtmlParser} from './html_parser';
|
||||
export {MessageBundle} from './message_bundle';
|
||||
export {Serializer} from './serializers/serializer';
|
||||
export {Xmb} from './serializers/xmb';
|
||||
export {Xtb} from './serializers/xtb';
|
||||
|
||||
export const TRANSLATIONS = new OpaqueToken('Translations');
|
||||
|
|
|
@ -15,7 +15,6 @@ import {extractMessages} from './extractor_merger';
|
|||
import {Message} from './i18n_ast';
|
||||
import {Serializer} from './serializers/serializer';
|
||||
|
||||
|
||||
/**
|
||||
* A container for message extracted from the templates.
|
||||
*/
|
||||
|
|
|
@ -28,15 +28,15 @@ export class ParseTreeResult {
|
|||
}
|
||||
|
||||
export class Parser {
|
||||
constructor(private _getTagDefinition: (tagName: string) => TagDefinition) {}
|
||||
constructor(public getTagDefinition: (tagName: string) => TagDefinition) {}
|
||||
|
||||
parse(
|
||||
source: string, url: string, parseExpansionForms: boolean = false,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ParseTreeResult {
|
||||
const tokensAndErrors =
|
||||
lex.tokenize(source, url, this._getTagDefinition, parseExpansionForms, interpolationConfig);
|
||||
lex.tokenize(source, url, this.getTagDefinition, parseExpansionForms, interpolationConfig);
|
||||
|
||||
const treeAndErrors = new _TreeBuilder(tokensAndErrors.tokens, this._getTagDefinition).build();
|
||||
const treeAndErrors = new _TreeBuilder(tokensAndErrors.tokens, this.getTagDefinition).build();
|
||||
|
||||
return new ParseTreeResult(
|
||||
treeAndErrors.rootNodes,
|
||||
|
|
|
@ -14,9 +14,10 @@ import {AST, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, ParserError,
|
|||
import {Parser} from '../expression_parser/parser';
|
||||
import {ListWrapper, SetWrapper, StringMapWrapper} from '../facade/collection';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
import {HtmlParser} from '../i18n/html_parser';
|
||||
import {Identifiers, identifierToken} from '../identifiers';
|
||||
import * as html from '../ml_parser/ast';
|
||||
import {HtmlParser, ParseTreeResult} from '../ml_parser/html_parser';
|
||||
import {ParseTreeResult} from '../ml_parser/html_parser';
|
||||
import {expandNodes} from '../ml_parser/icu_ast_expander';
|
||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {mergeNsAndName, splitNsName} from '../ml_parser/tags';
|
||||
|
@ -65,7 +66,7 @@ const TEXT_CSS_SELECTOR = CssSelector.parse('*')[0];
|
|||
*
|
||||
* This is currently an internal-only feature and not meant for general use.
|
||||
*/
|
||||
export const TEMPLATE_TRANSFORMS: any = new OpaqueToken('TemplateTransforms');
|
||||
export const TEMPLATE_TRANSFORMS = new OpaqueToken('TemplateTransforms');
|
||||
|
||||
export class TemplateParseError extends ParseError {
|
||||
constructor(message: string, span: ParseSourceSpan, level: ParseErrorLevel) {
|
||||
|
|
|
@ -193,14 +193,17 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
|
|||
var htmlAttrs = _readHtmlAttrs(ast.attrs);
|
||||
var attrNameAndValues = _mergeHtmlAndDirectiveAttrs(htmlAttrs, directives);
|
||||
for (var i = 0; i < attrNameAndValues.length; i++) {
|
||||
var attrName = attrNameAndValues[i][0];
|
||||
var attrValue = attrNameAndValues[i][1];
|
||||
const attrName = attrNameAndValues[i][0];
|
||||
if (ast.name !== NG_CONTAINER_TAG) {
|
||||
// <ng-container> are not rendered in the DOM
|
||||
const attrValue = attrNameAndValues[i][1];
|
||||
this.view.createMethod.addStmt(
|
||||
ViewProperties.renderer
|
||||
.callMethod(
|
||||
'setElementAttribute', [renderNode, o.literal(attrName), o.literal(attrValue)])
|
||||
.toStmt());
|
||||
}
|
||||
}
|
||||
var compileElement = new CompileElement(
|
||||
parent, this.view, nodeIndex, renderNode, ast, component, directives, ast.providers,
|
||||
ast.hasViewContainer, false, ast.references);
|
||||
|
|
|
@ -369,11 +369,16 @@ export function main() {
|
|||
expect(fakeTranslate(HTML)).toEqual('<p title="**foo**"></p>');
|
||||
});
|
||||
|
||||
it('should merge attributes', () => {
|
||||
it('should merge nested attributes', () => {
|
||||
const HTML = `<div>{count, plural, =0 {<p i18n-title title="foo"></p>}}</div>`;
|
||||
expect(fakeTranslate(HTML))
|
||||
.toEqual('<div>{count, plural, =0 {<p title="**foo**"></p>}}</div>');
|
||||
});
|
||||
|
||||
it('should merge attributes without values', () => {
|
||||
const HTML = `<p i18n-title="m|d" title=""></p>`;
|
||||
expect(fakeTranslate(HTML)).toEqual('<p title=""></p>');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {DirectiveResolver, XHR, i18n} from '@angular/compiler';
|
||||
import {MockDirectiveResolver} from '@angular/compiler/testing';
|
||||
import {Compiler, Component, DebugElement, Injector} from '@angular/core';
|
||||
import {TestBed, TestComponentBuilder, fakeAsync} from '@angular/core/testing';
|
||||
import {beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it, xdescribe, xit,} from '@angular/core/testing/testing_internal';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {SpyXHR} from '../spies';
|
||||
import {NgLocalization} from '@angular/common';
|
||||
import {stringifyElement} from '@angular/platform-browser/testing/browser_util';
|
||||
|
||||
export function main() {
|
||||
describe('i18n integration spec', () => {
|
||||
let compiler: Compiler;
|
||||
let xhr: SpyXHR;
|
||||
let tcb: TestComponentBuilder;
|
||||
let dirResolver: MockDirectiveResolver;
|
||||
let injector: Injector;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureCompiler({
|
||||
providers: [
|
||||
{provide: XHR, useClass: SpyXHR},
|
||||
{provide: NgLocalization, useClass: FrLocalization},
|
||||
{provide: i18n.TRANSLATIONS, useValue: XTB},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(fakeAsync(inject(
|
||||
[Compiler, TestComponentBuilder, XHR, DirectiveResolver, Injector],
|
||||
(_compiler: Compiler, _tcb: TestComponentBuilder, _xhr: SpyXHR,
|
||||
_dirResolver: MockDirectiveResolver, _injector: Injector) => {
|
||||
compiler = _compiler;
|
||||
tcb = _tcb;
|
||||
xhr = _xhr;
|
||||
dirResolver = _dirResolver;
|
||||
injector = _injector;
|
||||
})));
|
||||
|
||||
it('translate templates', () => {
|
||||
const tb = tcb.createSync(I18nComponent);
|
||||
const cmp = tb.componentInstance;
|
||||
const el = tb.debugElement;
|
||||
|
||||
expectHtml(el, 'h1').toBe('<h1>attributs i18n sur les balises</h1>');
|
||||
expectHtml(el, '#i18n-1').toBe('<div id="i18n-1"><p>imbriqué</p></div>');
|
||||
expectHtml(el, '#i18n-2').toBe('<div id="i18n-2"><p>imbriqué</p></div>');
|
||||
expectHtml(el, '#i18n-3')
|
||||
.toBe('<div id="i18n-3"><p><i>avec des espaces réservés</i></p></div>');
|
||||
expectHtml(el, '#i18n-4')
|
||||
.toBe('<p id="i18n-4" title="sur des balises non traductibles"></p>');
|
||||
expectHtml(el, '#i18n-5').toBe('<p id="i18n-5" title="sur des balises traductibles"></p>');
|
||||
expectHtml(el, '#i18n-6').toBe('<p id="i18n-6" title=""></p>');
|
||||
|
||||
cmp.count = 0;
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('zero');
|
||||
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('zero');
|
||||
cmp.count = 1;
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('un');
|
||||
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('un');
|
||||
cmp.count = 2;
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('deux');
|
||||
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('deux');
|
||||
cmp.count = 3;
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('beaucoup');
|
||||
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('beaucoup');
|
||||
|
||||
cmp.sex = 'm';
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-8')).nativeElement).toHaveText('homme');
|
||||
cmp.sex = 'f';
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-8')).nativeElement).toHaveText('femme');
|
||||
|
||||
cmp.count = 123;
|
||||
tb.detectChanges();
|
||||
expectHtml(el, '#i18n-9').toEqual('<div id="i18n-9">count = 123</div>');
|
||||
|
||||
cmp.sex = 'f';
|
||||
tb.detectChanges();
|
||||
expectHtml(el, '#i18n-10').toEqual('<div id="i18n-10">sexe = f</div>');
|
||||
|
||||
expectHtml(el, '#i18n-11').toEqual('<div id="i18n-11">custom name</div>');
|
||||
expectHtml(el, '#i18n-12')
|
||||
.toEqual('<h1 id="i18n-12">Balises dans les commentaires html</h1>');
|
||||
expectHtml(el, '#i18n-13')
|
||||
.toBe('<div id="i18n-13" title="dans une section traductible"></div>');
|
||||
|
||||
expectHtml(el, '#i18n-15').toMatch(/ca <b>devrait<\/b> marcher/);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function expectHtml(el: DebugElement, cssSelector: string): any {
|
||||
return expect(stringifyElement(el.query(By.css(cssSelector)).nativeElement));
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'i18n-cmp',
|
||||
template: `
|
||||
<div>
|
||||
<h1 i18n>i18n attribute on tags</h1>
|
||||
|
||||
<div id="i18n-1"><p i18n>nested</p></div>
|
||||
|
||||
<div id="i18n-2"><p i18n="different meaning|">nested</p></div>
|
||||
|
||||
<div id="i18n-3"><p i18n><i>with placeholders</i></p></div>
|
||||
|
||||
<div>
|
||||
<p id="i18n-4" i18n-title title="on not translatable node"></p>
|
||||
<p id="i18n-5" i18n i18n-title title="on translatable node"></p>
|
||||
<p id="i18n-6" i18n-title title></p>
|
||||
</div>
|
||||
|
||||
<!-- no ph below because the ICU node is the only child of the div, i.e. no text nodes -->
|
||||
<div i18n id="i18n-7">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
|
||||
|
||||
<div i18n id="i18n-8">
|
||||
{sex, sex, m {male} f {female}}
|
||||
</div>
|
||||
|
||||
<div i18n id="i18n-9">{{ "count = " + count }}</div>
|
||||
<div i18n id="i18n-10">sex = {{ sex }}</div>
|
||||
<div i18n id="i18n-11">{{ "custom name" //i18n(ph="CUSTOM_NAME") }}</div>
|
||||
</div>
|
||||
|
||||
<!-- i18n -->
|
||||
<h1 id="i18n-12" >Markers in html comments</h1>
|
||||
<div id="i18n-13" i18n-title title="in a translatable section"></div>
|
||||
<div id="i18n-14">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
|
||||
<!-- /i18n -->
|
||||
|
||||
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div>
|
||||
`
|
||||
})
|
||||
class I18nComponent {
|
||||
count: number = 0;
|
||||
sex: string = 'm';
|
||||
}
|
||||
|
||||
class FrLocalization extends NgLocalization {
|
||||
getPluralCategory(value: number): string {
|
||||
switch (value) {
|
||||
case 0:
|
||||
case 1:
|
||||
return 'one';
|
||||
default:
|
||||
return 'other';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const XTB = `
|
||||
<translationbundle>
|
||||
<translation id="3cb04208df1c2f62553ed48e75939cf7107f9dad">attributs i18n sur les balises</translation>
|
||||
<translation id="52895b1221effb3f3585b689f049d2784d714952">imbriqué</translation>
|
||||
<translation id="88d5f22050a9df477ee5646153558b3a4862d47e">imbriqué</translation>
|
||||
<translation id="34fec9cc62e28e8aa6ffb306fa8569ef0a8087fe"><ph name="START_ITALIC_TEXT"/>avec des espaces réservés<ph name="CLOSE_ITALIC_TEXT"/></translation>
|
||||
<translation id="1fe4616cce80a57c7707bac1c97054aa8e244a67">sur des balises non traductibles</translation>
|
||||
<translation id="67162b5af5f15fd0eb6480c88688dafdf952b93a">sur des balises traductibles</translation>
|
||||
<translation id="dc5536bb9e0e07291c185a0d306601a2ecd4813f">{count, plural, =0 {zero} =1 {un} =2 {deux} other {<ph name="START_BOLD_TEXT"/>beaucoup<ph name="CLOSE_BOLD_TEXT"/>}}</translation>
|
||||
<translation id="018efa03821ca41e27611e4a584736810d56ed8a"><ph name="ICU"/></translation>
|
||||
<translation id="fd3186ad2a9aa801fe072ddb16ca34cd98ae93da">{sex, sex, m {homme} f {femme}}</translation>
|
||||
<translation id="d9879678f727b244bc7c7e20f22b63d98cb14890"><ph name="INTERPOLATION"/></translation>
|
||||
<translation id="50dac33dc6fc0578884baac79d875785ed77c928">sexe = <ph name="INTERPOLATION"/></translation>
|
||||
<translation id="a46f833b1fe6ca49e8b97c18f4b7ea0b930c9383"><ph name="CUSTOM_NAME"/></translation>
|
||||
<translation id="2ec983b4893bcd5b24af33bebe3ecba63868453c">dans une section traductible</translation>
|
||||
<translation id="eee74a5be8a75881a4785905bd8302a71f7d9f75">
|
||||
<ph name="START_HEADING_LEVEL1"/>Balises dans les commentaires html<ph name="CLOSE_HEADING_LEVEL1"/>
|
||||
<ph name="START_TAG_DIV"/><ph name="CLOSE_TAG_DIV"/>
|
||||
<ph name="START_TAG_DIV_1"/><ph name="ICU"/><ph name="CLOSE_TAG_DIV"></ph>
|
||||
</translation>
|
||||
<translation id="93a30c67d4e6c9b37aecfe2ac0f2b5d366d7b520">ca <ph name="START_BOLD_TEXT"/>devrait<ph name="CLOSE_BOLD_TEXT"/> marcher</translation>
|
||||
</translationbundle>`;
|
||||
|
||||
// unused, for reference only
|
||||
// can be generated from xmb_spec as follow:
|
||||
// `iit('extract xmb', () => { console.log(toXmb(HTML)); });`
|
||||
const XMB = `
|
||||
<messagebundle>
|
||||
<msg id="3cb04208df1c2f62553ed48e75939cf7107f9dad">i18n attribute on tags</msg>
|
||||
<msg id="52895b1221effb3f3585b689f049d2784d714952">nested</msg>
|
||||
<msg id="88d5f22050a9df477ee5646153558b3a4862d47e" meaning="different meaning">nested</msg>
|
||||
<msg id="34fec9cc62e28e8aa6ffb306fa8569ef0a8087fe"><ph name="START_ITALIC_TEXT"><ex><i></ex></ph>with placeholders<ph name="CLOSE_ITALIC_TEXT"><ex></i></ex></ph></msg>
|
||||
<msg id="1fe4616cce80a57c7707bac1c97054aa8e244a67">on not translatable node</msg>
|
||||
<msg id="67162b5af5f15fd0eb6480c88688dafdf952b93a">on translatable node</msg>
|
||||
<msg id="dc5536bb9e0e07291c185a0d306601a2ecd4813f">{count, plural, =0 {zero}=1 {one}=2 {two}other {<ph name="START_BOLD_TEXT"><ex><b></ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>}}</msg>
|
||||
<msg id="018efa03821ca41e27611e4a584736810d56ed8a">
|
||||
<ph name="ICU"/>
|
||||
</msg>
|
||||
<msg id="fd3186ad2a9aa801fe072ddb16ca34cd98ae93da">{sex, sex, m {male}f {female}}</msg>
|
||||
<msg id="d9879678f727b244bc7c7e20f22b63d98cb14890"><ph name="INTERPOLATION"/></msg>
|
||||
<msg id="50dac33dc6fc0578884baac79d875785ed77c928">sex = <ph name="INTERPOLATION"/></msg>
|
||||
<msg id="a46f833b1fe6ca49e8b97c18f4b7ea0b930c9383"><ph name="CUSTOM_NAME"/></msg>
|
||||
<msg id="2ec983b4893bcd5b24af33bebe3ecba63868453c">in a translatable section</msg>
|
||||
<msg id="eee74a5be8a75881a4785905bd8302a71f7d9f75">
|
||||
<ph name="START_HEADING_LEVEL1"><ex><h1></ex></ph>Markers in html comments<ph name="CLOSE_HEADING_LEVEL1"><ex></h1></ex></ph>
|
||||
<ph name="START_TAG_DIV"><ex><div></ex></ph><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
<ph name="START_TAG_DIV_1"><ex><div></ex></ph><ph name="ICU"/><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
</msg>
|
||||
<msg id="93a30c67d4e6c9b37aecfe2ac0f2b5d366d7b520">it <ph name="START_BOLD_TEXT"><ex><b></ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph> work</msg>
|
||||
</messagebundle>`;
|
|
@ -23,6 +23,22 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||
|
||||
beforeEach(() => { TestBed.configureCompiler({useJit: useJit}); });
|
||||
|
||||
it('should support the "i18n" attribute',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb.overrideTemplate(MyComp, '<ng-container i18n>foo</ng-container>')
|
||||
.createAsync(MyComp)
|
||||
.then((fixture) => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const el = fixture.debugElement.nativeElement;
|
||||
expect(el).toHaveText('foo');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should be rendered as comment with children as siblings',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
|
|
Loading…
Reference in New Issue