From 234f05996cfc4e3c5261cd9b5e8b97a6de9399f3 Mon Sep 17 00:00:00 2001 From: Marc Laval Date: Thu, 16 Feb 2017 17:03:18 +0100 Subject: [PATCH] feat(compiler-cli): add a `locale` option to ng-xi18n Fixes #12303 Closes #14537 --- .../compiler-cli/integrationtest/test/i18n_spec.ts | 2 +- .../integrationtest/test/test_ngtools_api.ts | 1 + modules/@angular/compiler-cli/src/extract_i18n.ts | 3 ++- modules/@angular/compiler-cli/src/extractor.ts | 5 +++-- modules/@angular/compiler-cli/src/ngtools_api.ts | 4 +++- modules/@angular/compiler/src/i18n/extractor.ts | 5 +++-- modules/@angular/compiler/src/i18n/message_bundle.ts | 6 +++--- .../compiler/src/i18n/serializers/serializer.ts | 2 +- .../@angular/compiler/src/i18n/serializers/xliff.ts | 12 ++++++++---- .../@angular/compiler/src/i18n/serializers/xmb.ts | 4 ++-- .../@angular/compiler/src/i18n/serializers/xtb.ts | 2 +- .../compiler/test/i18n/message_bundle_spec.ts | 2 +- .../compiler/test/i18n/serializers/xliff_spec.ts | 8 +++++--- .../compiler/test/i18n/serializers/xmb_spec.ts | 10 +++++++--- .../compiler/test/i18n/serializers/xtb_spec.ts | 4 ++-- scripts/ci-lite/offline_compiler_test.sh | 2 +- tools/@angular/tsc-wrapped/src/cli_options.ts | 4 +++- 17 files changed, 47 insertions(+), 29 deletions(-) diff --git a/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts b/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts index e3d0431740..2f4af37884 100644 --- a/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts +++ b/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts @@ -42,7 +42,7 @@ const EXPECTED_XMB = ` const EXPECTED_XLIFF = ` - + translate me diff --git a/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts b/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts index 36fbf8afa5..d1ad408474 100644 --- a/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts +++ b/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts @@ -131,6 +131,7 @@ function i18nTest() { compilerOptions: config.parsed.options, program, host, angularCompilerOptions: config.ngOptions, i18nFormat: 'xlf', + locale: null, readResource: (fileName: string) => { readResources.push(fileName); return hostContext.readResource(fileName); diff --git a/modules/@angular/compiler-cli/src/extract_i18n.ts b/modules/@angular/compiler-cli/src/extract_i18n.ts index af4b09e60e..9582941104 100644 --- a/modules/@angular/compiler-cli/src/extract_i18n.ts +++ b/modules/@angular/compiler-cli/src/extract_i18n.ts @@ -22,7 +22,8 @@ import {Extractor} from './extractor'; function extract( ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions, program: ts.Program, host: ts.CompilerHost): Promise { - return Extractor.create(ngOptions, program, host).extract(cliOptions.i18nFormat); + return Extractor.create(ngOptions, program, host, cliOptions.locale) + .extract(cliOptions.i18nFormat); } // Entry point diff --git a/modules/@angular/compiler-cli/src/extractor.ts b/modules/@angular/compiler-cli/src/extractor.ts index a6f27fb9e2..25db7f1132 100644 --- a/modules/@angular/compiler-cli/src/extractor.ts +++ b/modules/@angular/compiler-cli/src/extractor.ts @@ -73,7 +73,8 @@ export class Extractor { static create( options: tsc.AngularCompilerOptions, program: ts.Program, tsCompilerHost: ts.CompilerHost, - compilerHostContext?: CompilerHostContext, ngCompilerHost?: CompilerHost): Extractor { + locale?: string|null, compilerHostContext?: CompilerHostContext, + ngCompilerHost?: CompilerHost): Extractor { if (!ngCompilerHost) { const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0; const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost); @@ -81,7 +82,7 @@ export class Extractor { new CompilerHost(program, options, context); } - const {extractor: ngExtractor} = compiler.Extractor.create(ngCompilerHost); + const {extractor: ngExtractor} = compiler.Extractor.create(ngCompilerHost, locale || null); return new Extractor(options, ngExtractor, tsCompilerHost, ngCompilerHost, program); } diff --git a/modules/@angular/compiler-cli/src/ngtools_api.ts b/modules/@angular/compiler-cli/src/ngtools_api.ts index 4fa9ab56e9..f86463a168 100644 --- a/modules/@angular/compiler-cli/src/ngtools_api.ts +++ b/modules/@angular/compiler-cli/src/ngtools_api.ts @@ -61,6 +61,7 @@ export interface NgTools_InternalApi_NG2_ExtractI18n_Options { i18nFormat: string; readResource: (fileName: string) => Promise; // Every new property under this line should be optional. + locale?: string; } /** @@ -142,8 +143,9 @@ export class NgTools_InternalApi_NG_2 { new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host); // Create the i18n extractor. + const locale = options.locale || null; const extractor = Extractor.create( - options.angularCompilerOptions, options.program, options.host, hostContext); + options.angularCompilerOptions, options.program, options.host, locale, hostContext); return extractor.extract(options.i18nFormat); } diff --git a/modules/@angular/compiler/src/i18n/extractor.ts b/modules/@angular/compiler/src/i18n/extractor.ts index 933bd30356..28a30360a6 100644 --- a/modules/@angular/compiler/src/i18n/extractor.ts +++ b/modules/@angular/compiler/src/i18n/extractor.ts @@ -86,7 +86,8 @@ export class Extractor { }); } - static create(host: ExtractorHost): {extractor: Extractor, staticReflector: StaticReflector} { + static create(host: ExtractorHost, locale: string|null): + {extractor: Extractor, staticReflector: StaticReflector} { const htmlParser = new I18NHtmlParser(new HtmlParser()); const urlResolver = createOfflineCompileUrlResolver(); @@ -112,7 +113,7 @@ export class Extractor { symbolCache, staticReflector); // TODO(vicb): implicit tags & attributes - const messageBundle = new MessageBundle(htmlParser, [], {}); + const messageBundle = new MessageBundle(htmlParser, [], {}, locale); const extractor = new Extractor(host, staticSymbolResolver, messageBundle, resolver); return {extractor, staticReflector}; diff --git a/modules/@angular/compiler/src/i18n/message_bundle.ts b/modules/@angular/compiler/src/i18n/message_bundle.ts index 386415fd34..7b42b4fa21 100644 --- a/modules/@angular/compiler/src/i18n/message_bundle.ts +++ b/modules/@angular/compiler/src/i18n/message_bundle.ts @@ -23,7 +23,7 @@ export class MessageBundle { constructor( private _htmlParser: HtmlParser, private _implicitTags: string[], - private _implicitAttrs: {[k: string]: string[]}) {} + private _implicitAttrs: {[k: string]: string[]}, private _locale: string|null = null) {} updateFromTemplate(html: string, url: string, interpolationConfig: InterpolationConfig): ParseError[] { @@ -67,7 +67,7 @@ export class MessageBundle { return new i18n.Message(nodes, {}, {}, src.meaning, src.description, id); }); - return serializer.write(msgList); + return serializer.write(msgList, this._locale); } } @@ -92,4 +92,4 @@ class MapPlaceholderNames extends i18n.CloneVisitor { visitIcuPlaceholder(ph: i18n.IcuPlaceholder, mapper: PlaceholderMapper): i18n.IcuPlaceholder { return new i18n.IcuPlaceholder(ph.value, mapper.toPublicName(ph.name), ph.sourceSpan); } -} \ No newline at end of file +} diff --git a/modules/@angular/compiler/src/i18n/serializers/serializer.ts b/modules/@angular/compiler/src/i18n/serializers/serializer.ts index 884be0497c..37708b0f8f 100644 --- a/modules/@angular/compiler/src/i18n/serializers/serializer.ts +++ b/modules/@angular/compiler/src/i18n/serializers/serializer.ts @@ -12,7 +12,7 @@ export abstract class Serializer { // - The `placeholders` and `placeholderToMessage` properties are irrelevant in the input messages // - The `id` contains the message id that the serializer is expected to use // - Placeholder names are already map to public names using the provided mapper - abstract write(messages: i18n.Message[]): string; + abstract write(messages: i18n.Message[], locale: string|null): string; abstract load(content: string, url: string): {locale: string | null, i18nNodesByMsgId: {[msgId: string]: i18n.Node[]}}; diff --git a/modules/@angular/compiler/src/i18n/serializers/xliff.ts b/modules/@angular/compiler/src/i18n/serializers/xliff.ts index 205e4f7c10..dd9549409c 100644 --- a/modules/@angular/compiler/src/i18n/serializers/xliff.ts +++ b/modules/@angular/compiler/src/i18n/serializers/xliff.ts @@ -18,7 +18,7 @@ import * as xml from './xml_helper'; const _VERSION = '1.2'; const _XMLNS = 'urn:oasis:names:tc:xliff:document:1.2'; // TODO(vicb): make this a param (s/_/-/) -const _SOURCE_LANG = 'en'; +const _DEFAULT_SOURCE_LANG = 'en'; const _PLACEHOLDER_TAG = 'x'; const _FILE_TAG = 'file'; @@ -29,7 +29,7 @@ const _UNIT_TAG = 'trans-unit'; // http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html // http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html export class Xliff extends Serializer { - write(messages: i18n.Message[]): string { + write(messages: i18n.Message[], locale: string|null): string { const visitor = new _WriteVisitor(); const transUnits: xml.Node[] = []; @@ -59,7 +59,11 @@ export class Xliff extends Serializer { const body = new xml.Tag('body', {}, [...transUnits, new xml.CR(4)]); const file = new xml.Tag( - 'file', {'source-language': _SOURCE_LANG, datatype: 'plaintext', original: 'ng2.template'}, + 'file', { + 'source-language': locale || _DEFAULT_SOURCE_LANG, + datatype: 'plaintext', + original: 'ng2.template', + }, [new xml.CR(4), body, new xml.CR(2)]); const xliff = new xml.Tag( 'xliff', {version: _VERSION, xmlns: _XMLNS}, [new xml.CR(2), file, new xml.CR()]); @@ -283,4 +287,4 @@ function getCtypeForTag(tag: string): string { default: return `x-${tag}`; } -} \ No newline at end of file +} diff --git a/modules/@angular/compiler/src/i18n/serializers/xmb.ts b/modules/@angular/compiler/src/i18n/serializers/xmb.ts index eb0870024b..9110b27221 100644 --- a/modules/@angular/compiler/src/i18n/serializers/xmb.ts +++ b/modules/@angular/compiler/src/i18n/serializers/xmb.ts @@ -38,7 +38,7 @@ const _DOCTYPE = ` `; export class Xmb extends Serializer { - write(messages: i18n.Message[]): string { + write(messages: i18n.Message[], locale: string|null): string { const exampleVisitor = new ExampleVisitor(); const visitor = new _Visitor(); let rootNode = new xml.Tag(_MESSAGES_TAG); @@ -161,4 +161,4 @@ class ExampleVisitor implements xml.IVisitor { // XMB/XTB placeholders can only contain A-Z, 0-9 and _ export function toPublicName(internalName: string): string { return internalName.toUpperCase().replace(/[^A-Z0-9_]/g, '_'); -} \ No newline at end of file +} diff --git a/modules/@angular/compiler/src/i18n/serializers/xtb.ts b/modules/@angular/compiler/src/i18n/serializers/xtb.ts index 506066c5dc..d8848fa970 100644 --- a/modules/@angular/compiler/src/i18n/serializers/xtb.ts +++ b/modules/@angular/compiler/src/i18n/serializers/xtb.ts @@ -19,7 +19,7 @@ const _TRANSLATION_TAG = 'translation'; const _PLACEHOLDER_TAG = 'ph'; export class Xtb extends Serializer { - write(messages: i18n.Message[]): string { throw new Error('Unsupported'); } + write(messages: i18n.Message[], locale: string|null): string { throw new Error('Unsupported'); } load(content: string, url: string): {locale: string, i18nNodesByMsgId: {[msgId: string]: i18n.Node[]}} { diff --git a/modules/@angular/compiler/test/i18n/message_bundle_spec.ts b/modules/@angular/compiler/test/i18n/message_bundle_spec.ts index 792d75367a..cae03595bd 100644 --- a/modules/@angular/compiler/test/i18n/message_bundle_spec.ts +++ b/modules/@angular/compiler/test/i18n/message_bundle_spec.ts @@ -57,4 +57,4 @@ class _TestSerializer extends Serializer { function humanizeMessages(catalog: MessageBundle): string[] { return catalog.write(new _TestSerializer()).split('//'); -} \ No newline at end of file +} diff --git a/modules/@angular/compiler/test/i18n/serializers/xliff_spec.ts b/modules/@angular/compiler/test/i18n/serializers/xliff_spec.ts index 6c02012835..e8a81b6611 100644 --- a/modules/@angular/compiler/test/i18n/serializers/xliff_spec.ts +++ b/modules/@angular/compiler/test/i18n/serializers/xliff_spec.ts @@ -107,8 +107,8 @@ const LOAD_XLIFF = ` export function main(): void { const serializer = new Xliff(); - function toXliff(html: string): string { - const catalog = new MessageBundle(new HtmlParser, [], {}); + function toXliff(html: string, locale: string | null = null): string { + const catalog = new MessageBundle(new HtmlParser, [], {}, locale); catalog.updateFromTemplate(html, '', DEFAULT_INTERPOLATION_CONFIG); return catalog.write(serializer); } @@ -126,6 +126,8 @@ export function main(): void { describe('XLIFF serializer', () => { describe('write', () => { it('should write a valid xliff file', () => { expect(toXliff(HTML)).toEqual(WRITE_XLIFF); }); + it('should write a valid xliff file with a source language', + () => { expect(toXliff(HTML, 'fr')).toContain('file source-language="fr"'); }); }); describe('load', () => { @@ -247,4 +249,4 @@ export function main(): void { }); }); }); -} \ No newline at end of file +} diff --git a/modules/@angular/compiler/test/i18n/serializers/xmb_spec.ts b/modules/@angular/compiler/test/i18n/serializers/xmb_spec.ts index dc0c2c5cbc..cb8c535fd5 100644 --- a/modules/@angular/compiler/test/i18n/serializers/xmb_spec.ts +++ b/modules/@angular/compiler/test/i18n/serializers/xmb_spec.ts @@ -56,7 +56,11 @@ export function main(): void { `; - it('should write a valid xmb file', () => { expect(toXmb(HTML)).toEqual(XMB); }); + it('should write a valid xmb file', () => { + expect(toXmb(HTML)).toEqual(XMB); + // the locale is not specified in the xmb file + expect(toXmb(HTML, 'fr')).toEqual(XMB); + }); it('should throw when trying to load an xmb file', () => { expect(() => { @@ -67,8 +71,8 @@ export function main(): void { }); } -function toXmb(html: string): string { - const catalog = new MessageBundle(new HtmlParser, [], {}); +function toXmb(html: string, locale: string | null = null): string { + const catalog = new MessageBundle(new HtmlParser, [], {}, locale); const serializer = new Xmb(); catalog.updateFromTemplate(html, '', DEFAULT_INTERPOLATION_CONFIG); diff --git a/modules/@angular/compiler/test/i18n/serializers/xtb_spec.ts b/modules/@angular/compiler/test/i18n/serializers/xtb_spec.ts index 41781880c5..65b1b05966 100644 --- a/modules/@angular/compiler/test/i18n/serializers/xtb_spec.ts +++ b/modules/@angular/compiler/test/i18n/serializers/xtb_spec.ts @@ -186,8 +186,8 @@ export function main(): void { }); it('should throw when trying to save an xtb file', - () => { expect(() => { serializer.write([]); }).toThrowError(/Unsupported/); }); + () => { expect(() => { serializer.write([], null); }).toThrowError(/Unsupported/); }); }); }); -} \ No newline at end of file +} diff --git a/scripts/ci-lite/offline_compiler_test.sh b/scripts/ci-lite/offline_compiler_test.sh index 1076926ad7..107d308e16 100755 --- a/scripts/ci-lite/offline_compiler_test.sh +++ b/scripts/ci-lite/offline_compiler_test.sh @@ -48,7 +48,7 @@ cp -v package.json $TMP ./node_modules/.bin/ngc -p tsconfig-build.json --i18nFile=src/messages.fi.xlf --locale=fi --i18nFormat=xlf - ./node_modules/.bin/ng-xi18n -p tsconfig-xi18n.json --i18nFormat=xlf + ./node_modules/.bin/ng-xi18n -p tsconfig-xi18n.json --i18nFormat=xlf --locale=fr ./node_modules/.bin/ng-xi18n -p tsconfig-xi18n.json --i18nFormat=xmb node test/test_summaries.js diff --git a/tools/@angular/tsc-wrapped/src/cli_options.ts b/tools/@angular/tsc-wrapped/src/cli_options.ts index 60d26fe45f..acd1db988b 100644 --- a/tools/@angular/tsc-wrapped/src/cli_options.ts +++ b/tools/@angular/tsc-wrapped/src/cli_options.ts @@ -12,10 +12,12 @@ export class CliOptions { export class I18nExtractionCliOptions extends CliOptions { public i18nFormat: string; + public locale: string; - constructor({i18nFormat = null}: {i18nFormat?: string}) { + constructor({i18nFormat = null, locale = null}: {i18nFormat?: string, locale: string|null}) { super({}); this.i18nFormat = i18nFormat; + this.locale = locale; } }