Skip to content

Commit 3675b9f

Browse files
committed
fix(stepper): unable to internationalize labels
Adds a provider that allows for labels inside the stepper to be internationalized.
1 parent df808b8 commit 3675b9f

File tree

6 files changed

+101
-17
lines changed

6 files changed

+101
-17
lines changed

src/lib/stepper/public_api.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {MdStepHeader} from './step-header';
1717
import {MdStepLabel} from './step-label';
1818
import {MdHorizontalStepper, MdStep, MdStepper, MdVerticalStepper} from './stepper';
1919
import {MdStepperNext, MdStepperPrevious} from './stepper-button';
20+
import {MdStepperIntl} from './stepper-intl';
2021

2122

2223
@NgModule({
@@ -39,12 +40,22 @@ import {MdStepperNext, MdStepperPrevious} from './stepper-button';
3940
MdStepperPrevious,
4041
MdStepHeader
4142
],
42-
declarations: [MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel, MdStepper,
43-
MdStepperNext, MdStepperPrevious, MdStepHeader],
43+
declarations: [
44+
MdHorizontalStepper,
45+
MdVerticalStepper,
46+
MdStep,
47+
MdStepLabel,
48+
MdStepper,
49+
MdStepperNext,
50+
MdStepperPrevious,
51+
MdStepHeader,
52+
],
53+
providers: [MdStepperIntl],
4454
})
4555
export class MdStepperModule {}
4656

4757
export * from './step-label';
4858
export * from './stepper';
4959
export * from './stepper-button';
5060
export * from './step-header';
61+
export * from './stepper-intl';

src/lib/stepper/step-header.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@
1313
<!-- It there is no label template, fall back to the text label. -->
1414
<div class="mat-step-text-label" *ngIf="_stringLabel()">{{label}}</div>
1515

16-
<div class="mat-step-optional" *ngIf="optional">Optional</div>
16+
<div class="mat-step-optional" *ngIf="optional">{{_intl.optionalLabel}}</div>
1717
</div>
1818

src/lib/stepper/step-header.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,17 @@
77
*/
88

99
import {coerceBooleanProperty, coerceNumberProperty} from '@angular/cdk/coercion';
10-
import {Component, Input, ViewEncapsulation} from '@angular/core';
10+
import {
11+
Component,
12+
Input,
13+
ViewEncapsulation,
14+
ChangeDetectorRef,
15+
OnDestroy,
16+
} from '@angular/core';
1117
import {MATERIAL_COMPATIBILITY_MODE} from '@angular/material/core';
1218
import {MdStepLabel} from './step-label';
19+
import {MdStepperIntl} from './stepper-intl';
20+
import {Subscription} from 'rxjs/Subscription';
1321

1422

1523
@Component({
@@ -24,7 +32,9 @@ import {MdStepLabel} from './step-label';
2432
encapsulation: ViewEncapsulation.None,
2533
providers: [{provide: MATERIAL_COMPATIBILITY_MODE, useValue: false}],
2634
})
27-
export class MdStepHeader {
35+
export class MdStepHeader implements OnDestroy {
36+
private _intlSubscription: Subscription;
37+
2838
/** Icon for the given step. */
2939
@Input() icon: string;
3040

@@ -63,6 +73,14 @@ export class MdStepHeader {
6373
}
6474
private _optional: boolean;
6575

76+
constructor(public _intl: MdStepperIntl, changeDetectorRef: ChangeDetectorRef) {
77+
this._intlSubscription = _intl.changes.subscribe(() => changeDetectorRef.markForCheck());
78+
}
79+
80+
ngOnDestroy() {
81+
this._intlSubscription.unsubscribe();
82+
}
83+
6684
/** Returns string label of given step if it is a text label. */
6785
_stringLabel(): string | null {
6886
return this.label instanceof MdStepLabel ? null : this.label;

src/lib/stepper/stepper-intl.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Injectable} from '@angular/core';
10+
import {Subject} from 'rxjs/Subject';
11+
12+
13+
/** Stepper data that is required for internationalization. */
14+
@Injectable()
15+
export class MdStepperIntl {
16+
/**
17+
* Stream that emits whenever the labels here are changed. Use this to notify
18+
* components if the labels have changed after initialization.
19+
*/
20+
changes: Subject<void> = new Subject<void>();
21+
22+
/** Label that is rendered below optional steps. */
23+
optionalLabel = 'Optional';
24+
}

src/lib/stepper/stepper.md

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ that drives a stepped workflow. Material stepper extends the CDK stepper and has
77
styling.
88

99
### Stepper variants
10-
There are two stepper components: `md-horizontal-stepper` and `md-vertical-stepper`. They
11-
can be used the same way. The only difference is the orientation of stepper.
10+
There are two stepper components: `md-horizontal-stepper` and `md-vertical-stepper`. They
11+
can be used the same way. The only difference is the orientation of stepper.
1212
`md-horizontal-stepper` selector can be used to create a horizontal stepper, and
1313
`md-vertical-stepper` can be used to create a vertical stepper. `md-step` components need to be
1414
placed inside either one of the two stepper components.
@@ -26,7 +26,7 @@ If a step's label is only text, then the `label` attribute can be used.
2626
</md-vertical-stepper>
2727
```
2828

29-
For more complex labels, add a template with the `mdStepLabel` directive inside the
29+
For more complex labels, add a template with the `mdStepLabel` directive inside the
3030
`md-step`.
3131
```html
3232
<md-vertical-stepper>
@@ -49,22 +49,22 @@ There are two button directives to support navigation between different steps:
4949
<button md-button mdStepperNext>Next</button>
5050
</div>
5151
</md-step>
52-
</md-horizontal-stepper>
52+
</md-horizontal-stepper>
5353
```
5454

5555
### Linear stepper
5656
The `linear` attribute can be set on `md-horizontal-stepper` and `md-vertical-stepper` to create
5757
a linear stepper that requires the user to complete previous steps before proceeding
5858
to following steps. For each `md-step`, the `stepControl` attribute can be set to the top level
59-
`AbstractControl` that is used to check the validity of the step.
59+
`AbstractControl` that is used to check the validity of the step.
6060

6161
There are two possible approaches. One is using a single form for stepper, and the other is
6262
using a different form for each step.
6363

6464
#### Using a single form
6565
When using a single form for the stepper, `mdStepperPrevious` and `mdStepperNext` have to be
6666
set to `type="button"` in order to prevent submission of the form before all steps
67-
are completed.
67+
are completed.
6868

6969
```html
7070
<form [formGroup]="formGroup">
@@ -83,7 +83,7 @@ are completed.
8383
</div>
8484
</md-step>
8585
...
86-
</md-horizontal-stepper>
86+
</md-horizontal-stepper>
8787
</form>
8888
```
8989

@@ -106,11 +106,11 @@ are completed.
106106

107107
#### Optional step
108108
If completion of a step in linear stepper is not required, then the `optional` attribute can be set
109-
on `md-step`.
109+
on `md-step`.
110110

111111
#### Editable step
112112
By default, steps are editable, which means users can return to previously completed steps and
113-
edit their responses. `editable="true"` can be set on `md-step` to change the default.
113+
edit their responses. `editable="true"` can be set on `md-step` to change the default.
114114

115115
#### Completed step
116116
By default, the `completed` attribute of a step returns `true` if the step is valid (in case of
@@ -124,11 +124,26 @@ this default `completed` behavior by setting the `completed` attribute as needed
124124
- <kbd>TAB</kbd>: Focuses the next tabbable element
125125
- <kbd>TAB</kbd>+<kbd>SHIFT</kbd>: Focuses the previous tabbable element
126126

127+
### Localizing labels
128+
The various text strings used by the stepper are provided through `MdStepperIntl`.
129+
Localization of these messages can be done by providing a subclass with translated values in your
130+
application root module.
131+
132+
```ts
133+
@NgModule({
134+
imports: [MdStepperModule],
135+
providers: [
136+
{provide: MdStepperIntl, useClass: MyIntl},
137+
],
138+
})
139+
export class MyApp {}
140+
```
141+
127142
### Accessibility
128143
The stepper is treated as a tabbed view for accessibility purposes, so it is given
129144
`role="tablist"` by default. The header of step that can be clicked to select the step
130145
is given `role="tab"`, and the content that can be expanded upon selection is given
131146
`role="tabpanel"`. `aria-selected` attribute of step header and `aria-expanded` attribute of
132147
step content is automatically set based on step selection change.
133148

134-
The stepper and each step should be given a meaningful label via `aria-label` or `aria-labelledby`.
149+
The stepper and each step should be given a meaningful label via `aria-label` or `aria-labelledby`.

src/lib/stepper/stepper.spec.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
1+
import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
22
import {Component, DebugElement} from '@angular/core';
33
import {MdStepperModule} from './index';
44
import {By} from '@angular/platform-browser';
@@ -9,6 +9,7 @@ import {dispatchKeyboardEvent} from '@angular/cdk/testing';
99
import {ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE} from '@angular/cdk/keycodes';
1010
import {MdStepper, MdHorizontalStepper, MdVerticalStepper} from './stepper';
1111
import {Directionality} from '@angular/material/core';
12+
import {MdStepperIntl} from './stepper-intl';
1213

1314
const VALID_REGEX = /valid/;
1415

@@ -95,6 +96,21 @@ describe('MdHorizontalStepper', () => {
9596
it('should set done icon if step is not editable and is completed', () => {
9697
assertCorrectStepIcon(fixture, false, 'done');
9798
});
99+
100+
it('should re-render when the i18n labels change',
101+
inject([MdStepperIntl], (intl: MdStepperIntl) => {
102+
const header = fixture.debugElement.queryAll(By.css('md-step-header'))[2].nativeElement;
103+
const optionalLabel = header.querySelector('.mat-step-optional');
104+
105+
expect(optionalLabel).toBeTruthy();
106+
expect(optionalLabel.textContent).toBe('Optional');
107+
108+
intl.optionalLabel = 'Valgfri';
109+
intl.changes.next();
110+
fixture.detectChanges();
111+
112+
expect(optionalLabel.textContent).toBe('Valgfri');
113+
}));
98114
});
99115

100116
describe('RTL', () => {
@@ -682,7 +698,7 @@ function assertCorrectStepIcon(fixture: ComponentFixture<any>,
682698
<button md-button mdStepperNext>Next</button>
683699
</div>
684700
</md-step>
685-
<md-step [label]="inputLabel">
701+
<md-step [label]="inputLabel" optional>
686702
Content 3
687703
<div>
688704
<button md-button mdStepperPrevious>Back</button>

0 commit comments

Comments
 (0)