mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-08 15:13:28 +08:00
添加国际化支持:在多个页面和组件中引入 vue-i18n
This commit is contained in:
@@ -1,19 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NoDataFound
|
||||
error-code="404"
|
||||
error-title="页面不存在 ⚠️"
|
||||
error-description="您想要访问的页面不存在,请检查地址是否正确。"
|
||||
>
|
||||
<NoDataFound error-code="404" :error-title="t('notFound.title')" :error-description="t('notFound.description')">
|
||||
<template #button>
|
||||
<VBtn
|
||||
to="/"
|
||||
class="mt-10"
|
||||
>
|
||||
返回
|
||||
<VBtn to="/" class="mt-10">
|
||||
{{ t('notFound.backButton') }}
|
||||
</VBtn>
|
||||
</template>
|
||||
</NoDataFound>
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
import { NavMenu } from '@/@layouts/types'
|
||||
import { getNavMenus } from '@/router/i18n-menu'
|
||||
import { useUserStore } from '@/stores'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 从 Store 中获取superuser信息
|
||||
const superUser = useUserStore().superUser
|
||||
@@ -18,7 +22,7 @@ function categorizeApps() {
|
||||
const groupedMenus: Record<string, NavMenu[]> = {}
|
||||
|
||||
menus.forEach(menu => {
|
||||
const header = menu.header || '其他'
|
||||
const header = menu.header || t('appcenter.others')
|
||||
if (!groupedMenus[header]) {
|
||||
groupedMenus[header] = []
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import MediaCardListView from '@/views/discover/MediaCardListView.vue'
|
||||
import PersonCardListView from '@/views/discover/PersonCardListView.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -16,7 +20,7 @@ let title = route.query?.title?.toString()
|
||||
|
||||
// 类型
|
||||
const type = route.query?.type?.toString()
|
||||
if (type === 'person') title = '演员:' + title
|
||||
if (type === 'person') title = t('browse.actor') + ': ' + title
|
||||
|
||||
// 计算API路径
|
||||
function getApiPath(paths: string[] | string) {
|
||||
|
||||
@@ -7,6 +7,10 @@ import BangumiView from '@/views/discover/BangumiView.vue'
|
||||
import ExtraSourceView from '@/views/discover/ExtraSourceView.vue'
|
||||
import { DiscoverSource } from '@/api/types'
|
||||
import api from '@/api'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
const activeTab = ref('')
|
||||
|
||||
@@ -180,13 +184,13 @@ onActivated(async () => {
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
<VIcon icon="mdi-order-alphabetical-ascending" size="small" class="me-2" />
|
||||
设置标签顺序
|
||||
{{ t('discover.setTabOrder') }}
|
||||
</VCardTitle>
|
||||
<VDialogCloseBtn @click="orderConfigDialog = false" />
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<p class="settings-hint">拖动对标签页进行排序</p>
|
||||
<p class="settings-hint">{{ t('discover.dragToReorder') }}</p>
|
||||
<draggable
|
||||
v-model="discoverTabs"
|
||||
handle=".cursor-move"
|
||||
@@ -210,7 +214,7 @@ onActivated(async () => {
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-content-save" />
|
||||
</template>
|
||||
保存
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
@@ -3,6 +3,10 @@ import api from '@/api'
|
||||
import { DownloaderConf } from '@/api/types'
|
||||
import DownloadingListView from '@/views/reorganize/DownloadingListView.vue'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
const route = useRoute()
|
||||
const activeTab = ref(route.query.tab)
|
||||
@@ -54,7 +58,7 @@ onActivated(async () => {
|
||||
<NoDataFound
|
||||
v-else
|
||||
error-code="404"
|
||||
error-title="没有下载器"
|
||||
error-description="请先在设置中正确配置并启用下载器。"
|
||||
:error-title="t('downloading.noDownloader')"
|
||||
:error-description="t('downloading.configureDownloader')"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -2,94 +2,98 @@
|
||||
import api from '@/api'
|
||||
import { RecommendSource } from '@/api/types'
|
||||
import MediaCardSlideView from '@/views/discover/MediaCardSlideView.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 当前选择的分类
|
||||
const currentCategory = ref('全部')
|
||||
const currentCategory = ref(t('recommend.all'))
|
||||
|
||||
const viewList = reactive<{ apipath: string; linkurl: string; title: string; type: string }[]>([
|
||||
{
|
||||
apipath: 'recommend/tmdb_trending',
|
||||
linkurl: '/browse/recommend/tmdb_trending?title=流行趋势',
|
||||
title: '流行趋势',
|
||||
type: '榜单',
|
||||
linkurl: '/browse/recommend/tmdb_trending?title=' + t('recommend.trendingNow'),
|
||||
title: t('recommend.trendingNow'),
|
||||
type: t('recommend.categoryRankings'),
|
||||
},
|
||||
{
|
||||
apipath: 'recommend/douban_showing',
|
||||
linkurl: '/browse/recommend/douban_showing?title=正在热映',
|
||||
title: '正在热映',
|
||||
type: '电影',
|
||||
linkurl: '/browse/recommend/douban_showing?title=' + t('recommend.nowShowing'),
|
||||
title: t('recommend.nowShowing'),
|
||||
type: t('recommend.categoryMovie'),
|
||||
},
|
||||
{
|
||||
apipath: 'recommend/bangumi_calendar',
|
||||
linkurl: '/browse/recommend/bangumi_calendar?title=Bangumi每日放送',
|
||||
title: 'Bangumi每日放送',
|
||||
type: '动漫',
|
||||
linkurl: '/browse/recommend/bangumi_calendar?title=' + t('recommend.bangumiDaily'),
|
||||
title: t('recommend.bangumiDaily'),
|
||||
type: t('recommend.categoryAnime'),
|
||||
},
|
||||
{
|
||||
apipath: 'recommend/tmdb_movies',
|
||||
linkurl: '/browse/recommend/tmdb_movies?title=TMDB热门电影',
|
||||
title: 'TMDB热门电影',
|
||||
type: '电影',
|
||||
linkurl: '/browse/recommend/tmdb_movies?title=' + t('recommend.tmdbHotMovies'),
|
||||
title: t('recommend.tmdbHotMovies'),
|
||||
type: t('recommend.categoryMovie'),
|
||||
},
|
||||
{
|
||||
apipath: 'recommend/tmdb_tvs?with_original_language=zh|en|ja|ko',
|
||||
linkurl: '/browse/recommend/tmdb_tvs??with_original_language=zh|en|ja|ko&title=TMDB热门电视剧',
|
||||
title: 'TMDB热门电视剧',
|
||||
type: '电视剧',
|
||||
linkurl: '/browse/recommend/tmdb_tvs??with_original_language=zh|en|ja|ko&title=' + t('recommend.tmdbHotTVShows'),
|
||||
title: t('recommend.tmdbHotTVShows'),
|
||||
type: t('recommend.categoryTV'),
|
||||
},
|
||||
{
|
||||
apipath: 'recommend/douban_movie_hot',
|
||||
linkurl: '/browse/recommend/douban_movie_hot?title=豆瓣热门电影',
|
||||
title: '豆瓣热门电影',
|
||||
type: '电影',
|
||||
linkurl: '/browse/recommend/douban_movie_hot?title=' + t('recommend.doubanHotMovies'),
|
||||
title: t('recommend.doubanHotMovies'),
|
||||
type: t('recommend.categoryMovie'),
|
||||
},
|
||||
{
|
||||
apipath: 'recommend/douban_tv_hot',
|
||||
linkurl: '/browse/recommend/douban_tv_hot?title=豆瓣热门电视剧',
|
||||
title: '豆瓣热门电视剧',
|
||||
type: '电视剧',
|
||||
linkurl: '/browse/recommend/douban_tv_hot?title=' + t('recommend.doubanHotTVShows'),
|
||||
title: t('recommend.doubanHotTVShows'),
|
||||
type: t('recommend.categoryTV'),
|
||||
},
|
||||
{
|
||||
apipath: 'recommend/douban_tv_animation',
|
||||
linkurl: '/browse/recommend/douban_tv_animation?title=豆瓣热门动漫',
|
||||
title: '豆瓣热门动漫',
|
||||
type: '动漫',
|
||||
linkurl: '/browse/recommend/douban_tv_animation?title=' + t('recommend.doubanHotAnime'),
|
||||
title: t('recommend.doubanHotAnime'),
|
||||
type: t('recommend.categoryAnime'),
|
||||
},
|
||||
{
|
||||
apipath: 'recommend/douban_movies',
|
||||
linkurl: '/browse/recommend/douban_movies?title=豆瓣最新电影',
|
||||
title: '豆瓣最新电影',
|
||||
type: '电影',
|
||||
linkurl: '/browse/recommend/douban_movies?title=' + t('recommend.doubanNewMovies'),
|
||||
title: t('recommend.doubanNewMovies'),
|
||||
type: t('recommend.categoryMovie'),
|
||||
},
|
||||
{
|
||||
apipath: 'recommend/douban_tvs',
|
||||
linkurl: '/browse/recommend/douban_tvs?title=豆瓣最新电视剧',
|
||||
title: '豆瓣最新电视剧',
|
||||
type: '电视剧',
|
||||
linkurl: '/browse/recommend/douban_tvs?title=' + t('recommend.doubanNewTVShows'),
|
||||
title: t('recommend.doubanNewTVShows'),
|
||||
type: t('recommend.categoryTV'),
|
||||
},
|
||||
{
|
||||
apipath: 'recommend/douban_movie_top250',
|
||||
linkurl: '/browse/recommend/douban_movie_top250?title=电影TOP250',
|
||||
title: '豆瓣电影TOP250',
|
||||
type: '榜单',
|
||||
linkurl: '/browse/recommend/douban_movie_top250?title=' + t('recommend.doubanTop250'),
|
||||
title: t('recommend.doubanTop250'),
|
||||
type: t('recommend.categoryRankings'),
|
||||
},
|
||||
{
|
||||
apipath: 'recommend/douban_tv_weekly_chinese',
|
||||
linkurl: '/browse/recommend/douban_tv_weekly_chinese?title=豆瓣国产剧集榜',
|
||||
title: '豆瓣国产剧集榜',
|
||||
type: '榜单',
|
||||
linkurl: '/browse/recommend/douban_tv_weekly_chinese?title=' + t('recommend.doubanChineseTVRankings'),
|
||||
title: t('recommend.doubanChineseTVRankings'),
|
||||
type: t('recommend.categoryRankings'),
|
||||
},
|
||||
{
|
||||
apipath: 'recommend/douban_tv_weekly_global',
|
||||
linkurl: '/browse/recommend/douban_tv_weekly_global?title=豆瓣全球剧集榜',
|
||||
title: '豆瓣全球剧集榜',
|
||||
type: '榜单',
|
||||
linkurl: '/browse/recommend/douban_tv_weekly_global?title=' + t('recommend.doubanGlobalTVRankings'),
|
||||
title: t('recommend.doubanGlobalTVRankings'),
|
||||
type: t('recommend.categoryRankings'),
|
||||
},
|
||||
])
|
||||
|
||||
// 计算当前分类下显示的视图
|
||||
const filteredViews = computed(() => {
|
||||
if (currentCategory.value === '全部') {
|
||||
if (currentCategory.value === t('recommend.all')) {
|
||||
return viewList.filter(item => enableConfig.value[item.title])
|
||||
}
|
||||
return viewList.filter(item => enableConfig.value[item.title] && item.type === currentCategory.value)
|
||||
@@ -158,29 +162,29 @@ async function saveConfig() {
|
||||
// 标签图标映射
|
||||
const categoryItems: Record<string, string>[] = [
|
||||
{
|
||||
title: '全部',
|
||||
title: t('recommend.all'),
|
||||
icon: 'mdi-filmstrip-box-multiple',
|
||||
tab: '全部',
|
||||
tab: t('recommend.all'),
|
||||
},
|
||||
{
|
||||
title: '电影',
|
||||
title: t('recommend.categoryMovie'),
|
||||
icon: 'mdi-movie',
|
||||
tab: '电影',
|
||||
tab: t('recommend.categoryMovie'),
|
||||
},
|
||||
{
|
||||
title: '电视剧',
|
||||
title: t('recommend.categoryTV'),
|
||||
icon: 'mdi-television-classic',
|
||||
tab: '电视剧',
|
||||
tab: t('recommend.categoryTV'),
|
||||
},
|
||||
{
|
||||
title: '动漫',
|
||||
title: t('recommend.categoryAnime'),
|
||||
icon: 'mdi-animation',
|
||||
tab: '动漫',
|
||||
tab: t('recommend.categoryAnime'),
|
||||
},
|
||||
{
|
||||
title: '榜单',
|
||||
title: t('recommend.categoryRankings'),
|
||||
icon: 'mdi-trophy',
|
||||
tab: '榜单',
|
||||
tab: t('recommend.categoryRankings'),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -221,8 +225,10 @@ onActivated(async () => {
|
||||
|
||||
<div v-if="filteredViews.length === 0" class="empty-category">
|
||||
<VIcon icon="mdi-alert-circle-outline" size="large" class="empty-icon" />
|
||||
<p class="empty-text">当前分类下没有可显示的内容</p>
|
||||
<VBtn color="primary" variant="tonal" size="small" @click="dialog = true"> 设置显示内容 </VBtn>
|
||||
<p class="empty-text">{{ t('recommend.noCategoryContent') }}</p>
|
||||
<VBtn color="primary" variant="tonal" size="small" @click="dialog = true">
|
||||
{{ t('recommend.configureContent') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -232,13 +238,13 @@ onActivated(async () => {
|
||||
<VCardItem class="settings-card-header">
|
||||
<VCardTitle>
|
||||
<VIcon icon="mdi-tune" size="small" class="me-2" />
|
||||
自定义内容
|
||||
{{ t('recommend.customizeContent') }}
|
||||
</VCardTitle>
|
||||
<VDialogCloseBtn @click="dialog = false" />
|
||||
</VCardItem>
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<p class="settings-hint">选择您想在页面显示的内容</p>
|
||||
<p class="settings-hint">{{ t('recommend.selectContentToDisplay') }}</p>
|
||||
<div class="settings-grid">
|
||||
<div
|
||||
v-for="(item, index) in viewList"
|
||||
@@ -266,17 +272,17 @@ onActivated(async () => {
|
||||
<VDivider />
|
||||
<VCardActions class="pt-5">
|
||||
<VBtn variant="text" @click="Object.keys(enableConfig).forEach(key => (enableConfig[key] = true))">
|
||||
全选
|
||||
{{ t('recommend.selectAll') }}
|
||||
</VBtn>
|
||||
<VBtn variant="text" @click="Object.keys(enableConfig).forEach(key => (enableConfig[key] = false))">
|
||||
全不选
|
||||
{{ t('recommend.selectNone') }}
|
||||
</VBtn>
|
||||
<VSpacer />
|
||||
<VBtn @click="saveConfig" variant="elevated" color="primary" class="px-5">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-content-save" />
|
||||
</template>
|
||||
保存
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
|
||||
@@ -4,6 +4,10 @@ import api from '@/api'
|
||||
import type { Context } from '@/api/types'
|
||||
import TorrentCardListView from '@/views/torrent/TorrentCardListView.vue'
|
||||
import TorrentRowListView from '@/views/torrent/TorrentRowListView.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 路由参数
|
||||
const route = useRoute()
|
||||
@@ -51,14 +55,14 @@ const progressValue = ref(0)
|
||||
const progressEventSource = ref<EventSource>()
|
||||
|
||||
// 错误标题
|
||||
const errorTitle = ref('没有数据')
|
||||
const errorTitle = ref(t('resource.noData'))
|
||||
|
||||
// 错误描述
|
||||
const errorDescription = ref('未搜索到任何资源')
|
||||
const errorDescription = ref(t('resource.noResourceFound'))
|
||||
|
||||
// 使用SSE监听加载进度
|
||||
function startLoadingProgress() {
|
||||
progressText.value = '正在搜索,请稍候...'
|
||||
progressText.value = t('resource.searching')
|
||||
progressValue.value = 10 // 初始进度设为10%,确保进度条显示
|
||||
progressEventSource.value = new EventSource(`${import.meta.env.VITE_API_BASE_URL}system/progress/search`)
|
||||
progressEventSource.value.onmessage = event => {
|
||||
@@ -199,14 +203,20 @@ onUnmounted(() => {
|
||||
<!-- 精简标题栏 -->
|
||||
<VCard v-if="isRefreshed" class="search-header d-flex align-center mb-3">
|
||||
<div class="search-info-container d-flex align-center flex-wrap">
|
||||
<div class="search-title text-primary">资源搜索结果</div>
|
||||
<div class="search-title text-primary">{{ t('resource.searchResults') }}</div>
|
||||
<div class="search-tags d-flex flex-wrap">
|
||||
<VChip v-if="keyword" class="search-tag" color="primary" size="small" variant="flat">
|
||||
关键词: {{ keyword }}
|
||||
{{ t('resource.keyword') }}: {{ keyword }}
|
||||
</VChip>
|
||||
<VChip v-if="title" class="search-tag" color="primary" size="small" variant="flat">
|
||||
{{ t('resource.title') }}: {{ title }}
|
||||
</VChip>
|
||||
<VChip v-if="year" class="search-tag" color="primary" size="small" variant="flat">
|
||||
{{ t('resource.year') }}: {{ year }}
|
||||
</VChip>
|
||||
<VChip v-if="season" class="search-tag" color="primary" size="small" variant="flat">
|
||||
{{ t('resource.season') }}: {{ season }}
|
||||
</VChip>
|
||||
<VChip v-if="title" class="search-tag" color="primary" size="small" variant="flat"> 标题: {{ title }} </VChip>
|
||||
<VChip v-if="year" class="search-tag" color="primary" size="small" variant="flat"> 年份: {{ year }} </VChip>
|
||||
<VChip v-if="season" class="search-tag" color="primary" size="small" variant="flat"> 季: {{ season }} </VChip>
|
||||
</div>
|
||||
</div>
|
||||
<VSpacer />
|
||||
@@ -232,7 +242,7 @@ onUnmounted(() => {
|
||||
<div class="pulse-circle"></div>
|
||||
<div class="pulse-circle"></div>
|
||||
</div>
|
||||
<div class="view-changing-text">切换视图</div>
|
||||
<div class="view-changing-text">{{ t('resource.switchingView') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</VFadeTransition>
|
||||
@@ -257,7 +267,7 @@ onUnmounted(() => {
|
||||
<!-- 无数据显示 -->
|
||||
<div v-else-if="isRefreshed && !isViewChanging" class="d-flex flex-column align-center justify-center py-8">
|
||||
<NoDataFound :errorTitle="errorTitle" :errorDescription="errorDescription" />
|
||||
<VBtn class="mt-4" color="primary" prepend-icon="mdi-magnify" to="/"> 返回首页 </VBtn>
|
||||
<VBtn class="mt-4" color="primary" prepend-icon="mdi-magnify" to="/">{{ t('resource.backToHome') }}</VBtn>
|
||||
</div>
|
||||
|
||||
<!-- 初始加载状态 -->
|
||||
@@ -269,7 +279,7 @@ onUnmounted(() => {
|
||||
<div class="wave-dot"></div>
|
||||
<div class="wave-dot"></div>
|
||||
</div>
|
||||
<div class="initial-loading-text">搜索中</div>
|
||||
<div class="initial-loading-text">{{ t('resource.searching') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 滚动到顶部按钮 -->
|
||||
|
||||
@@ -3,9 +3,13 @@ import SubscribeListView from '@/views/subscribe/SubscribeListView.vue'
|
||||
import SubscribePopularView from '@/views/subscribe/SubscribePopularView.vue'
|
||||
import SubscribeShareView from '@/views/subscribe/SubscribeShareView.vue'
|
||||
import SubscribeEditDialog from '@/components/dialog/SubscribeEditDialog.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { getSubscribeMovieTabs, getSubscribeTvTabs } from '@/router/i18n-menu'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const subType = route.meta.subType?.toString()
|
||||
@@ -69,12 +73,12 @@ const searchShares = () => {
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
<VIcon icon="mdi-filter-multiple-outline" class="mr-2" />
|
||||
筛选订阅
|
||||
{{ t('subscribe.filterSubscriptions') }}
|
||||
</VCardTitle>
|
||||
<VDialogCloseBtn @click="filterSubscribeDialog = false" />
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VTextField v-model="subscribeFilter" label="名称" clearable density="comfortable" />
|
||||
<VTextField v-model="subscribeFilter" :label="t('subscribe.name')" clearable density="comfortable" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VMenu>
|
||||
@@ -99,14 +103,14 @@ const searchShares = () => {
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
<VIcon icon="mdi-movie-search-outline" class="mr-2" />
|
||||
搜索订阅分享
|
||||
{{ t('subscribe.searchShares') }}
|
||||
</VCardTitle>
|
||||
<VDialogCloseBtn @click="searchShareDialog = false" />
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VTextField v-model="shareKeyword" label="关键词" clearable density="comfortable">
|
||||
<VTextField v-model="shareKeyword" :label="t('subscribe.keyword')" clearable density="comfortable">
|
||||
<template #append>
|
||||
<VBtn prepend-icon="mdi-magnify" color="primary" @click="searchShares">搜索</VBtn>
|
||||
<VBtn prepend-icon="mdi-magnify" color="primary" @click="searchShares">{{ t('common.search') }}</VBtn>
|
||||
</template>
|
||||
</VTextField>
|
||||
</VCardText>
|
||||
|
||||
Reference in New Issue
Block a user