更新国际化支持:在多个对话框组件中引入 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

View File

@@ -1,5 +1,9 @@
<script lang="ts" setup>
import api from '@/api'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
// 定义输入
const props = defineProps({
@@ -30,22 +34,32 @@ async function savaAlistConfig() {
<template>
<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')" />
<VCardText>
<VRow>
<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 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 cols="12" md="6">
<VTextField
type="password"
v-model="props.conf.password"
hint="AList登录密码"
label="密码"
:hint="t('dialog.alistConfig.password')"
:label="t('dialog.alistConfig.password')"
persistent-hint
/>
</VCol>
@@ -53,7 +67,9 @@ async function savaAlistConfig() {
</VCardText>
<VCardActions>
<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>
</VCard>
</VDialog>

View File

@@ -1,5 +1,9 @@
<script lang="ts" setup>
import api from '@/api'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
// 定义输入
defineProps({
@@ -16,7 +20,7 @@ const emit = defineEmits(['done', 'close'])
const qrCodeUrl = ref('')
// 下方的提示信息
const text = ref('请用阿里云盘 App 扫码')
const text = ref(t('dialog.aliyunAuth.scanQrCode'))
// 提醒类型
const alertType = ref<'success' | 'info' | 'error' | 'warning' | undefined>('info')
@@ -85,7 +89,7 @@ onUnmounted(() => {
<template>
<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')" />
<VCardText class="pt-2 flex flex-col items-center">
<div class="my-6 rounded text-center p-3 border">
@@ -103,7 +107,9 @@ onUnmounted(() => {
</VCardText>
<VCardActions>
<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>
</VCard>
</VDialog>

View File

@@ -1,5 +1,9 @@
<script lang="ts" setup>
import api from '@/api'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
// 定义输入
const props = defineProps({
@@ -14,7 +18,7 @@ if (!props.conf.filepath) {
}
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>
<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')" />
<VCardText>
<VRow>
<VCol cols="12">
<VTextField v-model="props.conf.filepath" label="rclone配置文件路径" />
<VTextField v-model="props.conf.filepath" :label="t('dialog.rcloneConfig.filePath')" />
</VCol>
<VCol cols="12">
<VAceEditor
@@ -59,7 +63,9 @@ async function savaRcloneConfig() {
</VCardText>
<VCardActions>
<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>
</VCard>
</VDialog>

View File

@@ -4,6 +4,10 @@ import api from '@/api'
import { useDisplay, useTheme } from 'vuetify'
import { formatFileSize } from '@/@core/utils/formatters'
import ProgressDialog from '@/components/dialog/ProgressDialog.vue'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
// 显示器宽度
const display = useDisplay()
@@ -36,11 +40,11 @@ const siteData = computed(() => siteDatas.value[siteDatas.value.length - 1])
const historySeries = computed(() => {
return [
{
name: '上传量',
name: t('dialog.siteUserData.uploadTitle'),
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)),
},
]
@@ -135,7 +139,7 @@ const historyChartOptions = computed(() => {
const seedingSeries = computed(() => {
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)]),
},
]
@@ -162,7 +166,7 @@ const seedingChartOptions = computed(() => {
enabled: true,
x: {
formatter: function (val: number) {
return '数量:' + val.toLocaleString()
return t('dialog.siteUserData.countTitle') + val.toLocaleString()
},
},
style: {
@@ -188,7 +192,7 @@ const seedingChartOptions = computed(() => {
},
},
title: {
text: '数量',
text: t('dialog.siteUserData.countTitle'),
},
tickAmount: 10,
},
@@ -282,7 +286,7 @@ onBeforeMount(async () => {
<VCard class="rounded-t">
<VCardItem>
<VCardTitle
>{{ `数据 - ${props.site?.name}` }}
>{{ t('dialog.siteUserData.title') }} - {{ props.site?.name }}
<IconBtn @click.stop="refreshSiteData" color="info"><VIcon icon="mdi-refresh" /></IconBtn>
</VCardTitle>
<VDialogCloseBtn @click="emit('close')" />
@@ -296,9 +300,9 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%">
<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">
{{ siteData?.user_level || '无' }}
{{ siteData?.user_level || t('dialog.siteUserData.noData') }}
</h5>
</div>
<VAvatar variant="tonal" size="42" rounded>
@@ -314,7 +318,7 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%">
<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">
{{ siteData?.bonus?.toLocaleString() }}
<span class="text-base font-weight-regular" :class="getDiffClass(diffData?.bonus)">
@@ -335,7 +339,7 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%">
<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">
{{ siteData?.ratio }}
<span class="text-base font-weight-regular" :class="getDiffClass(diffData?.ratio)">
@@ -356,7 +360,7 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%">
<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">
{{ formatFileSize(siteData?.upload || 0) }}
<span class="text-base font-weight-regular" :class="getDiffClass(diffData?.upload)">
@@ -377,7 +381,7 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%">
<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">
{{ formatFileSize(siteData?.download || 0) }}
<span class="text-base font-weight-regular" :class="getDiffClass(diffData?.download)">
@@ -398,7 +402,7 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%">
<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">
{{ siteData?.seeding?.toLocaleString() }}
<span class="text-base font-weight-regular" :class="getDiffClass(diffData?.seeding)">
@@ -419,7 +423,7 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%">
<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">
{{ formatFileSize(siteData?.seeding_size || 0) }}
<span class="text-base font-weight-regular" :class="getDiffClass(diffData?.seeding_size)">
@@ -440,7 +444,7 @@ onBeforeMount(async () => {
<VCardText class="d-flex align-center">
<div class="d-flex justify-space-between" style="inline-size: 100%">
<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">
{{ siteData?.join_at?.split(' ')[0] }}
</h5>
@@ -455,7 +459,7 @@ onBeforeMount(async () => {
</VRow>
<VRow>
<VCol>
<VCard title="历史流量">
<VCard :title="t('dialog.siteUserData.trafficHistory')">
<VCardText>
<VApexChart type="line" :options="historyChartOptions" :series="historySeries" :height="300" />
</VCardText>
@@ -464,7 +468,7 @@ onBeforeMount(async () => {
</VRow>
<VRow>
<VCol>
<VCard title="做种分布">
<VCard :title="t('dialog.siteUserData.seedingDistribution')">
<VCardText>
<VApexChart type="scatter" :options="seedingChartOptions" :series="seedingSeries" :height="300" />
</VCardText>
@@ -474,6 +478,6 @@ onBeforeMount(async () => {
</VCardText>
</VCard>
<!-- 进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" text="正在刷新站点数据..." />
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="t('dialog.siteUserData.refreshing')" />
</VDialog>
</template>

View File

@@ -5,6 +5,10 @@ import api from '@/api'
import type { DownloaderConf, FilterRuleGroup, Site, Subscribe, TransferDirectoryConf } from '@/api/types'
import { useDisplay } from 'vuetify'
import { useConfirm } from 'vuetify-use-dialog'
import { useI18n } from 'vue-i18n'
// i18n
const { t } = useI18n()
// 显示器宽度
const display = useDisplay()
@@ -80,7 +84,7 @@ const episodeGroupOptions = computed(() => {
// 生成1到100季的下拉框选项
const seasonItems = ref(
Array.from({ length: 101 }, (_, i) => i).map(item => ({
title: `${item}`,
title: t('dialog.subscribeEdit.seasonFormat', { number: item }),
value: item,
})),
)
@@ -106,7 +110,7 @@ async function loadDownloaderSetting() {
try {
const downloaders: DownloaderConf[] = await api.get('download/clients')
downloaderOptions.value = [
{ title: '默认', value: '' },
{ title: t('common.default'), value: '' },
...downloaders.map((item: { name: any }) => ({
title: item.name,
value: item.name,
@@ -229,8 +233,8 @@ async function getSubscribeInfo() {
// 删除订阅
async function removeSubscribe() {
const isConfirmed = await createConfirm({
title: '确认',
content: `是否确认取消订阅?`,
title: t('common.confirm'),
content: t('dialog.subscribeEdit.cancelSubscribeConfirm'),
})
if (!isConfirmed) return
@@ -268,7 +272,7 @@ const targetDirectories = computed(() => {
// 质量选择框数据
const qualityOptions = ref([
{
title: '全部',
title: t('common.all'),
value: '',
},
{
@@ -308,7 +312,7 @@ const qualityOptions = ref([
// 分辨率选择框数据
const resolutionOptions = ref([
{
title: '全部',
title: t('common.all'),
value: '',
},
{
@@ -328,7 +332,7 @@ const resolutionOptions = ref([
// 特效选择框数据
const effectOptions = ref([
{
title: '全部',
title: t('common.all'),
value: '',
},
{
@@ -362,11 +366,16 @@ onMounted(() => {
<template>
<VDialog scrollable max-width="45rem" :fullscreen="!display.mdAndUp.value">
<VCard
:title="`${
:title="
props.default
? `${props.type}默认订阅规则`
: `编辑订阅 - ${subscribeForm.name} ${subscribeForm.season ? `第 ${subscribeForm.season} 季` : ''}`
}`"
? t('dialog.subscribeEdit.titleDefault')
: t('dialog.subscribeEdit.titleEditFormat', {
name: subscribeForm.name,
season: subscribeForm.season
? t('dialog.subscribeEdit.seasonFormat', { number: subscribeForm.season })
: '',
})
"
class="rounded-t"
>
<VCardText>
@@ -374,10 +383,10 @@ onMounted(() => {
<VForm @submit.prevent="() => {}">
<VTabs v-model="activeTab" show-arrows>
<VTab value="basic">
<div>基础</div>
<div>{{ t('dialog.subscribeEdit.tabs.basic') }}</div>
</VTab>
<VTab value="advance">
<div>进阶</div>
<div>{{ t('dialog.subscribeEdit.tabs.advance') }}</div>
</VTab>
</VTabs>
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
@@ -387,26 +396,26 @@ onMounted(() => {
<VCol cols="12" md="4">
<VTextField
v-model="subscribeForm.keyword"
label="搜索关键词"
hint="指定搜索站点时使用的关键词"
:label="t('dialog.subscribeEdit.searchKeyword')"
:hint="t('dialog.subscribeEdit.searchKeywordHint')"
persistent-hint
/>
</VCol>
<VCol v-if="subscribeForm.type === '电视剧'" cols="12" md="4">
<VTextField
v-model="subscribeForm.total_episode"
label="总集数"
:label="t('dialog.subscribeEdit.totalEpisode')"
:rules="[numberValidator]"
hint="剧集总集数"
:hint="t('dialog.subscribeEdit.totalEpisodeHint')"
persistent-hint
/>
</VCol>
<VCol v-if="subscribeForm.type === '电视剧'" cols="12" md="4">
<VTextField
v-model="subscribeForm.start_episode"
label="开始集数"
:label="t('dialog.subscribeEdit.startEpisode')"
:rules="[numberValidator]"
hint="开始订阅集数"
:hint="t('dialog.subscribeEdit.startEpisodeHint')"
persistent-hint
/>
</VCol>
@@ -415,27 +424,27 @@ onMounted(() => {
<VCol cols="12" md="4">
<VSelect
v-model="subscribeForm.quality"
label="质量"
:label="t('dialog.subscribeEdit.quality')"
:items="qualityOptions"
hint="订阅资源质量"
:hint="t('dialog.subscribeEdit.qualityHint')"
persistent-hint
/>
</VCol>
<VCol cols="12" md="4">
<VSelect
v-model="subscribeForm.resolution"
label="分辨率"
:label="t('dialog.subscribeEdit.resolution')"
:items="resolutionOptions"
hint="订阅资源分辨率"
:hint="t('dialog.subscribeEdit.resolutionHint')"
persistent-hint
/>
</VCol>
<VCol cols="12" md="4">
<VSelect
v-model="subscribeForm.effect"
label="特效"
:label="t('dialog.subscribeEdit.effect')"
:items="effectOptions"
hint="订阅资源特效"
:hint="t('dialog.subscribeEdit.effectHint')"
persistent-hint
/>
</VCol>
@@ -446,10 +455,10 @@ onMounted(() => {
v-model="subscribeForm.sites"
:items="selectSitesOptions"
chips
label="订阅站点"
:label="t('dialog.subscribeEdit.subscribeSites')"
multiple
clearable
hint="订阅的站点范围,不选使用系统设置"
:hint="t('dialog.subscribeEdit.subscribeSitesHint')"
persistent-hint
/>
</VCol>
@@ -459,8 +468,8 @@ onMounted(() => {
<VSelect
v-model="subscribeForm.downloader"
:items="downloaderOptions"
label="下载器"
hint="指定该订阅使用的下载器"
:label="t('dialog.subscribeEdit.downloader')"
:hint="t('dialog.subscribeEdit.downloaderHint')"
persistent-hint
/>
</VCol>
@@ -468,8 +477,8 @@ onMounted(() => {
<VCombobox
v-model="subscribeForm.save_path"
:items="targetDirectories"
label="保存路径"
hint="指定该订阅的下载保存路径,留空自动使用设定的下载目录"
:label="t('dialog.subscribeEdit.savePath')"
:hint="t('dialog.subscribeEdit.savePathHint')"
persistent-hint
/>
</VCol>
@@ -478,24 +487,24 @@ onMounted(() => {
<VCol cols="12" md="4">
<VSwitch
v-model="subscribeForm.best_version"
label="洗版"
hint="根据洗版优先级进行洗版订阅"
:label="t('dialog.subscribeEdit.bestVersion')"
:hint="t('dialog.subscribeEdit.bestVersionHint')"
persistent-hint
/>
</VCol>
<VCol cols="12" md="4">
<VSwitch
v-model="subscribeForm.search_imdbid"
label="使用 ImdbID 搜索"
hint="开使用 ImdbID 精确搜索资源"
:label="t('dialog.subscribeEdit.searchImdbid')"
:hint="t('dialog.subscribeEdit.searchImdbidHint')"
persistent-hint
/>
</VCol>
<VCol v-if="props.default" cols="12" md="4">
<VSwitch
v-model="subscribeForm.show_edit_dialog"
label="订阅时编辑更多规则"
hint="添加订阅时显示此编辑订阅对话框"
:label="t('dialog.subscribeEdit.showEditDialog')"
:hint="t('dialog.subscribeEdit.showEditDialogHint')"
persistent-hint
/>
</VCol>
@@ -508,16 +517,16 @@ onMounted(() => {
<VCol cols="12" md="6">
<VTextField
v-model="subscribeForm.include"
label="包含(关键字、正则式)"
hint="包含规则,支持正则表达式"
:label="t('dialog.subscribeEdit.include')"
:hint="t('dialog.subscribeEdit.includeHint')"
persistent-hint
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="subscribeForm.exclude"
label="排除(关键字、正则式)"
hint="排除规则,支持正则表达式"
:label="t('dialog.subscribeEdit.exclude')"
:hint="t('dialog.subscribeEdit.excludeHint')"
persistent-hint
/>
</VCol>
@@ -530,8 +539,8 @@ onMounted(() => {
chips
multiple
clearable
label="优先级规则组"
hint="按选定的过滤规则组对订阅进行过滤"
:label="t('dialog.subscribeEdit.filterGroups')"
:hint="t('dialog.subscribeEdit.filterGroupsHint')"
persistent-hint
/>
</VCol>
@@ -540,8 +549,8 @@ onMounted(() => {
v-model="subscribeForm.episode_group"
:items="episodeGroupOptions"
:item-props="episodeGroupItemProps"
label="指定剧集组"
hint="按特定剧集组识别和刮削"
:label="t('dialog.subscribeEdit.episodeGroup')"
:hint="t('dialog.subscribeEdit.episodeGroupHint')"
persistent-hint
/>
</VCol>
@@ -549,16 +558,16 @@ onMounted(() => {
<VSelect
v-model="subscribeForm.season"
:items="seasonItems"
label="指定季"
hint="指定任意季订阅"
:label="t('dialog.subscribeEdit.season')"
:hint="t('dialog.subscribeEdit.seasonHint')"
persistent-hint
/>
</VCol>
<VCol cols="12" v-if="!props.default">
<VTextField
v-model="subscribeForm.media_category"
label="自定义类别"
hint="指定类别名称,留空自动识别"
:label="t('dialog.subscribeEdit.mediaCategory')"
:hint="t('dialog.subscribeEdit.mediaCategoryHint')"
persistent-hint
/>
</VCol>
@@ -567,14 +576,10 @@ onMounted(() => {
<VCol cols="12">
<VTextarea
v-model="subscribeForm.custom_words"
label="自定义识别词"
hint="只对该订阅使用的识别词"
:label="t('dialog.subscribeEdit.customWords')"
:hint="t('dialog.subscribeEdit.customWordsHint')"
persistent-hint
placeholder="屏蔽词
被替换词 => 替换词
前定位词 <> 后定位词 >> 集偏移量EP
被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量EP
其中替换词支持格式:{[tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID识别其中s、e为季数和集数可选"
:placeholder="t('dialog.subscribeEdit.customWordsPlaceholder')"
/>
</VCol>
</VRow>
@@ -585,7 +590,7 @@ onMounted(() => {
</VCardText>
<VCardActions class="pt-3">
<VBtn v-if="!props.default" color="error" @click="removeSubscribe" variant="outlined" class="me-3">
取消订阅
{{ t('dialog.subscribeEdit.cancelSubscribe') }}
</VBtn>
<VSpacer />
<VBtn
@@ -594,7 +599,7 @@ onMounted(() => {
prepend-icon="mdi-content-save"
class="px-5"
>
保存
{{ t('dialog.subscribeEdit.save') }}
</VBtn>
</VCardActions>
</VCard>

View File

@@ -2,6 +2,10 @@
import api from '@/api'
import { SubscrbieInfo } from '@/api/types'
import { useDisplay } from 'vuetify'
import { useI18n } from 'vue-i18n'
// i18n
const { t } = useI18n()
// 显示器宽度
const display = useDisplay()
@@ -21,15 +25,15 @@ const subScribeInfo = ref<SubscrbieInfo>()
// 下载文件表头
const downloadHeaders = [
{ title: '集', key: 'episode_number', sortable: true },
{ title: '种子', key: 'torrent_title', sortable: true },
{ title: '文件', key: 'file_path', sortable: true },
{ title: t('dialog.subscribeFiles.episodeColumn'), key: 'episode_number', sortable: true },
{ title: t('dialog.subscribeFiles.torrentColumn'), key: 'torrent_title', sortable: true },
{ title: t('dialog.subscribeFiles.fileColumn'), key: 'file_path', sortable: true },
]
// 媒体库文件表头
const libraryHeaders = [
{ title: '集', key: 'episode_number', sortable: true },
{ title: '文件', key: 'file_path', sortable: true },
{ title: t('dialog.subscribeFiles.episodeColumn'), key: 'episode_number', sortable: true },
{ title: t('dialog.subscribeFiles.fileColumn'), key: 'file_path', sortable: true },
]
// 调用API查询订阅文件信息
@@ -102,7 +106,7 @@ onBeforeMount(() => {
{{ subScribeInfo?.subscribe?.name }}
</div>
<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>
</h1>
<div>{{ subScribeInfo?.subscribe?.year }}</div>
@@ -119,13 +123,13 @@ onBeforeMount(() => {
<VTab value="download" selected-class="v-slide-group-item--active v-tab--selected">
<div>
<VIcon size="20" start icon="mdi-download" />
下载文件
{{ t('dialog.subscribeFiles.downloadTab') }}
</div>
</VTab>
<VTab value="library" selected-class="v-slide-group-item--active v-tab--selected">
<div>
<VIcon size="20" start icon="mdi-filmstrip-box-multiple" />
媒体库文件
{{ t('dialog.subscribeFiles.libraryTab') }}
</div>
</VTab>
</VTabs>
@@ -143,9 +147,9 @@ onBeforeMount(() => {
return-object
fixed-header
hover
items-per-page-text="每页条数"
page-text="{0}-{1} {2} "
loading-text="加载中..."
:items-per-page-text="t('dialog.subscribeFiles.itemsPerPage')"
:page-text="t('dialog.subscribeFiles.pageText')"
:loading-text="t('dialog.subscribeFiles.loadingText')"
>
<template #item.episode_number="{ item }">
<div class="text-high-emphasis pt-1">{{ item.episode_number }}. {{ item.title }}</div>
@@ -158,7 +162,7 @@ onBeforeMount(() => {
<template #item.file_path="{ item }">
<div class="text-xs" v-for="file in item.download">{{ file.file_path }}</div>
</template>
<template #no-data> 没有数据 </template>
<template #no-data> {{ t('dialog.subscribeFiles.noData') }} </template>
</VDataTable>
</div>
</transition>
@@ -176,9 +180,9 @@ onBeforeMount(() => {
return-object
fixed-header
hover
items-per-page-text="每页条数"
page-text="{0}-{1} {2} "
loading-text="加载中..."
:items-per-page-text="t('dialog.subscribeFiles.itemsPerPage')"
:page-text="t('dialog.subscribeFiles.pageText')"
:loading-text="t('dialog.subscribeFiles.loadingText')"
>
<template #item.episode_number="{ item }">
<div class="text-high-emphasis pt-1">{{ item.episode_number }}. {{ item.title }}</div>
@@ -186,7 +190,7 @@ onBeforeMount(() => {
<template #item.file_path="{ item }">
<div class="text-xs" v-for="file in item.library">{{ file.file_path }}</div>
</template>
<template #no-data> 没有数据 </template>
<template #no-data> {{ t('dialog.subscribeFiles.noData') }} </template>
</VDataTable>
</div>
</transition>

View File

@@ -3,6 +3,10 @@ import { formatFileSize } from '@/@core/utils/formatters'
import api from '@/api'
import { FileItem, TransferQueue } from '@/api/types'
import { useDisplay } from 'vuetify'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
// 显示器宽度
const display = useDisplay()
@@ -16,7 +20,7 @@ const dataList = ref<TransferQueue[]>([])
const progressEventSource = ref<EventSource>()
// 整理进度文本
const progressText = ref('请稍候 ...')
const progressText = ref(t('dialog.transferQueue.processing'))
// 整理进度
const progressValue = ref(0)
@@ -29,10 +33,11 @@ const activeTab = ref('')
// 状态标签
const stateDict: { [key: string]: string } = {
'waiting': '等待中',
'running': '正在整理',
'completed': '完成',
'failed': '失败',
'waiting': t('dialog.transferQueue.waitingState'),
'running': t('dialog.transferQueue.runningState'),
'completed': t('dialog.transferQueue.finishedState'),
'failed': t('dialog.transferQueue.failedState'),
'cancelled': t('dialog.transferQueue.cancelledState'),
}
// 获取状态颜色
@@ -88,13 +93,13 @@ async function remove_queue_task(fileitem: FileItem) {
// 使用SSE监听加载进度
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.onmessage = event => {
const progress = JSON.parse(event.data)
if (progress) {
if (!progress.enable) {
progressText.value = '请稍候 ...'
progressText.value = t('dialog.transferQueue.processing')
progressValue.value = 0
if (refreshFlag.value) {
refreshFlag.value = false
@@ -138,7 +143,7 @@ onUnmounted(() => {
<VDialog scrollable max-width="50rem" :fullscreen="!display.mdAndUp.value">
<VCard class="mx-auto" width="100%">
<VCardItem>
<VCardTitle>整理队列</VCardTitle>
<VCardTitle>{{ t('dialog.transferQueue.title') }}</VCardTitle>
</VCardItem>
<VDialogCloseBtn @click="emit('close')" />
<VDivider />
@@ -151,7 +156,7 @@ onUnmounted(() => {
<VCardItem v-if="dataList.length > 0 && progressValue > 0" class="text-center pt-2">
<span class="text-sm">{{ progressText }}</span>
</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>
<VTabs v-model="activeTab" show-arrows class="v-tabs-pill" stacked>
<VTab
@@ -169,7 +174,7 @@ onUnmounted(() => {
<VListItem v-for="task in activeTasks">
<VListItemTitle>{{ task.fileitem.name }}</VListItemTitle>
<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">
{{ stateDict[task.state] }}
</VChip>

View File

@@ -2,6 +2,10 @@
import { isNullOrEmptyObject } from '@/@core/utils'
import api from '@/api'
import { useToast } from 'vue-toast-notification'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
// 定义事件
const emit = defineEmits(['done', 'close'])
@@ -89,17 +93,17 @@ async function handleDone() {
// 认证处理
async function checkUser() {
if (!authForm.value.site) {
$toast.error('请选择认证站点!')
$toast.error(t('dialog.userAuth.selectSiteRequired'))
return
}
if (!authSites.value[authForm.value.site]) {
$toast.error('站点配置不存在!')
$toast.error(t('dialog.userAuth.siteConfigNotExist'))
return
}
if (formFields.value.length > 0) {
for (const field of formFields.value) {
if (!authForm.value.params[field.site.toUpperCase() + '_' + field.key.toUpperCase()]) {
$toast.error(`请输入${field.name}`)
$toast.error(t('dialog.userAuth.fieldRequired', { name: field.name }))
return
}
}
@@ -108,13 +112,13 @@ async function checkUser() {
try {
const result: { [key: string]: any } = await api.post(`site/auth`, authForm.value)
if (result.success) {
$toast.success('用户认证成功,请重新登录!')
$toast.success(t('dialog.userAuth.authSuccess'))
// 1秒后刷新页面
setTimeout(() => {
emit('done')
}, 1000)
} else {
$toast.error(`认证失败:${result.message}`)
$toast.error(t('dialog.userAuth.authFailed', { message: result.message }))
}
} catch (e) {
console.error(e)
@@ -130,7 +134,7 @@ onMounted(async () => {
<template>
<VDialog width="40rem" max-height="85vh">
<VCard title="用户认证" class="rounded-t">
<VCard :title="t('dialog.userAuth.title')" class="rounded-t">
<VDialogCloseBtn @click="emit('close')" />
<VCardText>
<VRow>
@@ -140,7 +144,7 @@ onMounted(async () => {
:items="dropdownItems"
item-value="key"
item-title="name"
label="选择认证站点"
:label="t('dialog.userAuth.selectSite')"
item-props
>
</VSelect>
@@ -169,7 +173,7 @@ onMounted(async () => {
size="large"
:disabled="loading"
>
开始认证
{{ t('dialog.userAuth.authBtn') }}
</VBtn>
</VCardText>
</VCard>

View File

@@ -9,6 +9,10 @@ import api from '@/api'
import WorkflowSidebar from '@/layouts/components/WorkflowSidebar.vue'
import DropzoneBackground from '@/layouts/components/DropzoneBackground.vue'
import ImportCodeDialog from '@/components/dialog/ImportCodeDialog.vue'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
const { onConnect, addEdges, nodes, edges, addNodes, screenToFlowCoordinate } = useVueFlow()
@@ -18,7 +22,7 @@ const { onDragOver, onDrop, onDragLeave, isDragOver } = useDragAndDrop()
onConnect((connection: Connection) => {
// 双重校验
if (!isValidConnection(connection)) {
$toast.warning('非法连接:不能连接自身或同类型端口!')
$toast.warning(t('dialog.workflowActions.invalidConnection'))
return
}
addEdges(connection)
@@ -67,7 +71,7 @@ const loadComponent = async (componentName: string) => {
if (component) {
return ((await component()) as any).default
}
throw new Error(`组件 ${componentName} 未找到`)
throw new Error(t('dialog.workflowActions.componentNotFound', { component: componentName }))
}
// 将所有components中的组件加载到nodeTypes中
@@ -132,7 +136,7 @@ function handleComponentClick(action: any) {
addNodes(newNode)
// 显示提示
$toast.success('已添加组件到画布')
$toast.success(t('dialog.workflowActions.componentAdded'))
}
// 调用API 编辑任务
@@ -144,10 +148,10 @@ async function updateWorkflow() {
try {
const result: { [key: string]: string } = await api.put(`workflow/${workflowForm.value.id}`, workflowForm.value)
if (result.success) {
$toast.success(`保存任务流程成功!`)
$toast.success(t('dialog.workflowActions.saveSuccess'))
emit('save')
} else {
$toast.error(`保存任务流程失败:${result.message}`)
$toast.error(t('dialog.workflowActions.saveFailed', { message: result.message }))
}
} catch (error) {
console.error(error)
@@ -164,10 +168,10 @@ function saveCodeString(type: string, code: any) {
edges.value = codeObject.flows || []
}
importCodeDialog.value = false
$toast.success('导入成功!')
$toast.success(t('dialog.workflowActions.importSuccess'))
}
} catch (error) {
$toast.error('导入失败!')
$toast.error(t('dialog.workflowActions.importFailed'))
console.error(error)
}
}
@@ -176,7 +180,7 @@ function saveCodeString(type: string, code: any) {
function shareWorkflow() {
const codeString = JSON.stringify({ actions: nodes.value, flows: edges.value })
navigator.clipboard.writeText(codeString)
$toast.success('任务流程代码已复制到剪贴板!')
$toast.success(t('dialog.workflowActions.codeCopied'))
}
onMounted(() => {
@@ -202,7 +206,7 @@ const isMacOS = computed(() => {
<VIcon size="large" color="white" icon="mdi-close" />
</VBtn>
</VToolbarItems>
<VToolbarTitle> 编辑流程 - {{ workflow?.name }} </VToolbarTitle>
<VToolbarTitle> {{ t('dialog.workflowActions.title') }} - {{ workflow?.name }} </VToolbarTitle>
<VSpacer></VSpacer>
<VToolbarItems>
<VBtn icon variant="text" @click="importCodeDialog = true" class="ms-2">
@@ -248,7 +252,7 @@ const isMacOS = computed(() => {
<ImportCodeDialog
v-if="importCodeDialog"
v-model="importCodeDialog"
title="导入任务流程"
:title="t('dialog.workflowActions.importTitle')"
dataType="workflow"
@close="importCodeDialog = false"
@save="saveCodeString"

View File

@@ -5,6 +5,10 @@ import { doneNProgress, startNProgress } from '@/api/nprogress'
import { requiredValidator } from '@/@validators'
import api from '@/api'
import { useDisplay } from 'vuetify'
import { useI18n } from 'vue-i18n'
// 多语言支持
const { t } = useI18n()
// 输入参数
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()
@@ -38,17 +44,17 @@ const $toast = useToast()
// 调用API 新增任务
async function addWorkflow() {
if (!workflowForm.value.name || !workflowForm.value.timer) {
$toast.error('请填写完整信息!')
$toast.error(t('dialog.workflowAddEdit.nameRequired'))
return
}
startNProgress()
try {
const result: { [key: string]: string } = await api.post('workflow/', workflowForm.value)
if (result.success) {
$toast.success(`创建任务成功,请编辑流程!`)
$toast.success(t('dialog.workflowAddEdit.addSuccess'))
emit('save')
} else {
$toast.error(`创建任务失败:${result.message}`)
$toast.error(t('dialog.workflowAddEdit.addFailed', { message: result.message }))
}
} catch (error) {
console.error(error)
@@ -59,17 +65,17 @@ async function addWorkflow() {
// 调用API 编辑任务
async function editWorkflow() {
if (!workflowForm.value.name || !workflowForm.value.timer) {
$toast.error('请填写完整信息!')
$toast.error(t('dialog.workflowAddEdit.nameRequired'))
return
}
startNProgress()
try {
const result: { [key: string]: string } = await api.put(`workflow/${workflowForm.value.id}`, workflowForm.value)
if (result.success) {
$toast.success(`修改任务成功!`)
$toast.success(t('dialog.workflowAddEdit.editSuccess'))
emit('save')
} else {
$toast.error(`修改任务失败:${result.message}`)
$toast.error(t('dialog.workflowAddEdit.editFailed', { message: result.message }))
}
} catch (error) {
console.error(error)
@@ -80,7 +86,7 @@ async function editWorkflow() {
<template>
<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')" />
<VDivider />
<VCardText>
@@ -89,24 +95,28 @@ async function editWorkflow() {
<VCol cols="12">
<VTextField
v-model="workflowForm.name"
label="别名"
:label="t('dialog.workflowAddEdit.name')"
:rules="[requiredValidator]"
persistent-hint
hint="任务名称"
:hint="t('dialog.workflowAddEdit.namePlaceholder')"
/>
</VCol>
<VCol cols="12">
<VCronField
v-model="workflowForm.timer"
label="定时"
:label="t('dialog.workflowAddEdit.schedule')"
:rules="[requiredValidator]"
placeholder="5位cron表达式"
persistent-hint
hint="任务执行周期"
:hint="t('dialog.workflowAddEdit.cronExprDesc')"
/>
</VCol>
<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>
</VRow>
</VForm>
@@ -122,10 +132,10 @@ async function editWorkflow() {
prepend-icon="mdi-content-save"
class="px-5"
>
保存
{{ t('dialog.workflowAddEdit.confirm') }}
</VBtn>
<VBtn v-else block color="primary" variant="elevated" @click="addWorkflow" prepend-icon="mdi-plus" class="px-5">
创建
{{ t('dialog.workflowAddEdit.confirm') }}
</VBtn>
</VCardActions>
</VCard>

View File

@@ -15,6 +15,8 @@ export default {
send: 'Send',
noData: 'No Data',
noContent: 'No Content Found',
all: 'All',
default: 'Default',
},
theme: {
light: 'Light',
@@ -835,5 +837,310 @@ export default {
scanned: 'QR code scanned, please confirm login',
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',
},
},
}

View File

@@ -15,6 +15,8 @@ export default {
send: '发送',
noData: '暂无数据',
noContent: '没有找到相关内容',
all: '全部',
default: '默认',
},
theme: {
light: '浅色',
@@ -820,5 +822,309 @@ export default {
scanned: '已扫码,请确认登录',
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: '关闭',
},
},
}