113 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			113 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | /** | ||
|  |  * @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} from '../html_parser'; | ||
|  | import {InterpolationConfig} from '../interpolation_config'; | ||
|  | 
 | ||
|  | import * as i18nAst from './i18n_ast'; | ||
|  | import {extractI18nMessages} from './i18n_parser'; | ||
|  | import {Serializer} from './serializers/serializer'; | ||
|  | 
 | ||
|  | export class Catalog { | ||
|  |   private _messageMap: {[k: string]: i18nAst.Message} = {}; | ||
|  | 
 | ||
|  |   constructor( | ||
|  |       private _htmlParser: HtmlParser, private _implicitTags: string[], | ||
|  |       private _implicitAttrs: {[k: string]: string[]}) {} | ||
|  | 
 | ||
|  |   public updateFromTemplate(html: string, url: string, interpolationConfig: InterpolationConfig): | ||
|  |       void { | ||
|  |     const htmlParserResult = this._htmlParser.parse(html, url, true, interpolationConfig); | ||
|  | 
 | ||
|  |     if (htmlParserResult.errors.length) { | ||
|  |       throw new Error(); | ||
|  |     } | ||
|  | 
 | ||
|  |     const messages = extractI18nMessages( | ||
|  |         htmlParserResult.rootNodes, interpolationConfig, this._implicitTags, this._implicitAttrs); | ||
|  | 
 | ||
|  |     messages.forEach((message) => { | ||
|  |       const id = strHash(serializeAst(message.nodes).join('') + `[${message.meaning}]`); | ||
|  |       this._messageMap[id] = message; | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   public load(content: string, serializer: Serializer): void { | ||
|  |     const nodeMap = serializer.load(content); | ||
|  |     this._messageMap = {}; | ||
|  | 
 | ||
|  |     Object.getOwnPropertyNames(nodeMap).forEach( | ||
|  |         (id) => { this._messageMap[id] = new i18nAst.Message(nodeMap[id], '', ''); }); | ||
|  |   } | ||
|  | 
 | ||
|  |   public write(serializer: Serializer): string { return serializer.write(this._messageMap); } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** | ||
|  |  * String hash function similar to java.lang.String.hashCode(). | ||
|  |  * The hash code for a string is computed as | ||
|  |  * s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1], | ||
|  |  * where s[i] is the ith character of the string and n is the length of | ||
|  |  * the string. We mod the result to make it between 0 (inclusive) and 2^32 (exclusive). | ||
|  |  * | ||
|  |  * Based on goog.string.hashCode from the Google Closure library | ||
|  |  * https://github.com/google/closure-library/
 | ||
|  |  * | ||
|  |  * @internal | ||
|  |  */ | ||
|  | // TODO(vicb): better algo (less collisions) ?
 | ||
|  | export function strHash(str: string): string { | ||
|  |   let result: number = 0; | ||
|  |   for (var i = 0; i < str.length; ++i) { | ||
|  |     // Normalize to 4 byte range, 0 ... 2^32.
 | ||
|  |     result = (31 * result + str.charCodeAt(i)) >>> 0; | ||
|  |   } | ||
|  |   return result.toString(16); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Serialize the i18n ast to something xml-like in order to generate an UID. | ||
|  |  * | ||
|  |  * The visitor is also used in the i18n parser tests | ||
|  |  * | ||
|  |  * @internal | ||
|  |  */ | ||
|  | class _SerializerVisitor implements i18nAst.Visitor { | ||
|  |   visitText(text: i18nAst.Text, context: any): any { return text.value; } | ||
|  | 
 | ||
|  |   visitContainer(container: i18nAst.Container, context: any): any { | ||
|  |     return `[${container.children.map(child => child.visit(this)).join(', ')}]`; | ||
|  |   } | ||
|  | 
 | ||
|  |   visitIcu(icu: i18nAst.Icu, context: any): any { | ||
|  |     let strCases = Object.keys(icu.cases).map((k: string) => `${k} {${icu.cases[k].visit(this)}}`); | ||
|  |     return `{${icu.expression}, ${icu.type}, ${strCases.join(', ')}}`; | ||
|  |   } | ||
|  | 
 | ||
|  |   visitTagPlaceholder(ph: i18nAst.TagPlaceholder, context: any): any { | ||
|  |     return ph.isVoid ? | ||
|  |         `<ph tag name="${ph.startName}"/>` : | ||
|  |         `<ph tag name="${ph.startName}">${ph.children.map(child => child.visit(this)).join(', ')}</ph name="${ph.closeName}">`; | ||
|  |   } | ||
|  | 
 | ||
|  |   visitPlaceholder(ph: i18nAst.Placeholder, context: any): any { | ||
|  |     return `<ph name="${ph.name}">${ph.value}</ph>`; | ||
|  |   } | ||
|  | 
 | ||
|  |   visitIcuPlaceholder(ph: i18nAst.IcuPlaceholder, context?: any): any { | ||
|  |     return `<ph icu name="${ph.name}">${ph.value.visit(this)}</ph>`; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | const serializerVisitor = new _SerializerVisitor(); | ||
|  | 
 | ||
|  | export function serializeAst(ast: i18nAst.Node[]): string[] { | ||
|  |   return ast.map(a => a.visit(serializerVisitor, null)); | ||
|  | } |