更新国际化支持:在多个组件中引入 vue-i18n

This commit is contained in:
jxxghp
2025-04-27 21:23:29 +08:00
parent 0396f180ae
commit a641e90031
10 changed files with 911 additions and 217 deletions

View File

@@ -1,6 +1,10 @@
<script lang="ts" setup>
import { formatDateDifference } from '@/@core/utils/formatters'
import api from '@/api'
import { useI18n } from 'vue-i18n'
// 国际化
const { t } = useI18n()
// 系统环境变量
const systemEnv = ref<any>({})
@@ -68,13 +72,13 @@ onMounted(() => {
<div class="px-3">
<div class="section">
<div>
<h3 class="heading">关于 MoviePilot</h3>
<h3 class="heading">{{ t('setting.about.title') }}</h3>
</div>
<div class="section border-t border-gray-800">
<dl>
<div>
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
<dt class="block text-sm font-bold">软件版本</dt>
<dt class="block text-sm font-bold">{{ t('setting.about.softwareVersion') }}</dt>
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
<span class="flex-grow flex flex-row items-center truncate">
<code class="truncate">{{ systemEnv.VERSION }}</code>
@@ -87,7 +91,7 @@ onMounted(() => {
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full whitespace-nowrap bg-green-500 bg-opacity-80 border border-green-500 !text-green-100 ml-2 !cursor-pointer transition hover:bg-green-400"
>
最新
{{ t('setting.about.latest') }}
</span>
</a>
</span>
@@ -96,7 +100,7 @@ onMounted(() => {
</div>
<div v-if="systemEnv.FRONTEND_VERSION">
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
<dt class="block text-sm font-bold">前端版本</dt>
<dt class="block text-sm font-bold">{{ t('setting.about.frontendVersion') }}</dt>
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
<span class="flex-grow flex flex-row items-center truncate">
<code class="truncate">{{ systemEnv.FRONTEND_VERSION }}</code>
@@ -106,7 +110,7 @@ onMounted(() => {
</div>
<div>
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
<dt class="block text-sm font-bold">认证资源版本</dt>
<dt class="block text-sm font-bold">{{ t('setting.about.authVersion') }}</dt>
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
<span class="flex-grow flex flex-row items-center truncate">
<code class="truncate">{{ systemEnv.AUTH_VERSION }}</code>
@@ -116,7 +120,7 @@ onMounted(() => {
</div>
<div>
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
<dt class="block text-sm font-bold">站点资源版本</dt>
<dt class="block text-sm font-bold">{{ t('setting.about.indexerVersion') }}</dt>
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
<span class="flex-grow flex flex-row items-center truncate">
<code class="truncate">{{ systemEnv.INDEXER_VERSION }}</code>
@@ -126,7 +130,7 @@ onMounted(() => {
</div>
<div>
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
<dt class="block text-sm font-bold">配置目录</dt>
<dt class="block text-sm font-bold">{{ t('setting.about.configDir') }}</dt>
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
<span class="flex-grow undefined">
<code>{{ systemEnv.CONFIG_DIR }}</code>
@@ -134,7 +138,7 @@ onMounted(() => {
</dd>
</div>
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
<dt class="block text-sm font-bold">数据目录</dt>
<dt class="block text-sm font-bold">{{ t('setting.about.dataDir') }}</dt>
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
<span class="flex-grow undefined"><code>/moviepilot</code></span>
</dd>
@@ -142,7 +146,7 @@ onMounted(() => {
</div>
<div>
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
<dt class="block text-sm font-bold">时区</dt>
<dt class="block text-sm font-bold">{{ t('setting.about.timezone') }}</dt>
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
<span class="flex-grow undefined">
<code>{{ systemEnv.TZ }}</code>
@@ -155,13 +159,13 @@ onMounted(() => {
</div>
<div class="section">
<div>
<h3 class="heading">支援</h3>
<h3 class="heading">{{ t('setting.about.support') }}</h3>
</div>
<div class="section border-t border-gray-800">
<dl>
<div>
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
<dt class="block text-sm font-bold">文档</dt>
<dt class="block text-sm font-bold">{{ t('setting.about.documentation') }}</dt>
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
<span class="flex-grow undefined">
<a
@@ -178,7 +182,7 @@ onMounted(() => {
</div>
<div>
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
<dt class="block text-sm font-bold">问题反馈</dt>
<dt class="block text-sm font-bold">{{ t('setting.about.feedback') }}</dt>
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
<span class="flex-grow undefined">
<a
@@ -195,7 +199,7 @@ onMounted(() => {
</div>
<div>
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
<dt class="block text-sm font-bold">发布频道</dt>
<dt class="block text-sm font-bold">{{ t('setting.about.channel') }}</dt>
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
<span class="flex-grow undefined">
<a
@@ -215,7 +219,7 @@ onMounted(() => {
</div>
<div class="section">
<div>
<h3 class="heading">软件版本</h3>
<h3 class="heading">{{ t('setting.about.versions') }}</h3>
<div class="section space-y-3">
<div>
<div
@@ -234,20 +238,20 @@ onMounted(() => {
v-if="release.tag_name === latestRelease"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full whitespace-nowrap cursor-default bg-green-500 bg-opacity-80 border border-green-500 !text-green-100"
>
最新软件版本
{{ t('setting.about.latestVersion') }}
</span>
<span
v-if="release.tag_name === systemEnv.VERSION"
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full whitespace-nowrap cursor-default bg-indigo-500 bg-opacity-80 border border-indigo-500 !text-indigo-100"
>
当前版本
{{ t('setting.about.currentVersion') }}
</span>
</div>
<VBtn @click.stop="showReleaseDialog(release.tag_name, release.body)">
<template #prepend>
<VIcon icon="mdi-text-box-outline" />
</template>
查看变更日志
{{ t('setting.about.viewChangelog') }}
</VBtn>
</div>
</div>
@@ -259,7 +263,7 @@ onMounted(() => {
<VCard>
<VCardItem>
<VDialogCloseBtn @click="releaseDialog = false" />
<VCardTitle>{{ releaseDialogTitle }} 变更日志</VCardTitle>
<VCardTitle>{{ releaseDialogTitle }} {{ t('setting.about.changelog') }}</VCardTitle>
</VCardItem>
<VCardText v-html="releaseDialogBody" />
</VCard>

View File

@@ -5,6 +5,10 @@ import draggable from 'vuedraggable'
import type { NotificationConf, NotificationSwitchConf } from '@/api/types'
import NotificationChannelCard from '@/components/cards/NotificationChannelCard.vue'
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
import { useI18n } from 'vue-i18n'
// 国际化
const { t } = useI18n()
// 所有消息渠道
const notifications = ref<NotificationConf[]>([])
@@ -18,35 +22,35 @@ const progressDialog = ref(false)
// 消息类型开关
const notificationSwitchs = ref<NotificationSwitchConf[]>([
{
type: '资源下载',
type: t('setting.notification.resourceDownload'),
action: 'all',
},
{
type: '整理入库',
type: t('setting.notification.mediaImport'),
action: 'all',
},
{
type: '订阅',
type: t('setting.notification.subscription'),
action: 'all',
},
{
type: '站点',
type: t('setting.notification.site'),
action: 'admin',
},
{
type: '媒体服务器',
type: t('setting.notification.mediaServer'),
action: 'admin',
},
{
type: '手动处理',
type: t('setting.notification.manualProcess'),
action: 'admin',
},
{
type: '插件',
type: t('setting.notification.plugin'),
action: 'admin',
},
{
type: '其它',
type: t('setting.notification.other'),
action: 'admin',
},
])
@@ -62,8 +66,8 @@ async function reloadSystem() {
progressDialog.value = true
try {
const result: { [key: string]: any } = await api.get('system/reload')
if (result.success) $toast.success('系统配置已生效')
else $toast.error('重载系统失败!')
if (result.success) $toast.success(t('setting.system.reloadSuccess'))
else $toast.error(t('setting.system.reloadFailed'))
} catch (error) {
console.log(error)
}
@@ -72,9 +76,9 @@ async function reloadSystem() {
// 添加通知渠道
function addNotification(notification: string) {
let name = `通知${notifications.value.length + 1}`
let name = `${t('setting.notification.channel')}${notifications.value.length + 1}`
while (notifications.value.some(item => item.name === name)) {
name = `通知${parseInt(name.split('通知')[1]) + 1}`
name = `${t('setting.notification.channel')}${parseInt(name.split(t('setting.notification.channel'))[1]) + 1}`
}
notifications.value.push({
name: name,
@@ -115,9 +119,9 @@ async function saveNotificationSetting() {
try {
const result: { [key: string]: any } = await api.post('system/setting/Notifications', notifications.value)
if (result.success) {
$toast.success('通知设置保存成功')
$toast.success(t('setting.notification.saveSuccess'))
await reloadSystem()
} else $toast.error('通知设置保存失败!')
} else $toast.error(t('setting.notification.saveFailed'))
} catch (error) {
console.log(error)
}
@@ -128,9 +132,9 @@ async function saveNotificationTime() {
try {
const result: { [key: string]: any } = await api.post('system/setting/NotificationSendTime', notificationTime.value)
if (result.success) {
$toast.success('通知发送时间保存成功')
$toast.success(t('setting.notification.timeSaveSuccess'))
await reloadSystem()
} else $toast.error('通知发送时间保存失败!')
} else $toast.error(t('setting.notification.timeSaveFailed'))
} catch (error) {
console.log(error)
}
@@ -159,8 +163,8 @@ async function saveNotificationSwitchs() {
'system/setting/NotificationSwitchs',
notificationSwitchs.value,
)
if (result.success) $toast.success('消息类型开关保存成功')
else $toast.error('消息类型开关保存失败!')
if (result.success) $toast.success(t('setting.notification.switchSaveSuccess'))
else $toast.error(t('setting.notification.switchSaveFailed'))
} catch (error) {
console.log(error)
}
@@ -179,8 +183,8 @@ onMounted(() => {
<VCol cols="12">
<VCard>
<VCardItem>
<VCardTitle>通知渠道</VCardTitle>
<VCardSubtitle>设置消息发送渠道参数</VCardSubtitle>
<VCardTitle>{{ t('setting.notification.channels') }}</VCardTitle>
<VCardSubtitle>{{ t('setting.notification.channelsDesc') }}</VCardSubtitle>
</VCardItem>
<VCardText>
<draggable
@@ -203,13 +207,13 @@ onMounted(() => {
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn mtype="submit" @click="saveNotificationSetting"> 保存 </VBtn>
<VBtn mtype="submit" @click="saveNotificationSetting"> {{ t('common.save') }} </VBtn>
<VBtn color="success" variant="tonal">
<VIcon icon="mdi-plus" />
<VMenu activator="parent" close-on-content-click>
<VList>
<VListItem @click="addNotification('wechat')">
<VListItemTitle>微信</VListItemTitle>
<VListItemTitle>{{ t('setting.notification.wechat') }}</VListItemTitle>
</VListItem>
<VListItem @click="addNotification('telegram')">
<VListItemTitle>Telegram</VListItemTitle>
@@ -239,14 +243,14 @@ onMounted(() => {
<VCol cols="12">
<VCard>
<VCardItem>
<VCardTitle>通知发送范围</VCardTitle>
<VCardSubtitle>对应消息类型只会发送给设定的用户</VCardSubtitle>
<VCardTitle>{{ t('setting.notification.scope') }}</VCardTitle>
<VCardSubtitle>{{ t('setting.notification.scopeDesc') }}</VCardSubtitle>
</VCardItem>
<VTable class="text-no-wrap">
<thead>
<tr>
<th scope="col">消息类型</th>
<th scope="col">范围</th>
<th scope="col">{{ t('setting.notification.messageType') }}</th>
<th scope="col">{{ t('setting.notification.scopeRange') }}</th>
</tr>
</thead>
<tbody>
@@ -256,10 +260,10 @@ onMounted(() => {
</td>
<td>
<VRadioGroup v-model="item.action" inline>
<VRadio value="user" label="仅操作用户" />
<VRadio value="admin" label="仅管理员" />
<VRadio value="user,admin" label="操作用户和管理员" />
<VRadio value="all" label="所有用户" />
<VRadio value="user" :label="t('setting.notification.operationUserOnly')" />
<VRadio value="admin" :label="t('setting.notification.adminOnly')" />
<VRadio value="user,admin" :label="t('setting.notification.userAndAdmin')" />
<VRadio value="all" :label="t('setting.notification.allUsers')" />
</VRadioGroup>
</td>
</tr>
@@ -269,7 +273,7 @@ onMounted(() => {
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn type="submit" @click="saveNotificationSwitchs"> 保存 </VBtn>
<VBtn type="submit" @click="saveNotificationSwitchs"> {{ t('common.save') }} </VBtn>
</div>
</VForm>
</VCardText>
@@ -280,23 +284,23 @@ onMounted(() => {
<VCol cols="12">
<VCard>
<VCardItem>
<VCardTitle>通知发送时间</VCardTitle>
<VCardSubtitle>设定消息发送的时间范围</VCardSubtitle>
<VCardTitle>{{ t('setting.notification.sendTime') }}</VCardTitle>
<VCardSubtitle>{{ t('setting.notification.sendTimeDesc') }}</VCardSubtitle>
</VCardItem>
<VCardText>
<VRow>
<VCol cols="6">
<VTextField v-model="notificationTime.start" label="开始时间" type="time" />
<VTextField v-model="notificationTime.start" :label="t('setting.notification.startTime')" type="time" />
</VCol>
<VCol cols="6">
<VTextField v-model="notificationTime.end" label="结束时间" type="time" />
<VTextField v-model="notificationTime.end" :label="t('setting.notification.endTime')" type="time" />
</VCol>
</VRow>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn type="submit" @click="saveNotificationTime"> 保存 </VBtn>
<VBtn type="submit" @click="saveNotificationTime"> {{ t('common.save') }} </VBtn>
</div>
</VForm>
</VCardText>
@@ -304,5 +308,10 @@ onMounted(() => {
</VCol>
</VRow>
<!-- 进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" text="正在应用配置..." />
<ProgressDialog
v-if="progressDialog"
v-model="progressDialog"
:text="t('setting.system.reloading')"
:indeterminate="true"
/>
</template>

View File

@@ -2,6 +2,10 @@
import { useToast } from 'vue-toast-notification'
import api from '@/api'
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
import { useI18n } from 'vue-i18n'
// 国际化
const { t } = useI18n()
// 提示框
const $toast = useToast()
@@ -13,7 +17,7 @@ const progressDialog = ref(false)
const isConfirmResetSites = ref(false)
// 站点重置按钮文本
const resetSitesText = ref('重置站点数据')
const resetSitesText = ref(t('site.resetSites'))
// 站点重置按钮可用状态
const resetSitesDisabled = ref(false)
@@ -39,37 +43,37 @@ const siteSetting = ref<any>({
// 同步间隔下拉框
const CookieCloudIntervalItems = [
{ title: '每小时', value: 60 },
{ title: '每6小时', value: 360 },
{ title: '每12小时', value: 720 },
{ title: '每天', value: 1440 },
{ title: '每周', value: 10080 },
{ title: '每月', value: 43200 },
{ title: '永不', value: 0 },
{ title: t('site.syncInterval.hourly'), value: 60 },
{ title: t('site.syncInterval.every6Hours'), value: 360 },
{ title: t('site.syncInterval.every12Hours'), value: 720 },
{ title: t('site.syncInterval.daily'), value: 1440 },
{ title: t('site.syncInterval.weekly'), value: 10080 },
{ title: t('site.syncInterval.monthly'), value: 43200 },
{ title: t('site.syncInterval.never'), value: 0 },
]
// 站点数据刷新间隔
const SiteDataRefreshIntervalItems = [
{ title: '每小时', value: 1 },
{ title: '每6小时', value: 6 },
{ title: '每12小时', value: 12 },
{ title: '每天', value: 24 },
{ title: '每周', value: 168 },
{ title: '永不', value: 0 },
{ title: t('site.syncInterval.hourly'), value: 1 },
{ title: t('site.syncInterval.every6Hours'), value: 6 },
{ title: t('site.syncInterval.every12Hours'), value: 12 },
{ title: t('site.syncInterval.daily'), value: 24 },
{ title: t('site.syncInterval.weekly'), value: 168 },
{ title: t('site.syncInterval.never'), value: 0 },
]
// 重置站点
async function resetSites() {
try {
resetSitesDisabled.value = true
resetSitesText.value = '正在重置...'
resetSitesText.value = t('site.resettingSites')
const result: { [key: string]: any } = await api.get('site/reset')
if (result.success) $toast.success('站点重置成功请等待CookieCloud同步完成')
else $toast.error('站点重置失败!')
if (result.success) $toast.success(t('site.resetSuccess'))
else $toast.error(t('site.resetFailed'))
resetSitesDisabled.value = false
resetSitesText.value = '重置站点数据'
resetSitesText.value = t('site.resetSites')
} catch (error) {
console.log(error)
}
@@ -97,8 +101,8 @@ async function reloadSystem() {
progressDialog.value = true
try {
const result: { [key: string]: any } = await api.get('system/reload')
if (result.success) $toast.success('系统配置已生效')
else $toast.error('重载系统失败!')
if (result.success) $toast.success(t('setting.system.reloadSuccess'))
else $toast.error(t('setting.system.reloadFailed'))
} catch (error) {
console.log(error)
}
@@ -111,14 +115,14 @@ async function saveSiteSetting(value: { [key: string]: any }) {
try {
const result: { [key: string]: any } = await api.post('system/env', value)
if (result.success) {
$toast.success('保存站点设置成功')
$toast.success(t('site.saveSuccess'))
await reloadSystem()
} else {
$toast.error('站点设置保存失败!')
$toast.error(t('site.saveFailed'))
}
} catch (error) {
console.log(error)
$toast.error('保存设置失败!')
$toast.error(t('setting.system.saveFailed', { message: error }))
}
}
@@ -133,8 +137,8 @@ onMounted(() => {
<VCol cols="12">
<VCard>
<VCardItem>
<VCardTitle>站点同步</VCardTitle>
<VCardSubtitle>从CookieCloud快速同步站点数据</VCardSubtitle>
<VCardTitle>{{ t('site.siteSync') }}</VCardTitle>
<VCardSubtitle>{{ t('site.siteSyncDesc') }}</VCardSubtitle>
</VCardItem>
<VCardText>
<VForm>
@@ -142,8 +146,8 @@ onMounted(() => {
<VCol cols="12" md="6">
<VCheckbox
v-model="siteSetting.CookieCloud.COOKIECLOUD_ENABLE_LOCAL"
label="启用本地CookieCloud服务器"
hint="使用内建CookieCloud服务同步站点数据服务地址为http://localhost:3000/cookiecloud"
:label="t('site.enableLocalCookieCloud')"
:hint="t('site.enableLocalCookieCloudHint')"
persistent-hint
/>
</VCol>
@@ -152,18 +156,18 @@ onMounted(() => {
<VCol cols="12" md="6">
<VTextField
v-model="siteSetting.CookieCloud.COOKIECLOUD_HOST"
label="服务地址"
placeholder="https://movie-pilot.org/cookiecloud"
:label="t('site.serviceAddress')"
:placeholder="t('site.serviceAddressPlaceholder')"
:disabled="siteSetting.CookieCloud.COOKIECLOUD_ENABLE_LOCAL"
hint="远端CookieCloud服务地址格式https://movie-pilot.org/cookiecloud"
:hint="t('site.serviceAddressHint')"
persistent-hint
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="siteSetting.CookieCloud.COOKIECLOUD_KEY"
label="用户KEY"
hint="CookieCloud浏览器插件生成的用户KEY"
:label="t('site.userKey')"
:hint="t('site.userKeyHint')"
persistent-hint
/>
</VCol>
@@ -173,34 +177,34 @@ onMounted(() => {
:type="isPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
@click:append-inner="isPasswordVisible = !isPasswordVisible"
label="端对端加密密码"
hint="CookieCloud浏览器插件生成的端对端加密密码"
:label="t('site.e2ePassword')"
:hint="t('site.e2ePasswordHint')"
persistent-hint
/>
</VCol>
<VCol cols="12" md="6">
<VSelect
v-model="siteSetting.CookieCloud.COOKIECLOUD_INTERVAL"
label="自动同步间隔"
:label="t('site.autoSyncInterval')"
:items="CookieCloudIntervalItems"
hint="从CookieCloud服务器自动同步站点Cookie到MoviePilot的时间间隔"
:hint="t('site.autoSyncIntervalHint')"
persistent-hint
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="siteSetting.CookieCloud.COOKIECLOUD_BLACKLIST"
label="同步域名黑名单"
placeholder="多个域名,分割"
hint="CookieCloud同步域名黑名单多个域名,分割"
:label="t('site.syncBlacklist')"
:placeholder="t('site.syncBlacklistPlaceholder')"
:hint="t('site.syncBlacklistHint')"
persistent-hint
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="siteSetting.CookieCloud.USER_AGENT"
label="浏览器User-Agent"
hint="CookieCloud插件所在的浏览器的User-Agent"
:label="t('site.userAgent')"
:hint="t('site.userAgentHint')"
persistent-hint
/>
</VCol>
@@ -210,7 +214,7 @@ onMounted(() => {
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn type="submit" @click="saveSiteSetting(siteSetting.CookieCloud)"> 保存 </VBtn>
<VBtn type="submit" @click="saveSiteSetting(siteSetting.CookieCloud)"> {{ t('common.save') }} </VBtn>
</div>
</VForm>
</VCardText>
@@ -219,16 +223,16 @@ onMounted(() => {
</VRow>
<VRow>
<VCol cols="12">
<VCard title="站点数据刷新">
<VCard :title="t('site.siteDataRefresh')">
<VCardText>
<VForm>
<VRow>
<VCol cols="12" md="6">
<VSelect
v-model="siteSetting.Site.SITEDATA_REFRESH_INTERVAL"
label="站点数据刷新间隔"
:label="t('site.siteDataRefreshInterval')"
:items="SiteDataRefreshIntervalItems"
hint="刷新站点用户上传下载等数据的时间间隔"
:hint="t('site.siteDataRefreshIntervalHint')"
persistent-hint
/>
</VCol>
@@ -237,8 +241,8 @@ onMounted(() => {
<VCol cols="12" md="6">
<VSwitch
v-model="siteSetting.Site.SITE_MESSAGE"
label="阅读站点消息"
hint="刷新数据时读取站点消息并发送通知"
:label="t('site.readSiteMessage')"
:hint="t('site.readSiteMessageHint')"
persistent-hint
/>
</VCol>
@@ -248,7 +252,7 @@ onMounted(() => {
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn type="submit" @click="saveSiteSetting(siteSetting.Site)"> 保存 </VBtn>
<VBtn type="submit" @click="saveSiteSetting(siteSetting.Site)"> {{ t('common.save') }} </VBtn>
</div>
</VForm>
</VCardText>
@@ -257,13 +261,13 @@ onMounted(() => {
</VRow>
<VRow>
<VCol cols="12">
<VCard title="站点重置">
<VCard :title="t('site.siteReset')">
<VCardText>
<div>
<VCheckbox
v-model="isConfirmResetSites"
label="确认删除所有站点数据并重新同步。"
hint="删除所有站点数据并重新从CookieCloud同步操作请先清空涉及站点的相关设置。"
:label="t('site.confirmReset')"
:hint="t('site.confirmResetHint')"
persistent-hint
/>
</div>
@@ -276,5 +280,10 @@ onMounted(() => {
</VCol>
</VRow>
<!-- 进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" text="正在应用配置..." />
<ProgressDialog
v-if="progressDialog"
v-model="progressDialog"
:text="t('setting.system.reloading')"
:indeterminate="true"
/>
</template>

View File

@@ -9,6 +9,10 @@ import DownloaderCard from '@/components/cards/DownloaderCard.vue'
import MediaServerCard from '@/components/cards/MediaServerCard.vue'
import { copyToClipboard } from '@/@core/utils/navigator'
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
import { useI18n } from 'vue-i18n'
// 国际化
const { t } = useI18n()
// 系统设置项
const SystemSettings = ref<any>({
@@ -92,8 +96,8 @@ async function reloadSystem() {
progressDialog.value = true
try {
const result: { [key: string]: any } = await api.get('system/reload')
if (result.success) $toast.success('系统配置已生效')
else $toast.error('重载系统失败!')
if (result.success) $toast.success(t('setting.system.reloadSuccess'))
else $toast.error(t('setting.system.reloadFailed'))
} catch (error) {
console.log(error)
}
@@ -110,8 +114,8 @@ async function saveDownloaderSetting() {
downloaders.value = handleDefaultDownloaders(enabledDownloaders, downloaders.value)
}
const result: { [key: string]: any } = await api.post('system/setting/Downloaders', downloaders.value)
if (result.success) $toast.success('下载器设置保存成功')
else $toast.error('下载器设置保存失败!')
if (result.success) $toast.success(t('setting.system.downloaderSaveSuccess'))
else $toast.error(t('setting.system.downloaderSaveFailed'))
await loadDownloaderSetting()
await reloadSystem()
@@ -126,7 +130,7 @@ function handleDefaultDownloaders(enabledDownloaders: any[], downloaders: any[])
if (enabledDownloaders.length > 0 && !enabledDefaultDownloader) {
downloaders = downloaders.map(item => {
if (item === enabledDownloaders[0]) {
$toast.info(`未设置默认下载器,已将【${item.name}】作为默认下载器`)
$toast.info(t('setting.system.defaultDownloaderNotice', { name: item.name }))
return { ...item, default: true }
}
// 清除其他下载器的默认下载器状态
@@ -150,8 +154,8 @@ async function loadMediaServerSetting() {
async function saveMediaServerSetting() {
try {
const result: { [key: string]: any } = await api.post('system/setting/MediaServers', mediaServers.value)
if (result.success) $toast.success('媒体服务器设置保存成功')
else $toast.error('媒体服务器设置保存失败!')
if (result.success) $toast.success(t('setting.system.mediaServerSaveSuccess'))
else $toast.error(t('setting.system.mediaServerSaveFailed'))
await loadMediaServerSetting()
await reloadSystem()
@@ -184,7 +188,7 @@ async function saveSystemSetting(value: { [key: string]: any }) {
if (result.success) {
return true
} else {
$toast.error(`设置保存失败:${result?.message}`)
$toast.error(t('setting.system.saveFailed', { message: result?.message }))
return false
}
} catch (error) {
@@ -196,7 +200,7 @@ async function saveSystemSetting(value: { [key: string]: any }) {
// 保存基础设置
async function saveBasicSettings() {
if (await saveSystemSetting(SystemSettings.value.Basic)) {
$toast.success('基础设置保存成功')
$toast.success(t('setting.system.basicSaveSuccess'))
await reloadSystem()
}
}
@@ -207,7 +211,7 @@ async function saveAdvancedSettings() {
if (await saveSystemSetting(SystemSettings.value.Advanced)) {
advancedDialog.value = false
$toast.success('高级设置保存成功')
$toast.success(t('setting.system.advancedSaveSuccess'))
await reloadSystem()
}
}
@@ -226,10 +230,10 @@ async function copyValue(value: string) {
try {
let success
success = copyToClipboard(value)
if (await success) $toast.success('已复制到剪贴板!')
else $toast.error(`复制失败:可能是浏览器不支持或被用户阻止!`)
if (await success) $toast.success(t('setting.system.copySuccess'))
else $toast.error(t('setting.system.copyFailed'))
} catch (error) {
$toast.error('复制失败!')
$toast.error(t('setting.system.copyError'))
console.log(error)
}
}
@@ -367,71 +371,97 @@ onDeactivated(() => {
</script>
<template>
<ProgressDialog
v-if="progressDialog"
v-model="progressDialog"
:text="t('setting.system.reloading')"
:indeterminate="true"
/>
<VRow>
<VCol cols="12">
<VCard>
<VCardItem>
<VCardTitle>基础设置</VCardTitle>
<VCardSubtitle>设置服务器的全局功能</VCardSubtitle>
<VCardTitle>{{ t('setting.system.basicSettings') }}</VCardTitle>
<VCardSubtitle>{{ t('setting.system.basicSettingsDesc') }}</VCardSubtitle>
</VCardItem>
<VCardText>
<VForm>
<VForm @submit.prevent="() => {}">
<VRow>
<VCol cols="12" md="6">
<VTextField
v-model="SystemSettings.Basic.APP_DOMAIN"
label="访问域名"
placeholder="格式http(s)://domain:port"
hint="用于发送通知时,添加快捷跳转地址"
:label="t('setting.system.appDomain')"
:hint="t('setting.system.appDomainHint')"
placeholder="http://localhost:3000"
persistent-hint
/>
</VCol>
<VCol cols="12" md="3">
<VCol cols="12" md="6">
<VSelect
v-model="SystemSettings.Basic.WALLPAPER"
label="背景壁纸"
hint="选择登陆页面背景来源"
:label="t('setting.system.wallpaper')"
:hint="t('setting.system.wallpaperHint')"
persistent-hint
:items="wallpaperItems"
:items="[
{ title: 'TMDB', value: 'tmdb' },
{ title: 'Bing', value: 'bing' },
{ title: 'Bing每日图片', value: 'bing-daily' },
{ title: '无壁纸', value: 'none' },
]"
/>
</VCol>
<VCol cols="12" md="3">
<VCol cols="12" md="6">
<VSelect
v-model="SystemSettings.Basic.RECOGNIZE_SOURCE"
:label="t('setting.system.recognizeSource')"
:hint="t('setting.system.recognizeSourceHint')"
persistent-hint
:items="[
{ title: 'TheMovieDb', value: 'themoviedb' },
{ title: '豆瓣', value: 'douban' },
]"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="SystemSettings.Basic.MEDIASERVER_SYNC_INTERVAL"
label="媒体服务器同步间隔"
hint="定时同步媒体服务器数据到本地的时间间隔"
:label="t('setting.system.mediaServerSyncInterval')"
:hint="t('setting.system.mediaServerSyncIntervalHint')"
persistent-hint
suffix="小时"
:suffix="t('setting.system.hours')"
type="number"
min="1"
:rules="[
(v: any) => !!v || '必选项,请勿留空',
(v: any) => !isNaN(v) || '仅支持输入数字,请勿输入其他字符',
(v: any) => v >= 1 || '间隔不能小于1个小时',
(v: any) => !!v || t('setting.system.required'),
(v: any) => !isNaN(v) || t('setting.system.numbersOnly'),
(v: any) => v >= 1 || t('setting.system.minInterval'),
]"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="SystemSettings.Basic.API_TOKEN"
label="API令牌"
hint="设置外部请求MoviePilot API时使用的token值"
placeholder="不能小于16位字符"
:label="t('setting.system.apiToken')"
:hint="t('setting.system.apiTokenHint')"
:placeholder="t('setting.system.apiTokenMinChars')"
persistent-hint
prependInnerIcon="mdi-reload"
:appendInnerIcon="SystemSettings.Basic.API_TOKEN ? 'mdi-content-copy' : ''"
@click:prependInner="createRandomString"
@click:appendInner="copyValue(SystemSettings.Basic.API_TOKEN)"
:rules="[(v: string) => !!v || '必填项请输入API Token', (v: string) => v.length >= 16 || 'API Token不得低于16位']"
:rules="[
(v: string) => !!v || t('setting.system.apiTokenRequired'),
(v: string) => v.length >= 16 || t('setting.system.apiTokenLength'),
]"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="SystemSettings.Basic.GITHUB_TOKEN"
label="Github Token"
placeholder="ghp_**** 或 github_pat_****"
hint="用于提高插件等访问Github API时的限流阈值"
:label="t('setting.system.githubToken')"
:placeholder="t('setting.system.githubTokenFormat')"
:hint="t('setting.system.githubTokenHint')"
persistent-hint
>
</VTextField>
@@ -439,9 +469,9 @@ onDeactivated(() => {
<VCol cols="12" md="6">
<VTextField
v-model="SystemSettings.Basic.OCR_HOST"
label="验证码识别服务器"
:label="t('setting.system.ocrHost')"
placeholder="https://movie-pilot.org"
hint="用于站点签到、更新站点Cookie等识别验证码"
:hint="t('setting.system.ocrHostHint')"
persistent-hint
/>
</VCol>
@@ -451,7 +481,7 @@ onDeactivated(() => {
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn type="submit" @click="saveBasicSettings"> 保存 </VBtn>
<VBtn type="submit" @click="saveBasicSettings"> {{ t('common.save') }} </VBtn>
<VSpacer />
<VBtn
color="error"
@@ -459,7 +489,7 @@ onDeactivated(() => {
prepend-icon="mdi-cog"
append-icon="mdi-dots-horizontal"
>
高级设置
{{ t('setting.system.advancedSettings') }}
</VBtn>
</div>
</VForm>
@@ -471,8 +501,8 @@ onDeactivated(() => {
<VCol cols="12">
<VCard>
<VCardItem>
<VCardTitle>下载器</VCardTitle>
<VCardSubtitle>只有默认下载器才会被默认使用</VCardSubtitle>
<VCardTitle>{{ t('setting.system.downloaders') }}</VCardTitle>
<VCardSubtitle>{{ t('setting.system.downloadersDesc') }}</VCardSubtitle>
</VCardItem>
<VCardText>
<draggable
@@ -496,7 +526,7 @@ onDeactivated(() => {
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn type="submit" @click="saveDownloaderSetting"> 保存 </VBtn>
<VBtn type="submit" @click="saveDownloaderSetting"> {{ t('common.save') }} </VBtn>
<VBtn color="success" variant="tonal">
<VIcon icon="mdi-plus" />
<VMenu activator="parent" close-on-content-click>
@@ -520,8 +550,8 @@ onDeactivated(() => {
<VCol cols="12">
<VCard>
<VCardItem>
<VCardTitle>媒体服务器</VCardTitle>
<VCardSubtitle>所有启用的媒体服务器都会被使用</VCardSubtitle>
<VCardTitle>{{ t('setting.system.mediaServers') }}</VCardTitle>
<VCardSubtitle>{{ t('setting.system.mediaServersDesc') }}</VCardSubtitle>
</VCardItem>
<VCardText>
<draggable
@@ -544,7 +574,7 @@ onDeactivated(() => {
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn type="submit" @click="saveMediaServerSetting"> 保存 </VBtn>
<VBtn type="submit" @click="saveMediaServerSetting"> {{ t('common.save') }} </VBtn>
<VBtn color="success" variant="tonal">
<VIcon icon="mdi-plus" />
<VMenu activator="parent" close-on-content-click>
@@ -559,7 +589,7 @@ onDeactivated(() => {
<VListItemTitle>Plex</VListItemTitle>
</VListItem>
<VListItem @click="addMediaServer('trimemedia')">
<VListItemTitle>飞牛影视</VListItemTitle>
<VListItemTitle>{{ t('setting.system.trimeMedia') }}</VListItemTitle>
</VListItem>
</VList>
</VMenu>
@@ -575,25 +605,25 @@ onDeactivated(() => {
<VCard>
<VCardItem>
<VDialogCloseBtn @click="advancedDialog = false" />
<VCardTitle>高级设置</VCardTitle>
<VCardSubtitle>系统进阶设置特殊情况下才需要调整</VCardSubtitle>
<VCardTitle>{{ t('setting.system.advancedSettings') }}</VCardTitle>
<VCardSubtitle>{{ t('setting.system.advancedSettingsDesc') }}</VCardSubtitle>
</VCardItem>
<VCardText>
<VTabs v-model="activeTab" show-arrows>
<VTab value="system">
<div>系统</div>
<div>{{ t('setting.system.system') }}</div>
</VTab>
<VTab value="media">
<div>媒体</div>
<div>{{ t('setting.system.media') }}</div>
</VTab>
<VTab value="network">
<div>网络</div>
<div>{{ t('setting.system.network') }}</div>
</VTab>
<VTab value="log">
<div>日志</div>
<div>{{ t('setting.system.log') }}</div>
</VTab>
<VTab value="dev">
<div>实验室</div>
<div>{{ t('setting.system.lab') }}</div>
</VTab>
</VTabs>
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
@@ -862,13 +892,11 @@ onDeactivated(() => {
@click="saveAdvancedSettings"
class="px-5"
>
保存
{{ t('common.save') }}
</VBtn>
</div>
</VForm>
</VCardActions>
</VCard>
</VDialog>
<!-- 进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" text="正在应用配置..." />
</template>

View File

@@ -1,6 +1,10 @@
<script lang="ts" setup>
import { useToast } from 'vue-toast-notification'
import api from '@/api'
import { useI18n } from 'vue-i18n'
// 国际化
const { t } = useI18n()
// 提示框
const $toast = useToast()
@@ -60,14 +64,13 @@ async function queryTransferExcludeWords() {
// 保存用户设置的识别词
async function saveCustomIdentifiers() {
try {
// 用户名密码
const result: { [key: string]: any } = await api.post(
'system/setting/CustomIdentifiers',
customIdentifiers.value.split('\n'),
)
if (result.success) $toast.success('自定义识别词保存成功')
else $toast.error('自定义识别词保存失败!')
if (result.success) $toast.success(t('setting.words.identifierSaveSuccess'))
else $toast.error(t('setting.words.identifierSaveFailed'))
} catch (error) {
console.log(error)
}
@@ -76,14 +79,13 @@ async function saveCustomIdentifiers() {
// 保存自定义制作组
async function saveCustomReleaseGroups() {
try {
// 用户名密码
const result: { [key: string]: any } = await api.post(
'system/setting/CustomReleaseGroups',
customReleaseGroups.value.split('\n'),
)
if (result.success) $toast.success('自定义制作组/字幕组保存成功')
else $toast.error('自定义制作组/字幕组保存失败!')
if (result.success) $toast.success(t('setting.words.releaseGroupSaveSuccess'))
else $toast.error(t('setting.words.releaseGroupSaveFailed'))
} catch (error) {
console.log(error)
}
@@ -92,14 +94,13 @@ async function saveCustomReleaseGroups() {
// 保存自定义占位符
async function saveCustomization() {
try {
// 用户名密码
const result: { [key: string]: any } = await api.post(
'system/setting/Customization',
customization.value.split('\n'),
)
if (result.success) $toast.success('自定义占位符保存成功')
else $toast.error('自定义占位符保存失败!')
if (result.success) $toast.success(t('setting.words.customizationSaveSuccess'))
else $toast.error(t('setting.words.customizationSaveFailed'))
} catch (error) {
console.log(error)
}
@@ -108,14 +109,13 @@ async function saveCustomization() {
// 保存文件整理屏蔽词
async function saveTransferExcludeWords() {
try {
// 用户名密码
const result: { [key: string]: any } = await api.post(
'system/setting/TransferExcludeWords',
transferExcludeWords.value.split('\n'),
)
if (result.success) $toast.success('文件整理屏蔽词保存成功')
else $toast.error('文件整理屏蔽词保存失败!')
if (result.success) $toast.success(t('setting.words.excludeWordsSaveSuccess'))
else $toast.error(t('setting.words.excludeWordsSaveFailed'))
} catch (error) {
console.log(error)
}
@@ -134,36 +134,26 @@ onMounted(() => {
<VCol cols="12">
<VCard>
<VCardItem>
<VCardTitle>自定义识别词</VCardTitle>
<VCardSubtitle> 添加规则对种子名或者文件名进行预处理以校正识别 </VCardSubtitle>
<VCardTitle>{{ t('setting.words.customIdentifiers') }}</VCardTitle>
<VCardSubtitle>{{ t('setting.words.identifiersDesc') }}</VCardSubtitle>
</VCardItem>
<VCardText>
<VTextarea
v-model="customIdentifiers"
placeholder="支持正则表达式,特殊字符需要\转义,一行为一组"
hint="支持正则表达式,特殊字符需要\转义,一行为一组"
:placeholder="t('setting.words.identifiersPlaceholder')"
:hint="t('setting.words.identifiersHint')"
persistent-hint
/>
</VCardText>
<VCardText>
<VAlert type="info" variant="tonal" title="支持的配置格式(注意空格):">
<span
v-html="
`
屏蔽词<br>
被替换词 => 替换词<br>
前定位词 <> 后定位词 >> 集偏移量EP<br>
被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量EP<br>
其中替换词支持格式:{[tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID识别其中s、e为季数和集数可选<br>
`
"
/>
<VAlert type="info" variant="tonal" :title="t('setting.words.formatTitle')">
<div style="white-space: pre-line" v-html="t('setting.words.formatContent').split('\n').join('<br>')"></div>
</VAlert>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn type="submit" @click="saveCustomIdentifiers"> 保存 </VBtn>
<VBtn type="submit" @click="saveCustomIdentifiers">{{ t('common.save') }}</VBtn>
</div>
</VForm>
</VCardText>
@@ -174,21 +164,21 @@ onMounted(() => {
<VCol cols="12">
<VCard>
<VCardItem>
<VCardTitle>自定义制作组/字幕组</VCardTitle>
<VCardSubtitle> 添加无法识别的制作组/字幕组。 </VCardSubtitle>
<VCardTitle>{{ t('setting.words.customReleaseGroups') }}</VCardTitle>
<VCardSubtitle>{{ t('setting.words.releaseGroupsDesc') }}</VCardSubtitle>
</VCardItem>
<VCardText>
<VTextarea
v-model="customReleaseGroups"
placeholder="支持正则表达式,特殊字符需要\转义,一行代表一个制作组/字幕组"
hint="支持正则表达式,特殊字符需要\转义,一行代表一个制作组/字幕组"
:placeholder="t('setting.words.releaseGroupsPlaceholder')"
:hint="t('setting.words.releaseGroupsHint')"
persistent-hint
/>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn type="submit" @click="saveCustomReleaseGroups"> 保存 </VBtn>
<VBtn type="submit" @click="saveCustomReleaseGroups">{{ t('common.save') }}</VBtn>
</div>
</VForm>
</VCardText>
@@ -199,21 +189,21 @@ onMounted(() => {
<VCol cols="12">
<VCard>
<VCardItem>
<VCardTitle>自定义占位符</VCardTitle>
<VCardSubtitle> 添加自定义占位符识别正则,重命名格式中添加{customization}使用。 </VCardSubtitle>
<VCardTitle>{{ t('setting.words.customization') }}</VCardTitle>
<VCardSubtitle>{{ t('setting.words.customizationDesc') }}</VCardSubtitle>
</VCardItem>
<VCardText>
<VTextarea
v-model="customization"
placeholder="支持正则表达式,特殊字符需要\转义,多个匹配对象请换行分隔"
hint="支持正则表达式,特殊字符需要\转义,多个匹配对象请换行分隔"
:placeholder="t('setting.words.customizationPlaceholder')"
:hint="t('setting.words.customizationHint')"
persistent-hint
/>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn type="submit" @click="saveCustomization"> 保存 </VBtn>
<VBtn type="submit" @click="saveCustomization">{{ t('common.save') }}</VBtn>
</div>
</VForm>
</VCardText>
@@ -224,21 +214,21 @@ onMounted(() => {
<VCol cols="12">
<VCard>
<VCardItem>
<VCardTitle>文件整理屏蔽词</VCardTitle>
<VCardSubtitle> 目录名或文件名中包含屏蔽词时不进行整理。 </VCardSubtitle>
<VCardTitle>{{ t('setting.words.transferExcludeWords') }}</VCardTitle>
<VCardSubtitle>{{ t('setting.words.excludeWordsDesc') }}</VCardSubtitle>
</VCardItem>
<VCardText>
<VTextarea
v-model="transferExcludeWords"
placeholder="支持正则表达式,特殊字符需要\转义,一行代表一个屏蔽词"
hint="支持正则表达式,特殊字符需要\转义,一行代表一个屏蔽词"
:placeholder="t('setting.words.excludeWordsPlaceholder')"
:hint="t('setting.words.excludeWordsHint')"
persistent-hint
/>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn type="submit" @click="saveTransferExcludeWords"> 保存 </VBtn>
<VBtn type="submit" @click="saveTransferExcludeWords">{{ t('common.save') }}</VBtn>
</div>
</VForm>
</VCardText>