-
+
diff --git a/aio/content/examples/template-syntax/src/app/app.component.html b/aio/content/examples/template-syntax/src/app/app.component.html
index e58460bf89..760fa3a367 100644
--- a/aio/content/examples/template-syntax/src/app/app.component.html
+++ b/aio/content/examples/template-syntax/src/app/app.component.html
@@ -544,13 +544,13 @@ bindon-ngModel
-Add {{currentHero.name}} with template
+Add {{currentHero.name}} with template
Hero Detail removed from DOM (via template) because isActive is false
-
+
-
+
diff --git a/aio/content/examples/testing/src/app/app.component.router.spec.ts b/aio/content/examples/testing/src/app/app.component.router.spec.ts
index de22db8ceb..226d6d1ff3 100644
--- a/aio/content/examples/testing/src/app/app.component.router.spec.ts
+++ b/aio/content/examples/testing/src/app/app.component.router.spec.ts
@@ -141,7 +141,7 @@ function createComponent() {
comp = fixture.componentInstance;
const injector = fixture.debugElement.injector;
- location = injector.get(Location);
+ location = injector.get(Location) as SpyLocation;
router = injector.get(Router);
router.initialNavigation();
spyOn(injector.get(TwainService), 'getQuote')
diff --git a/aio/content/examples/testing/src/app/bag/async-helper.spec.ts b/aio/content/examples/testing/src/app/bag/async-helper.spec.ts
index 5106361a58..12c8a4fbbd 100644
--- a/aio/content/examples/testing/src/app/bag/async-helper.spec.ts
+++ b/aio/content/examples/testing/src/app/bag/async-helper.spec.ts
@@ -34,7 +34,7 @@ describe('Angular async helper', () => {
// Use done. Cannot use setInterval with async or fakeAsync
// See https://github.com/angular/angular/issues/10127
- it('should run async test with successful delayed Observable', done => {
+ it('should run async test with successful delayed Observable', (done: any) => {
const source = Observable.of(true).delay(10);
source.subscribe(
val => actuallyDone = true,
diff --git a/aio/content/examples/testing/src/app/bag/bag.no-testbed.spec.ts b/aio/content/examples/testing/src/app/bag/bag.no-testbed.spec.ts
index 6bdbe86cd0..f29672ecf6 100644
--- a/aio/content/examples/testing/src/app/bag/bag.no-testbed.spec.ts
+++ b/aio/content/examples/testing/src/app/bag/bag.no-testbed.spec.ts
@@ -18,7 +18,7 @@ describe('FancyService without the TestBed', () => {
expect(service.getValue()).toBe('real value');
});
- it('#getAsyncValue should return async value', done => {
+ it('#getAsyncValue should return async value', (done: DoneFn) => {
service.getAsyncValue().then(value => {
expect(value).toBe('async value');
done();
@@ -26,7 +26,7 @@ describe('FancyService without the TestBed', () => {
});
// #docregion getTimeoutValue
- it('#getTimeoutValue should return timeout value', done => {
+ it('#getTimeoutValue should return timeout value', (done: DoneFn) => {
service = new FancyService();
service.getTimeoutValue().then(value => {
expect(value).toBe('timeout value');
@@ -35,7 +35,7 @@ describe('FancyService without the TestBed', () => {
});
// #enddocregion getTimeoutValue
- it('#getObservableValue should return observable value', done => {
+ it('#getObservableValue should return observable value', (done: DoneFn) => {
service.getObservableValue().subscribe(value => {
expect(value).toBe('observable value');
done();
diff --git a/aio/content/examples/testing/src/app/bag/bag.spec.ts b/aio/content/examples/testing/src/app/bag/bag.spec.ts
index 3890d8844d..d6fea7ca94 100644
--- a/aio/content/examples/testing/src/app/bag/bag.spec.ts
+++ b/aio/content/examples/testing/src/app/bag/bag.spec.ts
@@ -73,7 +73,7 @@ describe('use inject helper in beforeEach', () => {
}));
// Must use done. See https://github.com/angular/angular/issues/10127
- it('test should wait for FancyService.getObservableDelayValue', done => {
+ it('test should wait for FancyService.getObservableDelayValue', (done: DoneFn) => {
service.getObservableDelayValue().subscribe(value => {
expect(value).toBe('observable delay value');
done();
@@ -187,20 +187,21 @@ describe('TestBed Component Tests', () => {
expect(selected).toHaveText(hero.name);
});
- it('can access the instance variable of an `*ngFor` row', () => {
+ it('can access the instance variable of an `*ngFor` row component', () => {
const fixture = TestBed.createComponent(IoParentComponent);
const comp = fixture.componentInstance;
+ const heroName = comp.heroes[0].name; // first hero's name
fixture.detectChanges();
- const heroEl = fixture.debugElement.query(By.css('.hero')); // first hero
+ const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow
- const ngForRow = heroEl.parent; // Angular's NgForRow wrapper element
+ const hero = ngForRow.context['hero']; // the hero object passed into the row
+ expect(hero.name).toBe(heroName, 'ngRow.context.hero');
- // jasmine.any is instance-of-type test.
- expect(ngForRow.componentInstance).toEqual(jasmine.any(IoComponent), 'component is IoComp');
-
- const hero = ngForRow.context['$implicit']; // the hero object
- expect(hero.name).toBe(comp.heroes[0].name, '1st hero\'s name');
+ const rowComp = ngForRow.componentInstance;
+ // jasmine.any is an "instance-of-type" test.
+ expect(rowComp).toEqual(jasmine.any(IoComponent), 'component is IoComp');
+ expect(rowComp.hero.name).toBe(heroName, 'component.hero');
});
@@ -343,7 +344,7 @@ describe('TestBed Component Tests', () => {
const childComp = el.componentInstance as BankAccountComponent;
expect(childComp).toEqual(jasmine.any(BankAccountComponent));
- expect(el.context).toBe(comp, 'context is the parent component');
+ expect(el.context).toBe(childComp, 'context is the child component');
expect(el.attributes['account']).toBe(childComp.id, 'account attribute');
expect(el.attributes['bank']).toBe(childComp.bank, 'bank attribute');
@@ -447,7 +448,7 @@ describe('TestBed Component Overrides:', () => {
// `inject` uses TestBed's injector
inject([FancyService], (s: FancyService) => testBedProvider = s)();
tcProvider = fixture.debugElement.injector.get(FancyService);
- tpcProvider = fixture.debugElement.children[0].injector.get(FancyService);
+ tpcProvider = fixture.debugElement.children[0].injector.get(FancyService) as FakeFancyService;
expect(testBedProvider).not.toBe(tcProvider, 'testBed/tc not same providers');
expect(testBedProvider).not.toBe(tpcProvider, 'testBed/tpc not same providers');
diff --git a/aio/content/examples/testing/src/app/hero/hero-detail.component.no-testbed.spec.ts b/aio/content/examples/testing/src/app/hero/hero-detail.component.no-testbed.spec.ts
index a6c1af98d7..422dae6f77 100644
--- a/aio/content/examples/testing/src/app/hero/hero-detail.component.no-testbed.spec.ts
+++ b/aio/content/examples/testing/src/app/hero/hero-detail.component.no-testbed.spec.ts
@@ -12,7 +12,7 @@ describe('HeroDetailComponent - no TestBed', () => {
let hds: any;
let router: any;
- beforeEach( done => {
+ beforeEach((done: any) => {
expectedHero = new Hero(42, 'Bubba');
activatedRoute = new ActivatedRouteStub();
activatedRoute.testParams = { id: expectedHero.id };
@@ -45,7 +45,7 @@ describe('HeroDetailComponent - no TestBed', () => {
expect(router.navigate.calls.any()).toBe(false, 'router.navigate not called yet');
});
- it('should navigate when click save resolves', done => {
+ it('should navigate when click save resolves', (done: any) => {
comp.save();
// waits for async save to complete before navigating
hds.saveHero.calls.first().returnValue
diff --git a/aio/content/examples/testing/src/app/hero/hero-detail.component.spec.ts b/aio/content/examples/testing/src/app/hero/hero-detail.component.spec.ts
index 4e1ee034d7..80b6450ac5 100644
--- a/aio/content/examples/testing/src/app/hero/hero-detail.component.spec.ts
+++ b/aio/content/examples/testing/src/app/hero/hero-detail.component.spec.ts
@@ -90,7 +90,7 @@ function overrideSetup() {
beforeEach( async(() => {
createComponent();
// get the component's injected HeroDetailServiceSpy
- hdsSpy = fixture.debugElement.injector.get(HeroDetailService);
+ hdsSpy = fixture.debugElement.injector.get(HeroDetailService) as any;
}));
it('should have called `getHero`', () => {
diff --git a/aio/content/examples/testing/src/app/shared/twain.component.spec.ts b/aio/content/examples/testing/src/app/shared/twain.component.spec.ts
index b80993cc0b..b177c0bfc3 100644
--- a/aio/content/examples/testing/src/app/shared/twain.component.spec.ts
+++ b/aio/content/examples/testing/src/app/shared/twain.component.spec.ts
@@ -78,7 +78,7 @@ describe('TwainComponent', () => {
// #enddocregion tests
// #docregion done-test
- it('should show quote after getQuote promise (done)', done => {
+ it('should show quote after getQuote promise (done)', (done: any) => {
fixture.detectChanges();
// get the spy promise and wait for it to resolve
diff --git a/aio/content/examples/user-input/e2e-spec.ts b/aio/content/examples/user-input/e2e-spec.ts
index c0bb770848..3f178382c7 100644
--- a/aio/content/examples/user-input/e2e-spec.ts
+++ b/aio/content/examples/user-input/e2e-spec.ts
@@ -1,4 +1,4 @@
-'use strict'; // necessary for es6 output in node
+'use strict'; // necessary for es6 output in node
import { browser, element, by, protractor } from 'protractor';
@@ -53,7 +53,7 @@ describe('User Input Tests', function () {
expect(outputTextEle.getText()).toEqual('a | ab | abc |');
});
- xit('should be able to filter key events', () => {
+ it('should be able to filter key events', () => {
let mainEle = element(by.css('key-up3'));
let inputEle = mainEle.element(by.css('input'));
let outputTextEle = mainEle.element(by.css('p'));
diff --git a/aio/content/examples/webpack/config/webpack.common.js b/aio/content/examples/webpack/config/webpack.common.js
index be01ad5603..28be240e04 100644
--- a/aio/content/examples/webpack/config/webpack.common.js
+++ b/aio/content/examples/webpack/config/webpack.common.js
@@ -62,7 +62,7 @@ module.exports = {
// Workaround for angular/angular#11580
new webpack.ContextReplacementPlugin(
// The (\\|\/) piece accounts for path separators in *nix and Windows
- /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
+ /angular(\\|\/)core(\\|\/)@angular/,
helpers.root('./src'), // location of your src
{} // a map of your routes
),
diff --git a/aio/content/examples/webpack/config/webpack.dev.js b/aio/content/examples/webpack/config/webpack.dev.js
index c1484a0caa..57d29560a0 100644
--- a/aio/content/examples/webpack/config/webpack.dev.js
+++ b/aio/content/examples/webpack/config/webpack.dev.js
@@ -6,21 +6,21 @@ var helpers = require('./helpers');
module.exports = webpackMerge(commonConfig, {
devtool: 'cheap-module-eval-source-map',
-
+
output: {
path: helpers.root('dist'),
- publicPath: 'http://localhost:8080/',
+ publicPath: '/',
filename: '[name].js',
chunkFilename: '[id].chunk.js'
},
-
+
plugins: [
new ExtractTextPlugin('[name].css')
],
-
+
devServer: {
historyApiFallback: true,
stats: 'minimal'
}
});
-// #enddocregion
\ No newline at end of file
+// #enddocregion
diff --git a/aio/content/guide/dependency-injection.md b/aio/content/guide/dependency-injection.md
index 1a9e59d95b..3fb0efafb8 100644
--- a/aio/content/guide/dependency-injection.md
+++ b/aio/content/guide/dependency-injection.md
@@ -1079,7 +1079,7 @@ This is especially convenient when you consider that most dependency values are
What if the dependency value isn't a class? Sometimes the thing you want to inject is a
- span string, function, or object.
+ string, function, or object.
diff --git a/aio/content/guide/deployment.md b/aio/content/guide/deployment.md
index 18914f7061..ba52f85129 100644
--- a/aio/content/guide/deployment.md
+++ b/aio/content/guide/deployment.md
@@ -408,7 +408,7 @@ console:
- Angular 2 is running in the development mode. Call enableProdMode() to enable the production mode.
+ Angular is running in the development mode. Call enableProdMode() to enable the production mode.
diff --git a/aio/content/guide/dynamic-form.md b/aio/content/guide/dynamic-form.md
index 8da4890895..3f5e7bc0a2 100644
--- a/aio/content/guide/dynamic-form.md
+++ b/aio/content/guide/dynamic-form.md
@@ -7,48 +7,48 @@ Render dynamic forms with FormGroup.
@description
-We can't always justify the cost and time to build handcrafted forms,
-especially if we'll need a great number of them, they're similar to each other, and they change frequently
+Building handcrafted forms can be costly and time-consuming,
+especially if you need a great number of them, they're similar to each other, and they change frequently
to meet rapidly changing business and regulatory requirements.
-It may be more economical to create the forms dynamically, based on metadata that describe the business object model.
+It may be more economical to create the forms dynamically, based on
+metadata that describes the business object model.
-In this cookbook we show how to use `formGroup` to dynamically render a simple form with different control types and validation.
-It's a primitive start.
+This cookbook shows you how to use `formGroup` to dynamically
+render a simple form with different control types and validation.
+It's a primitive start.
It might evolve to support a much richer variety of questions, more graceful rendering, and superior user experience.
All such greatness has humble beginnings.
-In our example we use a dynamic form to build an online application experience for heroes seeking employment.
+The example in this cookbook is a dynamic form to build an
+online application experience for heroes seeking employment.
The agency is constantly tinkering with the application process.
-We can create the forms on the fly *without changing our application code*.
+You can create the forms on the fly *without changing the application code*.
{@a toc}
-## Table of contents
-
- [Bootstrap](guide/dynamic-form#bootstrap)
-
- [Question Model](guide/dynamic-form#object-model)
-
- [Form Component](guide/dynamic-form#form-component)
-
- [Questionnaire Metadata](guide/dynamic-form#questionnaire-metadata)
-
- [Dynamic Template](guide/dynamic-form#dynamic-template)
+# Contents
+ * [Bootstrap](guide/dynamic-form#bootstrap)
+ * [Question model](guide/dynamic-form#object-model)
+ * [Question form components](guide/dynamic-form#form-component)
+ * [Questionnaire data](guide/dynamic-form#questionnaire-data)
+ * [Dynamic template](guide/dynamic-form#dynamic-template)
-**See the **.
+See the .
{@a bootstrap}
## Bootstrap
-We start by creating an `NgModule` called `AppModule`.
+Start by creating an `NgModule` called `AppModule`.
-In our example we will be using Reactive Forms.
+This cookbook uses [reactive forms](guide/reactive-forms).
-Reactive Forms belongs to a different `NgModule` called `ReactiveFormsModule`, so in order to access any Reactive Forms directives, we have to import `ReactiveFormsModule` from the `@angular/forms` library.
+Reactive forms belongs to a different `NgModule` called `ReactiveFormsModule`,
+so in order to access any reactive forms directives, you have to import
+`ReactiveFormsModule` from the `@angular/forms` library.
-We bootstrap our `AppModule` in main.ts.
+Bootstrap the `AppModule` in `main.ts`.
@@ -66,13 +66,13 @@ We bootstrap our `AppModule` in main.ts.
{@a object-model}
-## Question Model
+## Question model
The next step is to define an object model that can describe all scenarios needed by the form functionality.
-The hero application process involves a form with a lot of questions.
-The "question" is the most fundamental object in the model.
+The hero application process involves a form with a lot of questions.
+The _question_ is the most fundamental object in the model.
-We have created `QuestionBase` as the most fundamental question class.
+The following `QuestionBase` is a fundamental question class.
@@ -81,10 +81,13 @@ We have created `QuestionBase` as the most fundamental question class.
-From this base we derived two new classes in `TextboxQuestion` and `DropdownQuestion` that represent Textbox and Dropdown questions.
-The idea is that the form will be bound to specific question types and render the appropriate controls dynamically.
+From this base you can derive two new classes in `TextboxQuestion` and `DropdownQuestion`
+that represent textbox and dropdown questions.
+The idea is that the form will be bound to specific question types and render the
+appropriate controls dynamically.
-`TextboxQuestion` supports multiple html5 types like text, email, url etc via the `type` property.
+`TextboxQuestion` supports multiple HTML5 types such as text, email, and url
+via the `type` property.
@@ -102,8 +105,9 @@ The idea is that the form will be bound to specific question types and render th
-Next we have defined `QuestionControlService`, a simple service for transforming our questions to a `FormGroup`.
-In a nutshell, the form group consumes the metadata from the question model and allows us to specify default values and validation rules.
+Next is `QuestionControlService`, a simple service for transforming the questions to a `FormGroup`.
+In a nutshell, the form group consumes the metadata from the question model and
+allows you to specify default values and validation rules.
@@ -113,10 +117,11 @@ In a nutshell, the form group consumes the metadata from the question model and
{@a form-component}
## Question form components
-Now that we have defined the complete model we are ready to create components to represent the dynamic form.
+Now that you have defined the complete model you are ready
+to create components to represent the dynamic form.
-`DynamicFormComponent` is the entry point and the main container for the form.
+`DynamicFormComponent` is the entry point and the main container for the form.
@@ -132,9 +137,10 @@ Now that we have defined the complete model we are ready to create components to
-It presents a list of questions, each question bound to a `` component element.
+It presents a list of questions, each bound to a `` component element.
The `` tag matches the `DynamicFormQuestionComponent`,
-the component responsible for rendering the details of each _individual_ question based on values in the data-bound question object.
+the component responsible for rendering the details of each _individual_
+question based on values in the data-bound question object.
@@ -151,26 +157,30 @@ the component responsible for rendering the details of each _individual_ questio
-Notice this component can present any type of question in our model.
-We only have two types of questions at this point but we can imagine many more.
+Notice this component can present any type of question in your model.
+You only have two types of questions at this point but you can imagine many more.
The `ngSwitch` determines which type of question to display.
-In both components we're relying on Angular's **formGroup** to connect the template HTML to the
+In both components you're relying on Angular's **formGroup** to connect the template HTML to the
underlying control objects, populated from the question model with display and validation rules.
-`formControlName` and `formGroup` are directives defined in `ReactiveFormsModule`. Our templates can access these directives directly since we imported `ReactiveFormsModule` from `AppModule`.
-{@a questionnaire-metadata}
+`formControlName` and `formGroup` are directives defined in
+`ReactiveFormsModule`. The templates can access these directives
+directly since you imported `ReactiveFormsModule` from `AppModule`.
+{@a questionnaire-data}
## Questionnaire data
-`DynamicFormComponent` expects the list of questions in the form of an array bound to `@Input() questions`.
+`DynamicFormComponent` expects the list of questions in the form of an array bound to `@Input() questions`.
+
+ The set of questions you've defined for the job application is returned from the `QuestionService`.
+ In a real app you'd retrieve these questions from storage.
+
+ The key point is that you control the hero job application questions
+ entirely through the objects returned from `QuestionService`.
+ Questionnaire maintenance is a simple matter of adding, updating,
+ and removing objects from the `questions` array.
- The set of questions we have defined for the job application is returned from the `QuestionService`.
- In a real app we'd retrieve these questions from storage.
-
- The key point is that we control the hero job application questions entirely through the objects returned from `QuestionService`.
- Questionnaire maintenance is a simple matter of adding, updating, and removing objects from the `questions` array.
-
@@ -178,7 +188,7 @@ underlying control objects, populated from the question model with display and v
-Finally, we display an instance of the form in the `AppComponent` shell.
+Finally, display an instance of the form in the `AppComponent` shell.
@@ -188,17 +198,18 @@ Finally, we display an instance of the form in the `AppComponent` shell.
{@a dynamic-template}
## Dynamic Template
-Although in this example we're modelling a job application for heroes, there are no references to any specific hero question
-outside the objects returned by `QuestionService`.
+Although in this example you're modelling a job application for heroes, there are
+no references to any specific hero question
+outside the objects returned by `QuestionService`.
-This is very important since it allows us to repurpose the components for any type of survey
-as long as it's compatible with our *question* object model.
-The key is the dynamic data binding of metadata used to render the form
-without making any hardcoded assumptions about specific questions.
-In addition to control metadata, we are also adding validation dynamically.
+This is very important since it allows you to repurpose the components for any type of survey
+as long as it's compatible with the *question* object model.
+The key is the dynamic data binding of metadata used to render the form
+without making any hardcoded assumptions about specific questions.
+In addition to control metadata, you are also adding validation dynamically.
-The *Save* button is disabled until the form is in a valid state.
-When the form is valid, we can click *Save* and the app renders the current form values as JSON.
+The *Save* button is disabled until the form is in a valid state.
+When the form is valid, you can click *Save* and the app renders the current form values as JSON.
This proves that any user input is bound back to the data model.
Saving and retrieving the data is an exercise for another time.
diff --git a/aio/content/guide/forms.md b/aio/content/guide/forms.md
index 12195b6ec1..ab2a702ac4 100644
--- a/aio/content/guide/forms.md
+++ b/aio/content/guide/forms.md
@@ -296,6 +296,19 @@ You added a *Submit* button at the bottom with some classes on it for styling.
*You're not using Angular yet*. There are no bindings or extra directives, just layout.
+
+
+
+
+
+In template driven forms, if you've imported `FormsModule`, you don't have to do anything
+to the `
+
+
+
The `container`, `form-group`, `form-control`, and `btn` classes
come from [Twitter Bootstrap](http://getbootstrap.com/css/). These classes are purely cosmetic.
Bootstrap gives the form a little style.
@@ -400,6 +413,42 @@ You left yourself a note to throw it away when you're done.
Focus on the binding syntax: `[(ngModel)]="..."`.
+You need one more addition to display the data. Declare
+a template variable for the form. Update the `
` tag.
Each `FormControl` is registered under the name you assigned to the `name` attribute.
-Read more in [The NgForm directive](guide/forms#ngForm), later in this page.
+Read more in the previous section, [The NgForm directive](guide/forms#ngForm).
@@ -784,32 +833,9 @@ to the hero form component's `onSubmit()` method:
-You added something extra at the end. You defined a
-template reference variable, `#heroForm`, and initialized it with the value "ngForm".
-
-The variable `heroForm` is now a reference to the `NgForm` directive that governs the form as a whole.
-
-
-
-
-
-
-### The _NgForm_ directive
-
-What `NgForm` directive?
-You didn't add an [NgForm](api/forms/index/NgForm-directive) directive.
-
-Angular did. Angular automatically creates and attaches an `NgForm` directive to the `
` tag.
-
-The `NgForm` directive supplements the `form` element with additional features.
-It holds the controls you created for the elements with an `ngModel` directive
-and `name` attribute, and monitors their properties, including their validity.
-It also has its own `valid` property which is true only *if every contained
-control* is valid.
-
-
-
-
+You'd already defined a template reference variable,
+`#heroForm`, and initialized it with the value "ngForm".
+Now, use that variable to access the form with the Submit button.
You'll bind the form's overall validity via
diff --git a/aio/content/guide/i18n.md b/aio/content/guide/i18n.md
index ace7ea4233..fdca5b7fc8 100644
--- a/aio/content/guide/i18n.md
+++ b/aio/content/guide/i18n.md
@@ -12,24 +12,36 @@ Translate the app's template text into multiple languages.
Angular's _internationalization_ (_i18n_) tools help make your app available in multiple languages.
-## Table of contents
+# Contents
* [Angular and i18n template translation](guide/i18n#angular-i18n)
* [Mark text with the _i18n_ attribute](guide/i18n#i18n-attribute)
+ * [Help the translator with a description and intent](guide/i18n#help-translator)
+ * [Translate text without creating an element](guide/i18n#no-element)
* [Add _i18n-..._ translation attributes](guide/i18n#translate-attributes)
* [Handle singular and plural](guide/i18n#cardinality)
* [Select among alternative texts](guide/i18n#select)
* [Create a translation source file with the **_ng-xi18n_ extraction tool**](guide/i18n#ng-xi18n)
+ * [Other translation formats](guide/i18n#other-formats)
+ * [Other options](guide/i18n#ng-xi18n-options)
+ * [Add an `npm` script for convenience](guide/i18n#npm-i18n-script)
* [Translate text messages](guide/i18n#translate)
+ * [Create a localization folder](guide/i18n#localization-folder)
+ * [Translate text nodes](guide/i18n#translate-text-nodes)
+ * [Translate `plural` and `select`](guide/i18n#translate-plural-select)
+ * [Translate `plural`](guide/i18n#translate-plural)
+ * [Translate `select`](guide/i18n#translate-select)
+ * [The app before translation](guide/i18n#app-pre-translation)
* [Merge the completed translation file into the app](guide/i18n#merge)
-
* [Merge with the JIT compiler](guide/i18n#jit)
+ * [SystemJS text plugin](guide/i18n#text-plugin)
+ * [Create translation providers](guide/i18n#create-translation-providers)
+ * [Bootstrap the app with translation providers](guide/i18n#bootstrap-the-app)
* [Internationalization with the AOT compiler](guide/i18n#aot)
-
* [Translation file maintenance and _id_ changes](guide/i18n#maintenance)
-**Try this** live example
+Try this live example
of a JIT-compiled app, translated into Spanish.
@@ -40,7 +52,7 @@ of a JIT-compiled app, translated into Spanish.
## Angular and _i18n_ template translation
-Application internationalization is a challenging, many-faceted effort that
+Application internationalization is a challenging, many-faceted effort that
takes dedication and enduring commitment.
Angular's _i18n_ internationalization facilities can help.
@@ -67,10 +79,10 @@ The _i18n_ template translation process has four phases:
1. An angular _i18n_ tool extracts the marked messages into an industry standard translation source file.
-1. A translator edits that file, translating the extracted text messages into the target language,
+1. A translator edits that file, translating the extracted text messages into the target language,
and returns the file to you.
-1. The Angular compiler imports the completed translation files,
+1. The Angular compiler imports the completed translation files,
replaces the original messages with translated text, and generates a new version of the application
in the target language.
@@ -91,7 +103,7 @@ Place it on every element tag whose fixed text should be translated.
-`i18n` is not an Angular _directive_.
+`i18n` is not an Angular _directive_.
It's a custom _attribute_, recognized by Angular tools and compilers.
After translation, the compiler removes it.
@@ -100,7 +112,7 @@ After translation, the compiler removes it.
-In the accompanying sample, an `
` tag displays a simple English language greeting
+In the accompanying sample, an `
` tag displays a simple English language greeting
that you translate into Spanish:
@@ -118,9 +130,12 @@ Add the `i18n` attribute to the tag to mark it for translation.
+{@a help-translator}
+
+
### Help the translator with a _description_ and _intent_
-In order to translate it accurately, the translator may
+In order to translate it accurately, the translator may
need a description of the message.
Assign a description to the i18n attribute:
@@ -131,8 +146,8 @@ Assign a description to the i18n attribute:
-In order to deliver a correct translation, the translator may need to
-know your _intent_—the true _meaning_ of the text
+In order to deliver a correct translation, the translator may need to
+know your _intent_—the true _meaning_ of the text
within _this particular_ application context.
In front of the description, add some contextual meaning to the assigned string,
separating it from the description with the `|` character (`|`):
@@ -144,11 +159,15 @@ separating it from the description with the `|` character (`||ICU Message Format
-that derives from the
-Common Locale Data Repository (CLDR),
+that derives from the
+Common Locale Data Repository (CLDR),
which specifies the
pluralization rules.
@@ -274,12 +293,12 @@ which specifies the
The application displays different text depending upon whether the hero is male or female.
These text alternatives require translation too.
-You can handle this with a `select` translation.
+You can handle this with a `select` translation.
A `select` also follows the
-ICU message syntax.
+ICU message syntax.
You choose among alternative translation based on a string value instead of a number.
-The following format message in the component template binds to the component's `gender`
+The following format message in the component template binds to the component's `gender`
property, which outputs either an "m" or an "f".
The message maps those values to the appropriate translation:
@@ -296,7 +315,7 @@ The message maps those values to the appropriate translation:
## Create a translation source file with the _ng-xi18n_ tool
-Use the **_ng-xi18n_ extraction tool** to extract the `i18n`-marked texts
+Use the **_ng-xi18n_ extraction tool** to extract the `i18n`-marked texts
into a translation source file in an industry standard format.
This is an Angular CLI tool in the `@angular/compiler-cli` npm package.
@@ -340,7 +359,7 @@ By default, the tool generates a translation file named **`messages.xlf`** in th
### Other translation formats
-You can generate a file named **`messages.xmb`** in the
+You can generate a file named **`messages.xmb`** in the
XML Message Bundle (XMB) format
by adding the `--i18nFormat=xmb` flag.
@@ -359,8 +378,8 @@ This sample sticks with the _XLIFF_ format.
### Other options
-You may have to specify additional options.
-For example, if the `tsconfig.json` TypeScript configuration
+You may have to specify additional options.
+For example, if the `tsconfig.json` TypeScript configuration
file is located somewhere other than in the root folder,
you must identify the path to it with the `-p` option:
@@ -383,7 +402,7 @@ to make the command easier to remember and run:
"scripts": {
- "i18n": "ng-xi18n",
+ "i18n": "ng-xi18n",
...
}
@@ -400,7 +419,7 @@ Now you can issue command variations such as these:
-Note the `--` flag before the options.
+Note the `--` flag before the options.
It tells _npm_ to pass every flag thereafter to `ng-xi18n`.
@@ -410,9 +429,9 @@ It tells _npm_ to pass every flag thereafter to `ng-xi18n`.
## Translate text messages
-The `ng-xi18n` command generates a translation source file
+The `ng-xi18n` command generates a translation source file
in the project root folder named `messages.xlf`.
-The next step is to translate the English language template
+The next step is to translate the English language template
text into the specific language translation
files. The cookbook sample creates a Spanish translation file.
@@ -425,14 +444,14 @@ files. The cookbook sample creates a Spanish translation file.
You will probably translate into more than one other language so it's a good idea
for the project structure to reflect your entire internationalization effort.
-One approach is to dedicate a folder to localization and store related assets
-(for example, internationalization files) there.
+One approach is to dedicate a folder to localization and store related assets,
+such as internationalization files, there.
@@ -443,10 +462,13 @@ This cookbook follows that suggestion. It has a `locale` folder under the `src/`
Assets within the folder carry a filename extension that matches a language-culture code from a
well-known codeset.
-Make a copy of the `messages.xlf` file, put it in the `locale` folder and
+Make a copy of the `messages.xlf` file, put it in the `locale` folder and
rename it `messages.es.xlf`for the Spanish language translation.
Do the same for each target language.
+{@a translate-text-nodes}
+
+
### Translate text nodes
In the real world, you send the `messages.es.xlf` file to a Spanish translator who fills in the translations
using one of the
@@ -462,7 +484,7 @@ Open `messages.es.xlf` and find the first `` section:
-This XML element represents the translation of the `
` greeting tag you marked with the `i18n` attribute.
+This XML element represents the translation of the `
` greeting tag you marked with the `i18n` attribute.
Using the _source_, _description_, and _meaning_ elements to guide your translation,
replace the `` tag with the Spanish greeting:
@@ -502,16 +524,19 @@ Translate the other text nodes the same way:
## Translate _plural_ and _select_
Translating _plural_ and _select_ messages is a little tricky.
-The `