feat(i18n): plurals support (#278)

This commit is contained in:
Joaquín Sánchez 2022-12-02 03:18:36 +01:00 committed by GitHub
parent 0f7de38c24
commit c4cf3fb371
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 234 additions and 91 deletions

View file

@ -12,7 +12,7 @@ const loaded = $ref(false)
<img
:key="account.avatar"
:src="account.avatar"
:alt="account.username"
:alt="$t('account.avatar_description', [account.username])"
loading="lazy"
rounded-full
:class="loaded ? 'bg-gray' : 'bg-gray:10'"

View file

@ -6,6 +6,8 @@ const { account } = defineProps<{
command?: boolean
}>()
const { t } = useI18n()
const createdAt = $(useFormattedDateTime(() => account.createdAt, {
month: 'long',
day: 'numeric',
@ -43,13 +45,16 @@ function getFieldNameIcon(fieldName: string) {
if (fieldNameIcons[name])
return fieldNameIcons[name]
}
function getFieldIconTitle(fieldName: string) {
return fieldName === 'Joined' ? t('account.joined') : fieldName
}
function previewHeader() {
openMediaPreview([{
id: `${account.acct}:header`,
type: 'image',
previewUrl: account.header,
description: `${account.username}'s profile header`,
description: t('account.profile_description', [account.username]),
}])
}
@ -58,7 +63,7 @@ function previewAvatar() {
id: `${account.acct}:avatar`,
type: 'image',
previewUrl: account.avatar,
description: `${account.username}'s avatar`,
description: t('account.avatar_description', [account.username]),
}])
}
@ -86,7 +91,7 @@ watchEffect(() => {
<template>
<div flex flex-col>
<button border="b base" z-1>
<img h-50 w-full object-cover :src="account.header" :alt="`${account.username}'s profile header`" @click="previewHeader">
<img h-50 w-full object-cover :src="account.header" :alt="t('account.profile_description', [account.username])" @click="previewHeader">
</button>
<div p4 mt--18 flex flex-col gap-4>
<div relative>
@ -122,7 +127,7 @@ watchEffect(() => {
</div>
<div v-if="iconFields.length" flex="~ wrap gap-4">
<div v-for="field in iconFields" :key="field.name" flex="~ gap-1" items-center>
<div text-secondary :class="getFieldNameIcon(field.name)" :title="field.name" />
<div text-secondary :class="getFieldNameIcon(field.name)" :title="getFieldIconTitle(field.name)" />
<ContentRich text-sm filter-saturate-0 :content="field.value" :emojis="account.emojis" />
</div>
</div>

View file

@ -1,31 +1,46 @@
<script setup lang="ts">
import type { Account } from 'masto'
defineProps<{
const props = defineProps<{
account: Account
}>()
const { formatHumanReadableNumber, formatNumber, forSR } = useHumanReadableNumber()
const statusesCount = $computed(() => formatNumber(props.account.statusesCount))
const followingCount = $computed(() => formatHumanReadableNumber(props.account.followingCount))
const followingCountSR = $computed(() => forSR(props.account.followingCount))
const followersCount = $computed(() => formatHumanReadableNumber(props.account.followersCount))
const followersCountSR = $computed(() => forSR(props.account.followersCount))
</script>
<template>
<div flex gap-5>
<NuxtLink :to="getAccountRoute(account)" text-secondary exact-active-class="text-primary">
<template #default="{ isExactActive }">
<i18n-t keypath="account.posts_count">
<span font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ formattedNumber(account.statusesCount) }}</span>
<i18n-t keypath="account.posts_count" :plural="account.statusesCount">
<span font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ statusesCount }}</span>
</i18n-t>
</template>
</NuxtLink>
<NuxtLink :to="getAccountFollowingRoute(account)" text-secondary exact-active-class="text-primary">
<template #default="{ isExactActive }">
<i18n-t keypath="account.following_count">
<span font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ humanReadableNumber(account.followingCount) }}</span>
<span v-if="followingCountSR">
<span aria-hidden="true" font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ followingCount }}</span>
<span sr-only font-bold>{{ account.followingCount }}</span>
</span>
<span v-else font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ followingCount }}</span>
</i18n-t>
</template>
</NuxtLink>
<NuxtLink :to="getAccountFollowersRoute(account)" text-secondary exact-active-class="text-primary">
<template #default="{ isExactActive }">
<i18n-t keypath="account.followers_count">
<span font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ humanReadableNumber(account.followersCount) }}</span>
<i18n-t keypath="account.followers_count" :plural="account.followersCount">
<span v-if="followersCountSR">
<span aria-hidden="true" font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ followersCount }}</span>
<span sr-only font-bold>{{ account.followersCount }}</span>
</span>
<span v-else font-bold :class="isExactActive ? 'text-primary' : 'text-base'">{{ followersCount }}</span>
</i18n-t>
</template>
</NuxtLink>

View file

@ -1,21 +1,24 @@
<script setup lang="ts">
const { t } = useI18n()
const buildTime = import.meta.env.__BUILD_TIME__ as string
const buildTimeAgo = useTimeAgo(buildTime)
const buildTimeDate = new Date(buildTime)
const timeAgoOptions = useTimeAgoOptions()
const buildTimeAgo = useTimeAgo(buildTime, timeAgoOptions)
</script>
<template>
<footer p4 text-sm text-secondary-light flex="~ col">
<div flex="~ gap2" items-center mb4>
<CommonTooltip :content="t('nav_footer.toggle_theme')">
<button flex i-ri:sun-line dark:i-ri:moon-line text-lg :aria-label="t('nav_footer.toggle_theme')" @click="toggleDark()" />
<CommonTooltip :content="$t('nav_footer.toggle_theme')">
<button flex i-ri:sun-line dark:i-ri:moon-line text-lg :aria-label="$t('nav_footer.toggle_theme')" @click="toggleDark()" />
</CommonTooltip>
<CommonTooltip :content="t('nav_footer.zen_mode')">
<CommonTooltip :content="$t('nav_footer.zen_mode')">
<button
flex
text-lg
:class="isZenMode ? 'i-ri:layout-right-2-line' : 'i-ri:layout-right-line'"
:aria-label="t('nav_footer.zen_mode')"
:aria-label="$t('nav_footer.zen_mode')"
@click="toggleZenMode()"
/>
</CommonTooltip>
@ -30,7 +33,7 @@ const buildTimeAgo = useTimeAgo(buildTime)
<div>{{ $t('app_desc_short') }}</div>
<div>
<i18n-t keypath="nav_footer.built_at">
<time :datetime="buildTime" :title="buildTime">{{ buildTimeAgo }}</time>
<time :datetime="buildTime" :title="$d(buildTimeDate, 'long')">{{ buildTimeAgo }}</time>
</i18n-t> · <a href="https://github.com/elk-zone/elk" target="_blank">GitHub</a>
</div>
</footer>

View file

@ -5,15 +5,30 @@ const { items } = defineProps<{
items: GroupedNotifications
}>()
const count = computed(() => items.items.length)
const { formatHumanReadableNumber, forSR } = useHumanReadableNumber()
const count = $computed(() => items.items.length)
const addSR = $computed(() => forSR(count))
const isExpanded = ref(false)
</script>
<template>
<article flex flex-col>
<div flex ml-4 items-center>
<div i-ri:user-follow-fill mr-3 color-primary />
{{ $t('notification.followed_you_count', [`${count}`]) }}
<div i-ri:user-follow-fill mr-3 color-primary aria-hidden="true" />
<template v-if="addSR">
<span
aria-hidden="true"
>
{{ $t('notification.followed_you_count', count, { named: { followers: formatHumanReadableNumber(count) } }) }}
</span>
<span sr-only>
{{ $t('notification.followed_you_count', count, { named: { followers: count } }) }}
</span>
</template>
<span v-else>
{{ $t('notification.followed_you_count', count, { named: { followers: count } }) }}
</span>
</div>
<div v-if="isExpanded">
<AccountCard

View file

@ -41,6 +41,7 @@ function go(evt: MouseEvent | KeyboardEvent) {
}
const createdAt = useFormattedDateTime(status.createdAt)
const timeAgoOptions = useTimeAgoOptions()
const timeago = useTimeAgo(() => status.createdAt, timeAgoOptions)
</script>

View file

@ -10,7 +10,8 @@ function toPercentage(num: number) {
const percentage = 100 * num
return `${percentage.toFixed(1).replace(/\.?0+$/, '')}%`
}
const expiredTimeAgo = useTimeAgo(poll.expiresAt!)
const timeAgoOptions = useTimeAgoOptions()
const expiredTimeAgo = useTimeAgo(poll.expiresAt!, timeAgoOptions)
const masto = useMasto()
async function vote(e: Event) {

View file

@ -10,6 +10,7 @@ const { data: statusEdits } = useAsyncData(`status:history:${status.id}`, () =>
const showHistory = (edit: StatusEdit) => {
openEditHistoryDialog(edit)
}
const timeAgoOptions = useTimeAgoOptions()
</script>
<template>
@ -22,7 +23,7 @@ const showHistory = (edit: StatusEdit) => {
>
{{ getDisplayName(edit.account) }}
<i18n-t :keypath="`status_history.${idx === statusEdits.length - 1 ? 'created' : 'edited'}`">
{{ useTimeAgo(edit.createdAt, { showSecond: true }).value }}
{{ useTimeAgo(edit.createdAt, timeAgoOptions).value }}
</i18n-t>
</CommonDropdownItem>
</template>

View file

@ -15,7 +15,7 @@ const virtualScroller = $(computedEager(() => useFeatureFlags().experimentalVirt
<CommonPaginator v-bind="{ paginator, stream }" :virtual-scroller="virtualScroller">
<template #updater="{ number, update }">
<button py-4 border="b base" flex="~ col" p-3 w-full text-primary font-bold @click="update">
{{ $t('timeline.show_new_items', [number]) }}
{{ $t('timeline.show_new_items', number) }}
</button>
</template>
<template #default="{ item, active }">