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

Accessibility: translate internal component labels #3717

Merged
merged 21 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {}
97 changes: 97 additions & 0 deletions apps/cookbook/src/app/localization/localization.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<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>
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 style="position: relative">
RasmusKjeldgaard marked this conversation as resolved.
Show resolved Hide resolved
<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 style="position: relative">
RasmusKjeldgaard marked this conversation as resolved.
Show resolved Hide resolved
<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>
If custom components are closely coupled to Kirby and need to use identical translations,
RasmusKjeldgaard marked this conversation as resolved.
Show resolved Hide resolved
<code>TranslationService</code>
can be used to fetch and display translated strings.
</p>
<p>
This can be done by injecting the service and getting any key defined in the
RasmusKjeldgaard marked this conversation as resolved.
Show resolved Hide resolved
<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>
26 changes: 26 additions & 0 deletions apps/cookbook/src/app/localization/localization.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@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;
}

.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;
}
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 {}`;
jakobe marked this conversation as resolved.
Show resolved Hide resolved
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
RasmusKjeldgaard marked this conversation as resolved.
Show resolved Hide resolved
) {
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
Loading