mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-11 10:00:08 +08:00
更新国际化支持:调整多个组件中的文本引入,优化语言切换和翻译功能,删除不再使用的类型文件。
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { SUPPORTED_LOCALES, SupportedLocale } from '@/locales/types'
|
||||
import { SUPPORTED_LOCALES, SupportedLocale } from '@/types/i18n'
|
||||
import { setI18nLanguage, getCurrentLocale } from '@/plugins/i18n'
|
||||
|
||||
// 当前语言
|
||||
|
||||
@@ -5,6 +5,10 @@ import { SubscribeShare } from '@/api/types'
|
||||
import router from '@/router'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { VBtn } from 'vuetify/lib/components/index.mjs'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -116,11 +120,11 @@ async function doFork() {
|
||||
const result: { [key: string]: any } = await api.post('subscribe/fork', props.media)
|
||||
// 订阅状态
|
||||
if (result.success) {
|
||||
$toast.success(`${props.media?.share_title} 添加订阅成功!`)
|
||||
$toast.success(t('subscribe.addSuccess', { name: props.media?.share_title }))
|
||||
// 完成
|
||||
emit('fork', result.data.id)
|
||||
} else {
|
||||
$toast.error(`${props.media?.share_title} 添加订阅失败:${result.message}!`)
|
||||
$toast.error(t('subscribe.addFailed', { name: props.media?.share_title, message: result.message }))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -144,11 +148,11 @@ async function doDelete() {
|
||||
})
|
||||
// 订阅状态
|
||||
if (result.success) {
|
||||
$toast.success(`${props.media?.share_title} 取消分享成功!`)
|
||||
$toast.success(t('subscribe.cancelSuccess'))
|
||||
// 完成
|
||||
emit('delete', result.data.id)
|
||||
} else {
|
||||
$toast.error(`${props.media?.share_title} 取消分享失败:${result.message}!`)
|
||||
$toast.error(t('subscribe.cancelFailed', { message: result.message }))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -200,13 +204,13 @@ onMounted(() => {
|
||||
<VList lines="one">
|
||||
<VListItem class="ps-0">
|
||||
<VListItemTitle class="text-center text-md-left">
|
||||
<span class="font-weight-medium">分享人:</span>
|
||||
<span class="font-weight-medium">{{ t('subscribe.sharer') }}:</span>
|
||||
<span class="text-body-1"> {{ media?.share_user }}</span>
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem class="ps-0" v-if="media?.keyword">
|
||||
<VListItemTitle class="text-center text-md-left">
|
||||
<span class="font-weight-medium">搜索词:</span>
|
||||
<span class="font-weight-medium">{{ t('subscribe.keyword') }}:</span>
|
||||
<span class="text-body-1"> {{ media?.keyword }}</span>
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
@@ -217,7 +221,7 @@ onMounted(() => {
|
||||
'line-clamp-4 overflow-hidden text-ellipsis': !isExpanded,
|
||||
}"
|
||||
>
|
||||
<span class="font-weight-medium">识别词:</span>
|
||||
<span class="font-weight-medium">{{ t('subscribe.recognitionWords') }}:</span>
|
||||
<span class="text-body-1"> {{ media?.custom_words }}</span>
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
@@ -232,7 +236,7 @@ onMounted(() => {
|
||||
:loading="processing"
|
||||
class="mb-2 me-2"
|
||||
>
|
||||
订阅
|
||||
{{ t('subscribe.normalSub') }}
|
||||
</VBtn>
|
||||
<VBtn
|
||||
v-if="isFollowed && props.media?.share_uid"
|
||||
@@ -241,7 +245,7 @@ onMounted(() => {
|
||||
prepend-icon="mdi-account-remove"
|
||||
class="mb-2 me-2"
|
||||
>
|
||||
取消关注
|
||||
{{ t('subscribe.unfollow') }}
|
||||
</VBtn>
|
||||
<VBtn
|
||||
v-else-if="props.media?.share_uid"
|
||||
@@ -250,7 +254,7 @@ onMounted(() => {
|
||||
prepend-icon="mdi-account-plus"
|
||||
class="mb-2 me-2"
|
||||
>
|
||||
关注
|
||||
{{ t('subscribe.follow') }}
|
||||
</VBtn>
|
||||
<VBtn
|
||||
v-if="
|
||||
@@ -264,11 +268,13 @@ onMounted(() => {
|
||||
:loading="deleting"
|
||||
class="mb-2 me-2"
|
||||
>
|
||||
取消分享
|
||||
{{ t('subscribe.cancelShare') }}
|
||||
</VBtn>
|
||||
</div>
|
||||
<div class="text-xs mt-2" v-if="props.media?.count">
|
||||
<VIcon icon="mdi-fire" />共 {{ props.media?.count?.toLocaleString() }} 次复用
|
||||
<VIcon icon="mdi-fire" />{{
|
||||
t('subscribe.usageCount', { count: props.media?.count?.toLocaleString() })
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</VCardItem>
|
||||
|
||||
@@ -232,6 +232,18 @@ export default {
|
||||
name: 'Name',
|
||||
searchShares: 'Search Subscription Shares',
|
||||
keyword: 'Keyword',
|
||||
noShareData:
|
||||
'No shared subscription data available, data sharing is not enabled or the server cannot be connected.',
|
||||
noPopularData:
|
||||
'No popular subscription data available, data sharing is not enabled or the server cannot be connected.',
|
||||
noFilterData: 'No matching content found. Please change the filter criteria.',
|
||||
noSubscribeData: 'Please search to add movie or TV show subscriptions.',
|
||||
sharer: 'Shared by',
|
||||
follow: 'Follow',
|
||||
unfollow: 'Unfollow',
|
||||
recognitionWords: 'Recognition Words',
|
||||
cancelShare: 'Cancel Share',
|
||||
usageCount: 'Used {count} times',
|
||||
},
|
||||
recommend: {
|
||||
all: 'All',
|
||||
@@ -307,4 +319,52 @@ export default {
|
||||
loadMore: 'Load More',
|
||||
noMatchingResults: 'No matching results',
|
||||
},
|
||||
calendar: {
|
||||
episode: 'Episode {number}',
|
||||
},
|
||||
storage: {
|
||||
usedPercent: 'Used {percent}%',
|
||||
},
|
||||
site: {
|
||||
noSites: 'No Sites',
|
||||
sitesWillBeShownHere: 'Added and supported sites will be displayed here.',
|
||||
},
|
||||
message: {
|
||||
loadMore: 'Load More',
|
||||
noMoreData: 'No More Data',
|
||||
},
|
||||
logging: {
|
||||
level: 'Level',
|
||||
time: 'Time',
|
||||
program: 'Program',
|
||||
content: 'Content',
|
||||
refreshing: 'Refreshing',
|
||||
},
|
||||
moduleTest: {
|
||||
normal: 'Normal',
|
||||
disabled: 'Disabled',
|
||||
error: 'Error',
|
||||
},
|
||||
nameTest: {
|
||||
recognize: 'Recognize',
|
||||
recognizing: 'Recognizing...',
|
||||
recognizeAgain: 'Recognize Again',
|
||||
title: 'Title',
|
||||
subtitle: 'Subtitle',
|
||||
},
|
||||
netTest: {
|
||||
notTested: 'Not Tested',
|
||||
testing: 'Testing...',
|
||||
normal: 'Normal',
|
||||
},
|
||||
ruleTest: {
|
||||
test: 'Test',
|
||||
testing: 'Testing...',
|
||||
testAgain: 'Test Again',
|
||||
title: 'Title',
|
||||
subtitle: 'Subtitle',
|
||||
ruleGroup: 'Rule Group',
|
||||
priority: 'Priority: {value}',
|
||||
noPriorityRule: 'No priority rule matched!',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -232,6 +232,16 @@ export default {
|
||||
name: '名称',
|
||||
searchShares: '搜索订阅分享',
|
||||
keyword: '关键词',
|
||||
noShareData: '未获取到分享订阅数据,未开启数据分享或服务器无法连接。',
|
||||
noPopularData: '未获取到热门订阅数据,未开启数据分享或服务器无法连接。',
|
||||
noFilterData: '没有筛选到相关内容,请更换筛选条件。',
|
||||
noSubscribeData: '请通过搜索添加电影、电视剧订阅。',
|
||||
sharer: '分享人',
|
||||
follow: '关注',
|
||||
unfollow: '取消关注',
|
||||
recognitionWords: '识别词',
|
||||
cancelShare: '取消分享',
|
||||
usageCount: '共 {count} 次复用',
|
||||
},
|
||||
recommend: {
|
||||
all: '全部',
|
||||
@@ -307,4 +317,52 @@ export default {
|
||||
loadMore: '加载更多',
|
||||
noMatchingResults: '没有匹配的结果',
|
||||
},
|
||||
calendar: {
|
||||
episode: '第{number}集',
|
||||
},
|
||||
storage: {
|
||||
usedPercent: '已使用 {percent}%',
|
||||
},
|
||||
site: {
|
||||
noSites: '没有站点',
|
||||
sitesWillBeShownHere: '已添加并支持的站点将会在这里显示。',
|
||||
},
|
||||
message: {
|
||||
loadMore: '加载更多',
|
||||
noMoreData: '没有更多数据',
|
||||
},
|
||||
logging: {
|
||||
level: '级别',
|
||||
time: '时间',
|
||||
program: '程序',
|
||||
content: '内容',
|
||||
refreshing: '正在刷新',
|
||||
},
|
||||
moduleTest: {
|
||||
normal: '正常',
|
||||
disabled: '未启用',
|
||||
error: '错误',
|
||||
},
|
||||
nameTest: {
|
||||
recognize: '识别',
|
||||
recognizing: '识别中...',
|
||||
recognizeAgain: '重新识别',
|
||||
title: '标题',
|
||||
subtitle: '副标题',
|
||||
},
|
||||
netTest: {
|
||||
notTested: '未测试',
|
||||
testing: '测试中...',
|
||||
normal: '正常',
|
||||
},
|
||||
ruleTest: {
|
||||
test: '测试',
|
||||
testing: '正在测试...',
|
||||
testAgain: '重新测试',
|
||||
title: '标题',
|
||||
subtitle: '副标题',
|
||||
ruleGroup: '规则组',
|
||||
priority: '优先级:{value}',
|
||||
noPriorityRule: '未命中任何优先级规则!',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import logo from '@images/logo.png'
|
||||
import { useTheme } from 'vuetify'
|
||||
import { urlBase64ToUint8Array } from '@/@core/utils/navigator'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { SUPPORTED_LOCALES, SupportedLocale } from '@/locales/types'
|
||||
import { SUPPORTED_LOCALES, SupportedLocale } from '@/types/i18n'
|
||||
import { getCurrentLocale, setI18nLanguage } from '@/plugins/i18n'
|
||||
|
||||
// 主题
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import { nextTick } from 'vue'
|
||||
import { SUPPORTED_LOCALES, SupportedLocale } from '@/locales/types'
|
||||
import { SUPPORTED_LOCALES, SupportedLocale } from '@/types/i18n'
|
||||
|
||||
// 导入语言文件
|
||||
import zhCN from '@/locales/zh-CN'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import zhCN from './zh-CN'
|
||||
import zhCN from '@/locales/zh-CN'
|
||||
|
||||
// 导出类型和常量,而不是作为语言消息文件
|
||||
export type MessageSchema = typeof zhCN
|
||||
export type LocaleKey = keyof typeof zhCN
|
||||
|
||||
@@ -5,6 +5,10 @@ import api from '@/api'
|
||||
import trophy from '@images/misc/storage.png'
|
||||
import triangleDark from '@images/misc/triangle-dark.png'
|
||||
import triangleLight from '@images/misc/triangle-light.png'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
const { global } = useTheme()
|
||||
|
||||
@@ -52,13 +56,13 @@ onActivated(() => {
|
||||
<template #append>
|
||||
<VIcon class="cursor-move" v-if="hover.isHovering">mdi-drag</VIcon>
|
||||
</template>
|
||||
<VCardTitle>存储空间</VCardTitle>
|
||||
<VCardTitle>{{ t('dashboard.storage') }}</VCardTitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<h5 class="text-2xl font-weight-medium text-primary">
|
||||
{{ formatFileSize(storage) }}
|
||||
</h5>
|
||||
<p class="mt-2">已使用 {{ usedPercent }}% 🚀</p>
|
||||
<p class="mt-2">{{ t('storage.usedPercent', { percent: usedPercent }) }} 🚀</p>
|
||||
<p class="mt-1">
|
||||
<VProgressLinear :model-value="usedPercent" color="primary" />
|
||||
</p>
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
import api from '@/api'
|
||||
import type { MediaServerConf, MediaServerLibrary } from '@/api/types'
|
||||
import LibraryCard from '@/components/cards/LibraryCard.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 媒体库列表
|
||||
const libraryList = ref<MediaServerLibrary[]>([])
|
||||
@@ -63,7 +67,7 @@ onActivated(() => {
|
||||
<template #append>
|
||||
<VIcon class="cursor-move" v-if="hover.isHovering">mdi-drag</VIcon>
|
||||
</template>
|
||||
<VCardTitle>我的媒体库</VCardTitle>
|
||||
<VCardTitle>{{ t('dashboard.library') }}</VCardTitle>
|
||||
</VCardItem>
|
||||
<div class="grid gap-4 grid-backdrop-card mx-3" tabindex="0">
|
||||
<LibraryCard v-for="item in libraryList" :key="item.id" :media="item" height="10rem" />
|
||||
|
||||
@@ -7,6 +7,10 @@ import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import SiteAddEditDialog from '@/components/dialog/SiteAddEditDialog.vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useDynamicButton } from '@/composables/useDynamicButton'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// APP
|
||||
const display = useDisplay()
|
||||
@@ -98,7 +102,7 @@ useDynamicButton({
|
||||
<template>
|
||||
<div class="card-list-container">
|
||||
<!-- 页面标题 -->
|
||||
<VPageContentTitle title="站点管理" />
|
||||
<VPageContentTitle :title="t('navItems.siteManager')" />
|
||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||
<draggable
|
||||
v-if="siteList.length > 0"
|
||||
@@ -117,8 +121,8 @@ useDynamicButton({
|
||||
<NoDataFound
|
||||
v-if="siteList.length === 0 && isRefreshed"
|
||||
error-code="404"
|
||||
error-title="没有站点"
|
||||
error-description="已添加并支持的站点将会在这里显示。"
|
||||
:error-title="t('site.noSites')"
|
||||
:error-description="t('site.sitesWillBeShownHere')"
|
||||
/>
|
||||
<!-- 新增站点按钮 -->
|
||||
<VFab
|
||||
|
||||
@@ -9,6 +9,11 @@ import type { MediaInfo, Subscribe, TmdbEpisode } from '@/api/types'
|
||||
import api from '@/api'
|
||||
import { formatEp, parseDate } from '@/@core/utils/formatters'
|
||||
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { getCurrentLocale } from '@/plugins/i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 进度框
|
||||
const progressDialog = ref(false)
|
||||
@@ -19,10 +24,13 @@ const loading = ref(false)
|
||||
// 已加载过
|
||||
const isLoaded = ref(false)
|
||||
|
||||
// 获取当前语言
|
||||
const currentLocale = getCurrentLocale().split('-')[0]
|
||||
|
||||
// 日历属性
|
||||
const calendarOptions: Ref<CalendarOptions> = ref({
|
||||
height: 'auto',
|
||||
locale: 'zh-cn',
|
||||
locale: currentLocale,
|
||||
plugins: [
|
||||
dayGridPlugin,
|
||||
timeGridPlugin,
|
||||
@@ -46,7 +54,7 @@ const calendarOptions: Ref<CalendarOptions> = ref({
|
||||
|
||||
async function eventsHander(subscribe: Subscribe) {
|
||||
// 如果是电影直接返回
|
||||
if (subscribe.type === '电影') {
|
||||
if (subscribe.type === t('media.movie')) {
|
||||
// 调用API查询TMDB详情
|
||||
const movie: MediaInfo = await api.get(`media/tmdb:${subscribe.tmdbid}`, {
|
||||
params: { type_name: subscribe.type },
|
||||
@@ -160,14 +168,14 @@ onActivated(() => {
|
||||
{{ arg.event.title }}
|
||||
</VCardSubtitle>
|
||||
<VCardText v-if="arg.event.extendedProps.subtitle" class="pa-0 px-2 break-words">
|
||||
第{{ arg.event.extendedProps.subtitle }}集
|
||||
{{ t('calendar.episode', { number: arg.event.extendedProps.subtitle }) }}
|
||||
</VCardText>
|
||||
</div>
|
||||
</div>
|
||||
</VCard>
|
||||
</div>
|
||||
<div class="md:hidden">
|
||||
<VTooltip :text="`${arg.event.title} 第 ${arg.event.extendedProps.subtitle} 集`">
|
||||
<VTooltip :text="`${arg.event.title} ${t('calendar.episode', { number: arg.event.extendedProps.subtitle })}`">
|
||||
<template #activator="{ props }">
|
||||
<VImg
|
||||
height="60"
|
||||
@@ -198,7 +206,7 @@ onActivated(() => {
|
||||
</div>
|
||||
</template>
|
||||
</FullCalendar>
|
||||
<ProgressDialog v-if="progressDialog" v-model="progressDialog" text="正在加载 ..." />
|
||||
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="t('common.loading') + ' ...'" />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -8,6 +8,10 @@ import SubscribeHistoryDialog from '@/components/dialog/SubscribeHistoryDialog.v
|
||||
import { useUserStore } from '@/stores'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useDynamicButton } from '@/composables/useDynamicButton'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// APP
|
||||
const display = useDisplay()
|
||||
@@ -31,8 +35,8 @@ const props = defineProps({
|
||||
let isRefreshed = ref(false)
|
||||
|
||||
// 顺序存储键值
|
||||
const localOrderKey = props.type === '电影' ? 'MP_SUBSCRIBE_MOVIE_ORDER' : 'MP_SUBSCRIBE_TV_ORDER'
|
||||
const orderRequestKey = props.type === '电影' ? 'SubscribeMovieOrder' : 'SubscribeTvOrder'
|
||||
const localOrderKey = props.type === t('media.movie') ? 'MP_SUBSCRIBE_MOVIE_ORDER' : 'MP_SUBSCRIBE_TV_ORDER'
|
||||
const orderRequestKey = props.type === t('media.movie') ? 'SubscribeMovieOrder' : 'SubscribeTvOrder'
|
||||
|
||||
// 刷新状态
|
||||
const loading = ref(false)
|
||||
@@ -160,7 +164,7 @@ useDynamicButton({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VPageContentTitle v-if="keyword" :title="`筛选:${keyword}`" />
|
||||
<VPageContentTitle v-if="keyword" :title="`${t('subscribe.filterSubscriptions')}:${keyword}`" />
|
||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||
<draggable
|
||||
v-if="displayList.length > 0"
|
||||
@@ -178,8 +182,8 @@ useDynamicButton({
|
||||
<NoDataFound
|
||||
v-if="displayList.length === 0 && isRefreshed"
|
||||
error-code="404"
|
||||
error-title="没有数据"
|
||||
:error-description="keyword ? '没有筛选到相关内容,请更换筛选条件。' : '请通过搜索添加电影、电视剧订阅。'"
|
||||
:error-title="t('common.noData')"
|
||||
:error-description="keyword ? t('subscribe.noFilterData') : t('subscribe.noSubscribeData')"
|
||||
/>
|
||||
<!-- 底部操作按钮 -->
|
||||
<div v-if="isRefreshed">
|
||||
|
||||
@@ -3,6 +3,10 @@ import api from '@/api'
|
||||
import type { MediaInfo } from '@/api/types'
|
||||
import MediaCard from '@/components/cards/MediaCard.vue'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -122,8 +126,8 @@ async function fetchData({ done }: { done: any }) {
|
||||
<NoDataFound
|
||||
v-if="dataList.length === 0 && isRefreshed"
|
||||
error-code="404"
|
||||
error-title="没有数据"
|
||||
error-description="未获取到热门订阅数据,未开启数据分享或服务器无法连接。"
|
||||
:error-title="t('common.noData')"
|
||||
:error-description="t('subscribe.noPopularData')"
|
||||
/>
|
||||
</VInfiniteScroll>
|
||||
</template>
|
||||
|
||||
@@ -3,6 +3,10 @@ import api from '@/api'
|
||||
import type { SubscribeShare } from '@/api/types'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import SubscribeShareCard from '@/components/cards/SubscribeShareCard.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 定义输入参数
|
||||
const props = defineProps({
|
||||
@@ -115,7 +119,7 @@ function removeData(id: number) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VPageContentTitle v-if="keyword" :title="`搜索:${keyword}`" />
|
||||
<VPageContentTitle v-if="keyword" :title="`${t('common.search')}:${keyword}`" />
|
||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||
<VInfiniteScroll mode="intersect" side="end" :items="dataList" class="overflow-visible" @load="fetchData">
|
||||
<template #loading />
|
||||
@@ -128,10 +132,8 @@ function removeData(id: number) {
|
||||
<NoDataFound
|
||||
v-if="dataList.length === 0 && isRefreshed"
|
||||
error-code="404"
|
||||
error-title="没有数据"
|
||||
:error-description="
|
||||
keyword ? '没有搜索到相关内容,请更换搜索关键词。' : '未获取到分享订阅数据,未开启数据分享或服务器无法连接。'
|
||||
"
|
||||
:error-title="t('common.noData')"
|
||||
:error-description="keyword ? t('common.noContent') : t('subscribe.noShareData')"
|
||||
/>
|
||||
</VInfiniteScroll>
|
||||
</template>
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 已解析的日志列表
|
||||
const parsedLogs = ref<{ level: string; time: string; program: string; content: string }[]>([])
|
||||
|
||||
// 表头
|
||||
const headers = [
|
||||
{ title: '级别', value: 'level' },
|
||||
{ title: '时间', value: 'time' },
|
||||
{ title: '程序', value: 'program' },
|
||||
{ title: '内容', value: 'content' },
|
||||
{ title: t('logging.level'), value: 'level' },
|
||||
{ title: t('logging.time'), value: 'time' },
|
||||
{ title: t('logging.program'), value: 'program' },
|
||||
{ title: t('logging.content'), value: 'content' },
|
||||
]
|
||||
|
||||
// SSE消息对象
|
||||
@@ -73,7 +78,7 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LoadingBanner v-if="parsedLogs.length === 0" class="mt-12" text="正在刷新 ..." />
|
||||
<LoadingBanner v-if="parsedLogs.length === 0" class="mt-12" :text="t('logging.refreshing') + ' ...'" />
|
||||
<div v-else>
|
||||
<VTable class="table-rounded" hide-default-footer disable-sort>
|
||||
<tbody>
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
import type { Message } from '@/api/types'
|
||||
import MessageCard from '@/components/cards/MessageCard.vue'
|
||||
import api from '@/api'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['scroll'])
|
||||
@@ -103,12 +107,12 @@ onBeforeUnmount(() => {
|
||||
:items="messages"
|
||||
class="overflow-visible"
|
||||
@load="loadMessages"
|
||||
load-more-text="加载更多 ..."
|
||||
:load-more-text="t('message.loadMore') + ' ...'"
|
||||
>
|
||||
<template #loading>
|
||||
<LoadingBanner />
|
||||
</template>
|
||||
<template #empty> 没有更多数据 </template>
|
||||
<template #empty> {{ t('message.noMoreData') }} </template>
|
||||
<div>
|
||||
<div
|
||||
v-for="(msg, index) in messages"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import api from '@/api'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 定义所有的模块ID、名称列表
|
||||
const modules = ref<
|
||||
@@ -41,13 +45,13 @@ async function moduleTest(index: number) {
|
||||
target.loading = false
|
||||
if (result.success) {
|
||||
target.state = 'success'
|
||||
target.name = `${target.name} - 正常`
|
||||
target.name = `${target.name} - ${t('moduleTest.normal')}`
|
||||
} else if (!result.message) {
|
||||
target.state = undefined
|
||||
target.name = `${target.name} - 未启用`
|
||||
target.name = `${target.name} - ${t('moduleTest.disabled')}`
|
||||
} else {
|
||||
target.state = 'error'
|
||||
target.name = `${target.name} - 错误!`
|
||||
target.name = `${target.name} - ${t('moduleTest.error')}!`
|
||||
target.errmsg = result.message
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -4,6 +4,10 @@ import { requiredValidator } from '@/@validators'
|
||||
import api from '@/api'
|
||||
import type { Context } from '@/api/types'
|
||||
import MediaInfoCard from '@/components/cards/MediaInfoCard.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 识别结果
|
||||
const nameTestResult = ref<Context>()
|
||||
@@ -18,19 +22,18 @@ const nameTestForm = reactive({
|
||||
const nameTestLoading = ref(false)
|
||||
|
||||
// 识别按钮文本
|
||||
const nameTestText = ref('识别')
|
||||
const nameTestText = ref(t('nameTest.recognize'))
|
||||
|
||||
// 是否显示结果
|
||||
const showResult = ref(false)
|
||||
|
||||
// 调用API识别
|
||||
async function nameTest() {
|
||||
if (!nameTestForm.title)
|
||||
return
|
||||
if (!nameTestForm.title) return
|
||||
|
||||
try {
|
||||
nameTestLoading.value = true
|
||||
nameTestText.value = '识别中...'
|
||||
nameTestText.value = t('nameTest.recognizing')
|
||||
showResult.value = false
|
||||
nameTestResult.value = await api.get('media/recognize', {
|
||||
params: {
|
||||
@@ -39,10 +42,9 @@ async function nameTest() {
|
||||
},
|
||||
})
|
||||
nameTestLoading.value = false
|
||||
nameTestText.value = '重新识别'
|
||||
nameTestText.value = t('nameTest.recognizeAgain')
|
||||
showResult.value = true
|
||||
}
|
||||
catch (error) {
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
@@ -52,30 +54,15 @@ async function nameTest() {
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow class="pt-2">
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="nameTestForm.title"
|
||||
label="标题"
|
||||
:rules="[requiredValidator]"
|
||||
/>
|
||||
<VTextField v-model="nameTestForm.title" :label="t('nameTest.title')" :rules="[requiredValidator]" />
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="nameTestForm.subtitle"
|
||||
label="副标题"
|
||||
rows="2"
|
||||
auto-grow
|
||||
/>
|
||||
<VTextarea v-model="nameTestForm.subtitle" :label="t('nameTest.subtitle')" rows="2" auto-grow />
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
class="text-center"
|
||||
>
|
||||
<VBtn
|
||||
:disabled="nameTestLoading"
|
||||
@click="nameTest"
|
||||
>
|
||||
<VCol cols="12" class="text-center">
|
||||
<VBtn :disabled="nameTestLoading" @click="nameTest">
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-text-recognition" />
|
||||
</template>
|
||||
|
||||
@@ -8,6 +8,10 @@ import tmdb from '@images/logos/tmdb.png'
|
||||
import wechat from '@images/logos/wechat.png'
|
||||
import fanart from '@images/logos/fanart.webp'
|
||||
import tvdb from '@images/logos/thetvdb.jpeg'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
interface Status {
|
||||
OK: string
|
||||
@@ -46,7 +50,7 @@ const targets = ref<Address[]>([
|
||||
proxy: true,
|
||||
status: 'Normal',
|
||||
time: '',
|
||||
message: '未测试',
|
||||
message: t('netTest.notTested'),
|
||||
btndisable: false,
|
||||
},
|
||||
{
|
||||
@@ -56,7 +60,7 @@ const targets = ref<Address[]>([
|
||||
proxy: true,
|
||||
status: 'Normal',
|
||||
time: '',
|
||||
message: '未测试',
|
||||
message: t('netTest.notTested'),
|
||||
btndisable: false,
|
||||
},
|
||||
{
|
||||
@@ -66,7 +70,7 @@ const targets = ref<Address[]>([
|
||||
proxy: true,
|
||||
status: 'Normal',
|
||||
time: '',
|
||||
message: '未测试',
|
||||
message: t('netTest.notTested'),
|
||||
btndisable: false,
|
||||
},
|
||||
{
|
||||
@@ -76,7 +80,7 @@ const targets = ref<Address[]>([
|
||||
proxy: true,
|
||||
status: 'Normal',
|
||||
time: '',
|
||||
message: '未测试',
|
||||
message: t('netTest.notTested'),
|
||||
btndisable: false,
|
||||
},
|
||||
{
|
||||
@@ -86,7 +90,7 @@ const targets = ref<Address[]>([
|
||||
proxy: true,
|
||||
status: 'Normal',
|
||||
time: '',
|
||||
message: '未测试',
|
||||
message: t('netTest.notTested'),
|
||||
btndisable: false,
|
||||
},
|
||||
{
|
||||
@@ -96,7 +100,7 @@ const targets = ref<Address[]>([
|
||||
proxy: false,
|
||||
status: 'Normal',
|
||||
time: '',
|
||||
message: '未测试',
|
||||
message: t('netTest.notTested'),
|
||||
btndisable: false,
|
||||
},
|
||||
{
|
||||
@@ -106,7 +110,7 @@ const targets = ref<Address[]>([
|
||||
proxy: false,
|
||||
status: 'Normal',
|
||||
time: '',
|
||||
message: '未测试',
|
||||
message: t('netTest.notTested'),
|
||||
btndisable: false,
|
||||
},
|
||||
{
|
||||
@@ -116,7 +120,7 @@ const targets = ref<Address[]>([
|
||||
proxy: false,
|
||||
status: 'Normal',
|
||||
time: '',
|
||||
message: '未测试',
|
||||
message: t('netTest.notTested'),
|
||||
btndisable: false,
|
||||
},
|
||||
{
|
||||
@@ -126,7 +130,7 @@ const targets = ref<Address[]>([
|
||||
proxy: true,
|
||||
status: 'Normal',
|
||||
time: '',
|
||||
message: '未测试',
|
||||
message: t('netTest.notTested'),
|
||||
btndisable: false,
|
||||
},
|
||||
{
|
||||
@@ -136,7 +140,7 @@ const targets = ref<Address[]>([
|
||||
proxy: true,
|
||||
status: 'Normal',
|
||||
time: '',
|
||||
message: '未测试',
|
||||
message: t('netTest.notTested'),
|
||||
btndisable: false,
|
||||
},
|
||||
{
|
||||
@@ -146,7 +150,7 @@ const targets = ref<Address[]>([
|
||||
proxy: true,
|
||||
status: 'Normal',
|
||||
time: '',
|
||||
message: '未测试',
|
||||
message: t('netTest.notTested'),
|
||||
btndisable: false,
|
||||
},
|
||||
])
|
||||
@@ -165,7 +169,7 @@ async function netTest(index: number) {
|
||||
|
||||
target.btndisable = true
|
||||
target.status = 'Doing'
|
||||
target.message = '测试中...'
|
||||
target.message = t('netTest.testing')
|
||||
|
||||
const result: { [key: string]: any } = await api.get('system/nettest', {
|
||||
params: {
|
||||
@@ -176,7 +180,7 @@ async function netTest(index: number) {
|
||||
|
||||
if (result.success) {
|
||||
target.status = 'OK'
|
||||
target.message = '正常'
|
||||
target.message = t('netTest.normal')
|
||||
} else {
|
||||
target.status = 'Fail'
|
||||
target.message = result.message
|
||||
|
||||
@@ -3,6 +3,10 @@ import { reactive, ref } from 'vue'
|
||||
import { requiredValidator } from '@/@validators'
|
||||
import api from '@/api'
|
||||
import { FilterRuleGroup } from '@/api/types'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// 识别结果
|
||||
const ruleTestResult = ref('')
|
||||
@@ -18,7 +22,7 @@ const ruleTestForm = reactive({
|
||||
const ruleTestLoading = ref(false)
|
||||
|
||||
// 识别按钮文本
|
||||
const ruleTestText = ref('测试')
|
||||
const ruleTestText = ref(t('ruleTest.test'))
|
||||
|
||||
// 是否显示结果
|
||||
const showResult = ref(false)
|
||||
@@ -47,7 +51,7 @@ async function ruleTest() {
|
||||
|
||||
try {
|
||||
ruleTestLoading.value = true
|
||||
ruleTestText.value = '正在测试...'
|
||||
ruleTestText.value = t('ruleTest.testing')
|
||||
showResult.value = false
|
||||
const result: { [key: string]: any } = await api.get('system/ruletest', {
|
||||
params: {
|
||||
@@ -56,11 +60,11 @@ async function ruleTest() {
|
||||
rulegroup_name: ruleTestForm.rulegroup,
|
||||
},
|
||||
})
|
||||
if (result.success) ruleTestResult.value = `优先级:${result.data.priority}`
|
||||
else ruleTestResult.value = '未命中任何优先级规则!'
|
||||
if (result.success) ruleTestResult.value = t('ruleTest.priority', { value: result.data.priority })
|
||||
else ruleTestResult.value = t('ruleTest.noPriorityRule')
|
||||
|
||||
ruleTestLoading.value = false
|
||||
ruleTestText.value = '重新测试'
|
||||
ruleTestText.value = t('ruleTest.testAgain')
|
||||
showResult.value = true
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -76,13 +80,13 @@ onMounted(() => {
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow class="pt-2">
|
||||
<VCol cols="12" md="8">
|
||||
<VTextField v-model="ruleTestForm.title" label="标题" :rules="[requiredValidator]" />
|
||||
<VTextField v-model="ruleTestForm.title" :label="t('ruleTest.title')" :rules="[requiredValidator]" />
|
||||
</VCol>
|
||||
<VCol cols="12" md="4">
|
||||
<VSelect v-model="ruleTestForm.rulegroup" label="规则组" :items="filterRuleGroupItems" />
|
||||
<VSelect v-model="ruleTestForm.rulegroup" :label="t('ruleTest.ruleGroup')" :items="filterRuleGroupItems" />
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea v-model="ruleTestForm.subtitle" label="副标题" rows="2" auto-grow />
|
||||
<VTextarea v-model="ruleTestForm.subtitle" :label="t('ruleTest.subtitle')" rows="2" auto-grow />
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
|
||||
@@ -29,7 +29,7 @@ export default defineConfig({
|
||||
vueTemplate: true,
|
||||
}),
|
||||
VueI18n({
|
||||
include: [resolve(__dirname, 'src/locales/**')],
|
||||
include: [resolve(__dirname, 'src/locales/*.ts')],
|
||||
}),
|
||||
VitePWA({
|
||||
injectRegister: 'script',
|
||||
|
||||
Reference in New Issue
Block a user