Skip to content

Commit

Permalink
feat: add support for i18n of static text in component templates
Browse files Browse the repository at this point in the history
Fixes #2314
Closes #2317
  • Loading branch information
jnizet authored and pkozlowski-opensource committed May 18, 2018
1 parent cc3a677 commit 65c232d
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 36 deletions.
19 changes: 19 additions & 0 deletions demo/src/app/getting-started/getting-started.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,25 @@ <h4>
<ngbd-code [code]="codeSystem" lang="typescript"></ngbd-code>
<br>

<h3>
Internationalization
</h3>
<p>
Some components contain static English text or symbols that you might want to internationalize. Some of them appear
on the screen, like for example the placeholders used in the timepicker input fields. Others appear in aria attributes
used for accessibility.
</p>
<p>
Internationalizing the ng-bootstrap components is done the same way as for any of your components, using the
<a href="https://angular.io/guide/i18n#template-translations">process described in the Angular documentation</a>.
The only difference is that we already did the first phase of this process: marking static text messages in the
ng-bootstrap component templates for translation.
</p>
<p>
So, if you execute `ng xi18n` on your project, you will also find the ng-bootstrap messages to translate in the
generated messages file. All our messages are identified by an ID of the form <code>ngb.[widget].[message]</code>
</p>

<h3>
Getting Help
</h3>
Expand Down
5 changes: 3 additions & 2 deletions src/alert/alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import {NgbAlertConfig} from './alert-config';
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div [class]="'alert alert-' + type + (dismissible ? ' alert-dismissible' : '')" role="alert">
<button *ngIf="dismissible" type="button" class="close" aria-label="Close" (click)="closeHandler()">
<span aria-hidden="true">&times;</span>
<button *ngIf="dismissible" type="button" class="close" aria-label="Close" i18n-aria-label="@@ngb.alert.close"
(click)="closeHandler()">
<span aria-hidden="true">&times;</span>
</button>
<ng-content></ng-content>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/carousel/carousel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ export class NgbSlide {
</div>
<a class="carousel-control-prev" role="button" (click)="cycleToPrev()">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
<span class="sr-only" i18n="@@ngb.carousel.previous">Previous</span>
</a>
<a class="carousel-control-next" role="button" (click)="cycleToNext()">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
<span class="sr-only" i18n="@@ngb.carousel.next">Next</span>
</a>
`
})
Expand Down
20 changes: 12 additions & 8 deletions src/pagination/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ import {NgbPaginationConfig} from './pagination-config';
<ul [class]="'pagination' + (size ? ' pagination-' + size : '')">
<li *ngIf="boundaryLinks" class="page-item"
[class.disabled]="!hasPrevious() || disabled">
<a aria-label="First" class="page-link" href (click)="!!selectPage(1)" [attr.tabindex]="(hasPrevious() ? null : '-1')">
<span aria-hidden="true">&laquo;&laquo;</span>
<a aria-label="First" i18n-aria-label="@@ngb.pagination.first-aria" class="page-link" href
(click)="!!selectPage(1)" [attr.tabindex]="(hasPrevious() ? null : '-1')">
<span aria-hidden="true" i18n="@@ngb.pagination.first">&laquo;&laquo;</span>
</a>
</li>
<li *ngIf="directionLinks" class="page-item"
[class.disabled]="!hasPrevious() || disabled">
<a aria-label="Previous" class="page-link" href (click)="!!selectPage(page-1)" [attr.tabindex]="(hasPrevious() ? null : '-1')">
<span aria-hidden="true">&laquo;</span>
<a aria-label="Previous" i18n-aria-label="@@ngb.pagination.previous-aria" class="page-link" href
(click)="!!selectPage(page-1)" [attr.tabindex]="(hasPrevious() ? null : '-1')">
<span aria-hidden="true" i18n="@@ngb.pagination.previous">&laquo;</span>
</a>
</li>
<li *ngFor="let pageNumber of pages" class="page-item" [class.active]="pageNumber === page"
Expand All @@ -33,14 +35,16 @@ import {NgbPaginationConfig} from './pagination-config';
</a>
</li>
<li *ngIf="directionLinks" class="page-item" [class.disabled]="!hasNext() || disabled">
<a aria-label="Next" class="page-link" href (click)="!!selectPage(page+1)" [attr.tabindex]="(hasNext() ? null : '-1')">
<span aria-hidden="true">&raquo;</span>
<a aria-label="Next" i18n-aria-label="@@ngb.pagination.next-aria" class="page-link" href
(click)="!!selectPage(page+1)" [attr.tabindex]="(hasNext() ? null : '-1')">
<span aria-hidden="true" i18n="@@ngb.pagination.next">&raquo;</span>
</a>
</li>
<li *ngIf="boundaryLinks" class="page-item" [class.disabled]="!hasNext() || disabled">
<a aria-label="Last" class="page-link" href (click)="!!selectPage(pageCount)" [attr.tabindex]="(hasNext() ? null : '-1')">
<span aria-hidden="true">&raquo;&raquo;</span>
<a aria-label="Last" i18n-aria-label="@@ngb.pagination.last-aria" class="page-link" href
(click)="!!selectPage(pageCount)" [attr.tabindex]="(hasNext() ? null : '-1')">
<span aria-hidden="true" i18n="@@ngb.pagination.last">&raquo;&raquo;</span>
</a>
</li>
</ul>
Expand Down
2 changes: 1 addition & 1 deletion src/progressbar/progressbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {NgbProgressbarConfig} from './progressbar-config';
<div class="progress-bar{{type ? ' bg-' + type : ''}}{{animated ? ' progress-bar-animated' : ''}}{{striped ?
' progress-bar-striped' : ''}}" role="progressbar" [style.width.%]="getPercentValue()"
[attr.aria-valuenow]="getValue()" aria-valuemin="0" [attr.aria-valuemax]="max">
<span *ngIf="showValue">{{getPercentValue()}}%</span><ng-content></ng-content>
<span *ngIf="showValue" i18n="ngb.progressbar.value">{{getPercentValue()}}%</span><ng-content></ng-content>
</div>
</div>
`
Expand Down
20 changes: 10 additions & 10 deletions src/timepicker/timepicker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ describe('ngb-timepicker', () => {
})
.then(() => {
expectToDisplayTime(fixture.nativeElement, '01:30:00');
expect(meridianButton.innerHTML).toBe('PM');
expect(meridianButton.textContent).toBe('PM');

fixture.componentInstance.model = {hour: 1, minute: 30, second: 0};
fixture.detectChanges();
Expand All @@ -518,7 +518,7 @@ describe('ngb-timepicker', () => {
})
.then(() => {
expectToDisplayTime(fixture.nativeElement, '01:30:00');
expect(meridianButton.innerHTML).toBe('AM');
expect(meridianButton.textContent).toBe('AM');
});
}));

Expand All @@ -536,7 +536,7 @@ describe('ngb-timepicker', () => {
})
.then(() => {
expectToDisplayTime(fixture.nativeElement, '12:30:00');
expect(meridianButton.innerHTML).toBe('PM');
expect(meridianButton.textContent).toBe('PM');

fixture.componentInstance.model = {hour: 0, minute: 30, second: 0};
fixture.detectChanges();
Expand All @@ -548,7 +548,7 @@ describe('ngb-timepicker', () => {
})
.then(() => {
expectToDisplayTime(fixture.nativeElement, '12:30:00');
expect(meridianButton.innerHTML).toBe('AM');
expect(meridianButton.textContent).toBe('AM');
});
}));

Expand All @@ -566,7 +566,7 @@ describe('ngb-timepicker', () => {
})
.then(() => {
expectToDisplayTime(fixture.nativeElement, '01:30:00');
expect(meridianButton.innerHTML).toBe('PM');
expect(meridianButton.textContent).toBe('PM');

meridianButton.click();
fixture.detectChanges();
Expand All @@ -575,7 +575,7 @@ describe('ngb-timepicker', () => {
.then(() => {
expectToDisplayTime(fixture.nativeElement, '01:30:00');
expect(fixture.componentInstance.model).toEqual({hour: 1, minute: 30, second: 0});
expect(meridianButton.innerHTML).toBe('AM');
expect(meridianButton.textContent).toBe('AM');
});
}));

Expand Down Expand Up @@ -670,7 +670,7 @@ describe('ngb-timepicker', () => {
})
.then(() => {
expectToDisplayTime(fixture.nativeElement, '10:30');
expect(meridianButton.innerHTML).toBe('PM');
expect(meridianButton.textContent).toBe('PM');
expect(fixture.componentInstance.model).toEqual({hour: 22, minute: 30, second: 0});
});
}));
Expand All @@ -693,7 +693,7 @@ describe('ngb-timepicker', () => {
})
.then(() => {
expectToDisplayTime(fixture.nativeElement, '10:30');
expect(meridianButton.innerHTML).toBe('PM');
expect(meridianButton.textContent).toBe('PM');
expect(fixture.componentInstance.model).toEqual({hour: 22, minute: 30, second: 0});
});
}));
Expand All @@ -716,7 +716,7 @@ describe('ngb-timepicker', () => {
})
.then(() => {
expectToDisplayTime(fixture.nativeElement, '09:30');
expect(meridianButton.innerHTML).toBe('AM');
expect(meridianButton.textContent).toBe('AM');
expect(fixture.componentInstance.model).toEqual({hour: 9, minute: 30, second: 0});
});
}));
Expand All @@ -739,7 +739,7 @@ describe('ngb-timepicker', () => {
})
.then(() => {
expectToDisplayTime(fixture.nativeElement, '09:30');
expect(meridianButton.innerHTML).toBe('AM');
expect(meridianButton.textContent).toBe('AM');
expect(fixture.componentInstance.model).toEqual({hour: 9, minute: 30, second: 0});
});
}));
Expand Down
32 changes: 19 additions & 13 deletions src/timepicker/timepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,54 +76,60 @@ const NGB_TIMEPICKER_VALUE_ACCESSOR = {
<button *ngIf="spinners" type="button" class="btn btn-link" [ngClass]="setButtonSize()" (click)="changeHour(hourStep)"
[disabled]="disabled" [class.disabled]="disabled">
<span class="chevron"></span>
<span class="sr-only">Increment hours</span>
<span class="sr-only" i18n="@@ngb.timepicker.increment-hours">Increment hours</span>
</button>
<input type="text" class="form-control" [ngClass]="setFormControlSize()" maxlength="2" size="2" placeholder="HH"
<input type="text" class="form-control" [ngClass]="setFormControlSize()" maxlength="2" size="2"
placeholder="HH" i18n-placeholder="@@ngb.timepicker.HH"
[value]="formatHour(model?.hour)" (change)="updateHour($event.target.value)"
[readonly]="readonlyInputs" [disabled]="disabled" aria-label="Hours">
[readonly]="readonlyInputs" [disabled]="disabled" aria-label="Hours" i18n-aria-label="@@ngb.timepicker.hours">
<button *ngIf="spinners" type="button" class="btn btn-link" [ngClass]="setButtonSize()" (click)="changeHour(-hourStep)"
[disabled]="disabled" [class.disabled]="disabled">
<span class="chevron bottom"></span>
<span class="sr-only">Decrement hours</span>
<span class="sr-only" i18n="@@ngb.timepicker.decrement-hours">Decrement hours</span>
</button>
</div>
<div class="ngb-tp-spacer">:</div>
<div class="ngb-tp-minute">
<button *ngIf="spinners" type="button" class="btn btn-link" [ngClass]="setButtonSize()" (click)="changeMinute(minuteStep)"
[disabled]="disabled" [class.disabled]="disabled">
<span class="chevron"></span>
<span class="sr-only">Increment minutes</span>
<span class="sr-only" i18n="@@ngb.timepicker.increment-minutes">Increment minutes</span>
</button>
<input type="text" class="form-control" [ngClass]="setFormControlSize()" maxlength="2" size="2" placeholder="MM"
<input type="text" class="form-control" [ngClass]="setFormControlSize()" maxlength="2" size="2"
placeholder="MM" i18n-placeholder="@@ngb.timepicker.MM"
[value]="formatMinSec(model?.minute)" (change)="updateMinute($event.target.value)"
[readonly]="readonlyInputs" [disabled]="disabled" aria-label="Minutes">
[readonly]="readonlyInputs" [disabled]="disabled" aria-label="Minutes" i18n-aria-label="@@ngb.timepicker.minutes">
<button *ngIf="spinners" type="button" class="btn btn-link" [ngClass]="setButtonSize()" (click)="changeMinute(-minuteStep)"
[disabled]="disabled" [class.disabled]="disabled">
<span class="chevron bottom"></span>
<span class="sr-only">Decrement minutes</span>
<span class="sr-only" i18n="@@ngb.timepicker.decrement-minutes">Decrement minutes</span>
</button>
</div>
<div *ngIf="seconds" class="ngb-tp-spacer">:</div>
<div *ngIf="seconds" class="ngb-tp-second">
<button *ngIf="spinners" type="button" class="btn btn-link" [ngClass]="setButtonSize()" (click)="changeSecond(secondStep)"
[disabled]="disabled" [class.disabled]="disabled">
<span class="chevron"></span>
<span class="sr-only">Increment seconds</span>
<span class="sr-only" i18n="@@ngb.timepicker.increment-seconds">Increment seconds</span>
</button>
<input type="text" class="form-control" [ngClass]="setFormControlSize()" maxlength="2" size="2" placeholder="SS"
<input type="text" class="form-control" [ngClass]="setFormControlSize()" maxlength="2" size="2"
placeholder="SS" i18n-placeholder="@@ngb.timepicker.SS"
[value]="formatMinSec(model?.second)" (change)="updateSecond($event.target.value)"
[readonly]="readonlyInputs" [disabled]="disabled" aria-label="Seconds">
[readonly]="readonlyInputs" [disabled]="disabled" aria-label="Seconds" i18n-aria-label="@@ngb.timepicker.seconds">
<button *ngIf="spinners" type="button" class="btn btn-link" [ngClass]="setButtonSize()" (click)="changeSecond(-secondStep)"
[disabled]="disabled" [class.disabled]="disabled">
<span class="chevron bottom"></span>
<span class="sr-only">Decrement seconds</span>
<span class="sr-only" i18n="@@ngb.timepicker.decrement-seconds">Decrement seconds</span>
</button>
</div>
<div *ngIf="meridian" class="ngb-tp-spacer"></div>
<div *ngIf="meridian" class="ngb-tp-meridian">
<button type="button" class="btn btn-outline-primary" [ngClass]="setButtonSize()"
[disabled]="disabled" [class.disabled]="disabled"
(click)="toggleMeridian()">{{model?.hour >= 12 ? 'PM' : 'AM'}}</button>
(click)="toggleMeridian()">
<ng-container *ngIf="model?.hour >= 12; else am" i18n="@@ngb.timepicker.PM">PM</ng-container>
<ng-template #am i18n="@@ngb.timepicker.AM">AM</ng-template>
</button>
</div>
</div>
</fieldset>
Expand Down

0 comments on commit 65c232d

Please sign in to comment.