Skip to content

Commit

Permalink
Use more descriptive accessible headings for search result pages (#3941)
Browse files Browse the repository at this point in the history
  • Loading branch information
sarayourfriend authored Apr 1, 2024
1 parent 6657a8d commit a51ac8d
Show file tree
Hide file tree
Showing 16 changed files with 951 additions and 58 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
exclude: Pipfile\.lock|migrations|\.idea|node_modules|archive|retired
exclude: Pipfile\.lock|migrations|\.idea|node_modules|archive|retired|\.snap

repos:
- repo: local # More local hooks are defined at the bottom.
Expand Down
3 changes: 3 additions & 0 deletions frontend/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ module.exports = {
globals: {
"vue-jest": {
experimentalCSSCompile: false,
templateCompiler: {
prettify: false,
},
},
},
moduleFileExtensions: ["ts", "js", "vue", "json"],
Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"talkback": "node ./test/proxy.js",
"pretest": "pnpm install",
"test": "pnpm test:unit && pnpm test:playwright",
"test:unit": "jest",
"test:unit": "pnpm run i18n:en && jest",
"test:unit:watch": "pnpm test:unit --collectCoverage=false --watch",
"test:playwright": "./bin/playwright.sh",
"test:playwright:local": "playwright test -c test/playwright",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
<template #header>
<header v-if="searchTerm" class="my-0 md:mb-8 md:mt-4">
<VSearchResultsTitle
:search-term="searchTerm"
:size="results.type === 'all' ? 'large' : 'default'"
:search-type="results.type"
:result-counts="resultCounts"
>{{ searchTerm }}</VSearchResultsTitle
>
</header>
Expand Down Expand Up @@ -99,7 +102,9 @@ export default defineComponent({
const collectionLabel = computed(() => {
return i18n
.t("browsePage.aria.results", { query: props.searchTerm })
.t(`browsePage.aria.results.${props.results.type}`, {
query: props.searchTerm,
})
.toString()
})
const contentLinkPath = (mediaType: SupportedMediaType) =>
Expand Down
100 changes: 98 additions & 2 deletions frontend/src/components/VSearchResultsTitle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,117 @@
: 'heading-2 !leading-none',
]"
>
<slot default />
<span aria-hidden="true">{{ searchTerm }}</span
><span class="sr-only">{{ ariaHeading }}</span>
</h1>
</template>

<script lang="ts">
import { defineComponent, PropType } from "vue"
import { defineComponent, PropType, computed } from "vue"
import type { SupportedMediaType, SupportedSearchType } from "~/constants/media"
import { useGetLocaleFormattedNumber } from "~/composables/use-get-locale-formatted-number"
import { getCountKey } from "~/composables/use-i18n-utilities"
import { useI18n } from "~/composables/use-i18n"
export default defineComponent({
name: "VSearchResultsTitle",
props: {
searchTerm: {
required: true,
type: String,
},
size: {
required: false,
default: "default",
type: String as PropType<"default" | "large">,
},
resultCounts: {
required: true,
type: Array as PropType<[SupportedMediaType, number][]>,
},
searchType: {
required: true,
type: String as PropType<SupportedSearchType>,
},
},
setup(props) {
const getLocaleFormattedNumber = useGetLocaleFormattedNumber()
const i18n = useI18n()
const mediaLocaleCounts = computed(() =>
props.resultCounts.reduce(
(acc, [mediaType, count]) => {
return {
...acc,
[mediaType]: {
count,
countKey: getCountKey(count),
localeCount: getLocaleFormattedNumber(count),
},
}
},
{} as Record<
SupportedMediaType,
{ count: number; countKey: string; localeCount: string }
>
)
)
const _getAllMediaAriaHeading = () => {
const imageLocaleCounts = mediaLocaleCounts.value.image
const imageResults = i18n.tc(
`browsePage.aria.allResultsHeadingCount.image.${imageLocaleCounts.countKey}`,
imageLocaleCounts.count,
{
localeCount: imageLocaleCounts.localeCount,
}
)
const audioLocaleCounts = mediaLocaleCounts.value.audio
const audioResults = i18n.tc(
`browsePage.aria.allResultsHeadingCount.audio.${audioLocaleCounts.countKey}`,
audioLocaleCounts.count,
{
localeCount: audioLocaleCounts.localeCount,
}
)
return i18n
.t("browsePage.aria.results.all", {
query: props.searchTerm,
imageResults,
audioResults,
})
.toString()
}
const ariaHeading = computed((): string => {
switch (props.searchType) {
case "image": {
const { count, countKey, localeCount } = mediaLocaleCounts.value.image
return i18n.tc(`browsePage.aria.results.image.${countKey}`, count, {
localeCount,
query: props.searchTerm,
})
}
case "audio": {
const { count, countKey, localeCount } = mediaLocaleCounts.value.audio
return i18n.tc(`browsePage.aria.results.audio.${countKey}`, count, {
localeCount,
query: props.searchTerm,
})
}
default:
case "all": {
return _getAllMediaAriaHeading()
}
}
})
return {
ariaHeading,
}
},
})
</script>
Expand Down
75 changes: 42 additions & 33 deletions frontend/src/composables/use-i18n-utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,72 +4,81 @@ import type { SupportedMediaType, SupportedSearchType } from "~/constants/media"
import { ALL_MEDIA, AUDIO, IMAGE } from "~/constants/media"
import { Collection } from "~/types/search"

type KeyCollection = {
zero: string
count: string
countMore: string
}
type KeyMapping = Record<SupportedMediaType, KeyCollection> & {
all?: KeyCollection
}
/**
* Not using dynamically-generated keys to ensure that
* correct line is shown in the 'po' locale files
*/
const searchResultKeys = {
[ALL_MEDIA]: {
noResult: "browsePage.allNoResults",
result: "browsePage.allResultCount",
more: "browsePage.allResultCountMore",
zero: "browsePage.allNoResults",
count: "browsePage.allResultCount",
countMore: "browsePage.allResultCountMore",
},
[IMAGE]: {
noResult: "browsePage.contentLink.image.zero",
result: "browsePage.contentLink.image.count",
more: "browsePage.contentLink.image.countMore",
zero: "browsePage.contentLink.image.zero",
count: "browsePage.contentLink.image.count",
countMore: "browsePage.contentLink.image.countMore",
},
[AUDIO]: {
noResult: "browsePage.contentLink.audio.zero",
result: "browsePage.contentLink.audio.count",
more: "browsePage.contentLink.audio.countMore",
zero: "browsePage.contentLink.audio.zero",
count: "browsePage.contentLink.audio.count",
countMore: "browsePage.contentLink.audio.countMore",
},
}
} satisfies KeyMapping

const collectionKeys = {
source: {
[IMAGE]: {
noResult: "collection.resultCountLabel.source.image.zero",
result: "collection.resultCountLabel.source.image.count",
more: "collection.resultCountLabel.source.image.countMore",
zero: "collection.resultCountLabel.source.image.zero",
count: "collection.resultCountLabel.source.image.count",
countMore: "collection.resultCountLabel.source.image.countMore",
},
[AUDIO]: {
noResult: "collection.resultCountLabel.source.audio.zero",
result: "collection.resultCountLabel.source.audio.count",
more: "collection.resultCountLabel.source.audio.countMore",
zero: "collection.resultCountLabel.source.audio.zero",
count: "collection.resultCountLabel.source.audio.count",
countMore: "collection.resultCountLabel.source.audio.countMore",
},
},
creator: {
[IMAGE]: {
noResult: "collection.resultCountLabel.creator.image.zero",
result: "collection.resultCountLabel.creator.image.count",
more: "collection.resultCountLabel.creator.image.countMore",
zero: "collection.resultCountLabel.creator.image.zero",
count: "collection.resultCountLabel.creator.image.count",
countMore: "collection.resultCountLabel.creator.image.countMore",
},
[AUDIO]: {
noResult: "collection.resultCountLabel.creator.audio.zero",
result: "collection.resultCountLabel.creator.audio.count",
more: "collection.resultCountLabel.creator.audio.countMore",
zero: "collection.resultCountLabel.creator.audio.zero",
count: "collection.resultCountLabel.creator.audio.count",
countMore: "collection.resultCountLabel.creator.audio.countMore",
},
},
tag: {
[IMAGE]: {
noResult: "collection.resultCountLabel.tag.image.zero",
result: "collection.resultCountLabel.tag.image.count",
more: "collection.resultCountLabel.tag.image.countMore",
zero: "collection.resultCountLabel.tag.image.zero",
count: "collection.resultCountLabel.tag.image.count",
countMore: "collection.resultCountLabel.tag.image.countMore",
},
[AUDIO]: {
noResult: "collection.resultCountLabel.tag.audio.zero",
result: "collection.resultCountLabel.tag.audio.count",
more: "collection.resultCountLabel.tag.audio.countMore",
zero: "collection.resultCountLabel.tag.audio.zero",
count: "collection.resultCountLabel.tag.audio.count",
countMore: "collection.resultCountLabel.tag.audio.countMore",
},
},
}
} satisfies Record<Collection, KeyMapping>

function getCountKey(resultsCount: number) {
export function getCountKey(resultsCount: number): keyof KeyCollection {
return resultsCount === 0
? "noResult"
? "zero"
: resultsCount >= 10000
? "more"
: "result"
? "countMore"
: "count"
}

/**
Expand Down
36 changes: 35 additions & 1 deletion frontend/src/locales/scripts/en.json5
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,41 @@
creator: "search by creator",
imageTitle: "Image: {title}",
audioTitle: "Audio: {title}",
results: "Search results for {query}.",
results: {
/**
* "imageResults" and "audioResults" are interpolated from the strings under
* browsePage.aria.allResultsHeadingCount.*
*/
all: 'All results for "{query}", {imageResults} and {audioResults}.',
image: {
zero: 'No image results for "{query}"',
count: '{localeCount} image result for "{query}".|{localeCount} image results for "{query}".',
countMore: 'Over {localeCount} image results for "{query}".',
},
audio: {
zero: 'No audio tracks for "{query}"',
count: '{localeCount} audio track for "{query}".|{localeCount} audio tracks for "{query}".',
countMore: 'Over {localeCount} audio tracks for "{query}".',
},
},
allResultsHeadingCount: {
image: {
/* Interpolated into browsePage.aria.results.all */
zero: "no images",
/* Interpolated into browsePage.aria.results.all */
count: "{localeCount} image|{localeCount} images",
/* Interpolated into browsePage.aria.results.all */
countMore: "over {localeCount} images",
},
audio: {
/* Interpolated into browsePage.aria.results.all */
zero: "no audio tracks",
/* Interpolated into browsePage.aria.results.all */
count: "{localeCount} audio track|{localeCount} audio tracks",
/* Interpolated into browsePage.aria.results.all */
countMore: "over {localeCount} audio tracks",
},
},
},
},
mediaDetails: {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit a51ac8d

Please sign in to comment.