refactor(compiler): i18n - render legacy i18n message ids (#34135)

Now that `@angular/localize` can interpret multiple legacy message ids in the
metablock of a `$localize` tagged template string, this commit adds those
ids to each i18n message extracted from component templates, but only if
the `enableI18nLegacyMessageIdFormat` is not `false`.

PR Close #34135
This commit is contained in:
Pete Bacon Darwin
2019-12-03 08:36:38 +00:00
committed by Miško Hevery
parent 8e96b450e2
commit e524322c43
12 changed files with 100 additions and 124 deletions

View File

@ -52,7 +52,7 @@ export class ComponentDecoratorHandler implements
private scopeReader: ComponentScopeReader, private scopeRegistry: LocalModuleScopeRegistry,
private isCore: boolean, private resourceLoader: ResourceLoader, private rootDirs: string[],
private defaultPreserveWhitespaces: boolean, private i18nUseExternalIds: boolean,
private i18nLegacyMessageIdFormat: string, private moduleResolver: ModuleResolver,
private enableI18nLegacyMessageIdFormat: boolean, private moduleResolver: ModuleResolver,
private cycleAnalyzer: CycleAnalyzer, private refEmitter: ReferenceEmitter,
private defaultImportRecorder: DefaultImportRecorder,
private annotateForClosureCompiler: boolean,
@ -710,7 +710,7 @@ export class ComponentDecoratorHandler implements
preserveWhitespaces,
interpolationConfig: interpolation,
range: templateRange, escapedString,
i18nLegacyMessageIdFormat: this.i18nLegacyMessageIdFormat, ...options,
enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat, ...options,
}),
template: templateStr, templateUrl,
isInline: component.has('template'),

View File

@ -63,7 +63,7 @@ runInEachFileSystem(() => {
reflectionHost, evaluator, metaRegistry, metaReader, scopeRegistry, scopeRegistry,
/* isCore */ false, new NoopResourceLoader(), /* rootDirs */[''],
/* defaultPreserveWhitespaces */ false, /* i18nUseExternalIds */ true,
/* i18nLegacyMessageIdFormat */ '', moduleResolver, cycleAnalyzer, refEmitter,
/* enableI18nLegacyMessageIdFormat */ false, moduleResolver, cycleAnalyzer, refEmitter,
NOOP_DEFAULT_IMPORT_RECORDER, /* annotateForClosureCompiler */ false);
const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration);
const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp));

View File

@ -623,9 +623,9 @@ export class NgtscProgram implements api.Program {
this.reflector, evaluator, metaRegistry, this.metaReader !, scopeReader, scopeRegistry,
this.isCore, this.resourceManager, this.rootDirs,
this.options.preserveWhitespaces || false, this.options.i18nUseExternalIds !== false,
this.getI18nLegacyMessageFormat(), this.moduleResolver, this.cycleAnalyzer,
this.refEmitter, this.defaultImportTracker, this.closureCompilerEnabled,
this.incrementalDriver),
this.options.enableI18nLegacyMessageIdFormat !== false, this.moduleResolver,
this.cycleAnalyzer, this.refEmitter, this.defaultImportTracker,
this.closureCompilerEnabled, this.incrementalDriver),
new DirectiveDecoratorHandler(
this.reflector, evaluator, metaRegistry, this.defaultImportTracker, this.isCore,
this.closureCompilerEnabled),
@ -648,11 +648,6 @@ export class NgtscProgram implements api.Program {
this.options.compileNonExportedClasses !== false);
}
private getI18nLegacyMessageFormat(): string {
return this.options.enableI18nLegacyMessageIdFormat !== false && this.options.i18nInFormat ||
'';
}
private get reflector(): TypeScriptReflectionHost {
if (this._reflector === undefined) {
this._reflector = new TypeScriptReflectionHost(this.tsProgram.getTypeChecker());

View File

@ -263,11 +263,10 @@ export interface CompilerOptions extends ts.CompilerOptions {
i18nUseExternalIds?: boolean;
/**
* Render `$localize` message ids with the legacy format (xlf, xlf2 or xmb) specified in
* `i18nInFormat`.
* Render `$localize` messages with legacy format ids.
*
* This is only active if we are building with `enableIvy: true` and a valid
* `i18nInFormat` has been provided. The default value for now is `true`.
* This is only active if we are building with `enableIvy: true`.
* The default value for now is `true`.
*
* Use this option when use are using the `$localize` based localization messages but
* have not migrated the translation files to use the new `$localize` message id format.

View File

@ -210,7 +210,8 @@ export function compile(
scripts, {
target: ts.ScriptTarget.ES2015,
module: ts.ModuleKind.ES2015,
moduleResolution: ts.ModuleResolutionKind.NodeJs, ...options,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
enableI18nLegacyMessageIdFormat: false, ...options,
},
mockCompilerHost);
program.emit();

View File

@ -3457,6 +3457,38 @@ describe('i18n support in the template compiler', () => {
});
});
describe('$localize legacy message ids', () => {
it('should add legacy message ids if `enableI18nLegacyMessageIdFormat` is true', () => {
const input = `<div i18n>Some Message</div>`;
const output = String.raw `
var $I18N_0$;
if (ngI18nClosureMode) { … }
else {
$I18N_0$ = $localize \`:␟ec93160d6d6a8822214060dd7938bf821c22b226␟6795333002533525253:Some Message\`;
}
`;
verify(input, output, {compilerOptions: {enableI18nLegacyMessageIdFormat: true}});
});
it('should add legacy message ids if `enableI18nLegacyMessageIdFormat` is undefined', () => {
const input = `<div i18n>Some Message</div>`;
const output = String.raw `
var $I18N_0$;
if (ngI18nClosureMode) { … }
else {
$I18N_0$ = $localize \`:␟ec93160d6d6a8822214060dd7938bf821c22b226␟6795333002533525253:Some Message\`;
}
`;
verify(input, output, {compilerOptions: {enableI18nLegacyMessageIdFormat: undefined}});
});
});
describe('errors', () => {
const verifyNestedSectionsError = (errorThrown: any, expectedErrorText: string) => {
expect(errorThrown.ngParseErrors.length).toBe(1);

View File

@ -2454,82 +2454,23 @@ runInEachFileSystem(os => {
expect(jsContents).not.toContain('MSG_EXTERNAL_');
});
it('should render legacy id when `enableI18nLegacyMessageIdFormat` is not false and `i18nInFormat` is set to "xlf"',
() => {
env.tsconfig({i18nInFormat: 'xlf'});
env.write(`test.ts`, `
it('should render legacy ids when `enableI18nLegacyMessageIdFormat` is not false', () => {
env.tsconfig({});
env.write(`test.ts`, `
import {Component} from '@angular/core';
@Component({
selector: 'test',
template: '<div i18n>Some text</div>'
})
class FooCmp {}`);
env.driveMain();
const jsContents = env.getContents('test.js');
expect(jsContents).toContain(':@@5dbba0a3da8dff890e20cf76eb075d58900fbcd3:Some text');
});
env.driveMain();
const jsContents = env.getContents('test.js');
expect(jsContents)
.toContain(
'":\\u241F5dbba0a3da8dff890e20cf76eb075d58900fbcd3\\u241F8321000940098097247:Some text"');
});
it('should render legacy id when `enableI18nLegacyMessageIdFormat` is not false and `i18nInFormat` is set to "xliff"',
() => {
env.tsconfig({i18nInFormat: 'xliff'});
env.write(`test.ts`, `
import {Component} from '@angular/core';
@Component({
selector: 'test',
template: '<div i18n>Some text</div>'
})
class FooCmp {}`);
env.driveMain();
const jsContents = env.getContents('test.js');
expect(jsContents).toContain(':@@5dbba0a3da8dff890e20cf76eb075d58900fbcd3:Some text');
});
it('should render legacy id when `enableI18nLegacyMessageIdFormat` is not false and `i18nInFormat` is set to "xlf2"',
() => {
env.tsconfig({i18nInFormat: 'xlf2'});
env.write(`test.ts`, `
import {Component} from '@angular/core';
@Component({
selector: 'test',
template: '<div i18n>Some text</div>'
})
class FooCmp {}`);
env.driveMain();
const jsContents = env.getContents('test.js');
expect(jsContents).toContain(':@@8321000940098097247:Some text');
});
it('should render legacy id when `enableI18nLegacyMessageIdFormat` is not false and `i18nInFormat` is set to "xliff2"',
() => {
env.tsconfig({i18nInFormat: 'xliff2'});
env.write(`test.ts`, `
import {Component} from '@angular/core';
@Component({
selector: 'test',
template: '<div i18n>Some text</div>'
})
class FooCmp {}`);
env.driveMain();
const jsContents = env.getContents('test.js');
expect(jsContents).toContain(':@@8321000940098097247:Some text');
});
it('should render legacy id when `enableI18nLegacyMessageIdFormat` is not false and `i18nInFormat` is set to "xmb"',
() => {
env.tsconfig({i18nInFormat: 'xmb'});
env.write(`test.ts`, `
import {Component} from '@angular/core';
@Component({
selector: 'test',
template: '<div i18n>Some text</div>'
})
class FooCmp {}`);
env.driveMain();
const jsContents = env.getContents('test.js');
expect(jsContents).toContain(':@@8321000940098097247:Some text');
});
it('should render custom id even if `enableI18nLegacyMessageIdFormat` is not false and `i18nInFormat` is set',
it('should render custom id and legacy ids if `enableI18nLegacyMessageIdFormat` is not false',
() => {
env.tsconfig({i18nFormatIn: 'xlf'});
env.write(`test.ts`, `
@ -2541,25 +2482,28 @@ runInEachFileSystem(os => {
class FooCmp {}`);
env.driveMain();
const jsContents = env.getContents('test.js');
expect(jsContents).toContain(':@@custom:Some text');
expect(jsContents)
.toContain(
':@@custom\\u241F5dbba0a3da8dff890e20cf76eb075d58900fbcd3\\u241F8321000940098097247:Some text');
});
it('should not render legacy id when `enableI18nLegacyMessageIdFormat` is set to false', () => {
env.tsconfig({enableI18nLegacyMessageIdFormat: false, i18nInFormat: 'xmb'});
env.write(`test.ts`, `
it('should not render legacy ids when `enableI18nLegacyMessageIdFormat` is set to false',
() => {
env.tsconfig({enableI18nLegacyMessageIdFormat: false, i18nInFormat: 'xmb'});
env.write(`test.ts`, `
import {Component} from '@angular/core';
@Component({
selector: 'test',
template: '<div i18n>Some text</div>'
})
class FooCmp {}`);
env.driveMain();
const jsContents = env.getContents('test.js');
// Note that the colon would only be there if there is an id attached to the string.
expect(jsContents).not.toContain(':Some text');
});
env.driveMain();
const jsContents = env.getContents('test.js');
// Note that the colon would only be there if there is an id attached to the string.
expect(jsContents).not.toContain(':Some text');
});
it('should also render legacy id for ICUs when normal messages are using legacy ids', () => {
it('should also render legacy ids for ICUs when normal messages are using legacy ids', () => {
env.tsconfig({i18nInFormat: 'xliff'});
env.write(`test.ts`, `
import {Component} from '@angular/core';
@ -2572,8 +2516,10 @@ runInEachFileSystem(os => {
const jsContents = env.getContents('test.js');
expect(jsContents)
.toContain(
':@@720ba589d043a0497ac721ff972f41db0c919efb:{VAR_PLURAL, plural, 10 {ten} other {other}}');
expect(jsContents).toContain(':@@custom:Some text');
':\\u241F720ba589d043a0497ac721ff972f41db0c919efb\\u241F3221232817843005870:{VAR_PLURAL, plural, 10 {ten} other {other}}');
expect(jsContents)
.toContain(
':@@custom\\u241Fdcb6170595f5d548a3d00937e87d11858f51ad04\\u241F7419139165339437596:Some text');
});
it('@Component\'s `interpolation` should override default interpolation config', () => {