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

feat(ivy): allow the locale to be set via a global property #33314

Closed
wants to merge 8 commits into from
4 changes: 2 additions & 2 deletions integration/_payload-limits.json
Expand Up @@ -30,8 +30,8 @@
"master": {
"uncompressed": {
"runtime-es2015": 1485,
"main-es2015": 128258,
"polyfills-es2015": 42102
"main-es2015": 138032,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is bigger because we added to the application component's template: the date pipe and the locale id.

"polyfills-es2015": 37494
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The polyfills.ts size is lower because I removed the runtime translations from it. The production build would inline them instead.

}
}
},
Expand Down
21 changes: 19 additions & 2 deletions integration/cli-hello-world-ivy-i18n/angular.json
Expand Up @@ -33,6 +33,7 @@
"tsConfig": "tsconfig.view-engine.json"
},
"production": {
"localize": false, // TODO: enable when CLI supports inlining the locale
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
Expand Down Expand Up @@ -61,6 +62,14 @@
}
]
},
"runtime-translations": {
"fileReplacements": [
{
"replace": "src/polyfills.ts",
"with": "src/polyfills-runtime.ts"
}
]
},
"translated-legacy": {
"tsConfig": "tsconfig.legacy.json",
"optimization": true,
Expand Down Expand Up @@ -90,6 +99,10 @@
"ci-production": {
"browserTarget": "cli-hello-world-ivy-i18n:build:production",
"progress": false
},
"runtime-translations": {
"browserTarget": "cli-hello-world-ivy-i18n:build:runtime-translations",
"progress": false
}
}
},
Expand Down Expand Up @@ -136,7 +149,7 @@
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/runtime/protractor.conf.js",
"protractorConfig": "e2e/en/protractor.conf.js",
"devServerTarget": "cli-hello-world-ivy-i18n:serve",
"webdriverUpdate": false
},
Expand All @@ -148,7 +161,11 @@
"devServerTarget": "cli-hello-world-ivy-i18n:serve:ci"
},
"ci-production": {
"devServerTarget": "cli-hello-world-ivy-i18n:serve:ci-production"
"devServerTarget": "cli-hello-world-ivy-i18n:serve:ci-production",
},
"runtime-translations": {
"devServerTarget": "cli-hello-world-ivy-i18n:serve:runtime-translations",
"protractorConfig": "e2e/fr/protractor.conf.js"
},
"translated-legacy": {
"devServerTarget": "",
Expand Down
5 changes: 4 additions & 1 deletion integration/cli-hello-world-ivy-i18n/e2e/README.md
Expand Up @@ -5,7 +5,10 @@ translation scenarios, but they are all built with IVY enabled.

### runtime

Translations are provided at runtime by calling `loadTranslations()` in the polyfill.ts
A new `polyfills.ts` file is provided (`polyfills-runtime.ts`) which is swapped in by a file
replacement in the `angular.json` configuration. In this new file:
* Runtime translations are provided (`loadTranslations()`).
* The current locale is set (`$localize.locale = 'fr'`) and loaded (`registerLocaleData(localeFr);`)

### de and fr

Expand Down
9 changes: 9 additions & 0 deletions integration/cli-hello-world-ivy-i18n/e2e/de/app.e2e-spec.ts
Expand Up @@ -14,4 +14,13 @@ describe('cli-hello-world-ivy App', () => {
it('should display welcome message', () => {
expect(page.getParagraph('message')).toEqual('Willkommen in der i18n App. (inline)');
});

it('should display the locale', () => { expect(page.getParagraph('locale')).toEqual('de'); });

// TODO : Re-enable when CLI translation inlining supports locale inlining (and so we can use it
// to load the correct locale data)
xit('the date pipe should show the localized month', () => {
page.navigateTo();
expect(page.getParagraph('date')).toEqual('Januar');
});
});
7 changes: 7 additions & 0 deletions integration/cli-hello-world-ivy-i18n/e2e/en/app.e2e-spec.ts
Expand Up @@ -12,4 +12,11 @@ describe('cli-hello-world-ivy App', () => {

it('should display welcome message',
() => { expect(page.getParagraph('message')).toEqual('Welcome to the i18n app.'); });

it('should display the locale', () => { expect(page.getParagraph('locale')).toEqual('en-US'); });

it('the date pipe should show the localized month', () => {
page.navigateTo();
expect(page.getParagraph('date')).toEqual('January');
});
});
7 changes: 7 additions & 0 deletions integration/cli-hello-world-ivy-i18n/e2e/fr/app.e2e-spec.ts
Expand Up @@ -13,4 +13,11 @@ describe('cli-hello-world-ivy App', () => {
it('should display welcome message', () => {
expect(page.getParagraph('message')).toEqual('Bienvenue sur l\'application i18n. (inline)');
});

it('should display the locale', () => { expect(page.getParagraph('locale')).toEqual('fr'); });

it('the date pipe should show the localized month', () => {
page.navigateTo();
expect(page.getParagraph('date')).toEqual('janvier');
});
});
Expand Up @@ -15,4 +15,6 @@ describe('cli-hello-world-ivy App', () => {
// See "translated:legacy:extract-and-update" in package.json.
expect(page.getParagraph('message')).toEqual('Welcome to the i18n app.');
});

it('should display the locale', () => { expect(page.getParagraph('locale')).toEqual('legacy'); });
});
Expand Up @@ -19,4 +19,11 @@ describe('cli-hello-world-ivy App', () => {
page.navigateTo();
expect(page.getParagraph('pipe')).toEqual('100 % awesome');
});

it('should display the locale', () => { expect(page.getParagraph('locale')).toEqual('fr'); });

it('the date pipe should show the localized month', () => {
page.navigateTo();
expect(page.getParagraph('date')).toEqual('janvier');
});
});
9 changes: 2 additions & 7 deletions integration/cli-hello-world-ivy-i18n/package.json
Expand Up @@ -13,18 +13,13 @@
"pretest": "ng version",
"test": "ng test && yarn e2e --configuration=ci && yarn e2e --configuration=ci-production && yarn translated:test && yarn translated:legacy:test",
"translate": "localize-translate -r \"dist/\" -s \"**/*\" -l \"en-US\" -t \"src/locales/messages.*\" -o \"../tmp/translations/{{LOCALE}}\"",

"translated:test": "yarn build && yarn translate && yarn translated:fr:e2e && yarn translated:de:e2e && yarn translated:en:e2e",

"translated:fr:serve": "serve ../tmp/translations/fr --listen 4200",
"translated:fr:e2e": "npm-run-all -p -r translated:fr:serve \"ng e2e --configuration=translated-fr\"",

"translated:de:serve": "serve ../tmp/translations/de --listen 4200",
"translated:de:e2e": "npm-run-all -p -r translated:de:serve \"ng e2e --configuration=translated-de\"",

"translated:en:serve": "serve ../tmp/translations/en-US --listen 4200",
"translated:en:e2e": "npm-run-all -p -r translated:en:serve \"ng e2e --configuration=translated-en\"",

"translated:legacy:test": "yarn translated:legacy:extract-and-update && ng build --configuration=translated-legacy && yarn translated:legacy:translate && yarn translated:legacy:e2e",
"translated:legacy:extract-and-update": "ng xi18n && sed -i.bak -e 's/source>/target>'/ -e 's/Hello/Bonjour/' -e 's/source-language=\"en-US\"/source-language=\"en-US\" target-language=\"legacy\"/' ../tmp/legacy-locales/messages.legacy.xlf",
"translated:legacy:translate": "localize-translate -r \"dist/\" -s \"**/*\" -t \"../tmp/legacy-locales/messages.legacy.xlf\" -o \"../tmp/translations/{{LOCALE}}\"",
Expand All @@ -50,8 +45,8 @@
"zone.js": "file:../../node_modules/zone.js"
},
"devDependencies": {
"@angular-devkit/build-angular": "0.900.0-next.12",
"@angular/cli": "file:../../node_modules/@angular/cli",
"@angular-devkit/build-angular": "^0.900.0-next.14",
"@angular/cli": "^9.0.0-next.14",
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer that you update the root package.json but I'll understand if it would be safer to do that in a separate pr

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll create a new PR directly after this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IgorMinar - as promised #33382

"@angular/language-service": "file:../../dist/packages-dist/language-service",
"@types/jasmine": "~3.4.0",
Expand Down
Expand Up @@ -7,7 +7,9 @@ <h1 i18n>
<p id="message">{{ message }}</p>
<img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<p id="locale">{{ locale }}</p>
<p id="pipe">{{ 1 | percent }} awesome</p>
<p id="date">{{ jan | date : 'LLLL' }}</p>
<h2>Here are some links to help you start: </h2>
<ul>
<li>
Expand Down
Expand Up @@ -27,6 +27,6 @@ describe('AppComponent', () => {
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent)
.toContain('Bonjour cli-hello-world-ivy-compat!');
.toContain('Hello cli-hello-world-ivy-compat!');
});
});
@@ -1,8 +1,10 @@
import {Component} from '@angular/core';
import {Component, Inject, LOCALE_ID} from '@angular/core';

@Component(
{selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css']})
export class AppComponent {
constructor(@Inject(LOCALE_ID) public locale: string) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 thanks for adding this check!

title = `cli-hello-world-ivy-compat`;
message = $localize `Welcome to the i18n app.`;
jan = new Date(2000, 0, 1);
}
10 changes: 3 additions & 7 deletions integration/cli-hello-world-ivy-i18n/src/app/app.module.ts
@@ -1,17 +1,13 @@
import {registerLocaleData} from '@angular/common';
import {LOCALE_ID, NgModule} from '@angular/core';
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import localeFr from '@angular/common/locales/fr';
import {AppComponent} from './app.component';

// adding this code to detect issues like https://github.com/angular/angular-cli/issues/10322
// it should not affect the CLI importing additional locale data for compile time inlined bundles.
registerLocaleData(localeFr);

@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [{provide: LOCALE_ID, useValue: 'fr'}],
bootstrap: [AppComponent]
})
@NgModule({declarations: [AppComponent], imports: [BrowserModule], bootstrap: [AppComponent]})
export class AppModule {
}
86 changes: 86 additions & 0 deletions integration/cli-hello-world-ivy-i18n/src/polyfills-runtime.ts
@@ -0,0 +1,86 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/

/***************************************************************************************************
* BROWSER POLYFILLS
*/

/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.

/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.

/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags.ts';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch
* requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch
* specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/

/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.

/***************************************************************************************************
* Load `$localize` onto the global scope - used if i18n tags appear in Angular templates.
*/
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.',
});

// Set up the locale for the runtime inlining (EXPERIMENTAL)
$localize.locale = 'fr';
import {registerLocaleData} from '@angular/common';
import localeFr from '@angular/common/locales/fr';
registerLocaleData(localeFr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a lot like our previous CLI transform to add the locale:
https://github.com/angular/angular-cli/blob/99c174c03b101f0118dfb647313fc7a8d5233446/packages/ngtools/webpack/src/transformers/register_locale_data_spec.ts#L28-L44

I imagine it will be added in the same way. Probably in main.ts too, as the polyfills file is not guaranteed to exist.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, when we move over to using the CLI for all the translation stuff then I would expect this to be done by the CLI. But we need to tidy up the locale-id loose ends before we can do that.

I put the calls here because they had to be done before the bootstrap and I preferred to swap out this file than the main.ts in my configuration for runtime translation (since the $localize.locale is probably best done here).


/***************************************************************************************************
* APPLICATION IMPORTS
*/
10 changes: 0 additions & 10 deletions integration/cli-hello-world-ivy-i18n/src/polyfills.ts
Expand Up @@ -62,16 +62,6 @@ 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
Expand Down