Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i18n - legacy message id changes #32867

Closed
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
15 changes: 9 additions & 6 deletions integration/cli-hello-world-ivy-i18n/e2e/src/app.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { AppPage } from './app.po';
import {AppPage} from './app.po';

describe('cli-hello-world-ivy App', () => {
let page: AppPage;

beforeEach(() => {
page = new AppPage();
beforeEach(() => { page = new AppPage(); });

it('should display title', () => {
page.navigateTo();
expect(page.getHeading()).toEqual('Bonjour cli-hello-world-ivy-compat!');
});

it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to cli-hello-world-ivy-compat!');
expect(page.getParagraph('message')).toEqual('Bienvenue sur l\'application i18n.');
});

it('the percent pipe should work', () => {
page.navigateTo();
expect(page.getPipeContent()).toEqual('100 % awesome');
})
expect(page.getParagraph('pipe')).toEqual('100 % awesome');
});
});
14 changes: 4 additions & 10 deletions integration/cli-hello-world-ivy-i18n/e2e/src/app.po.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { browser, by, element } from 'protractor';
import {browser, by, element} from 'protractor';

export class AppPage {
navigateTo() {
return browser.get('/');
}
navigateTo() { return browser.get('/'); }

getParagraphText() {
return element(by.css('app-root h1')).getText();
}
getHeading() { return element(by.css('app-root h1')).getText(); }

getPipeContent() {
return element(by.css('app-root p')).getText();
}
getParagraph(name: string) { return element(by.css('app-root p#' + name)).getText(); }
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1 i18n>
Welcome to {{ title }}!
Hello {{ title }}!
</h1>

<p id="message">{{ message }}</p>
<img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<p>{{ 1 | percent }} awesome</p>
<p id="pipe">{{ 1 | percent }} awesome</p>
<h2>Here are some links to help you start: </h2>
<ul>
<li>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
import {TestBed, async} from '@angular/core/testing';
import {AppComponent} from './app.component';

describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
TestBed
.configureTestingModule({
declarations: [AppComponent],
})
.compileComponents();
}));

it('should create the app', () => {
Expand All @@ -26,6 +26,7 @@ describe('AppComponent', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to cli-hello-world-ivy-compat!');
expect(compiled.querySelector('h1').textContent)
.toContain('Bonjour cli-hello-world-ivy-compat!');
});
});
12 changes: 5 additions & 7 deletions integration/cli-hello-world-ivy-i18n/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { Component } from '@angular/core';
import {Component} from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
@Component(
{selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css']})
export class AppComponent {
title = $localize `cli-hello-world-ivy-compat`;
title = `cli-hello-world-ivy-compat`;
message = $localize `Welcome to the i18n app.`;
}
22 changes: 9 additions & 13 deletions integration/cli-hello-world-ivy-i18n/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import { BrowserModule } from '@angular/platform-browser';
import { LOCALE_ID, NgModule } from '@angular/core';
import { registerLocaleData } from '@angular/common';
import {registerLocaleData} from '@angular/common';
import {LOCALE_ID, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import localeFr from '@angular/common/locales/fr';

import { AppComponent } from './app.component';
import {AppComponent} from './app.component';

// adding this code to detect issues like https://github.com/angular/angular-cli/issues/10322
registerLocaleData(localeFr);

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [{ provide: LOCALE_ID, useValue: 'fr' }],
declarations: [AppComponent],
imports: [BrowserModule],
providers: [{provide: LOCALE_ID, useValue: 'fr'}],
bootstrap: [AppComponent]
})
export class AppModule { }
export class AppModule {
}
11 changes: 11 additions & 0 deletions integration/cli-hello-world-ivy-i18n/src/polyfills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
*/
import '@angular/localize/init';

// Note that `computeMsgId` is a private API at this stage. It will probably be exported directly
// from `@angular/localize` at some point.
import {computeMsgId} from '@angular/compiler';
import {loadTranslations} from '@angular/localize';

// Load some runtime translations!
loadTranslations({
[computeMsgId(' Hello {$INTERPOLATION}! ')]: 'Bonjour {$INTERPOLATION}!',
[computeMsgId('Welcome to the i18n app.')]: 'Bienvenue sur l\'application i18n.',
});

/***************************************************************************************************
* APPLICATION IMPORTS
*/
28 changes: 8 additions & 20 deletions packages/compiler-cli/src/ngtsc/translator/src/translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import {ArrayType, AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, Type, TypeVisitor, TypeofExpr, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
import {LocalizedString} from '@angular/compiler/src/output/output_ast';
import {serializeI18nHead, serializeI18nTemplatePart} from '@angular/compiler/src/render3/view/i18n/meta';
import * as ts from 'typescript';

import {DefaultImportRecorder, ImportRewriter, NOOP_DEFAULT_IMPORT_RECORDER, NoopImportRewriter} from '../../imports';
Expand Down Expand Up @@ -528,16 +529,18 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
*/
function visitLocalizedString(ast: LocalizedString, context: Context, visitor: ExpressionVisitor) {
let template: ts.TemplateLiteral;
const metaBlock = serializeI18nHead(ast.metaBlock, ast.messageParts[0]);
if (ast.messageParts.length === 1) {
template = ts.createNoSubstitutionTemplateLiteral(ast.messageParts[0]);
template = ts.createNoSubstitutionTemplateLiteral(metaBlock);
} else {
const head = ts.createTemplateHead(ast.messageParts[0]);
const head = ts.createTemplateHead(metaBlock);
const spans: ts.TemplateSpan[] = [];
for (let i = 1; i < ast.messageParts.length; i++) {
const resolvedExpression = ast.expressions[i - 1].visitExpression(visitor, context);
spans.push(ts.createTemplateSpan(
resolvedExpression, ts.createTemplateMiddle(prefixWithPlaceholderMarker(
ast.messageParts[i], ast.placeHolderNames[i - 1]))));
const templatePart =
serializeI18nTemplatePart(ast.placeHolderNames[i - 1], ast.messageParts[i]);
const templateMiddle = ts.createTemplateMiddle(templatePart);
spans.push(ts.createTemplateSpan(resolvedExpression, templateMiddle));
}
if (spans.length > 0) {
// The last span is supposed to have a tail rather than a middle
Expand All @@ -547,18 +550,3 @@ function visitLocalizedString(ast: LocalizedString, context: Context, visitor: E
}
return ts.createTaggedTemplate(ts.createIdentifier('$localize'), template);
}

/**
* We want our tagged literals to include placeholder name information to aid runtime translation.
*
* The expressions are marked with placeholder names by postfixing the expression with
* `:placeHolderName:`. To achieve this, we actually "prefix" the message part that follows the
* expression.
*
* @param messagePart the message part that follows the current expression.
* @param placeHolderName the name of the placeholder for the current expression.
* @returns the prefixed message part.
*/
function prefixWithPlaceholderMarker(messagePart: string, placeHolderName: string) {
return `:${placeHolderName}:${messagePart}`;
}
Loading