Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4": "Traduisez moi",
"65cc4ab3b4c438e07c89be2b677d08369fb62da2": "Bienvenue",
"63a85808f03b8181e36a952e0fa38202c2304862": "other-3rdP-component"
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {createComponent} from './util';

describe('template codegen output', () => {
const outDir = 'src';
const genDir = 'gen_custom_serializer/src';

it('should lower Decorators without reflect-metadata', () => {
const jsOutput = path.join(outDir, 'basic.js');
Expand Down Expand Up @@ -88,5 +89,13 @@ describe('template codegen output', () => {
const pText = pElement.children.map((c: any) => c.data).join('').trim();
expect(pText).toBe('tervetuloa');
});

describe('with custom serializer', () => {
it('should generate translated factories', () => {
const jsOutput = path.join(genDir, 'basic.ngfactory.ts');
expect(fs.existsSync(jsOutput)).toBeTruthy();
expect(fs.readFileSync(jsOutput, {encoding: 'utf-8'})).toContain('Bienvenue');
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @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 {Container, Icu, IcuPlaceholder, Message, Node, Placeholder, Serializer, TagPlaceholder, Text, Visitor, digest} from '@angular/compiler';

class CustomSerializer extends Serializer {
constructor() { super(); }

write(messages: Message[]): string {
const visitor = new _WriteVisitor();
let result: {[id: string]: string} = {};
messages.forEach(message => { result[message.id] = visitor.serialize(message.nodes); });
return JSON.stringify(result);
}

load(content: string, url: string):
{locale: string | null, i18nNodesByMsgId: {[msgId: string]: Node[]}} {
const data = JSON.parse(content);
const i18nNodesByMsgId: {[msgId: string]: Node[]} = {};
Object.keys(data).forEach(
msgId => { i18nNodesByMsgId[msgId] = [new Text(data[msgId], null)]; });
return {locale: null, i18nNodesByMsgId: i18nNodesByMsgId};
}

digest(message: Message): string { return digest(message); }

getExtension(): string { return 'json'; }
}

class _WriteVisitor implements Visitor {
visitText(text: Text, context?: any): string { return text.value; }
visitContainer(container: Container, context?: any): string { return ''; }
visitIcu(icu: Icu, context?: any): string { return ''; }
visitTagPlaceholder(ph: TagPlaceholder, context?: any): string { return ''; }
visitPlaceholder(ph: Placeholder, context?: any): string { return ''; }
visitIcuPlaceholder(ph: IcuPlaceholder, context?: any): string { return ''; }
serialize(nodes: Node[]): string { return '' + nodes.map(node => node.visit(this)).join(''); }
}

module.exports = CustomSerializer;
10 changes: 10 additions & 0 deletions modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
</xliff>
`;

const EXPECTED_JSON =
`{"76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4":"translate me","65cc4ab3b4c438e07c89be2b677d08369fb62da2":"Welcome","63a85808f03b8181e36a952e0fa38202c2304862":"other-3rdP-component"}`;

describe('template i18n extraction output', () => {
const outDir = '';
const genDir = 'out';
Expand All @@ -85,4 +88,11 @@ describe('template i18n extraction output', () => {
const genOutput = path.join(genDir, '');
expect(fs.existsSync(genOutput)).toBeFalsy();
});

it('should extract i18n messages with custom serializer', () => {
const jsonOutput = path.join(outDir, 'messages.json');
expect(fs.existsSync(jsonOutput)).toBeTruthy();
const json = fs.readFileSync(jsonOutput, {encoding: 'utf-8'});
expect(json).toEqual(EXPECTED_JSON);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ function codeGenTest() {

// i18n options.
i18nFormat: null,
i18nSerializer: null,
i18nFile: null,
locale: null,

Expand Down Expand Up @@ -131,6 +132,7 @@ function i18nTest() {
compilerOptions: config.parsed.options, program, host,
angularCompilerOptions: config.ngOptions,
i18nFormat: 'xlf',
i18nSerializer: null,
readResource: (fileName: string) => {
readResources.push(fileName);
return hostContext.readResource(fileName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@
"src/module",
"src/bootstrap",
"test/all_spec",
"test/custom_serializer",
"test/test_ngtools_api",
"test/test_summaries",
"benchmarks/src/tree/ng2/index_aot.ts",
"benchmarks/src/tree/ng2_switch/index_aot.ts",
"benchmarks/src/largetable/ng2/index_aot.ts",
"benchmarks/src/largetable/ng2_switch/index_aot.ts"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"angularCompilerOptions": {
// For TypeScript 1.8, we have to lay out generated files
// in the same source directory with your code.
"genDir": ".",
"debug": true
},

"compilerOptions": {
"target": "es5",
"experimentalDecorators": true,
"noImplicitAny": true,
"moduleResolution": "node",
"rootDir": "",
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": ".",
// Prevent scanning up the directory tree for types
"typeRoots": ["node_modules/@types"]
},

"files": [
"src/module",
"src/bootstrap",
"test/all_spec",
"test/custom_serializer",
"test/test_ngtools_api",
"test/test_summaries",
"benchmarks/src/tree/ng2/index_aot.ts",
"benchmarks/src/tree/ng2_switch/index_aot.ts",
"benchmarks/src/largetable/ng2/index_aot.ts",
"benchmarks/src/largetable/ng2_switch/index_aot.ts"
],
"angularCompilerOptions": {
"genDir": "./gen_custom_serializer"
}
}
9 changes: 8 additions & 1 deletion modules/@angular/compiler-cli/src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
import * as compiler from '@angular/compiler';
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
import {readFileSync} from 'fs';
import * as path from 'path';
import * as ts from 'typescript';

import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {getI18nSerializer} from './utils';

const GENERATED_META_FILES = /\.json$/;

Expand Down Expand Up @@ -71,10 +71,17 @@ export class CodeGenerator {
}
transContent = readFileSync(transFile, 'utf8');
}

let serializer: compiler.Serializer;
const serializerPath = cliOptions.i18nSerializer;
if (serializerPath) {
serializer = getI18nSerializer(serializerPath);
}
const {compiler: aotCompiler} = compiler.createAotCompiler(ngCompilerHost, {
debug: options.debug === true,
translations: transContent,
i18nFormat: cliOptions.i18nFormat,
i18nSerializer: serializer,
locale: cliOptions.locale
});
return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost);
Expand Down
3 changes: 2 additions & 1 deletion modules/@angular/compiler-cli/src/extract_i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {Extractor} from './extractor';
function extract(
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
program: ts.Program, host: ts.CompilerHost): Promise<void> {
return Extractor.create(ngOptions, program, host).extract(cliOptions.i18nFormat);
return Extractor.create(ngOptions, program, host)
.extract(cliOptions.i18nFormat, cliOptions.i18nSerializer);
}

// Entry point
Expand Down
49 changes: 29 additions & 20 deletions modules/@angular/compiler-cli/src/extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,24 @@ import * as ts from 'typescript';

import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {getI18nSerializer} from './utils';

export class Extractor {
constructor(
private options: tsc.AngularCompilerOptions, private ngExtractor: compiler.Extractor,
public host: ts.CompilerHost, private ngCompilerHost: CompilerHost,
private program: ts.Program) {}

extract(formatName: string): Promise<void> {
// Checks the format and returns the extension
const ext = this.getExtension(formatName);
extract(formatName: string, serializerPath: string): Promise<void> {
// Checks the arguments first
this.checkArguments(formatName, serializerPath);

const promiseBundle = this.extractBundle();

return promiseBundle.then(bundle => {
const content = this.serialize(bundle, ext);
const dstPath = path.join(this.options.genDir, `messages.${ext}`);
const serializer = this.getSerializer(formatName, serializerPath);
const content = this.serialize(bundle, serializer);
const dstPath = path.join(this.options.genDir, `messages.${serializer.getExtension()}`);
this.host.writeFile(dstPath, content, false);
});
}
Expand All @@ -47,28 +49,35 @@ export class Extractor {
return this.ngExtractor.extract(files);
}

serialize(bundle: compiler.MessageBundle, ext: string): string {
getSerializer(ext: string, serializerPath: string): compiler.Serializer {
let serializer: compiler.Serializer;

switch (ext) {
case 'xmb':
serializer = new compiler.Xmb();
break;
case 'xlf':
default:
serializer = new compiler.Xliff();
if (serializerPath) {
serializer = getI18nSerializer(serializerPath);
} else {
switch (ext) {
case 'xmb':
serializer = new compiler.Xmb();
break;
case 'xlf':
case 'xlif':
default:
serializer = new compiler.Xliff();
}
}
return serializer;
}

serialize(bundle: compiler.MessageBundle, serializer: compiler.Serializer): string {
return bundle.write(serializer);
}

getExtension(formatName: string): string {
const format = (formatName || 'xlf').toLowerCase();

if (format === 'xmb') return 'xmb';
if (format === 'xlf' || format === 'xlif') return 'xlf';

throw new Error('Unsupported format "${formatName}"');
checkArguments(formatName: string, serializerPath: string) {
if (!serializerPath) {
if (formatName && ['xmb', 'xlf', 'xlif'].indexOf(formatName) == -1) {
throw new Error('Unsupported format "${formatName}"');
}
}
}

static create(
Expand Down
5 changes: 4 additions & 1 deletion modules/@angular/compiler-cli/src/ngtools_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface NgTools_InternalApi_NG2_CodeGen_Options {

// i18n options.
i18nFormat: string;
i18nSerializer: string;
i18nFile: string;
locale: string;

Expand All @@ -59,6 +60,7 @@ export interface NgTools_InternalApi_NG2_ExtractI18n_Options {
host: ts.CompilerHost;
angularCompilerOptions: AngularCompilerOptions;
i18nFormat: string;
i18nSerializer: string;
readResource: (fileName: string) => Promise<string>;
// Every new property under this line should be optional.
}
Expand Down Expand Up @@ -91,6 +93,7 @@ export class NgTools_InternalApi_NG_2 {
new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host);
const cliOptions: NgcCliOptions = {
i18nFormat: options.i18nFormat,
i18nSerializer: options.i18nSerializer,
i18nFile: options.i18nFile,
locale: options.locale,
basePath: options.basePath
Expand Down Expand Up @@ -145,6 +148,6 @@ export class NgTools_InternalApi_NG_2 {
const extractor = Extractor.create(
options.angularCompilerOptions, options.program, options.host, hostContext);

return extractor.extract(options.i18nFormat);
return extractor.extract(options.i18nFormat, options.i18nSerializer);
}
}
29 changes: 29 additions & 0 deletions modules/@angular/compiler-cli/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* @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
*/

/**
* @module
* @description
* Entry point for all public APIs of the common package.
*/

import {Serializer} from '@angular/compiler';
import * as path from 'path';

export function getI18nSerializer(serializerPath: string): Serializer {
let serializerClass: any = null;
if (serializerPath.startsWith('.')) {
const fullPath = path.join(process.cwd(), serializerPath);
serializerClass = require(path.relative(__dirname, fullPath));
} else if (serializerPath.startsWith('/')) {
serializerClass = require(path.relative(__dirname, serializerPath));
} else {
serializerClass = require(serializerPath);
}
return serializerClass ? new serializerClass() : null;
}
4 changes: 2 additions & 2 deletions modules/@angular/compiler/src/aot/compiler_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const console = new Console();
const htmlParser = new I18NHtmlParser(
new HtmlParser(), translations, options.i18nFormat, MissingTranslationStrategy.Warning,
console);
new HtmlParser(), translations, options.i18nFormat, options.i18nSerializer,
MissingTranslationStrategy.Warning, console);
const config = new CompilerConfig({
genDebugInfo: options.debug === true,
defaultEncapsulation: ViewEncapsulation.Emulated,
Expand Down
2 changes: 2 additions & 0 deletions modules/@angular/compiler/src/aot/compiler_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
* 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 {Serializer} from '../i18n/index';

export interface AotCompilerOptions {
debug?: boolean;
locale?: string;
i18nFormat?: string;
i18nSerializer?: Serializer;
translations?: string;
}
3 changes: 2 additions & 1 deletion modules/@angular/compiler/src/i18n/i18n_html_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ export class I18NHtmlParser implements HtmlParser {

constructor(
private _htmlParser: HtmlParser, translations?: string, translationsFormat?: string,
serializer?: Serializer,
missingTranslation: MissingTranslationStrategy = MissingTranslationStrategy.Warning,
console?: Console) {
if (translations) {
const serializer = createSerializer(translationsFormat);
serializer = serializer || createSerializer(translationsFormat);
this._translationBundle =
TranslationBundle.load(translations, 'i18n', serializer, missingTranslation, console);
}
Expand Down
2 changes: 2 additions & 0 deletions modules/@angular/compiler/src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/

export * from './digest';
export {Extractor, ExtractorHost} from './extractor';
export * from './i18n_ast';
export {I18NHtmlParser} from './i18n_html_parser';
export {MessageBundle} from './message_bundle';
export {Serializer} from './serializers/serializer';
Expand Down
Loading