/** * @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 * as fs from 'fs'; import * as path from 'path'; import * as ts from 'typescript'; import {mainXi18n} from '../src/extract_i18n'; import {isInBazel, makeTempDir, setup} from './test_support'; function getNgRootDir() { const moduleFilename = module.filename.replace(/\\/g, '/'); const distIndex = moduleFilename.indexOf('/dist/all'); return moduleFilename.substr(0, distIndex); } const EXPECTED_XMB = ` ]> src/basic.html:1src/comp2.ts:1src/basic.html:1translate me src/basic.html:3,4src/comp2.ts:3,4src/comp2.ts:2,3src/basic.html:3,4 Welcome src/icu.html:1,3src/icu.html:5{VAR_PLURAL, plural, =1 {book} other {books} } src/icu.html:4,6 foo { count, plural, =1 {...} other {...}}{ count, plural, =1 {...} other {...}} src/placeholders.html:1Name: <b><b>{{ name // i18n(ph="name") }}{{ name // i18n(ph="name") }}</b></b> `; const EXPECTED_XLIFF = ` translate me src/basic.html 1 src/comp2.ts 1 src/basic.html 1 desc meaning Welcome src/basic.html 3 src/comp2.ts 3 src/comp2.ts 2 src/basic.html 3 {VAR_PLURAL, plural, =1 {book} other {books} } src/icu.html 1 with ICU foo src/icu.html 4 with ICU and other things {VAR_PLURAL, plural, =1 {book} other {books} } src/icu.html 5 Name: src/placeholders.html 1 with placeholders `; const EXPECTED_XLIFF2 = ` desc meaning src/basic.html:1 src/comp2.ts:1 src/basic.html:1 translate me src/basic.html:3,4 src/comp2.ts:3,4 src/comp2.ts:2,3 src/basic.html:3,4 Welcome with ICU src/icu.html:1,3 src/icu.html:5 {VAR_PLURAL, plural, =1 {book} other {books} } with ICU and other things src/icu.html:4,6 foo with placeholders src/placeholders.html:1 Name: `; describe('extract_i18n command line', () => { let basePath: string; let outDir: string; let write: (fileName: string, content: string) => void; let errorSpy: jasmine.Spy&((s: string) => void); function writeConfig(tsconfig = '{"extends": "./tsconfig-base.json"}') { write('tsconfig.json', tsconfig); } beforeEach(() => { errorSpy = jasmine.createSpy('consoleError').and.callFake(console.error); if (isInBazel()) { const support = setup(); write = (fileName: string, content: string) => { support.write(fileName, content); }; basePath = support.basePath; outDir = path.join(basePath, 'built'); } else { basePath = makeTempDir(); write = (fileName: string, content: string) => { const dir = path.dirname(fileName); if (dir !== '.') { const newDir = path.join(basePath, dir); if (!fs.existsSync(newDir)) fs.mkdirSync(newDir); } fs.writeFileSync(path.join(basePath, fileName), content, {encoding: 'utf-8'}); }; outDir = path.resolve(basePath, 'built'); const ngRootDir = getNgRootDir(); const nodeModulesPath = path.resolve(basePath, 'node_modules'); fs.mkdirSync(nodeModulesPath); fs.symlinkSync( path.resolve(ngRootDir, 'dist', 'all', '@angular'), path.resolve(nodeModulesPath, '@angular')); fs.symlinkSync( path.resolve(ngRootDir, 'node_modules', 'rxjs'), path.resolve(nodeModulesPath, 'rxjs')); } write('tsconfig-base.json', `{ "compilerOptions": { "experimentalDecorators": true, "skipLibCheck": true, "noImplicitAny": true, "types": [], "outDir": "built", "rootDir": ".", "baseUrl": ".", "declaration": true, "target": "es5", "module": "es2015", "moduleResolution": "node", "lib": ["es6", "dom"], "typeRoots": ["node_modules/@types"] } }`); }); function writeSources() { const welcomeMessage = ` Welcome `; write('src/basic.html', `

${welcomeMessage}

`); write('src/comp1.ts', ` import {Component} from '@angular/core'; @Component({ selector: 'basic', templateUrl: './basic.html', }) export class BasicCmp1 {}`); write('src/comp2.ts', ` import {Component} from '@angular/core'; @Component({ selector: 'basic2', template: \`

${welcomeMessage}

\`, }) export class BasicCmp2 {} @Component({ selector: 'basic4', template: \`

${welcomeMessage}

\`, }) export class BasicCmp4 {}`); write('src/comp3.ts', ` import {Component} from '@angular/core'; @Component({ selector: 'basic3', templateUrl: './basic.html', }) export class BasicCmp3 {}`); write('src/placeholders.html', `
Name: {{ name // i18n(ph="name") }}
`); write('src/placeholder_cmp.ts', ` import {Component} from '@angular/core'; @Component({ selector: 'placeholders', templateUrl: './placeholders.html', }) export class PlaceholderCmp { name = 'whatever'; }`); write('src/icu.html', `
{ count, plural, =1 {book} other {books} }
foo { count, plural, =1 {book} other {books} }
`); write('src/icu_cmp.ts', ` import {Component} from '@angular/core'; @Component({ selector: 'icu', templateUrl: './icu.html', }) export class IcuCmp { count = 3; }`); write('src/module.ts', ` import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {BasicCmp1} from './comp1'; import {BasicCmp2, BasicCmp4} from './comp2'; import {BasicCmp3} from './comp3'; import {PlaceholderCmp} from './placeholder_cmp'; import {IcuCmp} from './icu_cmp'; @NgModule({ declarations: [ BasicCmp1, BasicCmp2, BasicCmp3, BasicCmp4, PlaceholderCmp, IcuCmp, ], imports: [CommonModule], }) export class I18nModule {} `); } it('should extract xmb', () => { writeConfig(); writeSources(); const exitCode = mainXi18n(['-p', basePath, '--i18nFormat=xmb', '--outFile=custom_file.xmb'], errorSpy); expect(errorSpy).not.toHaveBeenCalled(); expect(exitCode).toBe(0); const xmbOutput = path.join(outDir, 'custom_file.xmb'); expect(fs.existsSync(xmbOutput)).toBeTruthy(); const xmb = fs.readFileSync(xmbOutput, {encoding: 'utf-8'}); expect(xmb).toEqual(EXPECTED_XMB); }); it('should extract xlf', () => { writeConfig(); writeSources(); const exitCode = mainXi18n(['-p', basePath, '--i18nFormat=xlf', '--locale=fr'], errorSpy); expect(errorSpy).not.toHaveBeenCalled(); expect(exitCode).toBe(0); const xlfOutput = path.join(outDir, 'messages.xlf'); expect(fs.existsSync(xlfOutput)).toBeTruthy(); const xlf = fs.readFileSync(xlfOutput, {encoding: 'utf-8'}); expect(xlf).toEqual(EXPECTED_XLIFF); }); it('should extract xlf2', () => { writeConfig(); writeSources(); const exitCode = mainXi18n(['-p', basePath, '--i18nFormat=xlf2', '--outFile=messages.xliff2.xlf'], errorSpy); expect(errorSpy).not.toHaveBeenCalled(); expect(exitCode).toBe(0); const xlfOutput = path.join(outDir, 'messages.xliff2.xlf'); expect(fs.existsSync(xlfOutput)).toBeTruthy(); const xlf = fs.readFileSync(xlfOutput, {encoding: 'utf-8'}); expect(xlf).toEqual(EXPECTED_XLIFF2); }); it('should not emit js', () => { writeConfig(); writeSources(); const exitCode = mainXi18n(['-p', basePath, '--i18nFormat=xlf2', '--outFile=messages.xliff2.xlf'], errorSpy); expect(errorSpy).not.toHaveBeenCalled(); expect(exitCode).toBe(0); const moduleOutput = path.join(outDir, 'src', 'module.js'); expect(fs.existsSync(moduleOutput)).toBeFalsy(); }); });