mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-27 19:29:52 +08:00
更新国际化支持:为下载器和过滤规则组件添加多语言文本,提升用户体验
This commit is contained in:
@@ -7,6 +7,10 @@ import type { DownloaderInfo } from '@/api/types'
|
||||
import qbittorrent_image from '@images/logos/qbittorrent.png'
|
||||
import transmission_image from '@images/logos/transmission.png'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 获取i18n实例
|
||||
const { t } = useI18n()
|
||||
|
||||
// 定义输入
|
||||
const props = defineProps({
|
||||
@@ -91,12 +95,12 @@ function openDownloaderInfoDialog() {
|
||||
function saveDownloaderInfo() {
|
||||
// 为空不保存,跳出警告框
|
||||
if (!downloaderInfo.value.name) {
|
||||
$toast.error('名称不能为空,请输入后再确定')
|
||||
$toast.error(t('downloader.nameRequired'))
|
||||
return
|
||||
}
|
||||
// 重名判断
|
||||
if (props.downloaders.some(item => item.name === downloaderInfo.value.name && item !== props.downloader)) {
|
||||
$toast.error(`【${downloaderInfo.value.name}】已存在,请替换为其他名称`)
|
||||
$toast.error(t('downloader.nameDuplicate'))
|
||||
return
|
||||
}
|
||||
// 默认下载器去重
|
||||
@@ -104,7 +108,7 @@ function saveDownloaderInfo() {
|
||||
props.downloaders.forEach(item => {
|
||||
if (item.default && item !== props.downloader) {
|
||||
item.default = false
|
||||
$toast.info(`存在默认下载器【${item.name}】,已替换成【${downloaderInfo.value.name}】`)
|
||||
$toast.info(t('downloader.defaultChanged'))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -180,26 +184,30 @@ onUnmounted(() => {
|
||||
</VCard>
|
||||
</VHover>
|
||||
<VDialog v-if="downloaderInfoDialog" v-model="downloaderInfoDialog" scrollable max-width="40rem" persistent>
|
||||
<VCard :title="`${props.downloader.name} - 配置`" class="rounded-t">
|
||||
<VCard :title="`${props.downloader.name} - ${t('downloader.title')}`" class="rounded-t">
|
||||
<VDialogCloseBtn v-model="downloaderInfoDialog" />
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch v-model="downloaderInfo.enabled" label="启用下载器" />
|
||||
<VSwitch v-model="downloaderInfo.enabled" :label="t('downloader.enabled')" />
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch v-model="downloaderInfo.default" label="默认下载器" :disabled="!downloaderInfo.enabled" />
|
||||
<VSwitch
|
||||
v-model="downloaderInfo.default"
|
||||
:label="t('downloader.default')"
|
||||
:disabled="!downloaderInfo.enabled"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow v-if="downloaderInfo.type == 'qbittorrent'">
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="downloaderInfo.name"
|
||||
label="名称"
|
||||
placeholder="必填;不可与其他名称重名"
|
||||
hint="下载器的别名"
|
||||
:label="t('downloader.name')"
|
||||
:placeholder="t('downloader.nameRequired')"
|
||||
:hint="t('downloader.name')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -207,9 +215,9 @@ onUnmounted(() => {
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="downloaderInfo.config.host"
|
||||
label="地址"
|
||||
:label="t('downloader.host')"
|
||||
placeholder="http(s)://ip:port"
|
||||
hint="服务端地址,格式:http(s)://ip:port"
|
||||
:hint="t('downloader.host')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -217,8 +225,8 @@ onUnmounted(() => {
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="downloaderInfo.config.username"
|
||||
label="用户名"
|
||||
hint="登录使用的用户名"
|
||||
:label="t('downloader.username')"
|
||||
:hint="t('downloader.username')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -227,8 +235,8 @@ onUnmounted(() => {
|
||||
<VTextField
|
||||
v-model="downloaderInfo.config.password"
|
||||
type="password"
|
||||
label="密码"
|
||||
hint="登录使用的密码"
|
||||
:label="t('downloader.password')"
|
||||
:hint="t('downloader.password')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -236,8 +244,8 @@ onUnmounted(() => {
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="downloaderInfo.config.category"
|
||||
label="自动分类管理"
|
||||
hint="由下载器自动管理分类和下载目录"
|
||||
:label="t('downloader.category')"
|
||||
:hint="t('downloader.category')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -245,8 +253,8 @@ onUnmounted(() => {
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="downloaderInfo.config.sequentail"
|
||||
label="顺序下载"
|
||||
hint="按顺序依次下载文件"
|
||||
:label="t('downloader.sequentail')"
|
||||
:hint="t('downloader.sequentail')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -254,8 +262,8 @@ onUnmounted(() => {
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="downloaderInfo.config.force_resume"
|
||||
label="强制继续"
|
||||
hint="强制继续、强制上传模式"
|
||||
:label="t('downloader.force_resume')"
|
||||
:hint="t('downloader.force_resume')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -263,8 +271,8 @@ onUnmounted(() => {
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="downloaderInfo.config.first_last_piece"
|
||||
label="优先首尾文件"
|
||||
hint="优先下载首尾文件块"
|
||||
:label="t('downloader.first_last_piece')"
|
||||
:hint="t('downloader.first_last_piece')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -274,9 +282,9 @@ onUnmounted(() => {
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="downloaderInfo.name"
|
||||
label="名称"
|
||||
placeholder="必填;不可与其他名称重名"
|
||||
hint="下载器的别名"
|
||||
:label="t('downloader.name')"
|
||||
:placeholder="t('downloader.nameRequired')"
|
||||
:hint="t('downloader.name')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -284,9 +292,9 @@ onUnmounted(() => {
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="downloaderInfo.config.host"
|
||||
label="地址"
|
||||
:label="t('downloader.host')"
|
||||
placeholder="http(s)://ip:port"
|
||||
hint="服务端地址,格式:http(s)://ip:port"
|
||||
:hint="t('downloader.host')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -294,8 +302,8 @@ onUnmounted(() => {
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="downloaderInfo.config.username"
|
||||
label="用户名"
|
||||
hint="登录使用的用户名"
|
||||
:label="t('downloader.username')"
|
||||
:hint="t('downloader.username')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -304,8 +312,8 @@ onUnmounted(() => {
|
||||
<VTextField
|
||||
v-model="downloaderInfo.config.password"
|
||||
type="password"
|
||||
label="密码"
|
||||
hint="登录使用的密码"
|
||||
:label="t('downloader.password')"
|
||||
:hint="t('downloader.password')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -315,7 +323,7 @@ onUnmounted(() => {
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VBtn @click="saveDownloaderInfo" variant="elevated" prepend-icon="mdi-content-save" class="px-5">
|
||||
确定
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
import { innerFilterRules } from '@/api/constants'
|
||||
import { CustomRule } from '@/api/types'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 获取i18n实例
|
||||
const { t } = useI18n()
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -49,7 +53,7 @@ onMounted(() => {
|
||||
</span>
|
||||
<VDialogCloseBtn @click="onClose" />
|
||||
<VCardItem>
|
||||
<VCardTitle>优先级 {{ props.pri }}</VCardTitle>
|
||||
<VCardTitle>{{ t('filterRule.priority') }} {{ props.pri }}</VCardTitle>
|
||||
<VRow>
|
||||
<VCol>
|
||||
<VSelect
|
||||
@@ -57,7 +61,7 @@ onMounted(() => {
|
||||
variant="underlined"
|
||||
:items="selectFilterOptions"
|
||||
chips
|
||||
label=""
|
||||
:label="t('filterRule.rules')"
|
||||
multiple
|
||||
clearable
|
||||
@update:modelValue="filtersChanged"
|
||||
|
||||
@@ -7,6 +7,10 @@ import { useToast } from 'vue-toast-notification'
|
||||
import ImportCodeDialog from '@/components/dialog/ImportCodeDialog.vue'
|
||||
import filter_group_svg from '@images/svg/filter-group.svg'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 获取i18n实例
|
||||
const { t } = useI18n()
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -56,14 +60,14 @@ const groupInfo = ref<FilterRuleGroup>({
|
||||
|
||||
// 媒体类型字典
|
||||
const mediaTypeItems = [
|
||||
{ title: '通用', value: '' },
|
||||
{ title: '电影', value: '电影' },
|
||||
{ title: '电视剧', value: '电视剧' },
|
||||
{ title: t('common.all'), value: '' },
|
||||
{ title: t('mediaType.movie'), value: '电影' },
|
||||
{ title: t('mediaType.tv'), value: '电视剧' },
|
||||
]
|
||||
|
||||
// 根据选中的媒体类型,获取对应的媒体类别
|
||||
const getCategories = computed(() => {
|
||||
const default_value = [{ title: '全部', value: '' }]
|
||||
const default_value = [{ title: t('common.all'), value: '' }]
|
||||
if (!props.categories || !groupInfo.value.media_type || !props.categories[groupInfo.value.media_type]) {
|
||||
return default_value
|
||||
}
|
||||
@@ -72,11 +76,6 @@ const getCategories = computed(() => {
|
||||
|
||||
// 规则组规则卡片列表
|
||||
const filterRuleCards = ref<FilterCard[]>([])
|
||||
// 规则组类型,仅用于导入判断
|
||||
const filterRuleCardsType = ref<FilterCard>({
|
||||
pri: '',
|
||||
rules: [],
|
||||
})
|
||||
|
||||
// 导入代码弹窗
|
||||
const importCodeDialog = ref(false)
|
||||
@@ -112,10 +111,10 @@ async function shareRules() {
|
||||
try {
|
||||
let success
|
||||
success = copyToClipboard(value)
|
||||
if (await success) $toast.success('优先级规则已复制到剪贴板!')
|
||||
else $toast.error('优先级规则复制失败:可能是浏览器不支持或被用户阻止!')
|
||||
if (await success) $toast.success(t('filterRule.shareSuccess'))
|
||||
else $toast.error(t('filterRule.shareFailed'))
|
||||
} catch (error) {
|
||||
$toast.error('优先级规则复制失败!')
|
||||
$toast.error(t('filterRule.shareFailed'))
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
@@ -143,7 +142,7 @@ function saveCodeString(type: string, code: any) {
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
$toast.error('导入失败!')
|
||||
$toast.error(t('filterRule.importFailed'))
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
@@ -177,11 +176,11 @@ function opengroupInfoDialog() {
|
||||
// 保存详情数据
|
||||
function saveGroupInfo() {
|
||||
if (!groupInfo.value.name.trim()) {
|
||||
$toast.error('规则组名称不能为空')
|
||||
$toast.error(t('filterRule.nameRequired'))
|
||||
return
|
||||
}
|
||||
if (props.groups.some(item => item.name === groupInfo.value.name && item !== props.group)) {
|
||||
$toast.error(`规则组名称【${groupInfo.value.name}】已存在,请替换`)
|
||||
$toast.error(t('filterRule.nameDuplicate'))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -213,7 +212,7 @@ function onClose() {
|
||||
<div class="align-self-start">
|
||||
<h5 class="text-h6 mb-1">{{ props.group.name }}</h5>
|
||||
<div class="text-body-1 mb-3">
|
||||
<span v-if="!props.group.category">{{ props.group.media_type || '通用' }}</span>
|
||||
<span v-if="!props.group.category">{{ props.group.media_type || t('common.all') }}</span>
|
||||
<span v-else>{{ props.group.category }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -221,7 +220,7 @@ function onClose() {
|
||||
</VCardText>
|
||||
</VCard>
|
||||
<VDialog v-if="groupInfoDialog" v-model="groupInfoDialog" scrollable max-width="80rem" persistent>
|
||||
<VCard :title="`${props.group.name} - 配置`" class="rounded-t">
|
||||
<VCard :title="`${props.group.name} - ${t('filterRule.title')}`" class="rounded-t">
|
||||
<VDialogCloseBtn v-model="groupInfoDialog" />
|
||||
<VDivider />
|
||||
<VCardItem class="pt-1">
|
||||
@@ -229,9 +228,9 @@ function onClose() {
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="groupInfo.name"
|
||||
label="规则组名称"
|
||||
placeholder="必填;不可与其他规则组重名"
|
||||
hint="自定义规则组名称"
|
||||
:label="t('filterRule.name')"
|
||||
:placeholder="t('filterRule.nameRequired')"
|
||||
:hint="t('filterRule.name')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -239,9 +238,9 @@ function onClose() {
|
||||
<VCol cols="6" md="3">
|
||||
<VSelect
|
||||
v-model="groupInfo.media_type"
|
||||
label="适用媒体类型"
|
||||
:label="t('filterRule.mediaType')"
|
||||
:items="mediaTypeItems"
|
||||
hint="选择规则组适用的媒体类型"
|
||||
:hint="t('filterRule.mediaType')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -250,8 +249,8 @@ function onClose() {
|
||||
<VSelect
|
||||
v-model="groupInfo.category"
|
||||
:items="getCategories"
|
||||
label="适用媒体类别"
|
||||
hint="选择规则组适用的媒体类别"
|
||||
:label="t('filterRule.category')"
|
||||
:hint="t('filterRule.category')"
|
||||
persistent-hint
|
||||
active
|
||||
/>
|
||||
@@ -278,7 +277,7 @@ function onClose() {
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
<div class="text-center" v-if="filterRuleCards.length == 0">请添加或导入规则</div>
|
||||
<div class="text-center" v-if="filterRuleCards.length == 0">{{ t('filterRule.add') }}</div>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VBtn color="primary" variant="tonal" @click="addFilterCard">
|
||||
@@ -291,14 +290,16 @@ function onClose() {
|
||||
<VIcon icon="mdi-share" />
|
||||
</VBtn>
|
||||
<VSpacer />
|
||||
<VBtn @click="saveGroupInfo" variant="elevated" prepend-icon="mdi-content-save" class="px-5"> 确定 </VBtn>
|
||||
<VBtn @click="saveGroupInfo" variant="elevated" prepend-icon="mdi-content-save" class="px-5">
|
||||
{{ t('common.save') }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
<ImportCodeDialog
|
||||
v-if="importCodeDialog"
|
||||
v-model="importCodeDialog"
|
||||
title="导入规则优先级"
|
||||
:title="t('filterRule.import')"
|
||||
:dataType="importCodeType"
|
||||
@close="importCodeDialog = false"
|
||||
@save="saveCodeString"
|
||||
|
||||
Reference in New Issue
Block a user