Skip to content
Permalink
Browse files

fix(ngcc): render localized strings when in ES5 format

Recently the ngtsc translator was modified to be more `ScriptTarget`
aware, which basically means that it will not generate non-ES5 code
when the output format is ES5 or similar.

This commit enhances that change by also "downleveling" localized
messages. In ES2015 the messages use tagged template literals, which
are not available in ES5.
  • Loading branch information
petebacondarwin committed Nov 15, 2019
1 parent c68200c commit 81294f89f4abb7962eb1955d509ba2dc0f93f4b9
@@ -223,6 +223,38 @@ runInEachFileSystem(() => {
expect(esm5Contents).toContain(`export {InternalFooModule} from './src/internal';`);
});

it('should use `$localize` calls rather than tagged templates in ES5 generated code', () => {
compileIntoFlatEs5Package('test-package', {
'/index.ts': `
import {Component, Input, NgModule} from '@angular/core';
@Component({
selector: '[foo]',
template: '<div i18n="some:\`description\`">A message</div>'
})
export class FooComponent {
}
@NgModule({
declarations: [FooComponent],
})
export class FooModule {}
`,
});

mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'test-package',
propertiesToConsider: ['main'],
});

const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`));
expect(jsContents).not.toMatch(/\$localize\s*`/);
expect(jsContents)
.toMatch(
/\$localize\(ɵngcc\d+\.__makeTemplateObject\(\[":some:`description`:A message"], \[":some\\\\:\\\\`description\\\\`:A message"]\)\);/);
});

describe('in async mode', () => {
it('should run ngcc without errors for fesm2015', async() => {
const promise = mainNgcc({
@@ -262,13 +262,9 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
}

visitLocalizedString(ast: LocalizedString, context: Context): ts.Expression {
if (this.scriptTarget < ts.ScriptTarget.ES2015) {
// This should never happen.
throw new Error(
'Unsupported mode: Visiting a localized string (which produces a tagged template ' +
`literal) ' while targeting ${ts.ScriptTarget[this.scriptTarget]}.`);
}
return visitLocalizedString(ast, context, this);
return this.scriptTarget >= ts.ScriptTarget.ES2015 ?
createLocalizedStringTaggedTemplate(ast, context, this) :
createLocalizedStringFunctionCall(ast, context, this, this.imports);
}

visitExternalExpr(ast: ExternalExpr, context: Context): ts.PropertyAccessExpression
@@ -458,7 +454,7 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
}

visitLocalizedString(ast: LocalizedString, context: Context): ts.Expression {
return visitLocalizedString(ast, context, this);
throw new Error('Method not implemented.');
}

visitExternalExpr(ast: ExternalExpr, context: Context): ts.TypeNode {
@@ -541,10 +537,11 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
}

/**
* A helper to reduce duplication, since this functionality is required in both
* `ExpressionTranslatorVisitor` and `TypeTranslatorVisitor`.
* Translate the `LocalizedString` node into a `TaggedTemplateExpression` for ES2015 formatted
* output.
*/
function visitLocalizedString(ast: LocalizedString, context: Context, visitor: ExpressionVisitor) {
function createLocalizedStringTaggedTemplate(
ast: LocalizedString, context: Context, visitor: ExpressionVisitor) {
let template: ts.TemplateLiteral;
const metaBlock = ast.serializeI18nHead();
if (ast.messageParts.length === 1) {
@@ -566,3 +563,32 @@ function visitLocalizedString(ast: LocalizedString, context: Context, visitor: E
}
return ts.createTaggedTemplate(ts.createIdentifier('$localize'), template);
}

/**
* Translate the `LocalizedString` node into a `$localize` call using the imported
* `__makeTemplateObject` helper for ES5 formatted output.
*/
function createLocalizedStringFunctionCall(
ast: LocalizedString, context: Context, visitor: ExpressionVisitor, imports: ImportManager) {
const messageParts = [ast.serializeI18nHead()];
const expressions: any[] = [];
for (let i = 1; i < ast.messageParts.length; i++) {
expressions.push(ast.expressions[i - 1].visitExpression(visitor, context));
messageParts.push(ast.serializeI18nTemplatePart(i));
}
const {moduleImport, symbol} = imports.generateNamedImport('tslib', '__makeTemplateObject');
const __makeTemplateObjectHelper = (moduleImport === null) ?
ts.createIdentifier(symbol) :
ts.createPropertyAccess(ts.createIdentifier(moduleImport), ts.createIdentifier(symbol));
return ts.createCall(ts.createIdentifier('$localize'), undefined, [
ts.createCall(
__makeTemplateObjectHelper, undefined,
[
ts.createArrayLiteral(
messageParts.map(messagePart => ts.createStringLiteral(messagePart.cooked))),
ts.createArrayLiteral(
messageParts.map(messagePart => ts.createStringLiteral(messagePart.raw))),
]),
...expressions,
]);
}

0 comments on commit 81294f8

Please sign in to comment.
You can’t perform that action at this time.