feat(compiler): allow missing translations (#14113)

closes #13861
This commit is contained in:
Gion Kunz
2017-01-10 14:14:41 +01:00
committed by Miško Hevery
parent 5885c52c1f
commit 8775ab9495
9 changed files with 114 additions and 25 deletions

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {MissingTranslationStrategy} from '@angular/core';
import {HtmlParser} from '../ml_parser/html_parser';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
import {ParseTreeResult} from '../ml_parser/parser';
@ -26,7 +28,9 @@ export class I18NHtmlParser implements HtmlParser {
// TODO(vicb): remove the interpolationConfig from the Xtb serializer
constructor(
private _htmlParser: HtmlParser, private _translations?: string,
private _translationsFormat?: string) {}
private _translationsFormat?: string,
private _missingTranslationStrategy:
MissingTranslationStrategy = MissingTranslationStrategy.Error) {}
parse(
source: string, url: string, parseExpansionForms: boolean = false,
@ -46,7 +50,8 @@ export class I18NHtmlParser implements HtmlParser {
}
const serializer = this._createSerializer();
const translationBundle = TranslationBundle.load(this._translations, url, serializer);
const translationBundle = TranslationBundle.load(
this._translations, url, serializer, this._missingTranslationStrategy);
return mergeTranslations(parseResult.rootNodes, translationBundle, interpolationConfig, [], {});
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ParseError, ParseSourceSpan} from '../parse_util';
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
/**
* An i18n error.
@ -14,3 +14,7 @@ import {ParseError, ParseSourceSpan} from '../parse_util';
export class I18nError extends ParseError {
constructor(span: ParseSourceSpan, msg: string) { super(span, msg); }
}
export class I18nWarning extends ParseError {
constructor(span: ParseSourceSpan, msg: string) { super(span, msg, ParseErrorLevel.WARNING); }
}

View File

@ -6,11 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/
import {MissingTranslationStrategy} from '@angular/core';
import {warn} from '../facade/lang';
import * as html from '../ml_parser/ast';
import {HtmlParser} from '../ml_parser/html_parser';
import {serializeNodes} from './digest';
import * as i18n from './i18n_ast';
import {I18nError} from './parse_util';
import {I18nError, I18nWarning} from './parse_util';
import {PlaceholderMapper, Serializer} from './serializers/serializer';
/**
@ -22,22 +26,31 @@ export class TranslationBundle {
constructor(
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
public digest: (m: i18n.Message) => string,
public mapperFactory?: (m: i18n.Message) => PlaceholderMapper) {
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest, mapperFactory);
public mapperFactory?: (m: i18n.Message) => PlaceholderMapper,
missingTranslationStrategy: MissingTranslationStrategy = MissingTranslationStrategy.Warning) {
this._i18nToHtml =
new I18nToHtmlVisitor(_i18nNodesByMsgId, digest, mapperFactory, missingTranslationStrategy);
}
// Creates a `TranslationBundle` by parsing the given `content` with the `serializer`.
static load(content: string, url: string, serializer: Serializer): TranslationBundle {
static load(
content: string, url: string, serializer: Serializer,
missingTranslationStrategy: MissingTranslationStrategy): TranslationBundle {
const i18nNodesByMsgId = serializer.load(content, url);
const digestFn = (m: i18n.Message) => serializer.digest(m);
const mapperFactory = (m: i18n.Message) => serializer.createNameMapper(m);
return new TranslationBundle(i18nNodesByMsgId, digestFn, mapperFactory);
return new TranslationBundle(
i18nNodesByMsgId, digestFn, mapperFactory, missingTranslationStrategy);
}
// Returns the translation as HTML nodes from the given source message.
get(srcMsg: i18n.Message): html.Node[] {
const html = this._i18nToHtml.convert(srcMsg);
if (html.warnings.length) {
warn(html.warnings.join('\n'));
}
if (html.errors.length) {
throw new Error(html.errors.join('\n'));
}
@ -53,15 +66,19 @@ class I18nToHtmlVisitor implements i18n.Visitor {
private _contextStack: {msg: i18n.Message, mapper: (name: string) => string}[] = [];
private _errors: I18nError[] = [];
private _mapper: (name: string) => string;
private _warnings: I18nWarning[] = [];
constructor(
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
private _digest: (m: i18n.Message) => string,
private _mapperFactory: (m: i18n.Message) => PlaceholderMapper) {}
private _mapperFactory: (m: i18n.Message) => PlaceholderMapper,
private _missingTranslationStrategy: MissingTranslationStrategy) {}
convert(srcMsg: i18n.Message): {nodes: html.Node[], errors: I18nError[]} {
convert(srcMsg: i18n.Message):
{nodes: html.Node[], errors: I18nError[], warnings: I18nWarning[]} {
this._contextStack.length = 0;
this._errors.length = 0;
// i18n to text
const text = this._convertToText(srcMsg);
@ -72,6 +89,7 @@ class I18nToHtmlVisitor implements i18n.Visitor {
return {
nodes: html.rootNodes,
errors: [...this._errors, ...html.errors],
warnings: this._warnings
};
}
@ -134,11 +152,22 @@ class I18nToHtmlVisitor implements i18n.Visitor {
return text;
}
this._addError(srcMsg.nodes[0], `Missing translation for message ${digest}`);
return '';
// No valid translation found
if (this._missingTranslationStrategy === MissingTranslationStrategy.Error) {
this._addError(srcMsg.nodes[0], `Missing translation for message ${digest}`);
} else if (this._missingTranslationStrategy === MissingTranslationStrategy.Warning) {
this._addWarning(srcMsg.nodes[0], `Missing translation for message ${digest}`);
}
// In an case, Warning, Error or Ignore, return the srcMsg without translation
return serializeNodes(srcMsg.nodes).join('');
}
private _addError(el: i18n.Node, msg: string) {
this._errors.push(new I18nError(el.sourceSpan, msg));
}
}
private _addWarning(el: i18n.Node, msg: string) {
this._warnings.push(new I18nWarning(el.sourceSpan, msg));
}
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, InjectionToken, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, InjectionToken, MISSING_TRANSLATION_STRATEGY, MissingTranslationStrategy, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {AnimationParser} from '../animation/animation_parser';
import {CompilerConfig} from '../config';
@ -60,12 +60,15 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
},
{
provide: i18n.I18NHtmlParser,
useFactory: (parser: HtmlParser, translations: string, format: string) =>
new i18n.I18NHtmlParser(parser, translations, format),
useFactory:
(parser: HtmlParser, translations: string, format: string,
missingTranslationStrategy: MissingTranslationStrategy) =>
new i18n.I18NHtmlParser(parser, translations, format, missingTranslationStrategy),
deps: [
baseHtmlParser,
[new Optional(), new Inject(TRANSLATIONS)],
[new Optional(), new Inject(TRANSLATIONS_FORMAT)],
[new Optional(), new Inject(MISSING_TRANSLATION_STRATEGY)],
]
},
{