/** * @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 {MissingTranslationStrategy} from '@angular/core'; import * as i18n from '../../src/i18n/i18n_ast'; import {TranslationBundle} from '../../src/i18n/translation_bundle'; import * as html from '../../src/ml_parser/ast'; import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../src/parse_util'; import {serializeNodes} from '../ml_parser/ast_serializer_spec'; import {_extractMessages} from './i18n_parser_spec'; { describe('TranslationBundle', () => { const file = new ParseSourceFile('content', 'url'); const startLocation = new ParseLocation(file, 0, 0, 0); const endLocation = new ParseLocation(file, 0, 0, 7); const span = new ParseSourceSpan(startLocation, endLocation); const srcNode = new i18n.Text('src', span); it('should translate a plain text', () => { const msgMap = {foo: [new i18n.Text('bar', null !)]}; const tb = new TranslationBundle(msgMap, null, (_) => 'foo'); const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i'); expect(serializeNodes(tb.get(msg))).toEqual(['bar']); }); it('should translate html-like plain text', () => { const msgMap = {foo: [new i18n.Text('

bar

', null !)]}; const tb = new TranslationBundle(msgMap, null, (_) => 'foo'); const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i'); const nodes = tb.get(msg); expect(nodes.length).toEqual(1); const textNode: html.Text = nodes[0] as any; expect(textNode instanceof html.Text).toEqual(true); expect(textNode.value).toBe('

bar

'); }); it('should translate a message with placeholder', () => { const msgMap = { foo: [ new i18n.Text('bar', null !), new i18n.Placeholder('', 'ph1', null !), ] }; const phMap = { ph1: '*phContent*', }; const tb = new TranslationBundle(msgMap, null, (_) => 'foo'); const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd', 'i'); expect(serializeNodes(tb.get(msg))).toEqual(['bar*phContent*']); }); it('should translate a message with placeholder referencing messages', () => { const msgMap = { foo: [ new i18n.Text('--', null !), new i18n.Placeholder('', 'ph1', null !), new i18n.Text('++', null !), ], ref: [ new i18n.Text('*refMsg*', null !), ], }; const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i'); const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd', 'i'); let count = 0; const digest = (_: any) => count++ ? 'ref' : 'foo'; const tb = new TranslationBundle(msgMap, null, digest); expect(serializeNodes(tb.get(msg))).toEqual(['--*refMsg*++']); }); it('should use the original message or throw when a translation is not found', () => { const src = `some text{{ some_expression }}{count, plural, =0 {no} few {a few}}`; const messages = _extractMessages(`
${src}
`); const digest = (_: any) => `no matching id`; // Empty message map -> use source messages in Ignore mode let tb = new TranslationBundle({}, null, digest, null !, MissingTranslationStrategy.Ignore); expect(serializeNodes(tb.get(messages[0])).join('')).toEqual(src); // Empty message map -> use source messages in Warning mode tb = new TranslationBundle({}, null, digest, null !, MissingTranslationStrategy.Warning); expect(serializeNodes(tb.get(messages[0])).join('')).toEqual(src); // Empty message map -> throw in Error mode tb = new TranslationBundle({}, null, digest, null !, MissingTranslationStrategy.Error); expect(() => serializeNodes(tb.get(messages[0])).join('')).toThrow(); }); describe('errors reporting', () => { it('should report unknown placeholders', () => { const msgMap = { foo: [ new i18n.Text('bar', null !), new i18n.Placeholder('', 'ph1', span), ] }; const tb = new TranslationBundle(msgMap, null, (_) => 'foo'); const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i'); expect(() => tb.get(msg)).toThrowError(/Unknown placeholder/); }); it('should report missing translation', () => { const tb = new TranslationBundle({}, null, (_) => 'foo', null !, MissingTranslationStrategy.Error); const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i'); expect(() => tb.get(msg)).toThrowError(/Missing translation for message "foo"/); }); it('should report missing translation with MissingTranslationStrategy.Warning', () => { const log: string[] = []; const console = { log: (msg: string) => { throw `unexpected`; }, warn: (msg: string) => log.push(msg), }; const tb = new TranslationBundle( {}, 'en', (_) => 'foo', null !, MissingTranslationStrategy.Warning, console); const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i'); expect(() => tb.get(msg)).not.toThrowError(); expect(log.length).toEqual(1); expect(log[0]).toMatch(/Missing translation for message "foo" for locale "en"/); }); it('should not report missing translation with MissingTranslationStrategy.Ignore', () => { const tb = new TranslationBundle( {}, null, (_) => 'foo', null !, MissingTranslationStrategy.Ignore); const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i'); expect(() => tb.get(msg)).not.toThrowError(); }); it('should report missing referenced message', () => { const msgMap = { foo: [new i18n.Placeholder('', 'ph1', span)], }; const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i'); const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd', 'i'); let count = 0; const digest = (_: any) => count++ ? 'ref' : 'foo'; const tb = new TranslationBundle(msgMap, null, digest, null !, MissingTranslationStrategy.Error); expect(() => tb.get(msg)).toThrowError(/Missing translation for message "ref"/); }); it('should report invalid translated html', () => { const msgMap = { foo: [ new i18n.Text('text', null !), new i18n.Placeholder('', 'ph1', null !), ] }; const phMap = { ph1: '', }; const tb = new TranslationBundle(msgMap, null, (_) => 'foo'); const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd', 'i'); expect(() => tb.get(msg)).toThrowError(/Unexpected closing tag "b"/); }); }); }); }