Accessibility: add (translatable) aria labels, update to translation system, see #1181

This commit is contained in:
Pieter Vander Vennet 2023-12-12 19:18:50 +01:00
parent 825fd03adb
commit 8a7d8a43ce
12 changed files with 130 additions and 72 deletions

View file

@ -2,7 +2,6 @@
import { UIEventSource } from "../../Logic/UIEventSource.js"
export let value: UIEventSource<any>
let i: any = value.data
let htmlElement: HTMLSelectElement
function selectAppropriateValue() {
if (!htmlElement) {

View file

@ -3,36 +3,20 @@
* Properly renders a translation
*/
import { Translation } from "../i18n/Translation"
import { onDestroy } from "svelte"
import Locale from "../i18n/Locale"
import { Utils } from "../../Utils"
import FromHtml from "./FromHtml.svelte"
import WeblateLink from "./WeblateLink.svelte"
import { Store } from "../../Logic/UIEventSource"
export let t: Translation
export let cls: string = ""
export let tags: Record<string, string> | undefined = undefined
// Text for the current language
let txt: string | undefined
let txt: Store<string | undefined> = t.current
$: onDestroy(
Locale.language.addCallbackAndRunD((l) => {
const translation = t?.textFor(l)
if (translation === undefined) {
return
}
if (tags) {
txt = Utils.SubstituteKeys(txt, tags)
} else {
txt = translation
}
})
)
</script>
{#if t}
<span class={cls}>
<FromHtml src={txt} />
{$txt}
<WeblateLink context={t.context} />
</span>
{/if}

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource"
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
import type { Feature } from "geojson"
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg.js"
@ -30,7 +30,20 @@
let inputElement: HTMLInputElement
let feedback: string = undefined
let placeholder = Translations.t.general.search.search.current
$:{
if(inputElement){
inputElement.placeholder = placeholder.data
}
}
onDestroy(placeholder.addCallbackAndRunD(placeholder => {
if(inputElement){
inputElement.placeholder = placeholder
}
}))
Hotkeys.RegisterHotkey({ ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => {
feedback = undefined
requestAnimationFrame(() => {
@ -111,7 +124,6 @@
bind:this={inputElement}
on:keypress={(keypr) => (keypr.key === "Enter" ? performSearch() : undefined)}
bind:value={searchContents}
placeholder={Translations.t.general.search.search}
/>
{/if}
</form>

View file

@ -41,6 +41,7 @@
on:click={() => {
expanded = true
}}
aria-expanded={expanded}
>
<Camera_plus class="mr-2 block h-8 w-8 p-1" />
<Tr t={t.seeNearby} />

View file

@ -1,16 +1,17 @@
<script lang="ts">
// Languages in the language itself
import native from "../../assets/language_native.json"
// Translated languages
import language_translations from "../../assets/language_translations.json"
// Languages in the language itself
import native from "../../assets/language_native.json"
// Translated languages
import language_translations from "../../assets/language_translations.json"
import { UIEventSource } from "../../Logic/UIEventSource"
import Locale from "../i18n/Locale"
import { LanguageIcon } from "@babeard/svelte-heroicons/solid"
import Dropdown from "../Base/Dropdown.svelte"
import { twMerge } from "tailwind-merge"
/**
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Locale from "../i18n/Locale"
import { LanguageIcon } from "@babeard/svelte-heroicons/solid"
import Dropdown from "../Base/Dropdown.svelte"
import { twMerge } from "tailwind-merge"
import Translations from "../i18n/Translations"
import { ariaLabel } from "../../Utils/ariaLabel"
/**
* Languages one can choose from
* Defaults to _all_ languages known by MapComplete
*/
@ -19,7 +20,7 @@
* EventStore to assign to, defaults to 'Locale.langauge'
*/
export let assignTo: UIEventSource<string> = Locale.language
export let preferredLanguages: UIEventSource<string[]> = undefined
export let preferredLanguages: Store<string[]> = undefined
let preferredFiltered: string[] = undefined
preferredLanguages?.addCallbackAndRunD((preferredLanguages) => {
let lng = navigator.language
@ -31,34 +32,40 @@
}
preferredFiltered = preferredLanguages?.filter((l) => availableLanguages.indexOf(l) >= 0)
})
export let clss : string = undefined
export let clss: string = undefined
let current = Locale.language
</script>
{#if availableLanguages?.length > 1}
<form class={twMerge("flex items-center max-w-full pr-4", clss)}>
<LanguageIcon class="h-4 w-4 mr-1 shrink-0" />
<Dropdown cls="max-w-full" value={assignTo}>
{#if preferredFiltered}
{#each preferredFiltered as language}
<label class="flex neutral-label" use:ariaLabel={Translations.t.general.pickLanguage}>
<LanguageIcon class="h-4 w-4 mr-1 shrink-0" aria-hidden="true" />
<Dropdown cls="max-w-full" value={assignTo}>
{#if preferredFiltered}
{#each preferredFiltered as language}
<option value={language} class="font-bold">
{native[language] ?? ""}
{#if language !== $current}
({language_translations[language]?.[$current] ?? language})
{/if}
</option>
{/each}
<option disabled />
{/if}
{#each availableLanguages.filter(l => l !== "_context") as language}
<option value={language} class="font-bold">
{native[language] ?? ""}
{#if language !== $current}
({language_translations[language]?.[$current] ?? language})
{#if language_translations[language]?.[$current] !== undefined}
({ language_translations[language]?.[$current] + " - " + language ?? language})
{:else}
({language})
{/if}
{/if}
</option>
{/each}
<option disabled />
{/if}
{#each availableLanguages as language}
<option value={language} class="font-bold">
{native[language] ?? ""}
{#if language !== $current}
({language_translations[language]?.[$current] + " - " + language ?? language})
{/if}
</option>
{/each}
</Dropdown>
</Dropdown>
</label>
</form>
{/if}

View file

@ -63,6 +63,10 @@ export default class Locale {
source = LocalStorageSource.Get("language", browserLanguage)
}
source.addCallbackAndRun((l) => {
document.documentElement.setAttribute("lang", l)
})
if (!Utils.runningFromConsole) {
// @ts-ignore
window.setLanguage = function (language: string) {

View file

@ -2,6 +2,7 @@ import Locale from "./Locale"
import { Utils } from "../../Utils"
import BaseUIElement from "../BaseUIElement"
import LinkToWeblate from "../Base/LinkToWeblate"
import { Store } from "../../Logic/UIEventSource"
export class Translation extends BaseUIElement {
public static forcedLanguage = undefined
@ -9,6 +10,9 @@ export class Translation extends BaseUIElement {
public readonly translations: Record<string, string>
public readonly context?: string
private _current: Store<string>
private onDestroy: () => void
constructor(translations: string | Record<string, string>, context?: string) {
super()
if (translations === undefined) {
@ -66,6 +70,18 @@ export class Translation extends BaseUIElement {
return this.textFor(Translation.forcedLanguage ?? Locale.language.data)
}
get current(): Store<string> {
if (!this._current) {
this._current = Locale.language.map(
(l) => this.textFor(l),
[],
(f) => {
this.onDestroy = f
}
)
}
return this._current
}
static ExtractAllTranslationsFrom(
object: any,
context = ""
@ -108,6 +124,7 @@ export class Translation extends BaseUIElement {
Destroy() {
super.Destroy()
this.onDestroy()
this.isDestroyed = true
}