diff --git a/modules/@angular/compiler/src/i18n/extractor.ts b/modules/@angular/compiler/src/i18n/extractor.ts index ccc9219e09..8709afcea5 100644 --- a/modules/@angular/compiler/src/i18n/extractor.ts +++ b/modules/@angular/compiler/src/i18n/extractor.ts @@ -7,8 +7,14 @@ */ import * as html from '../html_parser/ast'; -import {I18nError, I18N_ATTR_PREFIX, getI18nAttr, meaning, description, isOpeningComment, isClosingComment,} from './shared'; +import {I18nError} from './parse_util'; +const _I18N_ATTR = 'i18n'; +const _I18N_ATTR_PREFIX = 'i18n-'; + +/** + * Extract translatable message from an html AST as a list of html AST nodes + */ export function extractAstMessages( sourceAst: html.Node[], implicitTags: string[], implicitAttrs: {[k: string]: string[]}): ExtractionResult { @@ -79,14 +85,14 @@ class _ExtractVisitor implements html.Visitor { } visitComment(comment: html.Comment, messages: Message[]): any { - const isOpening = isOpeningComment(comment); + const isOpening = _isOpeningComment(comment); if (isOpening && (this._inI18nBlock || this._inI18nNode)) { this._reportError(comment, 'Could not start a block inside a translatable section'); return; } - const isClosing = isClosingComment(comment); + const isClosing = _isClosingComment(comment); if (isClosing && !this._inI18nBlock) { this._reportError(comment, 'Trying to close an unopened block'); @@ -127,7 +133,7 @@ class _ExtractVisitor implements html.Visitor { // Extract only top level nodes with the (implicit) "i18n" attribute if not in a block or an ICU // message - const i18nAttr = getI18nAttr(el); + const i18nAttr = _getI18nAttr(el); const isImplicitI18n = this._implicitTags.some((tagName: string): boolean => el.name === tagName); if (!(this._inI18nNode || this._inIcu || this._inI18nBlock)) { @@ -169,10 +175,10 @@ class _ExtractVisitor implements html.Visitor { const explicitAttrNameToValue: Map = new Map(); const implicitAttrNames: string[] = this._implicitAttrs[el.name] || []; - el.attrs.filter(attr => attr.name.startsWith(I18N_ATTR_PREFIX)) + el.attrs.filter(attr => attr.name.startsWith(_I18N_ATTR_PREFIX)) .forEach( attr => explicitAttrNameToValue.set( - attr.name.substring(I18N_ATTR_PREFIX.length), attr.value)); + attr.name.substring(_I18N_ATTR_PREFIX.length), attr.value)); el.attrs.forEach(attr => { if (explicitAttrNameToValue.has(attr.name)) { @@ -189,7 +195,7 @@ class _ExtractVisitor implements html.Visitor { // Do not create empty messages return; } - messages.push(new Message(ast, meaning(meaningAndDesc), description(meaningAndDesc))); + messages.push(new Message(ast, _meaning(meaningAndDesc), _description(meaningAndDesc))); } /** @@ -266,3 +272,26 @@ class _ExtractVisitor implements html.Visitor { export class Message { constructor(public nodes: html.Node[], public meaning: string, public description: string) {} } + +function _isOpeningComment(n: html.Node): boolean { + return n instanceof html.Comment && n.value && n.value.startsWith('i18n'); +} + +function _isClosingComment(n: html.Node): boolean { + return n instanceof html.Comment && n.value && n.value === '/i18n'; +} + +function _getI18nAttr(p: html.Element): html.Attribute { + return p.attrs.find(attr => attr.name === _I18N_ATTR) || null; +} + +function _meaning(i18n: string): string { + if (!i18n || i18n == '') return ''; + return i18n.split('|', 2)[0]; +} + +function _description(i18n: string): string { + if (!i18n || i18n == '') return ''; + const parts = i18n.split('|', 2); + return parts.length > 1 ? parts[1] : ''; +} \ No newline at end of file diff --git a/modules/@angular/compiler/src/i18n/i18n_parser.ts b/modules/@angular/compiler/src/i18n/i18n_parser.ts index acadda0ba9..7f4bfd8842 100644 --- a/modules/@angular/compiler/src/i18n/i18n_parser.ts +++ b/modules/@angular/compiler/src/i18n/i18n_parser.ts @@ -16,7 +16,6 @@ import {ParseSourceSpan} from '../parse_util'; import {extractAstMessages} from './extractor'; import * as i18n from './i18n_ast'; import {PlaceholderRegistry} from './serializers/placeholder'; -import {extractPlaceholderName} from './shared'; /** * Extract all the i18n messages from a component template. @@ -133,7 +132,7 @@ class _I18nVisitor implements html.Visitor { for (let i = 0; i < splitInterpolation.strings.length - 1; i++) { const expression = splitInterpolation.expressions[i]; - const baseName = extractPlaceholderName(expression) || 'INTERPOLATION'; + const baseName = _extractPlaceholderName(expression) || 'INTERPOLATION'; const phName = this._placeholderRegistry.getPlaceholderName(baseName, expression); if (splitInterpolation.strings[i].length) { @@ -153,3 +152,9 @@ class _I18nVisitor implements html.Visitor { return container; } } + +const _CUSTOM_PH_EXP = /\/\/[\s\S]*i18n[\s\S]*\([\s\S]*ph[\s\S]*=[\s\S]*"([\s\S]*?)"[\s\S]*\)/g; + +function _extractPlaceholderName(input: string): string { + return input.split(_CUSTOM_PH_EXP)[1]; +} diff --git a/modules/@angular/compiler/src/i18n/parse_util.ts b/modules/@angular/compiler/src/i18n/parse_util.ts new file mode 100644 index 0000000000..37acd859ac --- /dev/null +++ b/modules/@angular/compiler/src/i18n/parse_util.ts @@ -0,0 +1,16 @@ +/** + * @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 {ParseError, ParseSourceSpan} from '../parse_util'; + +/** + * An i18n error. + */ +export class I18nError extends ParseError { + constructor(span: ParseSourceSpan, msg: string) { super(span, msg); } +} diff --git a/modules/@angular/compiler/src/i18n/serializers/xtb.ts b/modules/@angular/compiler/src/i18n/serializers/xtb.ts index ede1e0ae69..bc4ee68ef4 100644 --- a/modules/@angular/compiler/src/i18n/serializers/xtb.ts +++ b/modules/@angular/compiler/src/i18n/serializers/xtb.ts @@ -12,7 +12,7 @@ import {InterpolationConfig} from '../../html_parser/interpolation_config'; import {XmlParser} from '../../html_parser/xml_parser'; import {ParseError} from '../../parse_util'; import * as i18n from '../i18n_ast'; -import {I18nError} from '../shared'; +import {I18nError} from '../parse_util'; import {Serializer} from './serializer'; diff --git a/modules/@angular/compiler/src/i18n/shared.ts b/modules/@angular/compiler/src/i18n/shared.ts deleted file mode 100644 index dd43e59730..0000000000 --- a/modules/@angular/compiler/src/i18n/shared.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @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 {StringWrapper, isBlank, isPresent, normalizeBlank} from '../facade/lang'; -import * as html from '../html_parser/ast'; -import {ParseError, ParseSourceSpan} from '../parse_util'; - - -export const I18N_ATTR = 'i18n'; -export const I18N_ATTR_PREFIX = 'i18n-'; -const _CUSTOM_PH_EXP = /\/\/[\s\S]*i18n[\s\S]*\([\s\S]*ph[\s\S]*=[\s\S]*"([\s\S]*?)"[\s\S]*\)/g; - -/** - * An i18n error. - */ -export class I18nError extends ParseError { - constructor(span: ParseSourceSpan, msg: string) { super(span, msg); } -} - -export function isOpeningComment(n: html.Node): boolean { - return n instanceof html.Comment && isPresent(n.value) && n.value.startsWith('i18n'); -} - -export function isClosingComment(n: html.Node): boolean { - return n instanceof html.Comment && isPresent(n.value) && n.value === '/i18n'; -} - -export function getI18nAttr(p: html.Element): html.Attribute { - return normalizeBlank(p.attrs.find(attr => attr.name === I18N_ATTR)); -} - -export function meaning(i18n: string): string { - if (isBlank(i18n) || i18n == '') return ''; - return i18n.split('|')[0]; -} - -export function description(i18n: string): string { - if (isBlank(i18n) || i18n == '') return ''; - let parts = i18n.split('|', 2); - return parts.length > 1 ? parts[1] : ''; -} - -export function extractPlaceholderName(input: string): string { - return StringWrapper.split(input, _CUSTOM_PH_EXP)[1]; -} \ No newline at end of file