Skip to content

Commit

Permalink
Accessibility: translate internal component labels (#3717)
Browse files Browse the repository at this point in the history
Co-authored-by: Jakob Engelbrecht <jakob@basher.dk>
  • Loading branch information
RasmusKjeldgaard and jakobe authored Dec 4, 2024
1 parent aef5a86 commit 532ed37
Show file tree
Hide file tree
Showing 21 changed files with 377 additions and 9 deletions.
8 changes: 8 additions & 0 deletions apps/cookbook/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ComponentOverviewComponent } from './component-overview/component-overv
import { HomeComponent } from './home/home.component';
import { IntroComponent } from './intro/intro.component';
import { ExtensionsLandingPageComponent } from './extensions/extensions-landing-page.component';
import { LocalizationComponent } from './localization/localization.component';

export const routes: Routes = [
{
Expand Down Expand Up @@ -57,6 +58,13 @@ export const routes: Routes = [
resourceLink: 'Extensions',
},
},
{
path: 'localization',
component: LocalizationComponent,
data: {
resourceLink: 'Localization',
},
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Component, LOCALE_ID } from '@angular/core';
import { TranslationService } from '@kirbydesign/designsystem/shared';

@Component({
selector: 'cookbook-da-locale-provider',
template: '<ng-content></ng-content>',
standalone: true,
providers: [{ provide: LOCALE_ID, useValue: 'da' }, TranslationService],
})
export class DaLocaleProviderComponent {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Component, LOCALE_ID } from '@angular/core';
import { TranslationService } from '@kirbydesign/designsystem/shared';

@Component({
selector: 'cookbook-en-locale-provider',
template: '<ng-content></ng-content>',
standalone: true,
providers: [{ provide: LOCALE_ID, useValue: 'en' }, TranslationService],
})
export class EnLocaleProviderComponent {}
108 changes: 108 additions & 0 deletions apps/cookbook/src/app/localization/localization.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<article>
<h1>Localization</h1>
<p>
Locale information is used by components such as
<code>kirby-calendar</code>
and
<code>input type="date"</code>
to automatically format values according to locale. Support has been added for three locales out
of the box: American English (
<code>en-US</code>
), British English (
<code>en-GB</code>
) and Danish (
<code>da</code>
).
</p>

<p>
Locale is usually configured for the entire app in the
<code>AppModule</code>
.
</p>
<cookbook-example-viewer
class="code-only-example"
[ts]="localeConfigCodeSnippet"
></cookbook-example-viewer>
<p>
Specifying the locale will also ensure translation of
<em>internal</em>
component labels and texts that are not configurable. This allows assistive technology such as
screen readers to announce labels in the correct language.
</p>

<p>
In this example visible information such as the month and weekday headers, and accessible names
for headers, calendar cells and interactive buttons have been automatically translated by
specifying the locale.
</p>

<cookbook-example-viewer>
<div class="example-frame">
<div class="locale-examples">
<cookbook-da-locale-provider>
<kirby-card>
<kirby-calendar [selectedDate]="selectedDate"></kirby-calendar>
</kirby-card>
<kirby-avatar
class="locale-avatar"
size="sm"
imageSrc="assets/images/dk.svg"
altText="Danish locale"
></kirby-avatar>
</cookbook-da-locale-provider>
<cookbook-en-locale-provider>
<kirby-card>
<kirby-calendar [selectedDate]="selectedDate"></kirby-calendar>
</kirby-card>
<kirby-avatar
class="locale-avatar"
size="sm"
imageSrc="assets/images/gb.svg"
altText="English locale"
></kirby-avatar>
</cookbook-en-locale-provider>
</div>
</div>
</cookbook-example-viewer>

<h2>Using the service directly</h2>
<p>
When developing custom components closely coupled to Kirby that need to use identical
translations,
<code>TranslationService</code>
can be used to fetch and display translated strings.
</p>
<p>
This can be done by injecting the service into the component and get any key defined in the
<a
href="https://github.com/kirbydesign/designsystem/tree/develop/libs/designsystem/shared/src/translation/translation.interface.ts"
>
Translation
</a>
interface.
</p>
<cookbook-code-viewer [ts]="translationGetterCodeSnippet"></cookbook-code-viewer>

<h2>Additional locales</h2>
<p>
If support for other locales or additional translations are needed please
<!-- prettier-ignore -->
<a class="kirby-external-icon" href="https://github.com/kirbydesign/designsystem/issues/new/choose">create an enhancement issue on Github</a>
and consider contributing the code to make it happen. Please refer to the
<a
href="https://github.com/kirbydesign/designsystem/tree/develop/libs/designsystem/shared/src/translation/translations"
>
existing translation files
</a>
and how
<a
href="https://github.com/kirbydesign/designsystem/tree/develop/libs/designsystem/shared/src/translation/translation.service.ts"
>
translation registering
</a>
is done in the
<code>TranslationService</code>
.
</p>
</article>
34 changes: 34 additions & 0 deletions apps/cookbook/src/app/localization/localization.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@use 'sass:map';
@use '../showcase/showcase.shared';
@use '@kirbydesign/core/src/scss/utils';

$avatar-offset: -1 * utils.size('s');

:host {
display: block;
container-type: inline-size;

:has(> .locale-avatar) {
position: relative;
}
}

.locale-examples {
display: flex;
justify-content: center;
gap: var(--kirby-spacing-xxxl);

@container (width < #{map.get(utils.$breakpoints, small)}) {
flex-direction: column;
align-items: center;
}
}

.locale-avatar {
position: absolute;
inset: $avatar-offset $avatar-offset auto auto;
}

.code-only-example {
margin-block-end: utils.size('s');
}
51 changes: 51 additions & 0 deletions apps/cookbook/src/app/localization/localization.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Component } from '@angular/core';

import { CardModule } from '@kirbydesign/designsystem/card';
import { CalendarComponent } from '@kirbydesign/designsystem/calendar';
import { TranslationService } from '@kirbydesign/designsystem/shared';
import { AvatarComponent } from '@kirbydesign/designsystem/avatar';
import { CodeViewerModule } from '../shared/code-viewer/code-viewer.module';
import { ExamplesModule } from '../examples/examples.module';
import { ShowcaseModule } from '../showcase/showcase.module';
import { DaLocaleProviderComponent } from './locale-provider/da-locale-provider.component';
import { EnLocaleProviderComponent } from './locale-provider/en-locale-provider.component';

@Component({
selector: 'cookbook-localization',
templateUrl: './localization.component.html',
styleUrls: ['./localization.component.scss'],
standalone: true,
imports: [
CodeViewerModule,
ShowcaseModule,
ExamplesModule,
DaLocaleProviderComponent,
EnLocaleProviderComponent,
CalendarComponent,
CardModule,
AvatarComponent,
],
})
export class LocalizationComponent {
localeConfigCodeSnippet = `import { registerLocaleData } from '@angular/common';
import localeData from '@angular/common/locales/da';
import { LOCALE_ID, NgModule } from '@angular/core';
registerLocaleData(localeData);
@NgModule({
...,
providers: [
{ provide: LOCALE_ID, useValue: 'da' },
],
})
export class AppModule {}`;
translationGetterCodeSnippet = `constructor(private translationService: TranslationService) {}
get previousMonthLabel(): string {
return this.translationService.get('previousMonth');
}`;

constructor(public translations: TranslationService) {}
selectedDate: Date = new Date(2025, 0, 1);
}
5 changes: 5 additions & 0 deletions apps/cookbook/src/assets/images/dk.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions apps/cookbook/src/assets/images/gb.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions libs/designsystem/calendar/src/calendar.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
kirby-button
type="button"
class="no-margin"
aria-label="Previous month"
[attr.aria-label]="translations.get('previousMonth')"
[attr.aria-disabled]="_canNavigateBack ? null : true"
[noDecoration]="true"
(click)="_changeMonth(-1)"
Expand All @@ -21,7 +21,7 @@
kirby-button
type="button"
class="no-margin"
aria-label="Next month"
[attr.aria-label]="translations.get('nextMonth')"
[attr.aria-disabled]="_canNavigateForward ? null : true"
[noDecoration]="true"
(click)="_changeMonth(1)"
Expand All @@ -34,6 +34,7 @@
[usePopover]="usePopover"
[selectedIndex]="navigatedYear"
[items]="navigableYears"
[attr.aria-label]="translations.get('selectYear')"
popout="left"
(change)="_changeYear($event)"
></kirby-dropdown>
Expand Down
4 changes: 3 additions & 1 deletion libs/designsystem/calendar/src/calendar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { DropdownModule } from '@kirbydesign/designsystem/dropdown';
import { UniqueIdGenerator } from '@kirbydesign/designsystem/helpers';
import { IconModule } from '@kirbydesign/designsystem/icon';

import { TranslationService } from '@kirbydesign/designsystem/shared';
import { CalendarDay, CalendarDayMetadata } from './interfaces/calendar-day';
import { CalendarYearNavigatorConfig } from './interfaces/calendar-year-navigator-config';

Expand Down Expand Up @@ -223,7 +224,8 @@ export class CalendarComponent implements OnInit, OnChanges {
constructor(
@Inject(LOCALE_ID) locale: string,
private elementRef: ElementRef,
private cdr: ChangeDetectorRef
private cdr: ChangeDetectorRef,
public translations: TranslationService
) {
this.locale = this.mapLocale(locale);
this.timeZoneName = Intl.DateTimeFormat().resolvedOptions().timeZone;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
attentionLevel="3"
size="md"
[noDecoration]="config.interactWithBackground"
[attr.aria-label]="translations.get('close')"
(click)="close()"
>
<kirby-icon name="close"></kirby-icon>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { debounceTime, first, map, takeUntil } from 'rxjs/operators';

import { DesignTokenHelper } from '@kirbydesign/designsystem/helpers';

import { ResizeObserverService } from '@kirbydesign/designsystem/shared';
import { ResizeObserverService, TranslationService } from '@kirbydesign/designsystem/shared';
import { WindowRef } from '@kirbydesign/designsystem/types';
import { PlatformService } from '@kirbydesign/designsystem/helpers';
import { CommonModule } from '@angular/common';
Expand Down Expand Up @@ -174,7 +174,8 @@ export class ModalWrapperComponent
private windowRef: WindowRef,
private platform: PlatformService,
private canDismissHelper: CanDismissHelper,
private environmentInjector: EnvironmentInjector
private environmentInjector: EnvironmentInjector,
public translations: TranslationService
) {
this.setViewportHeight();
this.observeViewportResize();
Expand Down
3 changes: 2 additions & 1 deletion libs/designsystem/page/src/page.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
[defaultHref]="defaultBackHref"
icon="assets/kirby/icons/svg/arrow-back.svg"
[style.visibility]="hideBackButton ? 'hidden' : null"
[attr.aria-label]="translations.get('back')"
></ion-back-button>
</ion-buttons>
<ion-title [class.slide-and-fade-in]="toolbarTitleVisible">
Expand Down Expand Up @@ -68,7 +69,7 @@
class="page-header"
[ngClass]="{
'text-center': titleAlignment === 'center',
'text-right': titleAlignment === 'right'
'text-right': titleAlignment === 'right',
}"
>
<div #pageTitle class="page-title" [class.has-actions]="!!pageActionsTemplate">
Expand Down
9 changes: 7 additions & 2 deletions libs/designsystem/page/src/page.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ import {
ModalElementType,
ModalNavigationService,
} from '@kirbydesign/designsystem/modal';
import { FitHeadingConfig, ResizeObserverService } from '@kirbydesign/designsystem/shared';
import {
FitHeadingConfig,
ResizeObserverService,
TranslationService,
} from '@kirbydesign/designsystem/shared';

/**
* Specify scroll event debounce time in ms and scrolled offset from top in pixels
Expand Down Expand Up @@ -364,7 +368,8 @@ export class PageComponent
private routerOutlet: IonRouterOutlet,
@Optional()
private navCtrl: NavController,
private ionicElementPartHelper: IonicElementPartHelper
private ionicElementPartHelper: IonicElementPartHelper,
public translations: TranslationService
) {}

private contentReadyPromise: Promise<void>;
Expand Down
2 changes: 2 additions & 0 deletions libs/designsystem/shared/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export * from './dynamic-component';
export * from './fit-heading/index';

export * from './controls/label-helpers';

export * from './translation/translation.service';
10 changes: 10 additions & 0 deletions libs/designsystem/shared/src/translation/translation.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface Translation {
$code: string;
back: string;
close: string;
nextMonth: string;
nextSlide: string;
previousMonth: string;
previousSlide: string;
selectYear: string;
}
Loading

0 comments on commit 532ed37

Please sign in to comment.