更新国际化支持:在多个对话框组件中引入 vue-i18n,优化文本翻译,确保多语言显示的一致性和准确性。

This commit is contained in:
jxxghp
2025-04-28 08:55:52 +08:00
parent daf70b6da4
commit af7aa7d47b
12 changed files with 825 additions and 148 deletions
+22 -6
View File
@@ -1,5 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import api from '@/api' import api from '@/api'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
// 定义输入 // 定义输入
const props = defineProps({ const props = defineProps({
@@ -30,22 +34,32 @@ async function savaAlistConfig() {
<template> <template>
<VDialog width="50rem" scrollable max-height="85vh"> <VDialog width="50rem" scrollable max-height="85vh">
<VCard title="AList配置" class="rounded-t"> <VCard :title="t('dialog.alistConfig.title')" class="rounded-t">
<VDialogCloseBtn @click="emit('close')" /> <VDialogCloseBtn @click="emit('close')" />
<VCardText> <VCardText>
<VRow> <VRow>
<VCol cols="12"> <VCol cols="12">
<VTextField v-model="props.conf.url" hint="AList服务地址" label="地址" persistent-hint /> <VTextField
v-model="props.conf.url"
:hint="t('dialog.alistConfig.serverUrl')"
:label="t('dialog.alistConfig.serverUrl')"
persistent-hint
/>
</VCol> </VCol>
<VCol cols="12" md="6"> <VCol cols="12" md="6">
<VTextField v-model="props.conf.username" hint="AList登录用户名" label="用户名" persistent-hint /> <VTextField
v-model="props.conf.username"
:hint="t('dialog.alistConfig.username')"
:label="t('dialog.alistConfig.username')"
persistent-hint
/>
</VCol> </VCol>
<VCol cols="12" md="6"> <VCol cols="12" md="6">
<VTextField <VTextField
type="password" type="password"
v-model="props.conf.password" v-model="props.conf.password"
hint="AList登录密码" :hint="t('dialog.alistConfig.password')"
label="密码" :label="t('dialog.alistConfig.password')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
@@ -53,7 +67,9 @@ async function savaAlistConfig() {
</VCardText> </VCardText>
<VCardActions> <VCardActions>
<VSpacer /> <VSpacer />
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3"> 完成 </VBtn> <VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
{{ t('dialog.alistConfig.complete') }}
</VBtn>
</VCardActions> </VCardActions>
</VCard> </VCard>
</VDialog> </VDialog>
+9 -3
View File
@@ -1,5 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import api from '@/api' import api from '@/api'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
// 定义输入 // 定义输入
defineProps({ defineProps({
@@ -16,7 +20,7 @@ const emit = defineEmits(['done', 'close'])
const qrCodeUrl = ref('') const qrCodeUrl = ref('')
// 下方的提示信息 // 下方的提示信息
const text = ref('请用阿里云盘 App 扫码') const text = ref(t('dialog.aliyunAuth.scanQrCode'))
// 提醒类型 // 提醒类型
const alertType = ref<'success' | 'info' | 'error' | 'warning' | undefined>('info') const alertType = ref<'success' | 'info' | 'error' | 'warning' | undefined>('info')
@@ -85,7 +89,7 @@ onUnmounted(() => {
<template> <template>
<VDialog width="40rem" scrollable max-height="85vh"> <VDialog width="40rem" scrollable max-height="85vh">
<VCard title="阿里云盘登录" class="rounded-t"> <VCard :title="t('dialog.aliyunAuth.loginTitle')" class="rounded-t">
<VDialogCloseBtn @click="emit('close')" /> <VDialogCloseBtn @click="emit('close')" />
<VCardText class="pt-2 flex flex-col items-center"> <VCardText class="pt-2 flex flex-col items-center">
<div class="my-6 rounded text-center p-3 border"> <div class="my-6 rounded text-center p-3 border">
@@ -103,7 +107,9 @@ onUnmounted(() => {
</VCardText> </VCardText>
<VCardActions> <VCardActions>
<VSpacer /> <VSpacer />
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3"> 完成 </VBtn> <VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
{{ t('dialog.aliyunAuth.complete') }}
</VBtn>
</VCardActions> </VCardActions>
</VCard> </VCard>
</VDialog> </VDialog>
+10 -4
View File
@@ -1,5 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import api from '@/api' import api from '@/api'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
// 定义输入 // 定义输入
const props = defineProps({ const props = defineProps({
@@ -14,7 +18,7 @@ if (!props.conf.filepath) {
} }
if (!props.conf.content) { if (!props.conf.content) {
props.conf.content = '# 请在此处填写rclone配置文件内容 \n# 请参考 https://rclone.org/docs/ \n# 存储节点名必须为:MP' props.conf.content = t('dialog.rcloneConfig.defaultContent')
} }
// 定义事件 // 定义事件
@@ -38,12 +42,12 @@ async function savaRcloneConfig() {
<template> <template>
<VDialog width="50rem" scrollable max-height="85vh"> <VDialog width="50rem" scrollable max-height="85vh">
<VCard title="RClone配置" class="rounded-t"> <VCard :title="t('dialog.rcloneConfig.title')" class="rounded-t">
<VDialogCloseBtn @click="emit('close')" /> <VDialogCloseBtn @click="emit('close')" />
<VCardText> <VCardText>
<VRow> <VRow>
<VCol cols="12"> <VCol cols="12">
<VTextField v-model="props.conf.filepath" label="rclone配置文件路径" /> <VTextField v-model="props.conf.filepath" :label="t('dialog.rcloneConfig.filePath')" />
</VCol> </VCol>
<VCol cols="12"> <VCol cols="12">
<VAceEditor <VAceEditor
@@ -59,7 +63,9 @@ async function savaRcloneConfig() {
</VCardText> </VCardText>
<VCardActions> <VCardActions>
<VSpacer /> <VSpacer />
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3"> 完成 </VBtn> <VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
{{ t('dialog.rcloneConfig.complete') }}
</VBtn>
</VCardActions> </VCardActions>
</VCard> </VCard>
</VDialog> </VDialog>
+22 -18
View File
@@ -4,6 +4,10 @@ import api from '@/api'
import { useDisplay, useTheme } from 'vuetify' import { useDisplay, useTheme } from 'vuetify'
import { formatFileSize } from '@/@core/utils/formatters' import { formatFileSize } from '@/@core/utils/formatters'
import ProgressDialog from '@/components/dialog/ProgressDialog.vue' import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
// 显示器宽度 // 显示器宽度
const display = useDisplay() const display = useDisplay()
@@ -36,11 +40,11 @@ const siteData = computed(() => siteDatas.value[siteDatas.value.length - 1])
const historySeries = computed(() => { const historySeries = computed(() => {
return [ return [
{ {
name: '上传量', name: t('dialog.siteUserData.uploadTitle'),
data: siteDatas.value.map(item => Math.round((item.upload ?? 0) / 1024 / 1024 / 1024)), data: siteDatas.value.map(item => Math.round((item.upload ?? 0) / 1024 / 1024 / 1024)),
}, },
{ {
name: '下载量', name: t('dialog.siteUserData.downloadTitle'),
data: siteDatas.value.map(item => Math.round((item.download ?? 0) / 1024 / 1024 / 1024)), data: siteDatas.value.map(item => Math.round((item.download ?? 0) / 1024 / 1024 / 1024)),
}, },
] ]
@@ -135,7 +139,7 @@ const historyChartOptions = computed(() => {
const seedingSeries = computed(() => { const seedingSeries = computed(() => {
return [ return [
{ {
name: '体积', name: t('dialog.siteUserData.volumeTitle'),
data: siteData.value?.seeding_info?.map(item => [item[0] ?? 0, Math.round((item[1] ?? 0) / 1024 / 1024 / 1024)]), data: siteData.value?.seeding_info?.map(item => [item[0] ?? 0, Math.round((item[1] ?? 0) / 1024 / 1024 / 1024)]),
}, },
] ]
@@ -162,7 +166,7 @@ const seedingChartOptions = computed(() => {
enabled: true, enabled: true,
x: { x: {
formatter: function (val: number) { formatter: function (val: number) {
return '数量:' + val.toLocaleString() return t('dialog.siteUserData.countTitle') + val.toLocaleString()
}, },
}, },
style: { style: {
@@ -188,7 +192,7 @@ const seedingChartOptions = computed(() => {
}, },
}, },
title: { title: {
text: '数量', text: t('dialog.siteUserData.countTitle'),
}, },
tickAmount: 10, tickAmount: 10,
}, },
@@ -282,7 +286,7 @@ onBeforeMount(async () => {
<VCard class="rounded-t"> <VCard class="rounded-t">
<VCardItem> <VCardItem>
<VCardTitle <VCardTitle
>{{ `数据 - ${props.site?.name}` }} >{{ t('dialog.siteUserData.title') }} - {{ props.site?.name }}
<IconBtn @click.stop="refreshSiteData" color="info"><VIcon icon="mdi-refresh" /></IconBtn> <IconBtn @click.stop="refreshSiteData" color="info"><VIcon icon="mdi-refresh" /></IconBtn>
</VCardTitle> </VCardTitle>
<VDialogCloseBtn @click="emit('close')" /> <VDialogCloseBtn @click="emit('close')" />
@@ -296,9 +300,9 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center"> <VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%"> <div class="d-flex justify-space-between" style="inline-size: 100%">
<div class="d-flex flex-column gap-y-1 overflow-hidden"> <div class="d-flex flex-column gap-y-1 overflow-hidden">
<span class="text-base">用户等级</span> <span class="text-base">{{ t('dialog.siteUserData.userLevel') }}</span>
<h5 class="text-h5 d-flex align-center gap-2 text-wrap"> <h5 class="text-h5 d-flex align-center gap-2 text-wrap">
{{ siteData?.user_level || '无' }} {{ siteData?.user_level || t('dialog.siteUserData.noData') }}
</h5> </h5>
</div> </div>
<VAvatar variant="tonal" size="42" rounded> <VAvatar variant="tonal" size="42" rounded>
@@ -314,7 +318,7 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center"> <VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%"> <div class="d-flex justify-space-between" style="inline-size: 100%">
<div class="d-flex flex-column gap-y-1 overflow-hidden"> <div class="d-flex flex-column gap-y-1 overflow-hidden">
<span class="text-base">积分</span> <span class="text-base">{{ t('dialog.siteUserData.bonus') }}</span>
<h5 class="text-h5 d-flex align-center gap-2 text-wrap"> <h5 class="text-h5 d-flex align-center gap-2 text-wrap">
{{ siteData?.bonus?.toLocaleString() }} {{ siteData?.bonus?.toLocaleString() }}
<span class="text-base font-weight-regular" :class="getDiffClass(diffData?.bonus)"> <span class="text-base font-weight-regular" :class="getDiffClass(diffData?.bonus)">
@@ -335,7 +339,7 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center"> <VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%"> <div class="d-flex justify-space-between" style="inline-size: 100%">
<div class="d-flex flex-column gap-y-1"> <div class="d-flex flex-column gap-y-1">
<span class="text-base">分享率</span> <span class="text-base">{{ t('dialog.siteUserData.ratio') }}</span>
<h5 class="text-h5 d-flex align-center gap-2 text-wrap"> <h5 class="text-h5 d-flex align-center gap-2 text-wrap">
{{ siteData?.ratio }} {{ siteData?.ratio }}
<span class="text-base font-weight-regular" :class="getDiffClass(diffData?.ratio)"> <span class="text-base font-weight-regular" :class="getDiffClass(diffData?.ratio)">
@@ -356,7 +360,7 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center"> <VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%"> <div class="d-flex justify-space-between" style="inline-size: 100%">
<div class="d-flex flex-column gap-y-1 overflow-hidden"> <div class="d-flex flex-column gap-y-1 overflow-hidden">
<span class="text-base">总上传量</span> <span class="text-base">{{ t('dialog.siteUserData.uploadTotal') }}</span>
<h5 class="text-h5 d-flex align-center gap-2 text-wrap"> <h5 class="text-h5 d-flex align-center gap-2 text-wrap">
{{ formatFileSize(siteData?.upload || 0) }} {{ formatFileSize(siteData?.upload || 0) }}
<span class="text-base font-weight-regular" :class="getDiffClass(diffData?.upload)"> <span class="text-base font-weight-regular" :class="getDiffClass(diffData?.upload)">
@@ -377,7 +381,7 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center"> <VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%"> <div class="d-flex justify-space-between" style="inline-size: 100%">
<div class="d-flex flex-column gap-y-1 overflow-hidden"> <div class="d-flex flex-column gap-y-1 overflow-hidden">
<span class="text-base">总下载量</span> <span class="text-base">{{ t('dialog.siteUserData.downloadTotal') }}</span>
<h5 class="text-h5 d-flex align-center gap-2 text-wrap"> <h5 class="text-h5 d-flex align-center gap-2 text-wrap">
{{ formatFileSize(siteData?.download || 0) }} {{ formatFileSize(siteData?.download || 0) }}
<span class="text-base font-weight-regular" :class="getDiffClass(diffData?.download)"> <span class="text-base font-weight-regular" :class="getDiffClass(diffData?.download)">
@@ -398,7 +402,7 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center"> <VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%"> <div class="d-flex justify-space-between" style="inline-size: 100%">
<div class="d-flex flex-column gap-y-1 overflow-hidden"> <div class="d-flex flex-column gap-y-1 overflow-hidden">
<span class="text-base">总做种数</span> <span class="text-base">{{ t('dialog.siteUserData.seedingCount') }}</span>
<h5 class="text-h5 d-flex align-center gap-2 text-wrap"> <h5 class="text-h5 d-flex align-center gap-2 text-wrap">
{{ siteData?.seeding?.toLocaleString() }} {{ siteData?.seeding?.toLocaleString() }}
<span class="text-base font-weight-regular" :class="getDiffClass(diffData?.seeding)"> <span class="text-base font-weight-regular" :class="getDiffClass(diffData?.seeding)">
@@ -419,7 +423,7 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center"> <VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%"> <div class="d-flex justify-space-between" style="inline-size: 100%">
<div class="d-flex flex-column gap-y-1 overflow-hidden"> <div class="d-flex flex-column gap-y-1 overflow-hidden">
<span class="text-base">总做种体积</span> <span class="text-base">{{ t('dialog.siteUserData.seedingSize') }}</span>
<h5 class="text-h5 d-flex align-center gap-2 text-wrap"> <h5 class="text-h5 d-flex align-center gap-2 text-wrap">
{{ formatFileSize(siteData?.seeding_size || 0) }} {{ formatFileSize(siteData?.seeding_size || 0) }}
<span class="text-base font-weight-regular" :class="getDiffClass(diffData?.seeding_size)"> <span class="text-base font-weight-regular" :class="getDiffClass(diffData?.seeding_size)">
@@ -440,7 +444,7 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center"> <VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%"> <div class="d-flex justify-space-between" style="inline-size: 100%">
<div class="d-flex flex-column gap-y-1 overflow-hidden"> <div class="d-flex flex-column gap-y-1 overflow-hidden">
<span class="text-base">加入时间</span> <span class="text-base">{{ t('dialog.siteUserData.joinTime') }}</span>
<h5 class="text-h5 d-flex align-center gap-2 text-wrap"> <h5 class="text-h5 d-flex align-center gap-2 text-wrap">
{{ siteData?.join_at?.split(' ')[0] }} {{ siteData?.join_at?.split(' ')[0] }}
</h5> </h5>
@@ -455,7 +459,7 @@ onBeforeMount(async () => {
</VRow> </VRow>
<VRow> <VRow>
<VCol> <VCol>
<VCard title="历史流量"> <VCard :title="t('dialog.siteUserData.trafficHistory')">
<VCardText> <VCardText>
<VApexChart type="line" :options="historyChartOptions" :series="historySeries" :height="300" /> <VApexChart type="line" :options="historyChartOptions" :series="historySeries" :height="300" />
</VCardText> </VCardText>
@@ -464,7 +468,7 @@ onBeforeMount(async () => {
</VRow> </VRow>
<VRow> <VRow>
<VCol> <VCol>
<VCard title="做种分布"> <VCard :title="t('dialog.siteUserData.seedingDistribution')">
<VCardText> <VCardText>
<VApexChart type="scatter" :options="seedingChartOptions" :series="seedingSeries" :height="300" /> <VApexChart type="scatter" :options="seedingChartOptions" :series="seedingSeries" :height="300" />
</VCardText> </VCardText>
@@ -474,6 +478,6 @@ onBeforeMount(async () => {
</VCardText> </VCardText>
</VCard> </VCard>
<!-- 进度框 --> <!-- 进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" text="正在刷新站点数据..." /> <ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="t('dialog.siteUserData.refreshing')" />
</VDialog> </VDialog>
</template> </template>
+63 -58
View File
@@ -5,6 +5,10 @@ import api from '@/api'
import type { DownloaderConf, FilterRuleGroup, Site, Subscribe, TransferDirectoryConf } from '@/api/types' import type { DownloaderConf, FilterRuleGroup, Site, Subscribe, TransferDirectoryConf } from '@/api/types'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useConfirm } from 'vuetify-use-dialog' import { useConfirm } from 'vuetify-use-dialog'
import { useI18n } from 'vue-i18n'
// i18n
const { t } = useI18n()
// 显示器宽度 // 显示器宽度
const display = useDisplay() const display = useDisplay()
@@ -80,7 +84,7 @@ const episodeGroupOptions = computed(() => {
// 生成1到100季的下拉框选项 // 生成1到100季的下拉框选项
const seasonItems = ref( const seasonItems = ref(
Array.from({ length: 101 }, (_, i) => i).map(item => ({ Array.from({ length: 101 }, (_, i) => i).map(item => ({
title: `${item}`, title: t('dialog.subscribeEdit.seasonFormat', { number: item }),
value: item, value: item,
})), })),
) )
@@ -106,7 +110,7 @@ async function loadDownloaderSetting() {
try { try {
const downloaders: DownloaderConf[] = await api.get('download/clients') const downloaders: DownloaderConf[] = await api.get('download/clients')
downloaderOptions.value = [ downloaderOptions.value = [
{ title: '默认', value: '' }, { title: t('common.default'), value: '' },
...downloaders.map((item: { name: any }) => ({ ...downloaders.map((item: { name: any }) => ({
title: item.name, title: item.name,
value: item.name, value: item.name,
@@ -229,8 +233,8 @@ async function getSubscribeInfo() {
// 删除订阅 // 删除订阅
async function removeSubscribe() { async function removeSubscribe() {
const isConfirmed = await createConfirm({ const isConfirmed = await createConfirm({
title: '确认', title: t('common.confirm'),
content: `是否确认取消订阅?`, content: t('dialog.subscribeEdit.cancelSubscribeConfirm'),
}) })
if (!isConfirmed) return if (!isConfirmed) return
@@ -268,7 +272,7 @@ const targetDirectories = computed(() => {
// 质量选择框数据 // 质量选择框数据
const qualityOptions = ref([ const qualityOptions = ref([
{ {
title: '全部', title: t('common.all'),
value: '', value: '',
}, },
{ {
@@ -308,7 +312,7 @@ const qualityOptions = ref([
// 分辨率选择框数据 // 分辨率选择框数据
const resolutionOptions = ref([ const resolutionOptions = ref([
{ {
title: '全部', title: t('common.all'),
value: '', value: '',
}, },
{ {
@@ -328,7 +332,7 @@ const resolutionOptions = ref([
// 特效选择框数据 // 特效选择框数据
const effectOptions = ref([ const effectOptions = ref([
{ {
title: '全部', title: t('common.all'),
value: '', value: '',
}, },
{ {
@@ -362,11 +366,16 @@ onMounted(() => {
<template> <template>
<VDialog scrollable max-width="45rem" :fullscreen="!display.mdAndUp.value"> <VDialog scrollable max-width="45rem" :fullscreen="!display.mdAndUp.value">
<VCard <VCard
:title="`${ :title="
props.default props.default
? `${props.type}默认订阅规则` ? t('dialog.subscribeEdit.titleDefault')
: `编辑订阅 - ${subscribeForm.name} ${subscribeForm.season ? `第 ${subscribeForm.season} 季` : ''}` : t('dialog.subscribeEdit.titleEditFormat', {
}`" name: subscribeForm.name,
season: subscribeForm.season
? t('dialog.subscribeEdit.seasonFormat', { number: subscribeForm.season })
: '',
})
"
class="rounded-t" class="rounded-t"
> >
<VCardText> <VCardText>
@@ -374,10 +383,10 @@ onMounted(() => {
<VForm @submit.prevent="() => {}"> <VForm @submit.prevent="() => {}">
<VTabs v-model="activeTab" show-arrows> <VTabs v-model="activeTab" show-arrows>
<VTab value="basic"> <VTab value="basic">
<div>基础</div> <div>{{ t('dialog.subscribeEdit.tabs.basic') }}</div>
</VTab> </VTab>
<VTab value="advance"> <VTab value="advance">
<div>进阶</div> <div>{{ t('dialog.subscribeEdit.tabs.advance') }}</div>
</VTab> </VTab>
</VTabs> </VTabs>
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false"> <VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
@@ -387,26 +396,26 @@ onMounted(() => {
<VCol cols="12" md="4"> <VCol cols="12" md="4">
<VTextField <VTextField
v-model="subscribeForm.keyword" v-model="subscribeForm.keyword"
label="搜索关键词" :label="t('dialog.subscribeEdit.searchKeyword')"
hint="指定搜索站点时使用的关键词" :hint="t('dialog.subscribeEdit.searchKeywordHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
<VCol v-if="subscribeForm.type === '电视剧'" cols="12" md="4"> <VCol v-if="subscribeForm.type === '电视剧'" cols="12" md="4">
<VTextField <VTextField
v-model="subscribeForm.total_episode" v-model="subscribeForm.total_episode"
label="总集数" :label="t('dialog.subscribeEdit.totalEpisode')"
:rules="[numberValidator]" :rules="[numberValidator]"
hint="剧集总集数" :hint="t('dialog.subscribeEdit.totalEpisodeHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
<VCol v-if="subscribeForm.type === '电视剧'" cols="12" md="4"> <VCol v-if="subscribeForm.type === '电视剧'" cols="12" md="4">
<VTextField <VTextField
v-model="subscribeForm.start_episode" v-model="subscribeForm.start_episode"
label="开始集数" :label="t('dialog.subscribeEdit.startEpisode')"
:rules="[numberValidator]" :rules="[numberValidator]"
hint="开始订阅集数" :hint="t('dialog.subscribeEdit.startEpisodeHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
@@ -415,27 +424,27 @@ onMounted(() => {
<VCol cols="12" md="4"> <VCol cols="12" md="4">
<VSelect <VSelect
v-model="subscribeForm.quality" v-model="subscribeForm.quality"
label="质量" :label="t('dialog.subscribeEdit.quality')"
:items="qualityOptions" :items="qualityOptions"
hint="订阅资源质量" :hint="t('dialog.subscribeEdit.qualityHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
<VCol cols="12" md="4"> <VCol cols="12" md="4">
<VSelect <VSelect
v-model="subscribeForm.resolution" v-model="subscribeForm.resolution"
label="分辨率" :label="t('dialog.subscribeEdit.resolution')"
:items="resolutionOptions" :items="resolutionOptions"
hint="订阅资源分辨率" :hint="t('dialog.subscribeEdit.resolutionHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
<VCol cols="12" md="4"> <VCol cols="12" md="4">
<VSelect <VSelect
v-model="subscribeForm.effect" v-model="subscribeForm.effect"
label="特效" :label="t('dialog.subscribeEdit.effect')"
:items="effectOptions" :items="effectOptions"
hint="订阅资源特效" :hint="t('dialog.subscribeEdit.effectHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
@@ -446,10 +455,10 @@ onMounted(() => {
v-model="subscribeForm.sites" v-model="subscribeForm.sites"
:items="selectSitesOptions" :items="selectSitesOptions"
chips chips
label="订阅站点" :label="t('dialog.subscribeEdit.subscribeSites')"
multiple multiple
clearable clearable
hint="订阅的站点范围,不选使用系统设置" :hint="t('dialog.subscribeEdit.subscribeSitesHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
@@ -459,8 +468,8 @@ onMounted(() => {
<VSelect <VSelect
v-model="subscribeForm.downloader" v-model="subscribeForm.downloader"
:items="downloaderOptions" :items="downloaderOptions"
label="下载器" :label="t('dialog.subscribeEdit.downloader')"
hint="指定该订阅使用的下载器" :hint="t('dialog.subscribeEdit.downloaderHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
@@ -468,8 +477,8 @@ onMounted(() => {
<VCombobox <VCombobox
v-model="subscribeForm.save_path" v-model="subscribeForm.save_path"
:items="targetDirectories" :items="targetDirectories"
label="保存路径" :label="t('dialog.subscribeEdit.savePath')"
hint="指定该订阅的下载保存路径,留空自动使用设定的下载目录" :hint="t('dialog.subscribeEdit.savePathHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
@@ -478,24 +487,24 @@ onMounted(() => {
<VCol cols="12" md="4"> <VCol cols="12" md="4">
<VSwitch <VSwitch
v-model="subscribeForm.best_version" v-model="subscribeForm.best_version"
label="洗版" :label="t('dialog.subscribeEdit.bestVersion')"
hint="根据洗版优先级进行洗版订阅" :hint="t('dialog.subscribeEdit.bestVersionHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
<VCol cols="12" md="4"> <VCol cols="12" md="4">
<VSwitch <VSwitch
v-model="subscribeForm.search_imdbid" v-model="subscribeForm.search_imdbid"
label="使用 ImdbID 搜索" :label="t('dialog.subscribeEdit.searchImdbid')"
hint="开使用 ImdbID 精确搜索资源" :hint="t('dialog.subscribeEdit.searchImdbidHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
<VCol v-if="props.default" cols="12" md="4"> <VCol v-if="props.default" cols="12" md="4">
<VSwitch <VSwitch
v-model="subscribeForm.show_edit_dialog" v-model="subscribeForm.show_edit_dialog"
label="订阅时编辑更多规则" :label="t('dialog.subscribeEdit.showEditDialog')"
hint="添加订阅时显示此编辑订阅对话框" :hint="t('dialog.subscribeEdit.showEditDialogHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
@@ -508,16 +517,16 @@ onMounted(() => {
<VCol cols="12" md="6"> <VCol cols="12" md="6">
<VTextField <VTextField
v-model="subscribeForm.include" v-model="subscribeForm.include"
label="包含(关键字、正则式)" :label="t('dialog.subscribeEdit.include')"
hint="包含规则,支持正则表达式" :hint="t('dialog.subscribeEdit.includeHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
<VCol cols="12" md="6"> <VCol cols="12" md="6">
<VTextField <VTextField
v-model="subscribeForm.exclude" v-model="subscribeForm.exclude"
label="排除(关键字、正则式)" :label="t('dialog.subscribeEdit.exclude')"
hint="排除规则,支持正则表达式" :hint="t('dialog.subscribeEdit.excludeHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
@@ -530,8 +539,8 @@ onMounted(() => {
chips chips
multiple multiple
clearable clearable
label="优先级规则组" :label="t('dialog.subscribeEdit.filterGroups')"
hint="按选定的过滤规则组对订阅进行过滤" :hint="t('dialog.subscribeEdit.filterGroupsHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
@@ -540,8 +549,8 @@ onMounted(() => {
v-model="subscribeForm.episode_group" v-model="subscribeForm.episode_group"
:items="episodeGroupOptions" :items="episodeGroupOptions"
:item-props="episodeGroupItemProps" :item-props="episodeGroupItemProps"
label="指定剧集组" :label="t('dialog.subscribeEdit.episodeGroup')"
hint="按特定剧集组识别和刮削" :hint="t('dialog.subscribeEdit.episodeGroupHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
@@ -549,16 +558,16 @@ onMounted(() => {
<VSelect <VSelect
v-model="subscribeForm.season" v-model="subscribeForm.season"
:items="seasonItems" :items="seasonItems"
label="指定季" :label="t('dialog.subscribeEdit.season')"
hint="指定任意季订阅" :hint="t('dialog.subscribeEdit.seasonHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
<VCol cols="12" v-if="!props.default"> <VCol cols="12" v-if="!props.default">
<VTextField <VTextField
v-model="subscribeForm.media_category" v-model="subscribeForm.media_category"
label="自定义类别" :label="t('dialog.subscribeEdit.mediaCategory')"
hint="指定类别名称,留空自动识别" :hint="t('dialog.subscribeEdit.mediaCategoryHint')"
persistent-hint persistent-hint
/> />
</VCol> </VCol>
@@ -567,14 +576,10 @@ onMounted(() => {
<VCol cols="12"> <VCol cols="12">
<VTextarea <VTextarea
v-model="subscribeForm.custom_words" v-model="subscribeForm.custom_words"
label="自定义识别词" :label="t('dialog.subscribeEdit.customWords')"
hint="只对该订阅使用的识别词" :hint="t('dialog.subscribeEdit.customWordsHint')"
persistent-hint persistent-hint
placeholder="屏蔽词 :placeholder="t('dialog.subscribeEdit.customWordsPlaceholder')"
被替换词 => 替换词
前定位词 <> 后定位词 >> 集偏移量(EP
被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP
其中替换词支持格式:{[tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID识别,其中s、e为季数和集数(可选)"
/> />
</VCol> </VCol>
</VRow> </VRow>
@@ -585,7 +590,7 @@ onMounted(() => {
</VCardText> </VCardText>
<VCardActions class="pt-3"> <VCardActions class="pt-3">
<VBtn v-if="!props.default" color="error" @click="removeSubscribe" variant="outlined" class="me-3"> <VBtn v-if="!props.default" color="error" @click="removeSubscribe" variant="outlined" class="me-3">
取消订阅 {{ t('dialog.subscribeEdit.cancelSubscribe') }}
</VBtn> </VBtn>
<VSpacer /> <VSpacer />
<VBtn <VBtn
@@ -594,7 +599,7 @@ onMounted(() => {
prepend-icon="mdi-content-save" prepend-icon="mdi-content-save"
class="px-5" class="px-5"
> >
保存 {{ t('dialog.subscribeEdit.save') }}
</VBtn> </VBtn>
</VCardActions> </VCardActions>
</VCard> </VCard>
+20 -16
View File
@@ -2,6 +2,10 @@
import api from '@/api' import api from '@/api'
import { SubscrbieInfo } from '@/api/types' import { SubscrbieInfo } from '@/api/types'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useI18n } from 'vue-i18n'
// i18n
const { t } = useI18n()
// 显示器宽度 // 显示器宽度
const display = useDisplay() const display = useDisplay()
@@ -21,15 +25,15 @@ const subScribeInfo = ref<SubscrbieInfo>()
// 下载文件表头 // 下载文件表头
const downloadHeaders = [ const downloadHeaders = [
{ title: '集', key: 'episode_number', sortable: true }, { title: t('dialog.subscribeFiles.episodeColumn'), key: 'episode_number', sortable: true },
{ title: '种子', key: 'torrent_title', sortable: true }, { title: t('dialog.subscribeFiles.torrentColumn'), key: 'torrent_title', sortable: true },
{ title: '文件', key: 'file_path', sortable: true }, { title: t('dialog.subscribeFiles.fileColumn'), key: 'file_path', sortable: true },
] ]
// 媒体库文件表头 // 媒体库文件表头
const libraryHeaders = [ const libraryHeaders = [
{ title: '集', key: 'episode_number', sortable: true }, { title: t('dialog.subscribeFiles.episodeColumn'), key: 'episode_number', sortable: true },
{ title: '文件', key: 'file_path', sortable: true }, { title: t('dialog.subscribeFiles.fileColumn'), key: 'file_path', sortable: true },
] ]
// 调用API查询订阅文件信息 // 调用API查询订阅文件信息
@@ -102,7 +106,7 @@ onBeforeMount(() => {
{{ subScribeInfo?.subscribe?.name }} {{ subScribeInfo?.subscribe?.name }}
</div> </div>
<div v-if="subScribeInfo?.subscribe?.season" class="text-lg align-self-center align-self-lg-end ms-3"> <div v-if="subScribeInfo?.subscribe?.season" class="text-lg align-self-center align-self-lg-end ms-3">
{{ subScribeInfo?.subscribe?.season }} {{ t('dialog.subscribeFiles.season', { number: subScribeInfo?.subscribe?.season }) }}
</div> </div>
</h1> </h1>
<div>{{ subScribeInfo?.subscribe?.year }}</div> <div>{{ subScribeInfo?.subscribe?.year }}</div>
@@ -119,13 +123,13 @@ onBeforeMount(() => {
<VTab value="download" selected-class="v-slide-group-item--active v-tab--selected"> <VTab value="download" selected-class="v-slide-group-item--active v-tab--selected">
<div> <div>
<VIcon size="20" start icon="mdi-download" /> <VIcon size="20" start icon="mdi-download" />
下载文件 {{ t('dialog.subscribeFiles.downloadTab') }}
</div> </div>
</VTab> </VTab>
<VTab value="library" selected-class="v-slide-group-item--active v-tab--selected"> <VTab value="library" selected-class="v-slide-group-item--active v-tab--selected">
<div> <div>
<VIcon size="20" start icon="mdi-filmstrip-box-multiple" /> <VIcon size="20" start icon="mdi-filmstrip-box-multiple" />
媒体库文件 {{ t('dialog.subscribeFiles.libraryTab') }}
</div> </div>
</VTab> </VTab>
</VTabs> </VTabs>
@@ -143,9 +147,9 @@ onBeforeMount(() => {
return-object return-object
fixed-header fixed-header
hover hover
items-per-page-text="每页条数" :items-per-page-text="t('dialog.subscribeFiles.itemsPerPage')"
page-text="{0}-{1} {2} " :page-text="t('dialog.subscribeFiles.pageText')"
loading-text="加载中..." :loading-text="t('dialog.subscribeFiles.loadingText')"
> >
<template #item.episode_number="{ item }"> <template #item.episode_number="{ item }">
<div class="text-high-emphasis pt-1">{{ item.episode_number }}. {{ item.title }}</div> <div class="text-high-emphasis pt-1">{{ item.episode_number }}. {{ item.title }}</div>
@@ -158,7 +162,7 @@ onBeforeMount(() => {
<template #item.file_path="{ item }"> <template #item.file_path="{ item }">
<div class="text-xs" v-for="file in item.download">{{ file.file_path }}</div> <div class="text-xs" v-for="file in item.download">{{ file.file_path }}</div>
</template> </template>
<template #no-data> 没有数据 </template> <template #no-data> {{ t('dialog.subscribeFiles.noData') }} </template>
</VDataTable> </VDataTable>
</div> </div>
</transition> </transition>
@@ -176,9 +180,9 @@ onBeforeMount(() => {
return-object return-object
fixed-header fixed-header
hover hover
items-per-page-text="每页条数" :items-per-page-text="t('dialog.subscribeFiles.itemsPerPage')"
page-text="{0}-{1} {2} " :page-text="t('dialog.subscribeFiles.pageText')"
loading-text="加载中..." :loading-text="t('dialog.subscribeFiles.loadingText')"
> >
<template #item.episode_number="{ item }"> <template #item.episode_number="{ item }">
<div class="text-high-emphasis pt-1">{{ item.episode_number }}. {{ item.title }}</div> <div class="text-high-emphasis pt-1">{{ item.episode_number }}. {{ item.title }}</div>
@@ -186,7 +190,7 @@ onBeforeMount(() => {
<template #item.file_path="{ item }"> <template #item.file_path="{ item }">
<div class="text-xs" v-for="file in item.library">{{ file.file_path }}</div> <div class="text-xs" v-for="file in item.library">{{ file.file_path }}</div>
</template> </template>
<template #no-data> 没有数据 </template> <template #no-data> {{ t('dialog.subscribeFiles.noData') }} </template>
</VDataTable> </VDataTable>
</div> </div>
</transition> </transition>
+15 -10
View File
@@ -3,6 +3,10 @@ import { formatFileSize } from '@/@core/utils/formatters'
import api from '@/api' import api from '@/api'
import { FileItem, TransferQueue } from '@/api/types' import { FileItem, TransferQueue } from '@/api/types'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
// 显示器宽度 // 显示器宽度
const display = useDisplay() const display = useDisplay()
@@ -16,7 +20,7 @@ const dataList = ref<TransferQueue[]>([])
const progressEventSource = ref<EventSource>() const progressEventSource = ref<EventSource>()
// 整理进度文本 // 整理进度文本
const progressText = ref('请稍候 ...') const progressText = ref(t('dialog.transferQueue.processing'))
// 整理进度 // 整理进度
const progressValue = ref(0) const progressValue = ref(0)
@@ -29,10 +33,11 @@ const activeTab = ref('')
// 状态标签 // 状态标签
const stateDict: { [key: string]: string } = { const stateDict: { [key: string]: string } = {
'waiting': '等待中', 'waiting': t('dialog.transferQueue.waitingState'),
'running': '正在整理', 'running': t('dialog.transferQueue.runningState'),
'completed': '完成', 'completed': t('dialog.transferQueue.finishedState'),
'failed': '失败', 'failed': t('dialog.transferQueue.failedState'),
'cancelled': t('dialog.transferQueue.cancelledState'),
} }
// 获取状态颜色 // 获取状态颜色
@@ -88,13 +93,13 @@ async function remove_queue_task(fileitem: FileItem) {
// 使用SSE监听加载进度 // 使用SSE监听加载进度
function startLoadingProgress() { function startLoadingProgress() {
progressText.value = '请稍候 ...' progressText.value = t('dialog.transferQueue.processing')
progressEventSource.value = new EventSource(`${import.meta.env.VITE_API_BASE_URL}system/progress/filetransfer`) progressEventSource.value = new EventSource(`${import.meta.env.VITE_API_BASE_URL}system/progress/filetransfer`)
progressEventSource.value.onmessage = event => { progressEventSource.value.onmessage = event => {
const progress = JSON.parse(event.data) const progress = JSON.parse(event.data)
if (progress) { if (progress) {
if (!progress.enable) { if (!progress.enable) {
progressText.value = '请稍候 ...' progressText.value = t('dialog.transferQueue.processing')
progressValue.value = 0 progressValue.value = 0
if (refreshFlag.value) { if (refreshFlag.value) {
refreshFlag.value = false refreshFlag.value = false
@@ -138,7 +143,7 @@ onUnmounted(() => {
<VDialog scrollable max-width="50rem" :fullscreen="!display.mdAndUp.value"> <VDialog scrollable max-width="50rem" :fullscreen="!display.mdAndUp.value">
<VCard class="mx-auto" width="100%"> <VCard class="mx-auto" width="100%">
<VCardItem> <VCardItem>
<VCardTitle>整理队列</VCardTitle> <VCardTitle>{{ t('dialog.transferQueue.title') }}</VCardTitle>
</VCardItem> </VCardItem>
<VDialogCloseBtn @click="emit('close')" /> <VDialogCloseBtn @click="emit('close')" />
<VDivider /> <VDivider />
@@ -151,7 +156,7 @@ onUnmounted(() => {
<VCardItem v-if="dataList.length > 0 && progressValue > 0" class="text-center pt-2"> <VCardItem v-if="dataList.length > 0 && progressValue > 0" class="text-center pt-2">
<span class="text-sm">{{ progressText }}</span> <span class="text-sm">{{ progressText }}</span>
</VCardItem> </VCardItem>
<VCardText v-if="dataList.length === 0" class="text-center"> 没有正在整理的任务 </VCardText> <VCardText v-if="dataList.length === 0" class="text-center"> {{ t('dialog.transferQueue.noTasks') }} </VCardText>
<VCardText> <VCardText>
<VTabs v-model="activeTab" show-arrows class="v-tabs-pill" stacked> <VTabs v-model="activeTab" show-arrows class="v-tabs-pill" stacked>
<VTab <VTab
@@ -169,7 +174,7 @@ onUnmounted(() => {
<VListItem v-for="task in activeTasks"> <VListItem v-for="task in activeTasks">
<VListItemTitle>{{ task.fileitem.name }}</VListItemTitle> <VListItemTitle>{{ task.fileitem.name }}</VListItemTitle>
<VListItemSubtitle> <VListItemSubtitle>
大小{{ formatFileSize(task.fileitem.size || 0) }} {{ t('dialog.transferQueue.sizeTitle') }}{{ formatFileSize(task.fileitem.size || 0) }}
<VChip size="small" :color="getStateColor(task.state)" class="ms-2"> <VChip size="small" :color="getStateColor(task.state)" class="ms-2">
{{ stateDict[task.state] }} {{ stateDict[task.state] }}
</VChip> </VChip>
+12 -8
View File
@@ -2,6 +2,10 @@
import { isNullOrEmptyObject } from '@/@core/utils' import { isNullOrEmptyObject } from '@/@core/utils'
import api from '@/api' import api from '@/api'
import { useToast } from 'vue-toast-notification' import { useToast } from 'vue-toast-notification'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
// 定义事件 // 定义事件
const emit = defineEmits(['done', 'close']) const emit = defineEmits(['done', 'close'])
@@ -89,17 +93,17 @@ async function handleDone() {
// 认证处理 // 认证处理
async function checkUser() { async function checkUser() {
if (!authForm.value.site) { if (!authForm.value.site) {
$toast.error('请选择认证站点!') $toast.error(t('dialog.userAuth.selectSiteRequired'))
return return
} }
if (!authSites.value[authForm.value.site]) { if (!authSites.value[authForm.value.site]) {
$toast.error('站点配置不存在!') $toast.error(t('dialog.userAuth.siteConfigNotExist'))
return return
} }
if (formFields.value.length > 0) { if (formFields.value.length > 0) {
for (const field of formFields.value) { for (const field of formFields.value) {
if (!authForm.value.params[field.site.toUpperCase() + '_' + field.key.toUpperCase()]) { if (!authForm.value.params[field.site.toUpperCase() + '_' + field.key.toUpperCase()]) {
$toast.error(`请输入${field.name}`) $toast.error(t('dialog.userAuth.fieldRequired', { name: field.name }))
return return
} }
} }
@@ -108,13 +112,13 @@ async function checkUser() {
try { try {
const result: { [key: string]: any } = await api.post(`site/auth`, authForm.value) const result: { [key: string]: any } = await api.post(`site/auth`, authForm.value)
if (result.success) { if (result.success) {
$toast.success('用户认证成功,请重新登录!') $toast.success(t('dialog.userAuth.authSuccess'))
// 1秒后刷新页面 // 1秒后刷新页面
setTimeout(() => { setTimeout(() => {
emit('done') emit('done')
}, 1000) }, 1000)
} else { } else {
$toast.error(`认证失败:${result.message}`) $toast.error(t('dialog.userAuth.authFailed', { message: result.message }))
} }
} catch (e) { } catch (e) {
console.error(e) console.error(e)
@@ -130,7 +134,7 @@ onMounted(async () => {
<template> <template>
<VDialog width="40rem" max-height="85vh"> <VDialog width="40rem" max-height="85vh">
<VCard title="用户认证" class="rounded-t"> <VCard :title="t('dialog.userAuth.title')" class="rounded-t">
<VDialogCloseBtn @click="emit('close')" /> <VDialogCloseBtn @click="emit('close')" />
<VCardText> <VCardText>
<VRow> <VRow>
@@ -140,7 +144,7 @@ onMounted(async () => {
:items="dropdownItems" :items="dropdownItems"
item-value="key" item-value="key"
item-title="name" item-title="name"
label="选择认证站点" :label="t('dialog.userAuth.selectSite')"
item-props item-props
> >
</VSelect> </VSelect>
@@ -169,7 +173,7 @@ onMounted(async () => {
size="large" size="large"
:disabled="loading" :disabled="loading"
> >
开始认证 {{ t('dialog.userAuth.authBtn') }}
</VBtn> </VBtn>
</VCardText> </VCardText>
</VCard> </VCard>
+14 -10
View File
@@ -9,6 +9,10 @@ import api from '@/api'
import WorkflowSidebar from '@/layouts/components/WorkflowSidebar.vue' import WorkflowSidebar from '@/layouts/components/WorkflowSidebar.vue'
import DropzoneBackground from '@/layouts/components/DropzoneBackground.vue' import DropzoneBackground from '@/layouts/components/DropzoneBackground.vue'
import ImportCodeDialog from '@/components/dialog/ImportCodeDialog.vue' import ImportCodeDialog from '@/components/dialog/ImportCodeDialog.vue'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
const { onConnect, addEdges, nodes, edges, addNodes, screenToFlowCoordinate } = useVueFlow() const { onConnect, addEdges, nodes, edges, addNodes, screenToFlowCoordinate } = useVueFlow()
@@ -18,7 +22,7 @@ const { onDragOver, onDrop, onDragLeave, isDragOver } = useDragAndDrop()
onConnect((connection: Connection) => { onConnect((connection: Connection) => {
// 双重校验 // 双重校验
if (!isValidConnection(connection)) { if (!isValidConnection(connection)) {
$toast.warning('非法连接:不能连接自身或同类型端口!') $toast.warning(t('dialog.workflowActions.invalidConnection'))
return return
} }
addEdges(connection) addEdges(connection)
@@ -67,7 +71,7 @@ const loadComponent = async (componentName: string) => {
if (component) { if (component) {
return ((await component()) as any).default return ((await component()) as any).default
} }
throw new Error(`组件 ${componentName} 未找到`) throw new Error(t('dialog.workflowActions.componentNotFound', { component: componentName }))
} }
// 将所有components中的组件加载到nodeTypes中 // 将所有components中的组件加载到nodeTypes中
@@ -132,7 +136,7 @@ function handleComponentClick(action: any) {
addNodes(newNode) addNodes(newNode)
// 显示提示 // 显示提示
$toast.success('已添加组件到画布') $toast.success(t('dialog.workflowActions.componentAdded'))
} }
// 调用API 编辑任务 // 调用API 编辑任务
@@ -144,10 +148,10 @@ async function updateWorkflow() {
try { try {
const result: { [key: string]: string } = await api.put(`workflow/${workflowForm.value.id}`, workflowForm.value) const result: { [key: string]: string } = await api.put(`workflow/${workflowForm.value.id}`, workflowForm.value)
if (result.success) { if (result.success) {
$toast.success(`保存任务流程成功!`) $toast.success(t('dialog.workflowActions.saveSuccess'))
emit('save') emit('save')
} else { } else {
$toast.error(`保存任务流程失败:${result.message}`) $toast.error(t('dialog.workflowActions.saveFailed', { message: result.message }))
} }
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@@ -164,10 +168,10 @@ function saveCodeString(type: string, code: any) {
edges.value = codeObject.flows || [] edges.value = codeObject.flows || []
} }
importCodeDialog.value = false importCodeDialog.value = false
$toast.success('导入成功!') $toast.success(t('dialog.workflowActions.importSuccess'))
} }
} catch (error) { } catch (error) {
$toast.error('导入失败!') $toast.error(t('dialog.workflowActions.importFailed'))
console.error(error) console.error(error)
} }
} }
@@ -176,7 +180,7 @@ function saveCodeString(type: string, code: any) {
function shareWorkflow() { function shareWorkflow() {
const codeString = JSON.stringify({ actions: nodes.value, flows: edges.value }) const codeString = JSON.stringify({ actions: nodes.value, flows: edges.value })
navigator.clipboard.writeText(codeString) navigator.clipboard.writeText(codeString)
$toast.success('任务流程代码已复制到剪贴板!') $toast.success(t('dialog.workflowActions.codeCopied'))
} }
onMounted(() => { onMounted(() => {
@@ -202,7 +206,7 @@ const isMacOS = computed(() => {
<VIcon size="large" color="white" icon="mdi-close" /> <VIcon size="large" color="white" icon="mdi-close" />
</VBtn> </VBtn>
</VToolbarItems> </VToolbarItems>
<VToolbarTitle> 编辑流程 - {{ workflow?.name }} </VToolbarTitle> <VToolbarTitle> {{ t('dialog.workflowActions.title') }} - {{ workflow?.name }} </VToolbarTitle>
<VSpacer></VSpacer> <VSpacer></VSpacer>
<VToolbarItems> <VToolbarItems>
<VBtn icon variant="text" @click="importCodeDialog = true" class="ms-2"> <VBtn icon variant="text" @click="importCodeDialog = true" class="ms-2">
@@ -248,7 +252,7 @@ const isMacOS = computed(() => {
<ImportCodeDialog <ImportCodeDialog
v-if="importCodeDialog" v-if="importCodeDialog"
v-model="importCodeDialog" v-model="importCodeDialog"
title="导入任务流程" :title="t('dialog.workflowActions.importTitle')"
dataType="workflow" dataType="workflow"
@close="importCodeDialog = false" @close="importCodeDialog = false"
@save="saveCodeString" @save="saveCodeString"
+25 -15
View File
@@ -5,6 +5,10 @@ import { doneNProgress, startNProgress } from '@/api/nprogress'
import { requiredValidator } from '@/@validators' import { requiredValidator } from '@/@validators'
import api from '@/api' import api from '@/api'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
// 输入参数 // 输入参数
const props = defineProps({ const props = defineProps({
@@ -13,7 +17,9 @@ const props = defineProps({
}) })
// 新增或修改字样 // 新增或修改字样
const title = computed(() => (props.workflow ? '编辑' : '创建')) const title = computed(() =>
props.workflow ? t('dialog.workflowAddEdit.editTitle') : t('dialog.workflowAddEdit.addTitle'),
)
// 显示器宽度 // 显示器宽度
const display = useDisplay() const display = useDisplay()
@@ -38,17 +44,17 @@ const $toast = useToast()
// 调用API 新增任务 // 调用API 新增任务
async function addWorkflow() { async function addWorkflow() {
if (!workflowForm.value.name || !workflowForm.value.timer) { if (!workflowForm.value.name || !workflowForm.value.timer) {
$toast.error('请填写完整信息!') $toast.error(t('dialog.workflowAddEdit.nameRequired'))
return return
} }
startNProgress() startNProgress()
try { try {
const result: { [key: string]: string } = await api.post('workflow/', workflowForm.value) const result: { [key: string]: string } = await api.post('workflow/', workflowForm.value)
if (result.success) { if (result.success) {
$toast.success(`创建任务成功,请编辑流程!`) $toast.success(t('dialog.workflowAddEdit.addSuccess'))
emit('save') emit('save')
} else { } else {
$toast.error(`创建任务失败:${result.message}`) $toast.error(t('dialog.workflowAddEdit.addFailed', { message: result.message }))
} }
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@@ -59,17 +65,17 @@ async function addWorkflow() {
// 调用API 编辑任务 // 调用API 编辑任务
async function editWorkflow() { async function editWorkflow() {
if (!workflowForm.value.name || !workflowForm.value.timer) { if (!workflowForm.value.name || !workflowForm.value.timer) {
$toast.error('请填写完整信息!') $toast.error(t('dialog.workflowAddEdit.nameRequired'))
return return
} }
startNProgress() startNProgress()
try { try {
const result: { [key: string]: string } = await api.put(`workflow/${workflowForm.value.id}`, workflowForm.value) const result: { [key: string]: string } = await api.put(`workflow/${workflowForm.value.id}`, workflowForm.value)
if (result.success) { if (result.success) {
$toast.success(`修改任务成功!`) $toast.success(t('dialog.workflowAddEdit.editSuccess'))
emit('save') emit('save')
} else { } else {
$toast.error(`修改任务失败:${result.message}`) $toast.error(t('dialog.workflowAddEdit.editFailed', { message: result.message }))
} }
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@@ -80,7 +86,7 @@ async function editWorkflow() {
<template> <template>
<VDialog scrollable :close-on-back="false" eager max-width="30rem" :fullscreen="!display.mdAndUp.value"> <VDialog scrollable :close-on-back="false" eager max-width="30rem" :fullscreen="!display.mdAndUp.value">
<VCard :title="`${title}任务`" class="rounded-t"> <VCard :title="title" class="rounded-t">
<VDialogCloseBtn @click="emit('close')" /> <VDialogCloseBtn @click="emit('close')" />
<VDivider /> <VDivider />
<VCardText> <VCardText>
@@ -89,24 +95,28 @@ async function editWorkflow() {
<VCol cols="12"> <VCol cols="12">
<VTextField <VTextField
v-model="workflowForm.name" v-model="workflowForm.name"
label="别名" :label="t('dialog.workflowAddEdit.name')"
:rules="[requiredValidator]" :rules="[requiredValidator]"
persistent-hint persistent-hint
hint="任务名称" :hint="t('dialog.workflowAddEdit.namePlaceholder')"
/> />
</VCol> </VCol>
<VCol cols="12"> <VCol cols="12">
<VCronField <VCronField
v-model="workflowForm.timer" v-model="workflowForm.timer"
label="定时" :label="t('dialog.workflowAddEdit.schedule')"
:rules="[requiredValidator]" :rules="[requiredValidator]"
placeholder="5位cron表达式" placeholder="5位cron表达式"
persistent-hint persistent-hint
hint="任务执行周期" :hint="t('dialog.workflowAddEdit.cronExprDesc')"
/> />
</VCol> </VCol>
<VCol cols="12"> <VCol cols="12">
<VTextarea v-model="workflowForm.description" label="任务描述" /> <VTextarea
v-model="workflowForm.description"
:label="t('dialog.workflowAddEdit.desc')"
:placeholder="t('dialog.workflowAddEdit.descPlaceholder')"
/>
</VCol> </VCol>
</VRow> </VRow>
</VForm> </VForm>
@@ -122,10 +132,10 @@ async function editWorkflow() {
prepend-icon="mdi-content-save" prepend-icon="mdi-content-save"
class="px-5" class="px-5"
> >
保存 {{ t('dialog.workflowAddEdit.confirm') }}
</VBtn> </VBtn>
<VBtn v-else block color="primary" variant="elevated" @click="addWorkflow" prepend-icon="mdi-plus" class="px-5"> <VBtn v-else block color="primary" variant="elevated" @click="addWorkflow" prepend-icon="mdi-plus" class="px-5">
创建 {{ t('dialog.workflowAddEdit.confirm') }}
</VBtn> </VBtn>
</VCardActions> </VCardActions>
</VCard> </VCard>
+307
View File
@@ -15,6 +15,8 @@ export default {
send: 'Send', send: 'Send',
noData: 'No Data', noData: 'No Data',
noContent: 'No Content Found', noContent: 'No Content Found',
all: 'All',
default: 'Default',
}, },
theme: { theme: {
light: 'Light', light: 'Light',
@@ -835,5 +837,310 @@ export default {
scanned: 'QR code scanned, please confirm login', scanned: 'QR code scanned, please confirm login',
complete: 'Complete', complete: 'Complete',
}, },
aliyunAuth: {
loginTitle: 'Aliyun Drive Login',
scanQrCode: 'Please scan with Aliyun Drive App',
scanned: 'QR code scanned',
complete: 'Complete',
},
rcloneConfig: {
title: 'RClone Configuration',
filePath: 'Rclone Config File Path',
fileContent: 'Rclone Config File Content',
defaultContent:
'# Please fill in the rclone configuration file content here \n# Please refer to https://rclone.org/docs/ \n# Storage node name must be: MP',
complete: 'Complete',
},
alistConfig: {
title: 'Alist Configuration',
serverUrl: 'Alist Server URL',
username: 'Username',
password: 'Password',
tokenUrl: 'Token URL',
loginType: 'Login Type',
loginTypeOptions: {
guest: 'Guest',
username: 'Username & Password',
token: 'Token',
},
complete: 'Complete',
},
workflowAddEdit: {
addTitle: 'Add Workflow',
editTitle: 'Edit Workflow',
name: 'Name',
namePlaceholder: 'Workflow name',
desc: 'Description',
descPlaceholder: 'Workflow description',
enabled: 'Enabled',
schedule: 'Schedule',
cronExpr: 'Cron Expression',
cronExprDesc: 'Cron expression for workflow scheduling',
nameRequired: 'Please fill in all required information!',
addSuccess: 'Workflow created successfully. Please edit the process!',
addFailed: 'Failed to create workflow: {message}',
editSuccess: 'Workflow modified successfully!',
editFailed: 'Failed to modify workflow: {message}',
cancel: 'Cancel',
confirm: 'Confirm',
},
workflowActions: {
title: 'Edit Workflow',
noActionsMessage: 'No actions in the workflow, please add actions',
addAction: 'Add Action',
editAction: 'Edit Action',
deleteAction: 'Delete Action',
moveUp: 'Move Up',
moveDown: 'Move Down',
nameLabel: 'Action Name',
nameRequired: 'Action name cannot be empty',
typeLabel: 'Action Type',
typeRequired: 'Action type cannot be empty',
paramsLabel: 'Action Parameters',
outputLabel: 'Action Output',
saveAction: 'Save Action',
cancelAction: 'Cancel',
confirmDeleteTitle: 'Confirm Delete Action',
confirmDeleteMessage: 'Are you sure you want to delete this action? This operation cannot be undone.',
yesDelete: 'Yes, Delete',
noCancel: 'Cancel',
invalidConnection: 'Invalid connection: Cannot connect to self or same type port!',
componentNotFound: 'Component {component} not found',
componentAdded: 'Component added to canvas',
saveSuccess: 'Workflow saved successfully!',
saveFailed: 'Failed to save workflow: {message}',
importTitle: 'Import Workflow',
importSuccess: 'Import successful!',
importFailed: 'Import failed!',
codeCopied: 'Workflow code copied to clipboard!',
},
siteCookieUpdate: {
title: 'Update Site Cookie',
checkHint: 'Checking login status, please wait...',
confirmUpdateTitle: 'Confirm Update',
confirmUpdateMessage: "Do you want to update this site's cookie with the local cookie?",
processing: 'Processing...',
success: 'Cookie updated successfully',
failed: 'Cookie update failed',
confirm: 'Confirm',
cancel: 'Cancel',
},
siteAddEdit: {
addTitle: 'Add Site',
editTitle: 'Edit Site',
nameLabel: 'Site Name',
urlLabel: 'Site URL',
iconLabel: 'Site Icon',
uploadIcon: 'Upload Icon',
cookie: 'Cookie',
rssUrl: 'RSS URL',
enableLabel: 'Enable',
pubEnableLabel: 'Public Resources',
priorityLabel: 'Priority',
signInLabel: 'Sign In',
proxies: 'Proxies',
userInfo: 'User Info',
cancel: 'Cancel',
confirm: 'Save',
},
pluginConfig: {
title: 'Plugin Configuration',
save: 'Save',
close: 'Close',
},
pluginData: {
title: 'Plugin Data',
save: 'Save',
close: 'Close',
},
pluginMarketSetting: {
title: 'Plugin Market Settings',
repoUrl: 'Plugin Repository URL',
close: 'Close',
save: 'Save',
},
userAuth: {
title: 'User Authentication',
codeLabel: 'Authentication Code',
codePlaceholder: 'Please enter the authentication code',
authBtn: 'Authenticate',
closeBtn: 'Close',
selectSite: 'Select Authentication Site',
selectSiteRequired: 'Please select an authentication site!',
siteConfigNotExist: 'Site configuration does not exist!',
fieldRequired: 'Please enter {name}!',
authSuccess: 'User authentication successful, please login again!',
authFailed: 'Authentication failed: {message}',
},
transferQueue: {
title: 'Transfer Queue',
name: 'Name',
type: 'Type',
state: 'State',
progress: 'Progress',
startTime: 'Start Time',
speedTitle: 'Speed',
pathTitle: 'Path',
sizeTitle: 'Size',
waitingState: 'Waiting',
runningState: 'Organizing',
finishedState: 'Completed',
failedState: 'Failed',
cancelledState: 'Cancelled',
noTasks: 'No tasks being organized',
processing: 'Please wait ...',
stopAll: 'Stop All',
startAll: 'Start All',
refresh: 'Refresh',
close: 'Close',
},
reorganize: {
title: 'Reorganize',
sourceTitle: 'Source Files',
targetTitle: 'Target Files',
processingTitle: 'Processing',
confirmTitle: 'Confirm',
selectFile: 'Select File',
selectTarget: 'Select Target',
selectMediaType: 'Select Media Type',
movie: 'Movie',
tv: 'TV Show',
selectTmdbId: 'Select TMDB ID',
selectMediaInfo: 'Select Media Info',
selectTargetPath: 'Select Target Path',
selectTargetDir: 'Select Target Directory',
selectFileName: 'Select Filename',
confirmMoving: 'Please confirm moving!',
sourceLabel: 'Source:',
targetLabel: 'Target Directory:',
filenameLabel: 'Filename:',
close: 'Close',
next: 'Next',
previous: 'Previous',
confirm: 'Confirm',
},
forkSubscribe: {
title: 'Fork Subscription',
selectSubscriber: 'Select Target User',
overwriteExisting: 'Overwrite Existing',
overwriteExistingHint: 'Whether to overwrite if the target user already has this subscription',
confirm: 'Confirm',
cancel: 'Cancel',
},
subscribeEdit: {
titleDefault: 'Default Subscription Rule',
titleEditFormat: 'Edit Subscription - {name} {season}',
seasonFormat: 'Season {number}',
tabs: {
basic: 'Basic',
advance: 'Advanced',
},
searchKeyword: 'Search Keyword',
searchKeywordHint: 'Keyword used when searching sites',
totalEpisode: 'Total Episodes',
totalEpisodeHint: 'Total number of episodes',
startEpisode: 'Start Episode',
startEpisodeHint: 'Episode number to start subscription',
quality: 'Quality',
qualityHint: 'Resource quality for subscription',
resolution: 'Resolution',
resolutionHint: 'Resource resolution for subscription',
effect: 'Effect',
effectHint: 'Resource effect for subscription',
subscribeSites: 'Subscribe Sites',
subscribeSitesHint: 'Sites to subscribe from, use system settings if none selected',
downloader: 'Downloader',
downloaderHint: 'Specify which downloader to use for this subscription',
savePath: 'Save Path',
savePathHint: 'Specify download path for this subscription, leave empty to use default directories',
bestVersion: 'Version Upgrade',
bestVersionHint: 'Enable version upgrade based on priority',
searchImdbid: 'Use ImdbID Search',
searchImdbidHint: 'Use ImdbID for precise resource searching',
showEditDialog: 'Edit More Rules When Subscribing',
showEditDialogHint: 'Show this edit dialog when adding subscription',
include: 'Include (Keywords, Regex)',
includeHint: 'Include rules, supports regex',
exclude: 'Exclude (Keywords, Regex)',
excludeHint: 'Exclude rules, supports regex',
filterGroups: 'Priority Rule Groups',
filterGroupsHint: 'Filter subscription based on selected rule groups',
episodeGroup: 'Episode Group',
episodeGroupHint: 'Recognize and scrape based on specific episode group',
season: 'Season',
seasonHint: 'Specify season for subscription',
mediaCategory: 'Custom Category',
mediaCategoryHint: 'Specify category name, auto-detect if empty',
customWords: 'Custom Recognition Words',
customWordsHint: 'Recognition words only for this subscription',
customWordsPlaceholder:
'Filter word\nReplaced word => Replacement\nFront position word <> Back position word >> Episode offset (EP)\nReplaced word => Replacement && Front position word <> Back position word >> Episode offset (EP)\nReplacement format: {[tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx]} to specify TMDBID/Douban ID, where s and e are season and episode (optional)',
cancelSubscribe: 'Cancel Subscription',
cancelSubscribeConfirm: 'Are you sure you want to cancel this subscription?',
save: 'Save',
},
subscribeFiles: {
title: 'Downloaded Files',
noFilesMessage: 'No files',
close: 'Close',
downloadTab: 'Downloaded Files',
libraryTab: 'Media Library Files',
episodeColumn: 'Episode',
torrentColumn: 'Torrent',
fileColumn: 'File',
itemsPerPage: 'Items per page',
pageText: '{0}-{1} of {2} items',
loadingText: 'Loading...',
noData: 'No data',
season: 'Season {number}',
},
subscribeHistory: {
title: 'Subscription History',
name: 'Name',
time: 'Time',
type: 'Type',
mediaName: 'Media Name',
message: 'Message',
torrents: 'Torrents',
episodes: 'Episodes',
noRecords: 'No records',
close: 'Close',
},
subscribeSeason: {
title: 'Select Season',
seasonTitle: 'Season {number}',
close: 'Close',
confirm: 'Confirm',
},
siteUserData: {
title: 'Site User Data',
updateTime: 'Update Time',
username: 'Username',
uploadTitle: 'Upload',
uploadTotal: 'Total Upload',
downloadTitle: 'Download',
downloadTotal: 'Total Download',
seedingTitle: 'Seeding',
seedingCount: 'Total Seeding Count',
seedingSize: 'Total Seeding Size',
userLevel: 'User Level',
msgCount: 'Unread Messages',
inviteCount: 'Invites',
bonus: 'Bonus Points',
ratio: 'Share Ratio',
joinTime: 'Join Time',
trafficHistory: 'Traffic History',
seedingDistribution: 'Seeding Distribution',
volumeTitle: 'Volume',
countTitle: 'Count: ',
noData: 'None',
refreshing: 'Refreshing site data...',
close: 'Close',
},
siteResource: {
title: 'Site Resources',
searchHint: 'Search Resources',
close: 'Close',
},
}, },
} }
+306
View File
@@ -15,6 +15,8 @@ export default {
send: '发送', send: '发送',
noData: '暂无数据', noData: '暂无数据',
noContent: '没有找到相关内容', noContent: '没有找到相关内容',
all: '全部',
default: '默认',
}, },
theme: { theme: {
light: '浅色', light: '浅色',
@@ -820,5 +822,309 @@ export default {
scanned: '已扫码,请确认登录', scanned: '已扫码,请确认登录',
complete: '完成', complete: '完成',
}, },
aliyunAuth: {
loginTitle: '阿里云盘登录',
scanQrCode: '请用阿里云盘 App 扫码',
scanned: '已扫码',
complete: '完成',
},
rcloneConfig: {
title: 'RClone配置',
filePath: 'rclone配置文件路径',
fileContent: 'rclone配置文件内容',
defaultContent: '# 请在此处填写rclone配置文件内容 \n# 请参考 https://rclone.org/docs/ \n# 存储节点名必须为:MP',
complete: '完成',
},
alistConfig: {
title: 'Alist配置',
serverUrl: 'Alist服务地址',
username: '用户名',
password: '密码',
tokenUrl: '获取Token地址',
loginType: '登录方式',
loginTypeOptions: {
guest: '访客',
username: '用户名密码',
token: 'Token',
},
complete: '完成',
},
workflowAddEdit: {
addTitle: '添加工作流',
editTitle: '编辑工作流',
name: '名称',
namePlaceholder: '工作流名称',
desc: '描述',
descPlaceholder: '工作流描述',
enabled: '启用',
schedule: '定时执行',
cronExpr: 'Cron表达式',
cronExprDesc: '工作流定时执行的cron表达式',
nameRequired: '请填写完整信息!',
addSuccess: '创建任务成功,请编辑流程!',
addFailed: '创建任务失败:{message}',
editSuccess: '修改任务成功!',
editFailed: '修改任务失败:{message}',
cancel: '取消',
confirm: '确认',
},
workflowActions: {
title: '编辑流程',
noActionsMessage: '工作流没有动作,请添加动作',
addAction: '添加动作',
editAction: '编辑动作',
deleteAction: '删除动作',
moveUp: '上移',
moveDown: '下移',
nameLabel: '动作名称',
nameRequired: '动作名称不能为空',
typeLabel: '动作类型',
typeRequired: '动作类型不能为空',
paramsLabel: '动作参数',
outputLabel: '动作输出',
saveAction: '保存动作',
cancelAction: '取消',
confirmDeleteTitle: '确认删除动作',
confirmDeleteMessage: '确定要删除此动作吗?此操作无法撤销。',
yesDelete: '是的,删除',
noCancel: '取消',
invalidConnection: '非法连接:不能连接自身或同类型端口!',
componentNotFound: '组件 {component} 未找到',
componentAdded: '已添加组件到画布',
saveSuccess: '保存任务流程成功!',
saveFailed: '保存任务流程失败:{message}',
importTitle: '导入任务流程',
importSuccess: '导入成功!',
importFailed: '导入失败!',
codeCopied: '任务流程代码已复制到剪贴板!',
},
siteCookieUpdate: {
title: '更新站点Cookie',
checkHint: '正在检查登录状态,请稍候...',
confirmUpdateTitle: '确认更新',
confirmUpdateMessage: '是否要用本地Cookie更新该站点的Cookie',
processing: '处理中...',
success: '更新Cookie成功',
failed: '更新Cookie失败',
confirm: '确认',
cancel: '取消',
},
siteAddEdit: {
addTitle: '添加站点',
editTitle: '编辑站点',
nameLabel: '站点名称',
urlLabel: '站点URL',
iconLabel: '站点图标',
uploadIcon: '上传图标',
cookie: 'Cookie',
rssUrl: 'RSS链接',
enableLabel: '启用',
pubEnableLabel: '资源公开',
priorityLabel: '优先级',
signInLabel: '签到',
proxies: '代理',
userInfo: '用户信息',
cancel: '取消',
confirm: '保存',
},
pluginConfig: {
title: '插件配置',
save: '保存',
close: '关闭',
},
pluginData: {
title: '插件数据',
save: '保存',
close: '关闭',
},
pluginMarketSetting: {
title: '插件市场设置',
repoUrl: '插件仓库地址',
close: '关闭',
save: '保存',
},
userAuth: {
title: '用户认证',
codeLabel: '认证码',
codePlaceholder: '请输入认证码',
authBtn: '开始认证',
closeBtn: '关闭',
selectSite: '选择认证站点',
selectSiteRequired: '请选择认证站点!',
siteConfigNotExist: '站点配置不存在!',
fieldRequired: '请输入{name}',
authSuccess: '用户认证成功,请重新登录!',
authFailed: '认证失败:{message}',
},
transferQueue: {
title: '整理队列',
name: '名称',
type: '类型',
state: '状态',
progress: '进度',
startTime: '开始时间',
speedTitle: '速度',
pathTitle: '路径',
sizeTitle: '大小',
waitingState: '等待中',
runningState: '正在整理',
finishedState: '完成',
failedState: '失败',
cancelledState: '已取消',
noTasks: '没有正在整理的任务',
processing: '请稍候 ...',
stopAll: '全部停止',
startAll: '全部开始',
refresh: '刷新',
close: '关闭',
},
reorganize: {
title: '整理',
sourceTitle: '源文件',
targetTitle: '目标文件',
processingTitle: '处理中',
confirmTitle: '确认',
selectFile: '选择文件',
selectTarget: '选择目标',
selectMediaType: '选择媒体类型',
movie: '电影',
tv: '电视剧',
selectTmdbId: '选择TMDB ID',
selectMediaInfo: '选择媒体信息',
selectTargetPath: '选择目标路径',
selectTargetDir: '选择目标目录',
selectFileName: '选择文件名',
confirmMoving: '请确认移动!',
sourceLabel: '源文件:',
targetLabel: '目标目录:',
filenameLabel: '文件名:',
close: '关闭',
next: '下一步',
previous: '上一步',
confirm: '确认',
},
forkSubscribe: {
title: '复制订阅',
selectSubscriber: '选择复制目标',
overwriteExisting: '覆盖现有订阅',
overwriteExistingHint: '目标用户已存在该订阅时,是否覆盖',
confirm: '确认',
cancel: '取消',
},
subscribeEdit: {
titleDefault: '默认订阅规则',
titleEditFormat: '编辑订阅 - {name} {season}',
seasonFormat: '第 {number} 季',
tabs: {
basic: '基础',
advance: '进阶',
},
searchKeyword: '搜索关键词',
searchKeywordHint: '指定搜索站点时使用的关键词',
totalEpisode: '总集数',
totalEpisodeHint: '剧集总集数',
startEpisode: '开始集数',
startEpisodeHint: '开始订阅集数',
quality: '质量',
qualityHint: '订阅资源质量',
resolution: '分辨率',
resolutionHint: '订阅资源分辨率',
effect: '特效',
effectHint: '订阅资源特效',
subscribeSites: '订阅站点',
subscribeSitesHint: '订阅的站点范围,不选使用系统设置',
downloader: '下载器',
downloaderHint: '指定该订阅使用的下载器',
savePath: '保存路径',
savePathHint: '指定该订阅的下载保存路径,留空自动使用设定的下载目录',
bestVersion: '洗版',
bestVersionHint: '根据洗版优先级进行洗版订阅',
searchImdbid: '使用 ImdbID 搜索',
searchImdbidHint: '开使用 ImdbID 精确搜索资源',
showEditDialog: '订阅时编辑更多规则',
showEditDialogHint: '添加订阅时显示此编辑订阅对话框',
include: '包含(关键字、正则式)',
includeHint: '包含规则,支持正则表达式',
exclude: '排除(关键字、正则式)',
excludeHint: '排除规则,支持正则表达式',
filterGroups: '优先级规则组',
filterGroupsHint: '按选定的过滤规则组对订阅进行过滤',
episodeGroup: '指定剧集组',
episodeGroupHint: '按特定剧集组识别和刮削',
season: '指定季',
seasonHint: '指定任意季订阅',
mediaCategory: '自定义类别',
mediaCategoryHint: '指定类别名称,留空自动识别',
customWords: '自定义识别词',
customWordsHint: '只对该订阅使用的识别词',
customWordsPlaceholder:
'屏蔽词\n被替换词 => 替换词\n前定位词 <> 后定位词 >> 集偏移量(EP\n被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)\n其中替换词支持格式:{[tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID识别,其中s、e为季数和集数(可选)',
cancelSubscribe: '取消订阅',
save: '保存',
cancelSubscribeConfirm: '是否确认取消订阅?',
},
subscribeFiles: {
title: '已下载文件',
noFilesMessage: '暂无文件',
close: '关闭',
downloadTab: '下载文件',
libraryTab: '媒体库文件',
episodeColumn: '集',
torrentColumn: '种子',
fileColumn: '文件',
itemsPerPage: '每页条数',
pageText: '{0}-{1} 共 {2} 条',
loadingText: '加载中...',
noData: '没有数据',
season: '第 {number} 季',
},
subscribeHistory: {
title: '订阅历史',
name: '名称',
time: '时间',
type: '类型',
mediaName: '媒体名称',
message: '消息',
torrents: '种子',
episodes: '集数',
noRecords: '无记录',
close: '关闭',
},
subscribeSeason: {
title: '选择季',
seasonTitle: '第 {number} 季',
close: '关闭',
confirm: '确认',
},
siteUserData: {
title: '站点用户数据',
updateTime: '更新时间',
username: '用户名',
uploadTitle: '上传量',
uploadTotal: '总上传量',
downloadTitle: '下载量',
downloadTotal: '总下载量',
seedingTitle: '做种数',
seedingCount: '总做种数',
seedingSize: '总做种体积',
userLevel: '用户等级',
msgCount: '未读消息',
inviteCount: '邀请数',
bonus: '积分',
ratio: '分享率',
joinTime: '加入时间',
trafficHistory: '历史流量',
seedingDistribution: '做种分布',
volumeTitle: '体积',
countTitle: '数量:',
noData: '无',
refreshing: '正在刷新站点数据...',
close: '关闭',
},
siteResource: {
title: '站点资源',
searchHint: '搜索资源',
close: '关闭',
},
}, },
} }