i18n: improve translations

This commit is contained in:
Anthony Fu 2022-11-30 07:25:29 +08:00
parent acdd33ef7d
commit ccffe9daa8
15 changed files with 231 additions and 143 deletions

1
.eslintignore Normal file
View file

@ -0,0 +1 @@
*.css

View file

@ -51,7 +51,7 @@ const { items, prevItems, update, state, endAnchor, error } = usePaginator(pagin
<slot v-if="state === 'loading'" name="loading"> <slot v-if="state === 'loading'" name="loading">
<div p5 text-center flex="~ col" items-center animate-pulse> <div p5 text-center flex="~ col" items-center animate-pulse>
<div text-secondary i-ri:loader-2-fill animate-spin text-2xl /> <div text-secondary i-ri:loader-2-fill animate-spin text-2xl />
<span text-secondary>Loading...</span> <span text-secondary>{{ $t('state.loading') }}</span>
</div> </div>
</slot> </slot>
<div v-else-if="state === 'done'" p5 text-secondary italic text-center> <div v-else-if="state === 'done'" p5 text-secondary italic text-center>

View file

@ -7,7 +7,7 @@ const sub = process.dev ? 'dev' : window.location.hostname.includes('deploy-prev
<NuxtLink flex px3 py2 items-center text-2xl gap-2 hover:bg-active focus-visible:ring="2 current" rounded-full to="/" external> <NuxtLink flex px3 py2 items-center text-2xl gap-2 hover:bg-active focus-visible:ring="2 current" rounded-full to="/" external>
<img alt="Elk Logo" src="/logo.svg" w-10 h-10> <img alt="Elk Logo" src="/logo.svg" w-10 h-10>
<div> <div>
Elk <sup text-sm italic text-secondary mt-1>{{ sub }}</sup> {{ $t('app_name') }} <sup text-sm italic text-secondary mt-1>{{ sub }}</sup>
</div> </div>
</NuxtLink> </NuxtLink>
</template> </template>

View file

@ -245,9 +245,9 @@ const { isOverDropZone } = useDropZone(dropZoneRef, onDrop)
:checked="visibility.value === draft.params.visibility" :checked="visibility.value === draft.params.visibility"
@click="chooseVisibility(visibility.value)" @click="chooseVisibility(visibility.value)"
> >
{{ visibility.label }} {{ $t(`visibility.${visibility.value}`) }}
<template #description> <template #description>
{{ visibility.description }} {{ $t(`visibility.${visibility.value}_desc`) }}
</template> </template>
</CommonDropdownItem> </CommonDropdownItem>
</template> </template>

View file

@ -133,7 +133,7 @@ function editStatus() {
<div flex justify-between> <div flex justify-between>
<div flex-1> <div flex-1>
<StatusActionButton <StatusActionButton
content="Reply" :content="$t('action.reply')"
:text="status.repliesCount" :text="status.repliesCount"
color="text-blue" hover="text-blue" group-hover="bg-blue/10" color="text-blue" hover="text-blue" group-hover="bg-blue/10"
icon="i-ri:chat-3-line" icon="i-ri:chat-3-line"
@ -144,7 +144,7 @@ function editStatus() {
<div flex-1> <div flex-1>
<StatusActionButton <StatusActionButton
content="Boost" :content="$t('action.boost')"
:text="status.reblogsCount" :text="status.reblogsCount"
color="text-green" hover="text-green" group-hover="bg-green/10" color="text-green" hover="text-green" group-hover="bg-green/10"
icon="i-ri:repeat-line" icon="i-ri:repeat-line"
@ -158,7 +158,7 @@ function editStatus() {
<div flex-1> <div flex-1>
<StatusActionButton <StatusActionButton
content="Favourite" :content="$t('action.favourite')"
:text="status.favouritesCount" :text="status.favouritesCount"
color="text-rose" hover="text-rose" group-hover="bg-rose/10" color="text-rose" hover="text-rose" group-hover="bg-rose/10"
icon="i-ri:heart-3-line" icon="i-ri:heart-3-line"
@ -172,7 +172,7 @@ function editStatus() {
<div flex-none> <div flex-none>
<StatusActionButton <StatusActionButton
content="Bookmark" :content="$t('action.bookmark')"
color="text-yellow" hover="text-yellow" group-hover="bg-yellow/10" color="text-yellow" hover="text-yellow" group-hover="bg-yellow/10"
icon="i-ri:bookmark-line" icon="i-ri:bookmark-line"
active-icon="i-ri:bookmark-fill" active-icon="i-ri:bookmark-fill"
@ -185,7 +185,7 @@ function editStatus() {
<CommonDropdown flex-none ml3 placement="bottom" :eager-mount="command"> <CommonDropdown flex-none ml3 placement="bottom" :eager-mount="command">
<StatusActionButton <StatusActionButton
content="More" :content="$t('action.more')"
color="text-purple" hover="text-purple" group-hover="bg-purple/10" color="text-purple" hover="text-purple" group-hover="bg-purple/10"
icon="i-ri:more-line" icon="i-ri:more-line"
/> />
@ -193,7 +193,7 @@ function editStatus() {
<template #popper> <template #popper>
<div flex="~ col"> <div flex="~ col">
<CommonDropdownItem <CommonDropdownItem
text="Copy link to this post" :text="$t('menu.copy_link_to_post')"
icon="i-ri:link" icon="i-ri:link"
:command="command" :command="command"
@click="copyLink(status)" @click="copyLink(status)"
@ -202,7 +202,7 @@ function editStatus() {
<NuxtLink :to="status.url" target="_blank"> <NuxtLink :to="status.url" target="_blank">
<CommonDropdownItem <CommonDropdownItem
v-if="status.url" v-if="status.url"
text="Open in original site" :text="$t('menu.open_in_original_site')"
icon="i-ri:arrow-right-up-line" icon="i-ri:arrow-right-up-line"
:command="command" :command="command"
/> />
@ -210,7 +210,7 @@ function editStatus() {
<CommonDropdownItem <CommonDropdownItem
v-if="isTranslationEnabled && status.language !== languageCode" v-if="isTranslationEnabled && status.language !== languageCode"
:text="translation.visible ? 'Show untranslated' : 'Translate post'" :text="translation.visible ? $t('menu.show_untranslated') : $t('menu.translate_post')"
icon="i-ri:translate" icon="i-ri:translate"
:command="command" :command="command"
@click="toggleTranslation" @click="toggleTranslation"
@ -219,21 +219,21 @@ function editStatus() {
<template v-if="currentUser"> <template v-if="currentUser">
<template v-if="isAuthor"> <template v-if="isAuthor">
<CommonDropdownItem <CommonDropdownItem
:text="status.pinned ? 'Unpin on profile' : 'Pin on profile'" :text="status.pinned ? $t('menu.unpin_on_profile') : $t('menu.pin_on_profile')"
icon="i-ri:pushpin-line" icon="i-ri:pushpin-line"
:command="command" :command="command"
@click="togglePin" @click="togglePin"
/> />
<CommonDropdownItem <CommonDropdownItem
text="Edit" :text="$t('menu.edit')"
icon="i-ri:edit-line" icon="i-ri:edit-line"
:command="command" :command="command"
@click="editStatus" @click="editStatus"
/> />
<CommonDropdownItem <CommonDropdownItem
text="Delete" :text="$t('menu.delete')"
icon="i-ri:delete-bin-line" icon="i-ri:delete-bin-line"
text-red-600 text-red-600
:command="command" :command="command"
@ -241,7 +241,7 @@ function editStatus() {
/> />
<CommonDropdownItem <CommonDropdownItem
text="Delete & re-draft" :text="$t('menu.delete_and_redraft')"
icon="i-ri:eraser-line" icon="i-ri:eraser-line"
text-red-600 text-red-600
:command="command" :command="command"
@ -250,7 +250,7 @@ function editStatus() {
</template> </template>
<template v-else> <template v-else>
<CommonDropdownItem <CommonDropdownItem
:text="`Mention @${status.account.acct}`" :text="$t('menu.mention_account', [`@${status.account.acct}`])"
icon="i-ri:at-line" icon="i-ri:at-line"
:command="command" :command="command"
@click="mentionUser(status.account)" @click="mentionUser(status.account)"

View file

@ -43,11 +43,11 @@ const visibility = $computed(() => STATUS_VISIBILITIES.find(v => v.value === sta
:status="status" :status="status"
:inline="false" :inline="false"
> >
<span ml1 font-bold cursor-pointer>(Edited)</span> <span ml1 font-bold cursor-pointer>{{ $t('state.edited') }}</span>
</StatusEditIndicator> </StatusEditIndicator>
</div> </div>
<div>·</div> <div>·</div>
<CommonTooltip :content="visibility.label" placement="bottom"> <CommonTooltip :content="$t(`visibility.${visibility.value}`)" placement="bottom">
<div :class="visibility.icon" /> <div :class="visibility.icon" />
</CommonTooltip> </CommonTooltip>
<div v-if="status.application?.name"> <div v-if="status.application?.name">

View file

@ -33,17 +33,17 @@ const switchUser = (user: UserLogin) => {
</button> </button>
</template> </template>
<div border="t base" pt2> <div border="t base" pt2>
<button btn-text flex="~ gap-1" items-center @click="openSigninDialog"> <CommonDropdownItem
<div i-ri:user-add-line /> :text=" $t('user.add_existing')"
Add an existing account icon="i-ri:user-add-line"
</button> @click="openSigninDialog"
<button />
v-if="currentUser" btn-text hover:text-red4 flex="~ gap-1" items-center <CommonDropdownItem
v-if="currentUser"
:text="$t('user.sign_out_account', [getFullHandle(currentUser.account)])"
icon="i-ri:logout-box-line"
@click="signout" @click="signout"
> />
<div i-ri:logout-box-line />
Sign out {{ getFullHandle(currentUser.account) }}
</button>
</div> </div>
</div> </div>
</template> </template>

View file

@ -6,27 +6,19 @@ import { withoutProtocol } from 'ufo'
export const STATUS_VISIBILITIES = [ export const STATUS_VISIBILITIES = [
{ {
value: 'public', value: 'public',
label: 'Public',
icon: 'i-ri:global-line', icon: 'i-ri:global-line',
description: 'Visible for all',
}, },
{ {
value: 'unlisted', value: 'unlisted',
label: 'Unlisted',
icon: 'i-ri:lock-unlock-line', icon: 'i-ri:lock-unlock-line',
description: 'Visible for all, but opted-out of discovery features',
}, },
{ {
value: 'private', value: 'private',
label: 'Followers only',
icon: 'i-ri:lock-line', icon: 'i-ri:lock-line',
description: 'Visible for followers only',
}, },
{ {
value: 'direct', value: 'direct',
label: 'Mentioned people only',
icon: 'i-ri:at-line', icon: 'i-ri:at-line',
description: 'Visible for mentioned users only',
}, },
] as const ] as const

View file

@ -24,11 +24,12 @@ export const currentUserDrafts = computed(() => {
}) })
export function getDefaultDraft(options: Partial<Draft['params'] & Omit<Draft, 'params'>> = {}): Draft { export function getDefaultDraft(options: Partial<Draft['params'] & Omit<Draft, 'params'>> = {}): Draft {
const { t } = useI18n()
const { const {
status = '', status = '',
inReplyToId, inReplyToId,
visibility = 'public', visibility = 'public',
placeholder = 'What is on your mind?', placeholder = t('placeholder.default_1'),
attachments = [], attachments = [],
} = options } = options
return { return {
@ -52,11 +53,12 @@ export function getDraftFromStatus(status: Status, text?: null | string): Draft
} }
export function getReplyDraft(status: Status) { export function getReplyDraft(status: Status) {
const { t } = useI18n()
return { return {
key: `reply-${status.id}`, key: `reply-${status.id}`,
draft: () => getDefaultDraft({ draft: () => getDefaultDraft({
inReplyToId: status!.id, inReplyToId: status!.id,
placeholder: `Reply to ${status?.account ? getDisplayName(status.account) : 'this thread'}`, placeholder: t('placeholder.reply_to_account', [status?.account ? getDisplayName(status.account) : t('placeholder.the_thread')]),
visibility: status.visibility, visibility: status.visibility,
}), }),
} }

View file

@ -4,7 +4,8 @@ export const useFormattedDateTime = (
value: MaybeComputedRef<string | Date | undefined | null>, value: MaybeComputedRef<string | Date | undefined | null>,
options: Intl.DateTimeFormatOptions = { dateStyle: 'long', timeStyle: 'medium' }, options: Intl.DateTimeFormatOptions = { dateStyle: 'long', timeStyle: 'medium' },
) => { ) => {
const formatter = Intl.DateTimeFormat(undefined, options) const { locale } = useI18n()
const formatter = Intl.DateTimeFormat(locale.value, options)
return computed(() => { return computed(() => {
const v = resolveUnref(value) const v = resolveUnref(value)
return v ? formatter.format(new Date(v)) : '' return v ? formatter.format(new Date(v)) : ''

View file

@ -15,12 +15,18 @@
"unfollow": "Unfollow" "unfollow": "Unfollow"
}, },
"action": { "action": {
"bookmark": "Bookmark",
"boost": "Boost",
"compose": "Compose", "compose": "Compose",
"enter_app": "Enter App", "enter_app": "Enter App",
"favourite": "Favourite",
"more": "More",
"publish": "Publish!", "publish": "Publish!",
"reply": "Reply",
"save_changes": "Save changes", "save_changes": "Save changes",
"sign_in": "Sign in" "sign_in": "Sign in"
}, },
"app_name": "Elk",
"command": { "command": {
"activate": "Activate", "activate": "Activate",
"complete": "Complete" "complete": "Complete"
@ -39,13 +45,21 @@
"menu": { "menu": {
"block_account": "Block {0}", "block_account": "Block {0}",
"block_domain": "Block domain {0}", "block_domain": "Block domain {0}",
"copy_link_to_post": "Copy link to this post",
"delete": "Delete",
"delete_and_redraft": "Delete & re-draft",
"direct_message_account": "Direct message {0}", "direct_message_account": "Direct message {0}",
"edit": "Edit",
"mention_account": "Mention {0}", "mention_account": "Mention {0}",
"mute_account": "Mute {0}", "mute_account": "Mute {0}",
"open_in_original_site": "Open in original site", "open_in_original_site": "Open in original site",
"pin_on_profile": "Pin on profile",
"show_untranslated": "Show untranslated",
"translate_post": "Translate post",
"unblock_account": "Unblock {0}", "unblock_account": "Unblock {0}",
"unblock_domain": "Unblock domain {0}", "unblock_domain": "Unblock domain {0}",
"unmute_account": "Unmute {0}" "unmute_account": "Unmute {0}",
"unpin_on_profile": "Unpin on profile"
}, },
"nav_footer": { "nav_footer": {
"select_feature_flags": "Toggle Feature Flags", "select_feature_flags": "Toggle Feature Flags",
@ -75,12 +89,21 @@
"request_to_follow": "requested to follow you", "request_to_follow": "requested to follow you",
"update_status": "updated their status" "update_status": "updated their status"
}, },
"placeholder": {
"default_1": "What is on your mind?",
"reply_to_account": "Reply to {0}",
"the_thread": "the thread"
},
"state": { "state": {
"edited": "(Edited)",
"editing": "Editing", "editing": "Editing",
"loading": "Loading...",
"uploading": "Uploading..." "uploading": "Uploading..."
}, },
"tab": { "tab": {
"media": "Media", "media": "Media",
"notifications_all": "All",
"notifications_mention": "Mention",
"posts": "Posts", "posts": "Posts",
"posts_with_replies": "Posts & Replies" "posts_with_replies": "Posts & Replies"
}, },
@ -96,5 +119,19 @@
"add_media": "Add images, a video or an audio file", "add_media": "Add images, a video or an audio file",
"change_content_visibility": "Change content visibility", "change_content_visibility": "Change content visibility",
"toggle_code_block": "Toggle code block" "toggle_code_block": "Toggle code block"
},
"user": {
"add_existing": "Add an existing account",
"sign_out_account": "Sign out {0}"
},
"visibility": {
"direct": "Direct",
"direct_desc": "Visible for mentioned users only",
"private": "Followers only",
"private_desc": "Visible for followers only",
"public": "Public",
"public_desc": "Visible for all",
"unlisted": "Unlisted",
"unlisted_desc": "Visible for all, but opted-out of discovery features"
} }
} }

View file

@ -90,5 +90,4 @@
"change_content_visibility": "Cambiar visibilidad", "change_content_visibility": "Cambiar visibilidad",
"toggle_code_block": "Toggle code block" "toggle_code_block": "Toggle code block"
} }
} }

View file

@ -78,12 +78,11 @@
"posts_with_replies": "投稿と返信" "posts_with_replies": "投稿と返信"
}, },
"timeline": { "timeline": {
"name": "タイムライン",
"show_new_items": "{0}件の新しい投稿" "show_new_items": "{0}件の新しい投稿"
}, },
"title": { "title": {
"federated_timeline": "@:nav_side.federated @:timeline.name", "federated_timeline": "連合タイムライン",
"local_timeline": "@:nav_side.local @:timeline.name" "local_timeline": "ローカルタイムライン"
}, },
"tooltip": { "tooltip": {
"add_content_warning": "警告を追加", "add_content_warning": "警告を追加",

View file

@ -15,30 +15,51 @@
"unfollow": "取消关注" "unfollow": "取消关注"
}, },
"action": { "action": {
"bookmark": "收藏",
"boost": "转发",
"compose": "撰写", "compose": "撰写",
"enter_app": "进入应用", "enter_app": "进入应用",
"favourite": "喜欢",
"more": "更多",
"publish": "发布!", "publish": "发布!",
"reply": "回复",
"save_changes": "保存更改", "save_changes": "保存更改",
"sign_in": "登录" "sign_in": "登录"
}, },
"app_name": "鹿鸣",
"command": {
"activate": "执行",
"complete": "完成"
},
"common": { "common": {
"end_of_list": "列表到底啦", "end_of_list": "列表到底啦",
"error": "错误", "error": "错误",
"not_found": "无法找到相关内容" "not_found": "无法找到相关内容"
}, },
"error": {
"account_not_found": "未找到用户 {0}"
},
"feature_flag": { "feature_flag": {
"virtual_scroll": "虚拟滚动" "virtual_scroll": "虚拟滚动"
}, },
"menu": { "menu": {
"block_account": "拉黑 {0}", "block_account": "拉黑 {0}",
"block_domain": "拉黑域名 {0}", "block_domain": "拉黑域名 {0}",
"copy_link_to_post": "复制这篇文章的链接",
"delete": "删除",
"delete_and_redraft": "删除并重新编辑",
"direct_message_account": "私信 {0}", "direct_message_account": "私信 {0}",
"edit": "编辑",
"mention_account": "提及 {0}", "mention_account": "提及 {0}",
"mute_account": "屏蔽 {0}", "mute_account": "屏蔽 {0}",
"open_in_original_site": "从源站打开", "open_in_original_site": "从源站打开",
"pin_on_profile": "钉选在个人资料上",
"show_untranslated": "显示原文",
"translate_post": "翻译帖子",
"unblock_account": "解除拉黑 {0}", "unblock_account": "解除拉黑 {0}",
"unblock_domain": "解除拉黑域名 {0}", "unblock_domain": "解除拉黑域名 {0}",
"unmute_account": "解除屏蔽 {0}" "unmute_account": "解除屏蔽 {0}",
"unpin_on_profile": "取消钉选"
}, },
"nav_footer": { "nav_footer": {
"select_feature_flags": "功能开关", "select_feature_flags": "功能开关",
@ -68,12 +89,21 @@
"request_to_follow": "请求关注你", "request_to_follow": "请求关注你",
"update_status": "更新了他们的状态" "update_status": "更新了他们的状态"
}, },
"placeholder": {
"default_1": "在想些什么?",
"reply_to_account": "回复 {0}",
"the_thread": "这个帖子"
},
"state": { "state": {
"edited": "(已编辑)",
"editing": "编辑中", "editing": "编辑中",
"loading": "加载中...",
"uploading": "上传中..." "uploading": "上传中..."
}, },
"tab": { "tab": {
"media": "媒体", "media": "媒体",
"notifications_all": "全部",
"notifications_mention": "提及",
"posts": "帖文", "posts": "帖文",
"posts_with_replies": "帖文与留言" "posts_with_replies": "帖文与留言"
}, },
@ -89,5 +119,19 @@
"add_media": "添加图片、视频或者音频文件", "add_media": "添加图片、视频或者音频文件",
"change_content_visibility": "修改内容是否可见", "change_content_visibility": "修改内容是否可见",
"toggle_code_block": "切换代码块" "toggle_code_block": "切换代码块"
},
"user": {
"add_existing": "添加现有帐户",
"sign_out_account": "登出 {0}"
},
"visibility": {
"direct": "私信",
"direct_desc": "仅对提及的用户可见",
"private": "仅限关注者",
"private_desc": "仅关注者可见",
"public": "公开",
"public_desc": "所有人可见",
"unlisted": "不列出",
"unlisted_desc": "对所有人可见,但不出现在公共时间线上"
} }
} }

View file

@ -7,12 +7,25 @@ definePageMeta({
const { t } = useI18n() const { t } = useI18n()
const tabNames = ['All', 'Mentions'] as const const paginatorAll = useMasto().notifications.getIterator()
const tab = $(useLocalStorage<typeof tabNames[number]>(STORAGE_KEY_NOTIFY_TAB, 'All')) const paginatorMention = useMasto().notifications.getIterator({ types: ['mention'] })
const paginator = $computed(() => { const tabs = $computed(() => [
return useMasto().notifications.getIterator(tab === 'All' ? undefined : { types: ['mention'] }) {
}) name: 'all',
display: t('tab.notifications_all'),
paginator: paginatorAll,
},
{
name: 'mention',
display: t('tab.notifications_mention'),
paginator: paginatorMention,
},
] as const)
// Don't use local storage because it is better to default to Posts every time you visit a user's profile.
const tab = $ref(tabs[0].name)
const paginator = $computed(() => tabs.find(t => t.name === tab)!.paginator)
useHeadFixed({ useHeadFixed({
title: () => t('nav_side.notifications'), title: () => t('nav_side.notifications'),
@ -29,7 +42,7 @@ useHeadFixed({
</template> </template>
<template #header> <template #header>
<CommonTabs v-model="tab" :options="tabNames" /> <CommonTabs v-model="tab" :options="tabs" />
</template> </template>
<slot> <slot>
<NotificationPaginator :key="tab" :paginator="paginator" /> <NotificationPaginator :key="tab" :paginator="paginator" />