diff --git a/packages/compiler/src/output/abstract_js_emitter.ts b/packages/compiler/src/output/abstract_js_emitter.ts index 621a67af40..fde1fbe80b 100644 --- a/packages/compiler/src/output/abstract_js_emitter.ts +++ b/packages/compiler/src/output/abstract_js_emitter.ts @@ -7,7 +7,7 @@ */ -import {AbstractEmitterVisitor, CATCH_ERROR_VAR, CATCH_STACK_VAR, EmitterVisitorContext} from './abstract_emitter'; +import {AbstractEmitterVisitor, CATCH_ERROR_VAR, CATCH_STACK_VAR, EmitterVisitorContext, escapeIdentifier} from './abstract_emitter'; import * as o from './output_ast'; export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { @@ -150,6 +150,42 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { return null; } + visitLocalizedString(ast: o.LocalizedString, ctx: EmitterVisitorContext): any { + // The following convoluted piece of code is effectively the downlevelled equivalent of + // ``` + // $localize `...` + // ``` + // which is effectively like: + // ``` + // $localize(__makeTemplateObject(cooked, raw), expression1, expression2, ...); + // ``` + // + // The `$localize` function expects a "template object", which is an array of "cooked" strings + // plus a `raw` property that contains an array of "raw" strings. + // + // In some environments a helper function called `__makeTemplateObject(cooked, raw)` might be + // available, in which case we use that. Otherwise we must create our own helper function + // inline. + // + // In the inline function, if `Object.defineProperty` is available we use that to attach the + // `raw` array. + ctx.print( + ast, + '$localize((this&&this.__makeTemplateObject||function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e})('); + const parts = [ast.serializeI18nHead()]; + for (let i = 1; i < ast.messageParts.length; i++) { + parts.push(ast.serializeI18nTemplatePart(i)); + } + ctx.print(ast, `[${parts.map(part => escapeIdentifier(part.cooked, false)).join(', ')}], `); + ctx.print(ast, `[${parts.map(part => escapeIdentifier(part.raw, false)).join(', ')}])`); + ast.expressions.forEach(expression => { + ctx.print(ast, ', '); + expression.visitExpression(this, ctx); + }); + ctx.print(ast, ')'); + return null; + } + private _visitParams(params: o.FnParam[], ctx: EmitterVisitorContext): void { this.visitAllObjects(param => ctx.print(null, param.name), params, ctx, ','); } diff --git a/packages/compiler/test/output/js_emitter_spec.ts b/packages/compiler/test/output/js_emitter_spec.ts index e38e310ba5..a60c15c706 100644 --- a/packages/compiler/test/output/js_emitter_spec.ts +++ b/packages/compiler/test/output/js_emitter_spec.ts @@ -200,6 +200,15 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some ].join('\n')); }); + it('should support ES5 localized strings', () => { + expect(emitStmt(new o.ExpressionStatement(o.localizedString( + {}, ['ab\\:c', 'd"e\'f'], ['ph1'], + [o.literal(7, o.NUMBER_TYPE).plus(o.literal(8, o.NUMBER_TYPE))])))) + .toEqual( + String.raw + `$localize((this&&this.__makeTemplateObject||function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e})(['ab\\:c', ':ph1:d"e\'f'], ['ab\\\\:c', ':ph1:d"e\'f']), (7 + 8));`); + }); + it('should support try/catch', () => { const bodyStmt = o.variable('body').callFn([]).toStmt(); const catchStmt = diff --git a/packages/compiler/test/output/ts_emitter_spec.ts b/packages/compiler/test/output/ts_emitter_spec.ts index aa4b7ab649..cc287c22a8 100644 --- a/packages/compiler/test/output/ts_emitter_spec.ts +++ b/packages/compiler/test/output/ts_emitter_spec.ts @@ -249,6 +249,13 @@ const externalModuleIdentifier = new o.ExternalReference(anotherModuleUrl, 'some ].join('\n')); }); + it('should support localized strings', () => { + expect(emitStmt(new o.ExpressionStatement(o.localizedString( + {}, ['ab\\:c', 'd"e\'f'], ['ph1'], + [o.literal(7, o.NUMBER_TYPE).plus(o.literal(8, o.NUMBER_TYPE))])))) + .toEqual('$localize `ab\\\\:c${(7 + 8)}:ph1:d"e\'f`;'); + }); + it('should support try/catch', () => { const bodyStmt = o.variable('body').callFn([]).toStmt(); const catchStmt =