mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-04 23:18:44 +08:00
更新国际化支持
This commit is contained in:
Vendored
+6
-1
@@ -25,6 +25,7 @@ declare global {
|
|||||||
const createPinia: typeof import('pinia')['createPinia']
|
const createPinia: typeof import('pinia')['createPinia']
|
||||||
const createProjection: typeof import('@vueuse/math')['createProjection']
|
const createProjection: typeof import('@vueuse/math')['createProjection']
|
||||||
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
||||||
|
const createRef: typeof import('@vueuse/core')['createRef']
|
||||||
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
|
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
|
||||||
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
||||||
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
|
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
|
||||||
@@ -159,6 +160,7 @@ declare global {
|
|||||||
const useCloned: typeof import('@vueuse/core')['useCloned']
|
const useCloned: typeof import('@vueuse/core')['useCloned']
|
||||||
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
||||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||||
|
const useCountdown: typeof import('@vueuse/core')['useCountdown']
|
||||||
const useCounter: typeof import('@vueuse/core')['useCounter']
|
const useCounter: typeof import('@vueuse/core')['useCounter']
|
||||||
const useCssModule: typeof import('vue')['useCssModule']
|
const useCssModule: typeof import('vue')['useCssModule']
|
||||||
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
||||||
@@ -326,7 +328,7 @@ declare global {
|
|||||||
// for type re-export
|
// for type re-export
|
||||||
declare global {
|
declare global {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||||
import('vue')
|
import('vue')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,6 +356,7 @@ declare module 'vue' {
|
|||||||
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
|
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
|
||||||
readonly createProjection: UnwrapRef<typeof import('@vueuse/math')['createProjection']>
|
readonly createProjection: UnwrapRef<typeof import('@vueuse/math')['createProjection']>
|
||||||
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
|
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
|
||||||
|
readonly createRef: UnwrapRef<typeof import('@vueuse/core')['createRef']>
|
||||||
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
|
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
|
||||||
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
|
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
|
||||||
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
|
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
|
||||||
@@ -488,6 +491,7 @@ declare module 'vue' {
|
|||||||
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
||||||
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
||||||
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
||||||
|
readonly useCountdown: UnwrapRef<typeof import('@vueuse/core')['useCountdown']>
|
||||||
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
|
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
|
||||||
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
||||||
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
|
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
|
||||||
@@ -527,6 +531,7 @@ declare module 'vue' {
|
|||||||
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
|
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
|
||||||
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
|
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
|
||||||
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
|
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
|
||||||
|
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
|
||||||
readonly useId: UnwrapRef<typeof import('vue')['useId']>
|
readonly useId: UnwrapRef<typeof import('vue')['useId']>
|
||||||
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
|
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
|
||||||
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
|
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
|
||||||
|
|||||||
Vendored
+1
@@ -2,6 +2,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
// Generated by unplugin-vue-components
|
// Generated by unplugin-vue-components
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
|
// biome-ignore lint: disable
|
||||||
export {}
|
export {}
|
||||||
|
|
||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
|
|||||||
Generated
-14609
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
|||||||
"name": "moviepilot",
|
"name": "moviepilot",
|
||||||
"version": "2.4.3",
|
"version": "2.4.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
"bin": "dist/service.js",
|
"bin": "dist/service.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
|
|||||||
@@ -12,6 +12,14 @@
|
|||||||
*/
|
*/
|
||||||
import { promises as fs } from 'node:fs'
|
import { promises as fs } from 'node:fs'
|
||||||
import { dirname, join } from 'node:path'
|
import { dirname, join } from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { createRequire } from 'node:module'
|
||||||
|
|
||||||
|
// Get current directory
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
|
// Create require function for importing JSON files in ESM
|
||||||
|
const require = createRequire(import.meta.url)
|
||||||
|
|
||||||
// Installation: npm install --save-dev @iconify/tools @iconify/utils @iconify/json @iconify/iconify
|
// Installation: npm install --save-dev @iconify/tools @iconify/utils @iconify/json @iconify/iconify
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "CommonJS",
|
"module": "Node16",
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"declarationMap": false,
|
"declarationMap": false,
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"composite": false,
|
"composite": false,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node16",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"./*.js"
|
"./*.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"root":["./build-icons.ts"],"version":"5.7.3"}
|
{"root":["./build-icons.ts"],"version":"5.8.3"}
|
||||||
+1
-1
@@ -6,7 +6,7 @@ import api from '@/api'
|
|||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { getBrowserLocale, setI18nLanguage } from './plugins/i18n'
|
import { getBrowserLocale, setI18nLanguage } from './plugins/i18n'
|
||||||
import { SupportedLocale } from './locales/types'
|
import { SupportedLocale } from '@/types/i18n'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import FileNavigator from './filebrowser/FileNavigator.vue'
|
|||||||
import type { EndPoints, FileItem, StorageConf } from '@/api/types'
|
import type { EndPoints, FileItem, StorageConf } from '@/api/types'
|
||||||
import { storageOptions } from '@/api/constants'
|
import { storageOptions } from '@/api/constants'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
// 国际化
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import type { TransferDirectoryConf } from '@/api/types'
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { nextTick } from 'vue'
|
import { nextTick } from 'vue'
|
||||||
import { storageOptions } from '@/api/constants'
|
import { storageOptions } from '@/api/constants'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
// 国际化
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -23,11 +27,11 @@ const props = defineProps({
|
|||||||
const isCollapsed = ref(true)
|
const isCollapsed = ref(true)
|
||||||
|
|
||||||
// 类型下拉字典
|
// 类型下拉字典
|
||||||
const typeItems = [
|
const typeItems = computed(() => [
|
||||||
{ title: '全部', value: '' },
|
{ title: t('common.all'), value: '' },
|
||||||
{ title: '电影', value: '电影' },
|
{ title: t('media.movie'), value: '电影' },
|
||||||
{ title: '电视剧', value: '电视剧' },
|
{ title: t('media.tv'), value: '电视剧' },
|
||||||
]
|
])
|
||||||
|
|
||||||
// 计算资源存储字典(整理方式为下载器时不能为远程存储)
|
// 计算资源存储字典(整理方式为下载器时不能为远程存储)
|
||||||
const resourceStorageOptions = computed(() => {
|
const resourceStorageOptions = computed(() => {
|
||||||
@@ -35,18 +39,18 @@ const resourceStorageOptions = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 自动整理方式下拉字典
|
// 自动整理方式下拉字典
|
||||||
const transferSourceItems = [
|
const transferSourceItems = computed(() => [
|
||||||
{ title: '不整理', value: '' },
|
{ title: t('directory.noTransfer'), value: '' },
|
||||||
{ title: '下载器监控', value: 'downloader' },
|
{ title: t('directory.downloaderMonitor'), value: 'downloader' },
|
||||||
{ title: '目录监控', value: 'monitor' },
|
{ title: t('directory.directoryMonitor'), value: 'monitor' },
|
||||||
{ title: '手动整理', value: 'manual' },
|
{ title: t('directory.manualTransfer'), value: 'manual' },
|
||||||
]
|
])
|
||||||
|
|
||||||
// 监控模式下拉字典
|
// 监控模式下拉字典
|
||||||
const MonitorModeItems = [
|
const MonitorModeItems = computed(() => [
|
||||||
{ title: '性能模式', value: 'fast' },
|
{ title: t('directory.performanceMode'), value: 'fast' },
|
||||||
{ title: '兼容模式', value: 'compatibility' },
|
{ title: t('directory.compatibilityMode'), value: 'compatibility' },
|
||||||
]
|
])
|
||||||
|
|
||||||
// 整理方式下拉字典
|
// 整理方式下拉字典
|
||||||
const transferTypeItems = ref<{ title: string; value: string }[]>([])
|
const transferTypeItems = ref<{ title: string; value: string }[]>([])
|
||||||
@@ -103,23 +107,23 @@ async function loadTransferTypeItems() {
|
|||||||
// 整理方式无数据提示
|
// 整理方式无数据提示
|
||||||
const computedNoDataText = computed(() => {
|
const computedNoDataText = computed(() => {
|
||||||
if (!props.directory.library_storage && !props.directory.storage) {
|
if (!props.directory.library_storage && !props.directory.storage) {
|
||||||
return '请选择储存'
|
return t('directory.pleaseSelectStorage')
|
||||||
} else if (!props.directory.library_storage) {
|
} else if (!props.directory.library_storage) {
|
||||||
return '请选择媒体库储存'
|
return t('directory.pleaseSelectLibraryStorage')
|
||||||
} else if (!props.directory.storage) {
|
} else if (!props.directory.storage) {
|
||||||
return '请选择下载器储存'
|
return t('directory.pleaseSelectDownloadStorage')
|
||||||
} else {
|
} else {
|
||||||
return '选择的存储类型没有支持的整理方式'
|
return t('directory.noSupportedTransferType')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 覆盖模式下拉字典
|
// 覆盖模式下拉字典
|
||||||
const overwriteModeItems = [
|
const overwriteModeItems = computed(() => [
|
||||||
{ title: '从不', value: 'never' },
|
{ title: t('directory.never'), value: 'never' },
|
||||||
{ title: '总是', value: 'always' },
|
{ title: t('directory.always'), value: 'always' },
|
||||||
{ title: '按文件大小', value: 'size' },
|
{ title: t('directory.byFileSize'), value: 'size' },
|
||||||
{ title: '仅保留最新版本', value: 'latest' },
|
{ title: t('directory.keepLatestOnly'), value: 'latest' },
|
||||||
]
|
])
|
||||||
|
|
||||||
// 定义触发的自定义事件
|
// 定义触发的自定义事件
|
||||||
const emit = defineEmits(['close', 'changed', 'update:modelValue'])
|
const emit = defineEmits(['close', 'changed', 'update:modelValue'])
|
||||||
@@ -131,7 +135,7 @@ function onClose() {
|
|||||||
|
|
||||||
// 根据选中的媒体类型,获取对应的媒体类别
|
// 根据选中的媒体类型,获取对应的媒体类别
|
||||||
const getCategories = computed(() => {
|
const getCategories = computed(() => {
|
||||||
const default_value = [{ title: '全部', value: '' }]
|
const default_value = [{ title: t('common.all'), value: '' }]
|
||||||
if (!props.categories || !props.categories[props.directory?.media_type ?? '']) return default_value
|
if (!props.categories || !props.categories[props.directory?.media_type ?? '']) return default_value
|
||||||
return default_value.concat(props.categories[props.directory.media_type ?? ''])
|
return default_value.concat(props.categories[props.directory.media_type ?? ''])
|
||||||
})
|
})
|
||||||
@@ -180,7 +184,7 @@ watch(
|
|||||||
<VTextField
|
<VTextField
|
||||||
v-model="props.directory.name"
|
v-model="props.directory.name"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
label="别名"
|
:label="t('directory.alias')"
|
||||||
class="me-20 text-high-emphasis font-weight-bold"
|
class="me-20 text-high-emphasis font-weight-bold"
|
||||||
/>
|
/>
|
||||||
<span class="absolute top-3 right-12">
|
<span class="absolute top-3 right-12">
|
||||||
@@ -197,7 +201,7 @@ watch(
|
|||||||
v-model="props.directory.media_type"
|
v-model="props.directory.media_type"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
:items="typeItems"
|
:items="typeItems"
|
||||||
label="媒体类型"
|
:label="t('directory.mediaType')"
|
||||||
@update:modelValue="props.directory.media_category = ''"
|
@update:modelValue="props.directory.media_category = ''"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
@@ -206,7 +210,7 @@ watch(
|
|||||||
v-model="props.directory.media_category"
|
v-model="props.directory.media_category"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
:items="getCategories"
|
:items="getCategories"
|
||||||
label="媒体类别"
|
:label="t('directory.mediaCategory')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="4">
|
<VCol cols="4">
|
||||||
@@ -214,7 +218,7 @@ watch(
|
|||||||
v-model="props.directory.storage"
|
v-model="props.directory.storage"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
:items="resourceStorageOptions"
|
:items="resourceStorageOptions"
|
||||||
label="资源存储"
|
:label="t('directory.resourceStorage')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="8">
|
<VCol cols="8">
|
||||||
@@ -222,14 +226,17 @@ watch(
|
|||||||
v-model="props.directory.download_path"
|
v-model="props.directory.download_path"
|
||||||
:storage="props.directory.storage"
|
:storage="props.directory.storage"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
label="资源目录"
|
:label="t('directory.resourceDirectory')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6" v-if="!props.directory.media_type || props.directory.media_type === ''">
|
<VCol cols="6" v-if="!props.directory.media_type || props.directory.media_type === ''">
|
||||||
<VSwitch v-model="props.directory.download_type_folder" label="按类型分类"></VSwitch>
|
<VSwitch v-model="props.directory.download_type_folder" :label="t('directory.sortByType')"></VSwitch>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6" v-if="!props.directory.media_category || props.directory.media_category === ''">
|
<VCol cols="6" v-if="!props.directory.media_category || props.directory.media_category === ''">
|
||||||
<VSwitch v-model="props.directory.download_category_folder" label="按类别分类"></VSwitch>
|
<VSwitch
|
||||||
|
v-model="props.directory.download_category_folder"
|
||||||
|
:label="t('directory.sortByCategory')"
|
||||||
|
></VSwitch>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
<VDivider v-if="$props.directory.monitor_type" class="my-3 bg-primary" />
|
<VDivider v-if="$props.directory.monitor_type" class="my-3 bg-primary" />
|
||||||
@@ -239,7 +246,7 @@ watch(
|
|||||||
v-model="props.directory.monitor_type"
|
v-model="props.directory.monitor_type"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
:items="transferSourceItems"
|
:items="transferSourceItems"
|
||||||
label="自动整理"
|
:label="t('directory.autoTransfer')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
@@ -249,7 +256,7 @@ watch(
|
|||||||
v-model="props.directory.monitor_mode"
|
v-model="props.directory.monitor_mode"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
:items="MonitorModeItems"
|
:items="MonitorModeItems"
|
||||||
label="监控模式"
|
:label="t('directory.monitorMode')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="4">
|
<VCol cols="4">
|
||||||
@@ -257,7 +264,7 @@ watch(
|
|||||||
v-model="props.directory.library_storage"
|
v-model="props.directory.library_storage"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
:items="storageOptions"
|
:items="storageOptions"
|
||||||
label="媒体库存储"
|
:label="t('directory.libraryStorage')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="8">
|
<VCol cols="8">
|
||||||
@@ -265,7 +272,7 @@ watch(
|
|||||||
v-model="props.directory.library_path"
|
v-model="props.directory.library_path"
|
||||||
:storage="props.directory.library_storage"
|
:storage="props.directory.library_storage"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
label="媒体库目录"
|
:label="t('directory.libraryDirectory')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="4">
|
<VCol cols="4">
|
||||||
@@ -273,7 +280,7 @@ watch(
|
|||||||
v-model="props.directory.transfer_type"
|
v-model="props.directory.transfer_type"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
:items="transferTypeItems"
|
:items="transferTypeItems"
|
||||||
label="整理方式"
|
:label="t('directory.transferType')"
|
||||||
:no-data-text="computedNoDataText"
|
:no-data-text="computedNoDataText"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
@@ -282,23 +289,23 @@ watch(
|
|||||||
v-model="props.directory.overwrite_mode"
|
v-model="props.directory.overwrite_mode"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
:items="overwriteModeItems"
|
:items="overwriteModeItems"
|
||||||
label="覆盖模式"
|
:label="t('directory.overwriteMode')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6" v-if="!props.directory.media_type || props.directory.media_type === ''">
|
<VCol cols="6" v-if="!props.directory.media_type || props.directory.media_type === ''">
|
||||||
<VSwitch v-model="props.directory.library_type_folder" label="按类型分类"></VSwitch>
|
<VSwitch v-model="props.directory.library_type_folder" :label="t('directory.sortByType')"></VSwitch>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6" v-if="!props.directory.media_category || props.directory.media_category === ''">
|
<VCol cols="6" v-if="!props.directory.media_category || props.directory.media_category === ''">
|
||||||
<VSwitch v-model="props.directory.library_category_folder" label="按类别分类"></VSwitch>
|
<VSwitch v-model="props.directory.library_category_folder" :label="t('directory.sortByCategory')"></VSwitch>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6">
|
<VCol cols="6">
|
||||||
<VSwitch v-model="props.directory.renaming" label="智能重命名"></VSwitch>
|
<VSwitch v-model="props.directory.renaming" :label="t('directory.smartRename')"></VSwitch>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6">
|
<VCol cols="6">
|
||||||
<VSwitch v-model="props.directory.scraping" label="刮削元数据"></VSwitch>
|
<VSwitch v-model="props.directory.scraping" :label="t('directory.scrapingMetadata')"></VSwitch>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6">
|
<VCol cols="6">
|
||||||
<VSwitch v-model="props.directory.notify" label="发送通知"></VSwitch>
|
<VSwitch v-model="props.directory.notify" :label="t('directory.sendNotification')"></VSwitch>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
</VForm>
|
</VForm>
|
||||||
|
|||||||
@@ -118,14 +118,14 @@ function getMediaId() {
|
|||||||
|
|
||||||
// 角标颜色
|
// 角标颜色
|
||||||
function getChipColor(type: string) {
|
function getChipColor(type: string) {
|
||||||
if (type === t('media.movie')) return 'border-blue-500 bg-blue-600'
|
if (type === '电影') return 'border-blue-500 bg-blue-600'
|
||||||
else if (type === t('media.tv')) return ' bg-indigo-500 border-indigo-600'
|
else if (type === '电视剧') return ' bg-indigo-500 border-indigo-600'
|
||||||
else return 'border-purple-600 bg-purple-600'
|
else return 'border-purple-600 bg-purple-600'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加订阅处理
|
// 添加订阅处理
|
||||||
async function handleAddSubscribe() {
|
async function handleAddSubscribe() {
|
||||||
if (props.media?.type === t('media.tv')) {
|
if (props.media?.type === '电视剧') {
|
||||||
// 弹出季选择列表,支持多选
|
// 弹出季选择列表,支持多选
|
||||||
seasonsSelected.value = []
|
seasonsSelected.value = []
|
||||||
subscribeSeasonDialog.value = true
|
subscribeSeasonDialog.value = true
|
||||||
@@ -141,7 +141,7 @@ async function addSubscribe(season: number = 0, best_version: number = 0) {
|
|||||||
startNProgress()
|
startNProgress()
|
||||||
try {
|
try {
|
||||||
// 是否洗版
|
// 是否洗版
|
||||||
if (!best_version && props.media?.type == t('media.movie')) best_version = isExists.value ? 1 : 0
|
if (!best_version && props.media?.type == '电影') best_version = isExists.value ? 1 : 0
|
||||||
// 请求API
|
// 请求API
|
||||||
const result: { [key: string]: any } = await api.post('subscribe/', {
|
const result: { [key: string]: any } = await api.post('subscribe/', {
|
||||||
name: props.media?.title,
|
name: props.media?.title,
|
||||||
@@ -279,7 +279,7 @@ async function queryDefaultSubscribeConfig() {
|
|||||||
if (!userStore.superUser) return false
|
if (!userStore.superUser) return false
|
||||||
try {
|
try {
|
||||||
let subscribe_config_url = ''
|
let subscribe_config_url = ''
|
||||||
if (props.media?.type === t('media.movie')) subscribe_config_url = 'system/setting/DefaultMovieSubscribeConfig'
|
if (props.media?.type === '电影') subscribe_config_url = 'system/setting/DefaultMovieSubscribeConfig'
|
||||||
else subscribe_config_url = 'system/setting/DefaultTvSubscribeConfig'
|
else subscribe_config_url = 'system/setting/DefaultTvSubscribeConfig'
|
||||||
const result: { [key: string]: any } = await api.get(subscribe_config_url)
|
const result: { [key: string]: any } = await api.get(subscribe_config_url)
|
||||||
if (result.data?.value) return result.data.value.show_edit_dialog
|
if (result.data?.value) return result.data.value.show_edit_dialog
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import api from '@/api'
|
|||||||
import ProgressDialog from '../dialog/ProgressDialog.vue'
|
import ProgressDialog from '../dialog/ProgressDialog.vue'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import MediaInfoDialog from '../dialog/MediaInfoDialog.vue'
|
import MediaInfoDialog from '../dialog/MediaInfoDialog.vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
// 国际化
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
// 显示器宽度
|
// 显示器宽度
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
@@ -687,25 +691,31 @@ onMounted(() => {
|
|||||||
</VCard>
|
</VCard>
|
||||||
<!-- 重命名弹窗 -->
|
<!-- 重命名弹窗 -->
|
||||||
<VDialog v-if="renamePopper" v-model="renamePopper" max-width="35rem">
|
<VDialog v-if="renamePopper" v-model="renamePopper" max-width="35rem">
|
||||||
<VCard title="重命名">
|
<VCard :title="t('file.rename')">
|
||||||
<VDialogCloseBtn @click="renamePopper = false" />
|
<VDialogCloseBtn @click="renamePopper = false" />
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VRow>
|
<div class="mb-3">
|
||||||
<VCol cols="12">
|
<span>{{ t('file.currentName') }}: {{ currentItem?.name }}</span>
|
||||||
<VTextField v-model="newName" label="新名称" :loading="renameLoading" />
|
</div>
|
||||||
</VCol>
|
<VTextField v-model="newName" :label="t('file.newName')" />
|
||||||
<VCol cols="12" v-if="currentItem && currentItem.type == 'dir'">
|
<VCheckbox
|
||||||
<VSwitch v-model="renameAll" label="自动重命名目录内所有媒体文件" />
|
v-if="false && currentItem?.type == 'dir'"
|
||||||
</VCol>
|
v-model="renameAll"
|
||||||
</VRow>
|
:label="t('file.includeSubfolders')"
|
||||||
|
></VCheckbox>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardActions>
|
<VCardActions>
|
||||||
<VBtn color="success" variant="elevated" @click="get_recommend_name" prepend-icon="mdi-magic" class="px-5 me-3">
|
<div class="flex-grow-1" />
|
||||||
自动识别名称
|
<VBtn
|
||||||
</VBtn>
|
:disabled="!newName"
|
||||||
<VBtn :disabled="!newName" variant="elevated" @click="rename" prepend-icon="mdi-check" class="px-5 me-3">
|
variant="elevated"
|
||||||
确定
|
:loading="renameLoading"
|
||||||
|
@click="rename"
|
||||||
|
prepend-icon="mdi-check"
|
||||||
|
class="px-5 me-3"
|
||||||
|
>
|
||||||
|
{{ t('common.save') }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</VCardActions>
|
</VCardActions>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import type { PropType } from 'vue'
|
|||||||
import type { FileItem } from '@/api/types'
|
import type { FileItem } from '@/api/types'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import type { AxiosRequestConfig } from 'axios'
|
import type { AxiosRequestConfig } from 'axios'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
// 国际化
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
// 显示器宽度
|
// 显示器宽度
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
@@ -276,7 +280,7 @@ function getIndentLevel(path: string, ancestorPath: string) {
|
|||||||
>
|
>
|
||||||
<div class="folder-content">
|
<div class="folder-content">
|
||||||
<VIcon icon="mdi-home" class="me-2" color="primary" />
|
<VIcon icon="mdi-home" class="me-2" color="primary" />
|
||||||
<span>根目录</span>
|
<span>{{ t('file.rootDirectory') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
import type { AxiosRequestConfig } from 'axios'
|
import type { AxiosRequestConfig } from 'axios'
|
||||||
import type { EndPoints, FileItem } from '@/api/types'
|
import type { EndPoints, FileItem } 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()
|
||||||
@@ -167,16 +171,16 @@ const sortIcon = computed(() => {
|
|||||||
<VIcon v-bind="props" icon="mdi-folder-plus-outline" />
|
<VIcon v-bind="props" icon="mdi-folder-plus-outline" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</template>
|
</template>
|
||||||
<VCard title="新建文件夹">
|
<VCard :title="t('file.newFolder')">
|
||||||
<VDialogCloseBtn @click="newFolderPopper = false" />
|
<VDialogCloseBtn @click="newFolderPopper = false" />
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VTextField v-model="newFolderName" label="名称" />
|
<VTextField v-model="newFolderName" :label="t('common.name')" />
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardActions>
|
<VCardActions>
|
||||||
<div class="flex-grow-1" />
|
<div class="flex-grow-1" />
|
||||||
<VBtn :disabled="!newFolderName" variant="elevated" @click="mkdir" prepend-icon="mdi-check" class="px-5 me-3">
|
<VBtn :disabled="!newFolderName" variant="elevated" @click="mkdir" prepend-icon="mdi-check" class="px-5 me-3">
|
||||||
新建
|
{{ t('common.create') }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</VCardActions>
|
</VCardActions>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|||||||
+294
-173
@@ -14,14 +14,18 @@ export default {
|
|||||||
inputMessage: 'Enter message or command',
|
inputMessage: 'Enter message or command',
|
||||||
send: 'Send',
|
send: 'Send',
|
||||||
noData: 'No Data',
|
noData: 'No Data',
|
||||||
noContent: 'No Content Found',
|
noContent: 'No relevant content found',
|
||||||
all: 'All',
|
all: 'All',
|
||||||
default: 'Default',
|
default: 'Default',
|
||||||
|
name: 'Name',
|
||||||
|
create: 'Create',
|
||||||
|
saving: 'Saving',
|
||||||
|
reset: 'Reset',
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
light: 'Light',
|
light: 'Light',
|
||||||
dark: 'Dark',
|
dark: 'Dark',
|
||||||
auto: 'Auto',
|
auto: 'Follow System',
|
||||||
transparent: 'Transparent',
|
transparent: 'Transparent',
|
||||||
purple: 'Purple',
|
purple: 'Purple',
|
||||||
custom: 'Custom Theme',
|
custom: 'Custom Theme',
|
||||||
@@ -36,22 +40,22 @@ export default {
|
|||||||
selectLanguage: 'Select Language',
|
selectLanguage: 'Select Language',
|
||||||
logout: 'Logout',
|
logout: 'Logout',
|
||||||
restarting: 'Restarting...',
|
restarting: 'Restarting...',
|
||||||
confirmRestart: 'Confirm Restart?',
|
confirmRestart: 'Confirm restart system?',
|
||||||
restartTip: 'After restarting, you will be logged out and need to log in again.',
|
restartTip: 'After restart, you will be logged out and need to log in again.',
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
wallpapers: 'wallpapers',
|
wallpapers: 'Wallpapers',
|
||||||
username: 'Username',
|
username: 'Username',
|
||||||
password: 'Password',
|
password: 'Password',
|
||||||
otpCode: 'OTP Code',
|
otpCode: 'Two-Factor Code',
|
||||||
stayLoggedIn: 'Stay Logged In',
|
stayLoggedIn: 'Stay Logged In',
|
||||||
login: 'Login',
|
login: 'Login',
|
||||||
networkError: 'Login failed, please check your network connection!',
|
networkError: 'Login failed, please check your network connection!',
|
||||||
authFailure: 'Login failed, please check your username, password or OTP code!',
|
authFailure: 'Login failed, please check your username, password or two-factor authentication!',
|
||||||
permissionDenied: 'Login failed, you do not have permission to access!',
|
permissionDenied: 'Login failed, you do not have permission to access!',
|
||||||
serverError: 'Login failed, server error!',
|
serverError: 'Login failed, server error!',
|
||||||
loginFailed: 'Login Failed',
|
loginFailed: 'Login Failed',
|
||||||
checkCredentials: 'Please check your username, password or OTP code!',
|
checkCredentials: 'Please check your username, password or two-factor authentication code!',
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
start: 'Start',
|
start: 'Start',
|
||||||
@@ -71,7 +75,7 @@ export default {
|
|||||||
movieSubscribe: 'Movie Subscription',
|
movieSubscribe: 'Movie Subscription',
|
||||||
tvSubscribe: 'TV Subscription',
|
tvSubscribe: 'TV Subscription',
|
||||||
history: 'History',
|
history: 'History',
|
||||||
transfer: 'Transfer',
|
transfer: 'Organize',
|
||||||
rename: 'Rename',
|
rename: 'Rename',
|
||||||
statistic: 'Statistics',
|
statistic: 'Statistics',
|
||||||
setting: 'Settings',
|
setting: 'Settings',
|
||||||
@@ -84,11 +88,11 @@ export default {
|
|||||||
workflow: 'Workflow',
|
workflow: 'Workflow',
|
||||||
calendar: 'Calendar',
|
calendar: 'Calendar',
|
||||||
downloadManager: 'Download Manager',
|
downloadManager: 'Download Manager',
|
||||||
mediaOrganize: 'Media Organizer',
|
mediaOrganize: 'Media Organize',
|
||||||
fileManager: 'File Manager',
|
fileManager: 'File Manager',
|
||||||
pluginManager: 'Plugins',
|
pluginManager: 'Plugins',
|
||||||
siteManager: 'Site Manager',
|
siteManager: 'Site Management',
|
||||||
userManager: 'User Manager',
|
userManager: 'User Management',
|
||||||
settings: 'Settings',
|
settings: 'Settings',
|
||||||
},
|
},
|
||||||
settingTabs: {
|
settingTabs: {
|
||||||
@@ -98,7 +102,7 @@ export default {
|
|||||||
},
|
},
|
||||||
directory: {
|
directory: {
|
||||||
title: 'Storage & Directories',
|
title: 'Storage & Directories',
|
||||||
description: 'Download directories, media library directories, organization, metadata scraping',
|
description: 'Download directory, media library directory, organization, scraping',
|
||||||
},
|
},
|
||||||
site: {
|
site: {
|
||||||
title: 'Sites',
|
title: 'Sites',
|
||||||
@@ -110,15 +114,15 @@ export default {
|
|||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
title: 'Search & Download',
|
title: 'Search & Download',
|
||||||
description: 'Search data sources (TheMovieDb, Douban, Bangumi), download task labels, search sites',
|
description: 'Search data sources (TheMovieDb, Douban, Bangumi), download task tags, search sites',
|
||||||
},
|
},
|
||||||
subscribe: {
|
subscribe: {
|
||||||
title: 'Subscription',
|
title: 'Subscription',
|
||||||
description: 'Subscription sites, subscription modes, subscription rules, upgrade rules',
|
description: 'Subscription sites, subscription mode, subscription rules, version upgrade rules',
|
||||||
},
|
},
|
||||||
scheduler: {
|
scheduler: {
|
||||||
title: 'Services',
|
title: 'Services',
|
||||||
description: 'Scheduled tasks',
|
description: 'Scheduled jobs',
|
||||||
},
|
},
|
||||||
notification: {
|
notification: {
|
||||||
title: 'Notifications',
|
title: 'Notifications',
|
||||||
@@ -126,7 +130,7 @@ export default {
|
|||||||
},
|
},
|
||||||
words: {
|
words: {
|
||||||
title: 'Word Lists',
|
title: 'Word Lists',
|
||||||
description: 'Custom recognition words, custom groups, custom placeholders, file organization filter words',
|
description: 'Custom recognition words, custom production/subtitle groups, custom placeholders, file organization block words',
|
||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
title: 'About',
|
title: 'About',
|
||||||
@@ -136,12 +140,12 @@ export default {
|
|||||||
subscribeTabs: {
|
subscribeTabs: {
|
||||||
movie: {
|
movie: {
|
||||||
mysub: 'My Subscriptions',
|
mysub: 'My Subscriptions',
|
||||||
popular: 'Popular',
|
popular: 'Popular Subscriptions',
|
||||||
},
|
},
|
||||||
tv: {
|
tv: {
|
||||||
mysub: 'My Subscriptions',
|
mysub: 'My Subscriptions',
|
||||||
popular: 'Popular',
|
popular: 'Popular Subscriptions',
|
||||||
share: 'Shared',
|
share: 'Subscription Shares',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pluginTabs: {
|
pluginTabs: {
|
||||||
@@ -155,12 +159,26 @@ export default {
|
|||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
admin: 'Administrator',
|
admin: 'Administrator',
|
||||||
normalUser: 'Normal User',
|
normalUser: 'Regular User',
|
||||||
profile: 'Profile',
|
profile: 'Profile',
|
||||||
systemSettings: 'System Settings',
|
systemSettings: 'System Settings',
|
||||||
siteAuth: 'User Authentication',
|
siteAuth: 'User Authentication',
|
||||||
helpDocs: 'Help Docs',
|
helpDocs: 'Help Documents',
|
||||||
restart: 'Restart',
|
restart: 'Restart',
|
||||||
|
management: 'User Management',
|
||||||
|
noUsers: 'No Users',
|
||||||
|
clickToAddUser: 'Click Add User card to add users',
|
||||||
|
addUser: 'Add User',
|
||||||
|
editUser: 'Edit User',
|
||||||
|
username: 'Username',
|
||||||
|
password: 'Password',
|
||||||
|
confirmPassword: 'Confirm Password',
|
||||||
|
role: 'Role',
|
||||||
|
email: 'Email',
|
||||||
|
enabled: 'Enabled',
|
||||||
|
disabled: 'Disabled',
|
||||||
|
status: 'Status',
|
||||||
|
operations: 'Operations',
|
||||||
},
|
},
|
||||||
nav: {
|
nav: {
|
||||||
more: 'More',
|
more: 'More',
|
||||||
@@ -178,7 +196,7 @@ export default {
|
|||||||
},
|
},
|
||||||
rule: {
|
rule: {
|
||||||
title: 'Rules',
|
title: 'Rules',
|
||||||
subtitle: 'Rule Test',
|
subtitle: 'Rule Testing',
|
||||||
},
|
},
|
||||||
log: {
|
log: {
|
||||||
title: 'Logs',
|
title: 'Logs',
|
||||||
@@ -186,7 +204,7 @@ export default {
|
|||||||
},
|
},
|
||||||
network: {
|
network: {
|
||||||
title: 'Network',
|
title: 'Network',
|
||||||
subtitle: 'Speed & Connectivity Test',
|
subtitle: 'Network Speed and Connectivity Test',
|
||||||
},
|
},
|
||||||
system: {
|
system: {
|
||||||
title: 'System',
|
title: 'System',
|
||||||
@@ -202,7 +220,7 @@ export default {
|
|||||||
clickToAdd: 'Click to Add',
|
clickToAdd: 'Click to Add',
|
||||||
dragToCanvas: 'Drag to Canvas',
|
dragToCanvas: 'Drag to Canvas',
|
||||||
tapComponentHint: 'Tap component to add to canvas',
|
tapComponentHint: 'Tap component to add to canvas',
|
||||||
dragComponentHint: 'Drag components to canvas',
|
dragComponentHint: 'Drag component to canvas',
|
||||||
},
|
},
|
||||||
dashboard: {
|
dashboard: {
|
||||||
storage: 'Storage Space',
|
storage: 'Storage Space',
|
||||||
@@ -224,9 +242,9 @@ export default {
|
|||||||
tv: 'TV Show',
|
tv: 'TV Show',
|
||||||
},
|
},
|
||||||
subscribe: {
|
subscribe: {
|
||||||
normalSub: 'Subscription',
|
normalSub: 'Subscribe',
|
||||||
versionSub: 'Version Upgrade',
|
versionSub: 'Version Upgrade Subscribe',
|
||||||
addSuccess: '{name} added successfully!',
|
addSuccess: 'Added {name} successfully!',
|
||||||
addFailed: 'Failed to add {name}: {message}!',
|
addFailed: 'Failed to add {name}: {message}!',
|
||||||
cancelSuccess: 'Subscription cancelled!',
|
cancelSuccess: 'Subscription cancelled!',
|
||||||
cancelFailed: 'Failed to cancel subscription: {message}!',
|
cancelFailed: 'Failed to cancel subscription: {message}!',
|
||||||
@@ -234,18 +252,16 @@ export default {
|
|||||||
name: 'Name',
|
name: 'Name',
|
||||||
searchShares: 'Search Subscription Shares',
|
searchShares: 'Search Subscription Shares',
|
||||||
keyword: 'Keyword',
|
keyword: 'Keyword',
|
||||||
noShareData:
|
noShareData: 'No shared subscription data received, data sharing not enabled or server cannot connect.',
|
||||||
'No shared subscription data available, data sharing is not enabled or the server cannot be connected.',
|
noPopularData: 'No popular subscription data received, data sharing not enabled or server cannot connect.',
|
||||||
noPopularData:
|
noFilterData: 'No related content found with current filters, please change filter conditions.',
|
||||||
'No popular subscription data available, data sharing is not enabled or the server cannot be connected.',
|
|
||||||
noFilterData: 'No matching content found. Please change the filter criteria.',
|
|
||||||
noSubscribeData: 'Please search to add movie or TV show subscriptions.',
|
noSubscribeData: 'Please search to add movie or TV show subscriptions.',
|
||||||
sharer: 'Shared by',
|
sharer: 'Sharer',
|
||||||
follow: 'Follow',
|
follow: 'Follow',
|
||||||
unfollow: 'Unfollow',
|
unfollow: 'Unfollow',
|
||||||
recognitionWords: 'Recognition Words',
|
recognitionWords: 'Recognition Words',
|
||||||
cancelShare: 'Cancel Share',
|
cancelShare: 'Cancel Share',
|
||||||
usageCount: 'Used {count} times',
|
usageCount: '{count} Uses',
|
||||||
},
|
},
|
||||||
recommend: {
|
recommend: {
|
||||||
all: 'All',
|
all: 'All',
|
||||||
@@ -255,7 +271,7 @@ export default {
|
|||||||
categoryRankings: 'Rankings',
|
categoryRankings: 'Rankings',
|
||||||
trendingNow: 'Trending Now',
|
trendingNow: 'Trending Now',
|
||||||
nowShowing: 'Now Showing',
|
nowShowing: 'Now Showing',
|
||||||
bangumiDaily: 'Bangumi Daily',
|
bangumiDaily: 'Bangumi Daily Release',
|
||||||
tmdbHotMovies: 'TMDB Hot Movies',
|
tmdbHotMovies: 'TMDB Hot Movies',
|
||||||
tmdbHotTVShows: 'TMDB Hot TV Shows',
|
tmdbHotTVShows: 'TMDB Hot TV Shows',
|
||||||
doubanHotMovies: 'Douban Hot Movies',
|
doubanHotMovies: 'Douban Hot Movies',
|
||||||
@@ -263,13 +279,13 @@ export default {
|
|||||||
doubanHotAnime: 'Douban Hot Anime',
|
doubanHotAnime: 'Douban Hot Anime',
|
||||||
doubanNewMovies: 'Douban New Movies',
|
doubanNewMovies: 'Douban New Movies',
|
||||||
doubanNewTVShows: 'Douban New TV Shows',
|
doubanNewTVShows: 'Douban New TV Shows',
|
||||||
doubanTop250: 'Douban Movie TOP250',
|
doubanTop250: 'Douban Top 250 Movies',
|
||||||
doubanChineseTVRankings: 'Douban Chinese TV Rankings',
|
doubanChineseTVRankings: 'Douban Chinese TV Rankings',
|
||||||
doubanGlobalTVRankings: 'Douban Global TV Rankings',
|
doubanGlobalTVRankings: 'Douban Global TV Rankings',
|
||||||
noCategoryContent: 'No content to display in this category',
|
noCategoryContent: 'No content to display in current category',
|
||||||
configureContent: 'Configure Content',
|
configureContent: 'Configure Display Content',
|
||||||
customizeContent: 'Customize Content',
|
customizeContent: 'Customize Content',
|
||||||
selectContentToDisplay: 'Select the content you want to display',
|
selectContentToDisplay: 'Select content you want to display on the page',
|
||||||
selectAll: 'Select All',
|
selectAll: 'Select All',
|
||||||
selectNone: 'Select None',
|
selectNone: 'Select None',
|
||||||
},
|
},
|
||||||
@@ -278,8 +294,8 @@ export default {
|
|||||||
dragToReorder: 'Drag to reorder tabs',
|
dragToReorder: 'Drag to reorder tabs',
|
||||||
},
|
},
|
||||||
downloading: {
|
downloading: {
|
||||||
noDownloader: 'No Downloaders',
|
noDownloader: 'No Downloader',
|
||||||
configureDownloader: 'Please configure and enable downloaders in settings first.',
|
configureDownloader: 'Please configure and enable a downloader in settings first.',
|
||||||
},
|
},
|
||||||
resource: {
|
resource: {
|
||||||
searchResults: 'Resource Search Results',
|
searchResults: 'Resource Search Results',
|
||||||
@@ -301,8 +317,8 @@ export default {
|
|||||||
},
|
},
|
||||||
notFound: {
|
notFound: {
|
||||||
title: 'Page Not Found ⚠️',
|
title: 'Page Not Found ⚠️',
|
||||||
description: 'The page you are trying to access does not exist. Please check the URL.',
|
description: 'The page you tried to access does not exist. Please check if the address is correct.',
|
||||||
backButton: 'Back',
|
backButton: 'Go Back',
|
||||||
},
|
},
|
||||||
torrent: {
|
torrent: {
|
||||||
sortDefault: 'Default',
|
sortDefault: 'Default',
|
||||||
@@ -310,9 +326,9 @@ export default {
|
|||||||
sortSize: 'Size',
|
sortSize: 'Size',
|
||||||
sortSeeder: 'Seeders',
|
sortSeeder: 'Seeders',
|
||||||
filterSite: 'Site',
|
filterSite: 'Site',
|
||||||
filterSeason: 'Season',
|
filterSeason: 'Season/Episode',
|
||||||
filterFreeState: 'Promotion',
|
filterFreeState: 'Promotion Status',
|
||||||
filterVideoCode: 'Video Code',
|
filterVideoCode: 'Video Codec',
|
||||||
filterEdition: 'Quality',
|
filterEdition: 'Quality',
|
||||||
filterResolution: 'Resolution',
|
filterResolution: 'Resolution',
|
||||||
filterReleaseGroup: 'Release Group',
|
filterReleaseGroup: 'Release Group',
|
||||||
@@ -325,15 +341,15 @@ export default {
|
|||||||
episode: 'Episode {number}',
|
episode: 'Episode {number}',
|
||||||
},
|
},
|
||||||
storage: {
|
storage: {
|
||||||
usedPercent: 'Used {percent}%',
|
usedPercent: '{percent}% Used',
|
||||||
},
|
},
|
||||||
site: {
|
site: {
|
||||||
noSites: 'No Sites',
|
noSites: 'No Sites',
|
||||||
sitesWillBeShownHere: 'Added and supported sites will be displayed here.',
|
sitesWillBeShownHere: 'Added and supported sites will be shown here.',
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
loadMore: 'Load More',
|
loadMore: 'Load More',
|
||||||
noMoreData: 'No More Data',
|
noMoreData: 'No more data',
|
||||||
},
|
},
|
||||||
logging: {
|
logging: {
|
||||||
level: 'Level',
|
level: 'Level',
|
||||||
@@ -833,7 +849,7 @@ export default {
|
|||||||
otp: 'Enable Two-Factor Authentication',
|
otp: 'Enable Two-Factor Authentication',
|
||||||
avatar: 'Avatar',
|
avatar: 'Avatar',
|
||||||
uploadAvatar: 'Upload Avatar',
|
uploadAvatar: 'Upload Avatar',
|
||||||
resetDefaultAvatar: 'Reset to Default Avatar',
|
resetDefaultAvatar: 'Reset Default Avatar',
|
||||||
restoreCurrentAvatar: 'Restore Current Avatar',
|
restoreCurrentAvatar: 'Restore Current Avatar',
|
||||||
notifications: 'Notifications',
|
notifications: 'Notifications',
|
||||||
wechat: 'WeChat UserID',
|
wechat: 'WeChat UserID',
|
||||||
@@ -846,28 +862,28 @@ export default {
|
|||||||
updatingUser: 'Updating user [{name}], please wait',
|
updatingUser: 'Updating user [{name}], please wait',
|
||||||
usernameRequired: 'Username cannot be empty',
|
usernameRequired: 'Username cannot be empty',
|
||||||
usernameExists: 'Username already exists',
|
usernameExists: 'Username already exists',
|
||||||
passwordMismatch: 'Passwords do not match',
|
passwordMismatch: 'The two passwords do not match',
|
||||||
userCreated: 'User [{name}] created successfully',
|
userCreated: 'User [{name}] created successfully',
|
||||||
userCreateFailed: 'Failed to create user: {message}',
|
userCreateFailed: 'Failed to create user: {message}',
|
||||||
userUpdateSuccess: 'User [{name}] updated successfully',
|
userUpdateSuccess: 'User [{name}] updated successfully',
|
||||||
userUpdateFailed: 'Failed to update user: {message}',
|
userUpdateFailed: 'Failed to update user: {message}',
|
||||||
userDeleteSuccess: 'User [{name}] deleted successfully',
|
userDeleteSuccess: 'User [{name}] deleted successfully',
|
||||||
userDeleteFailed: 'Failed to delete user: {message}',
|
userDeleteFailed: 'Failed to delete user: {message}',
|
||||||
invalidFile: 'The uploaded file does not meet requirements, please select another avatar',
|
invalidFile: 'The uploaded file does not meet the requirements, please choose a new avatar',
|
||||||
fileSizeLimit: 'File size cannot exceed 800KB',
|
fileSizeLimit: 'File size must not exceed 800KB',
|
||||||
avatarUploadSuccess: 'New avatar uploaded successfully, will take effect after saving!',
|
avatarUploadSuccess: 'New avatar uploaded successfully, will take effect after saving!',
|
||||||
resetAvatarSuccess: 'Reset to default avatar, will take effect after saving!',
|
resetAvatarSuccess: 'Reset to default avatar, will take effect after saving!',
|
||||||
restoreAvatarSuccess: 'Current avatar restored!',
|
restoreAvatarSuccess: 'Restored current avatar!',
|
||||||
deleteConfirm: 'Confirm delete user [{name}]?',
|
deleteConfirm: 'Confirm delete user [{name}]?',
|
||||||
saveUserInfo: 'Save User Information',
|
saveUserInfo: 'Save User Information',
|
||||||
cannotDeleteCurrentUser: 'Cannot delete the currently logged-in user',
|
cannotDeleteCurrentUser: 'Cannot delete current logged-in user',
|
||||||
deleteUser: 'Delete User',
|
deleteUser: 'Delete User',
|
||||||
},
|
},
|
||||||
searchBar: {
|
searchBar: {
|
||||||
search: 'Search',
|
search: 'Search',
|
||||||
searchPlaceholder: 'Search functions, subscriptions, settings...',
|
searchPlaceholder: 'Search features, subscriptions, settings...',
|
||||||
recentSearches: 'Recent Searches',
|
recentSearches: 'Recent Searches',
|
||||||
noRecentSearches: 'No recent searches',
|
noRecentSearches: 'No recent search history',
|
||||||
functions: 'Functions',
|
functions: 'Functions',
|
||||||
noFunctionsFound: 'No matching functions',
|
noFunctionsFound: 'No matching functions',
|
||||||
plugins: 'Plugins',
|
plugins: 'Plugins',
|
||||||
@@ -877,13 +893,13 @@ export default {
|
|||||||
searchSites: 'Search Sites',
|
searchSites: 'Search Sites',
|
||||||
selectSites: 'Select Sites',
|
selectSites: 'Select Sites',
|
||||||
collections: 'Collections',
|
collections: 'Collections',
|
||||||
collectionSearch: 'related collections',
|
collectionSearch: 'Related series works',
|
||||||
actorSearch: 'related actors, directors, etc.',
|
actorSearch: 'Related actors, directors, etc.',
|
||||||
historySearch: 'related history records',
|
historySearch: 'Related history records',
|
||||||
siteResources: 'Site Resources',
|
siteResources: 'Site Resources',
|
||||||
searchInSites: 'Search for torrents in sites',
|
searchInSites: 'Search for torrent resources in sites',
|
||||||
relatedResources: 'related resources',
|
relatedResources: 'Related Resources',
|
||||||
searchTip: 'Search for movies, TV shows, actors, resources, etc.',
|
searchTip: 'You can search for movies, TV shows, actors, resources, etc.',
|
||||||
},
|
},
|
||||||
searchSite: {
|
searchSite: {
|
||||||
selectSites: 'Select Sites',
|
selectSites: 'Select Sites',
|
||||||
@@ -903,7 +919,7 @@ export default {
|
|||||||
downloader: 'Downloader (Default)',
|
downloader: 'Downloader (Default)',
|
||||||
saveDirectory: 'Save Directory (Auto)',
|
saveDirectory: 'Save Directory (Auto)',
|
||||||
defaultPlaceholder: 'Leave empty for default',
|
defaultPlaceholder: 'Leave empty for default',
|
||||||
autoPlaceholder: 'Leave empty for auto match',
|
autoPlaceholder: 'Leave empty for auto-match',
|
||||||
downloading: 'Downloading...',
|
downloading: 'Downloading...',
|
||||||
startDownload: 'Start Download',
|
startDownload: 'Start Download',
|
||||||
downloadSuccess: '{site} {title} downloaded successfully!',
|
downloadSuccess: '{site} {title} downloaded successfully!',
|
||||||
@@ -914,41 +930,39 @@ export default {
|
|||||||
season: 'Season {number}',
|
season: 'Season {number}',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
description: 'Description',
|
description: 'Description',
|
||||||
descriptionHint:
|
descriptionHint: 'Add a description about this subscription. Search terms, recognition words, etc. will be included in the share by default',
|
||||||
'Fill in the description of the subscription. Search terms and identification terms in the subscription will be included in the share by default',
|
|
||||||
shareUser: 'Share User',
|
shareUser: 'Share User',
|
||||||
shareUserHint: 'Nickname of the sharer',
|
shareUserHint: 'Sharer\'s nickname',
|
||||||
confirmShare: 'Confirm Share',
|
confirmShare: 'Confirm Share',
|
||||||
shareSuccess: '{name} shared successfully!',
|
shareSuccess: '{name} shared successfully!',
|
||||||
shareFailed: '{name} share failed: {message}!',
|
shareFailed: '{name} share failed: {message}!',
|
||||||
},
|
},
|
||||||
u115Auth: {
|
u115Auth: {
|
||||||
loginTitle: '115 Cloud Login',
|
loginTitle: '115 Cloud Login',
|
||||||
scanQrCode: 'Please scan the QR code with WeChat or 115 client',
|
scanQrCode: 'Please scan with WeChat or 115 client',
|
||||||
scanned: 'QR code scanned, please confirm login',
|
scanned: 'Scanned, please confirm login',
|
||||||
complete: 'Complete',
|
complete: 'Complete',
|
||||||
},
|
},
|
||||||
aliyunAuth: {
|
aliyunAuth: {
|
||||||
loginTitle: 'Aliyun Drive Login',
|
loginTitle: 'Aliyun Drive Login',
|
||||||
scanQrCode: 'Please scan with Aliyun Drive App',
|
scanQrCode: 'Please scan with Aliyun Drive App',
|
||||||
scanned: 'QR code scanned',
|
scanned: 'Scanned',
|
||||||
complete: 'Complete',
|
complete: 'Complete',
|
||||||
},
|
},
|
||||||
rcloneConfig: {
|
rcloneConfig: {
|
||||||
title: 'RClone Configuration',
|
title: 'RClone Configuration',
|
||||||
filePath: 'Rclone Config File Path',
|
filePath: 'rclone config file path',
|
||||||
fileContent: 'Rclone Config File Content',
|
fileContent: 'rclone config file content',
|
||||||
defaultContent:
|
defaultContent: '# Please fill in your rclone config file content here \n# Please refer to https://rclone.org/docs/ \n# Storage node name must be: MP',
|
||||||
'# 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',
|
complete: 'Complete',
|
||||||
},
|
},
|
||||||
alistConfig: {
|
alistConfig: {
|
||||||
title: 'Alist Configuration',
|
title: 'Alist Configuration',
|
||||||
serverUrl: 'Alist Server URL',
|
serverUrl: 'Alist server address',
|
||||||
username: 'Username',
|
username: 'Username',
|
||||||
password: 'Password',
|
password: 'Password',
|
||||||
tokenUrl: 'Token URL',
|
tokenUrl: 'Token acquisition address',
|
||||||
loginType: 'Login Type',
|
loginType: 'Login method',
|
||||||
loginTypeOptions: {
|
loginTypeOptions: {
|
||||||
guest: 'Guest',
|
guest: 'Guest',
|
||||||
username: 'Username & Password',
|
username: 'Username & Password',
|
||||||
@@ -967,17 +981,17 @@ export default {
|
|||||||
schedule: 'Schedule',
|
schedule: 'Schedule',
|
||||||
cronExpr: 'Cron Expression',
|
cronExpr: 'Cron Expression',
|
||||||
cronExprDesc: 'Cron expression for workflow scheduling',
|
cronExprDesc: 'Cron expression for workflow scheduling',
|
||||||
nameRequired: 'Please fill in all required information!',
|
nameRequired: 'Please fill in complete information!',
|
||||||
addSuccess: 'Workflow created successfully. Please edit the process!',
|
addSuccess: 'Task created successfully, please edit the workflow!',
|
||||||
addFailed: 'Failed to create workflow: {message}',
|
addFailed: 'Failed to create task: {message}',
|
||||||
editSuccess: 'Workflow modified successfully!',
|
editSuccess: 'Task modified successfully!',
|
||||||
editFailed: 'Failed to modify workflow: {message}',
|
editFailed: 'Failed to modify task: {message}',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
},
|
},
|
||||||
workflowActions: {
|
workflowActions: {
|
||||||
title: 'Edit Workflow',
|
title: 'Edit Workflow',
|
||||||
noActionsMessage: 'No actions in the workflow, please add actions',
|
noActionsMessage: 'Workflow has no actions, please add actions',
|
||||||
addAction: 'Add Action',
|
addAction: 'Add Action',
|
||||||
editAction: 'Edit Action',
|
editAction: 'Edit Action',
|
||||||
deleteAction: 'Delete Action',
|
deleteAction: 'Delete Action',
|
||||||
@@ -995,24 +1009,24 @@ export default {
|
|||||||
confirmDeleteMessage: 'Are you sure you want to delete this action? This operation cannot be undone.',
|
confirmDeleteMessage: 'Are you sure you want to delete this action? This operation cannot be undone.',
|
||||||
yesDelete: 'Yes, Delete',
|
yesDelete: 'Yes, Delete',
|
||||||
noCancel: 'Cancel',
|
noCancel: 'Cancel',
|
||||||
invalidConnection: 'Invalid connection: Cannot connect to self or same type port!',
|
invalidConnection: 'Invalid connection: cannot connect to self or ports of the same type!',
|
||||||
componentNotFound: 'Component {component} not found',
|
componentNotFound: 'Component {component} not found',
|
||||||
componentAdded: 'Component added to canvas',
|
componentAdded: 'Component added to canvas',
|
||||||
saveSuccess: 'Workflow saved successfully!',
|
saveSuccess: 'Task workflow saved successfully!',
|
||||||
saveFailed: 'Failed to save workflow: {message}',
|
saveFailed: 'Failed to save task workflow: {message}',
|
||||||
importTitle: 'Import Workflow',
|
importTitle: 'Import Task Workflow',
|
||||||
importSuccess: 'Import successful!',
|
importSuccess: 'Import successful!',
|
||||||
importFailed: 'Import failed!',
|
importFailed: 'Import failed!',
|
||||||
codeCopied: 'Workflow code copied to clipboard!',
|
codeCopied: 'Task workflow code copied to clipboard!',
|
||||||
},
|
},
|
||||||
siteCookieUpdate: {
|
siteCookieUpdate: {
|
||||||
title: 'Update Site Cookie',
|
title: 'Update Site Cookie',
|
||||||
checkHint: 'Checking login status, please wait...',
|
checkHint: 'Checking login status, please wait...',
|
||||||
confirmUpdateTitle: 'Confirm Update',
|
confirmUpdateTitle: 'Confirm Update',
|
||||||
confirmUpdateMessage: "Do you want to update this site's cookie with the local cookie?",
|
confirmUpdateMessage: 'Do you want to update this site\'s cookie with the local cookie?',
|
||||||
processing: 'Processing...',
|
processing: 'Processing...',
|
||||||
success: 'Cookie updated successfully',
|
success: 'Cookie updated successfully',
|
||||||
failed: 'Cookie update failed',
|
failed: 'Failed to update cookie',
|
||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
},
|
},
|
||||||
@@ -1024,7 +1038,7 @@ export default {
|
|||||||
iconLabel: 'Site Icon',
|
iconLabel: 'Site Icon',
|
||||||
uploadIcon: 'Upload Icon',
|
uploadIcon: 'Upload Icon',
|
||||||
cookie: 'Cookie',
|
cookie: 'Cookie',
|
||||||
rssUrl: 'RSS URL',
|
rssUrl: 'RSS Link',
|
||||||
enableLabel: 'Enable',
|
enableLabel: 'Enable',
|
||||||
pubEnableLabel: 'Public Resources',
|
pubEnableLabel: 'Public Resources',
|
||||||
priorityLabel: 'Priority',
|
priorityLabel: 'Priority',
|
||||||
@@ -1048,7 +1062,7 @@ export default {
|
|||||||
title: 'Plugin Market Settings',
|
title: 'Plugin Market Settings',
|
||||||
repoUrl: 'Plugin Repository URL',
|
repoUrl: 'Plugin Repository URL',
|
||||||
repoPlaceholder: 'Format: https://github.com/jxxghp/MoviePilot-Plugins/,https://github.com/xxxx/xxxxxx/',
|
repoPlaceholder: 'Format: https://github.com/jxxghp/MoviePilot-Plugins/,https://github.com/xxxx/xxxxxx/',
|
||||||
repoHint: 'Multiple addresses separated by commas, only supports Github repositories',
|
repoHint: 'Multiple URLs separated by commas, only Github repositories are supported',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
saveSuccess: 'Plugin repository saved successfully',
|
saveSuccess: 'Plugin repository saved successfully',
|
||||||
@@ -1057,21 +1071,21 @@ export default {
|
|||||||
userAuth: {
|
userAuth: {
|
||||||
title: 'User Authentication',
|
title: 'User Authentication',
|
||||||
codeLabel: 'Authentication Code',
|
codeLabel: 'Authentication Code',
|
||||||
codePlaceholder: 'Please enter the authentication code',
|
codePlaceholder: 'Please enter authentication code',
|
||||||
authBtn: 'Authenticate',
|
authBtn: 'Start Authentication',
|
||||||
closeBtn: 'Close',
|
closeBtn: 'Close',
|
||||||
selectSite: 'Select Authentication Site',
|
selectSite: 'Select Authentication Site',
|
||||||
selectSiteRequired: 'Please select an authentication site!',
|
selectSiteRequired: 'Please select authentication site!',
|
||||||
siteConfigNotExist: 'Site configuration does not exist!',
|
siteConfigNotExist: 'Site configuration does not exist!',
|
||||||
fieldRequired: 'Please enter {name}!',
|
fieldRequired: 'Please enter {name}!',
|
||||||
authSuccess: 'User authentication successful, please login again!',
|
authSuccess: 'User authentication successful, please log in again!',
|
||||||
authFailed: 'Authentication failed: {message}',
|
authFailed: 'Authentication failed: {message}',
|
||||||
},
|
},
|
||||||
transferQueue: {
|
transferQueue: {
|
||||||
title: 'Transfer Queue',
|
title: 'Organization Queue',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
type: 'Type',
|
type: 'Type',
|
||||||
state: 'State',
|
state: 'Status',
|
||||||
progress: 'Progress',
|
progress: 'Progress',
|
||||||
startTime: 'Start Time',
|
startTime: 'Start Time',
|
||||||
speedTitle: 'Speed',
|
speedTitle: 'Speed',
|
||||||
@@ -1090,9 +1104,9 @@ export default {
|
|||||||
close: 'Close',
|
close: 'Close',
|
||||||
},
|
},
|
||||||
reorganize: {
|
reorganize: {
|
||||||
title: 'Reorganize',
|
title: 'Organize',
|
||||||
sourceTitle: 'Source Files',
|
sourceTitle: 'Source File',
|
||||||
targetTitle: 'Target Files',
|
targetTitle: 'Target File',
|
||||||
processingTitle: 'Processing',
|
processingTitle: 'Processing',
|
||||||
confirmTitle: 'Confirm',
|
confirmTitle: 'Confirm',
|
||||||
selectFile: 'Select File',
|
selectFile: 'Select File',
|
||||||
@@ -1105,120 +1119,119 @@ export default {
|
|||||||
selectTargetPath: 'Select Target Path',
|
selectTargetPath: 'Select Target Path',
|
||||||
selectTargetDir: 'Select Target Directory',
|
selectTargetDir: 'Select Target Directory',
|
||||||
selectFileName: 'Select Filename',
|
selectFileName: 'Select Filename',
|
||||||
confirmMoving: 'Please confirm moving!',
|
confirmMoving: 'Please confirm move!',
|
||||||
sourceLabel: 'Source:',
|
sourceLabel: 'Source file:',
|
||||||
targetLabel: 'Target Directory:',
|
targetLabel: 'Target directory:',
|
||||||
filenameLabel: 'Filename:',
|
filenameLabel: 'Filename:',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
next: 'Next',
|
next: 'Next',
|
||||||
previous: 'Previous',
|
previous: 'Previous',
|
||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
manualTitle: 'Manual Reorganize',
|
manualTitle: 'Manual Organization',
|
||||||
multipleItemsTitle: 'Reorganize - {count} Items',
|
multipleItemsTitle: 'Organize - {count} Items',
|
||||||
singleItemTitle: 'Reorganize - {path}',
|
singleItemTitle: 'Organize - {path}',
|
||||||
targetStorage: 'Target Storage',
|
targetStorage: 'Target Storage',
|
||||||
targetStorageHint: 'Storage for reorganization',
|
targetStorageHint: 'Organization target storage',
|
||||||
transferType: 'Transfer Method',
|
transferType: 'Organization Method',
|
||||||
transferTypeHint: 'File operation method',
|
transferTypeHint: 'File operation organization method',
|
||||||
targetPath: 'Target Path',
|
targetPath: 'Target Path',
|
||||||
targetPathHint: 'Target path for reorganization, leave empty for automatic matching',
|
targetPathHint: 'Organization target path, leave empty for auto-match',
|
||||||
targetPathPlaceholder: 'Leave empty for automatic',
|
targetPathPlaceholder: 'Leave empty for auto',
|
||||||
mediaType: 'Type',
|
mediaType: 'Type',
|
||||||
mediaTypeHint: 'Media type of the file',
|
mediaTypeHint: 'File media type',
|
||||||
tmdbId: 'TheMovieDb ID',
|
tmdbId: 'TheMovieDb ID',
|
||||||
doubanId: 'Douban ID',
|
doubanId: 'Douban ID',
|
||||||
mediaIdHint: 'Search media ID by name, leave empty for automatic recognition',
|
mediaIdHint: 'Query media ID by name, leave empty for auto recognition',
|
||||||
mediaIdPlaceholder: 'Leave empty for automatic recognition',
|
mediaIdPlaceholder: 'Leave empty for auto recognition',
|
||||||
episodeGroup: 'Episode Group ID',
|
episodeGroup: 'Episode Group ID',
|
||||||
episodeGroupHint: 'Specify episode group',
|
episodeGroupHint: 'Specify episode group',
|
||||||
episodeGroupPlaceholder: 'Manually search episode group',
|
episodeGroupPlaceholder: 'Manually query episode group',
|
||||||
season: 'Season',
|
season: 'Season',
|
||||||
seasonHint: 'Which season',
|
seasonHint: 'Which season',
|
||||||
episodeDetail: 'Episode',
|
episodeDetail: 'Episode',
|
||||||
episodeDetailHint: 'Episode number or range, e.g., 1 or 1,2',
|
episodeDetailHint: 'Episode number or range, e.g. 1 or 1,2',
|
||||||
episodeDetailPlaceholder: 'Start episode,end episode',
|
episodeDetailPlaceholder: 'Start episode,End episode',
|
||||||
episodeFormat: 'Episode Format',
|
episodeFormat: 'Episode Positioning',
|
||||||
episodeFormatHint: 'Use {ep} to locate the episode part in the filename for recognition',
|
episodeFormatHint: 'Use {ep} to position episode number part in filename to assist recognition',
|
||||||
episodeFormatPlaceholder: 'Use {ep} to locate episode',
|
episodeFormatPlaceholder: 'Use {ep} to position episode',
|
||||||
episodeOffset: 'Episode Offset',
|
episodeOffset: 'Episode Offset',
|
||||||
episodeOffsetHint: 'Episode offset calculation, e.g., -10 or EP*2',
|
episodeOffsetHint: 'Episode offset calculation, e.g. -10 or EP*2',
|
||||||
episodeOffsetPlaceholder: 'e.g., -10',
|
episodeOffsetPlaceholder: 'e.g. -10',
|
||||||
episodePart: 'Specify Part',
|
episodePart: 'Specify Part',
|
||||||
episodePartHint: 'Specify part, e.g., part1',
|
episodePartHint: 'Specify part, e.g. part1',
|
||||||
episodePartPlaceholder: 'e.g., part1',
|
episodePartPlaceholder: 'e.g. part1',
|
||||||
minFileSize: 'Minimum File Size (MB)',
|
minFileSize: 'Min File Size (MB)',
|
||||||
minFileSizeHint: 'Only reorganize files larger than the minimum file size',
|
minFileSizeHint: 'Only organize files larger than minimum file size',
|
||||||
typeFolderOption: 'Categorize by Type',
|
typeFolderOption: 'Classify by Type',
|
||||||
typeFolderHint: 'Add subdirectories by media type in the target path during reorganization',
|
typeFolderHint: 'Add subdirectory by media type in target path during organization',
|
||||||
categoryFolderOption: 'Categorize by Category',
|
categoryFolderOption: 'Classify by Category',
|
||||||
categoryFolderHint: 'Add subdirectories by media category in the target path during reorganization',
|
categoryFolderHint: 'Add subdirectory by media category in target path during organization',
|
||||||
scrapeOption: 'Scrape Metadata',
|
scrapeOption: 'Scrape Metadata',
|
||||||
scrapeHint: 'Automatically scrape metadata after reorganization',
|
scrapeHint: 'Automatically scrape metadata after organization',
|
||||||
fromHistoryOption: 'Reuse Historical Recognition Info',
|
fromHistoryOption: 'Reuse Historical Recognition Info',
|
||||||
fromHistoryHint: 'Use media information recognized in historical reorganization records',
|
fromHistoryHint: 'Use media info already recognized in historical organization records',
|
||||||
addToQueue: 'Add to Queue',
|
addToQueue: 'Add to Organization Queue',
|
||||||
reorganizeNow: 'Reorganize Now',
|
reorganizeNow: 'Organize Now',
|
||||||
auto: 'Auto',
|
auto: 'Auto',
|
||||||
processing: 'Processing ...',
|
processing: 'Processing ...',
|
||||||
successMessage: 'File {name} has been added to the reorganization queue!',
|
successMessage: 'File {name} has been added to the organization queue!',
|
||||||
},
|
},
|
||||||
subscribeEdit: {
|
subscribeEdit: {
|
||||||
titleDefault: 'Default Subscription Rule',
|
titleDefault: 'Default Subscription Rules',
|
||||||
titleEditFormat: 'Edit Subscription - {name} {season}',
|
titleEditFormat: 'Edit Subscription - {name} {season}',
|
||||||
seasonFormat: 'Season {number}',
|
seasonFormat: 'Season {number}',
|
||||||
tabs: {
|
tabs: {
|
||||||
basic: 'Basic',
|
basic: 'Basic',
|
||||||
advance: 'Advanced',
|
advance: 'Advanced',
|
||||||
},
|
},
|
||||||
searchKeyword: 'Search Keyword',
|
searchKeyword: 'Search Keywords',
|
||||||
searchKeywordHint: 'Keyword used when searching sites',
|
searchKeywordHint: 'Specify keywords used when searching sites',
|
||||||
totalEpisode: 'Total Episodes',
|
totalEpisode: 'Total Episodes',
|
||||||
totalEpisodeHint: 'Total number of episodes',
|
totalEpisodeHint: 'Total number of episodes',
|
||||||
startEpisode: 'Start Episode',
|
startEpisode: 'Start Episode',
|
||||||
startEpisodeHint: 'Episode number to start subscription',
|
startEpisodeHint: 'Starting episode number to subscribe',
|
||||||
quality: 'Quality',
|
quality: 'Quality',
|
||||||
qualityHint: 'Resource quality for subscription',
|
qualityHint: 'Subscription resource quality',
|
||||||
resolution: 'Resolution',
|
resolution: 'Resolution',
|
||||||
resolutionHint: 'Resource resolution for subscription',
|
resolutionHint: 'Subscription resource resolution',
|
||||||
effect: 'Effect',
|
effect: 'Effects',
|
||||||
effectHint: 'Resource effect for subscription',
|
effectHint: 'Subscription resource effects',
|
||||||
subscribeSites: 'Subscribe Sites',
|
subscribeSites: 'Subscription Sites',
|
||||||
subscribeSitesHint: 'Sites to subscribe from, use system settings if none selected',
|
subscribeSitesHint: 'Range of sites for subscription, use system settings if none selected',
|
||||||
downloader: 'Downloader',
|
downloader: 'Downloader',
|
||||||
downloaderHint: 'Specify which downloader to use for this subscription',
|
downloaderHint: 'Specify downloader for this subscription',
|
||||||
savePath: 'Save Path',
|
savePath: 'Save Path',
|
||||||
savePathHint: 'Specify download path for this subscription, leave empty to use default directories',
|
savePathHint: 'Specify download save path for this subscription, leave empty to use default download directory',
|
||||||
bestVersion: 'Version Upgrade',
|
bestVersion: 'Version Upgrade',
|
||||||
bestVersionHint: 'Enable version upgrade based on priority',
|
bestVersionHint: 'Perform version upgrade subscription based on upgrade priorities',
|
||||||
searchImdbid: 'Use ImdbID Search',
|
searchImdbid: 'Search Using ImdbID',
|
||||||
searchImdbidHint: 'Use ImdbID for precise resource searching',
|
searchImdbidHint: 'Use ImdbID for precise resource searching',
|
||||||
showEditDialog: 'Edit More Rules When Subscribing',
|
showEditDialog: 'Edit More Rules When Subscribing',
|
||||||
showEditDialogHint: 'Show this edit dialog when adding subscription',
|
showEditDialogHint: 'Show this edit subscription dialog when adding subscription',
|
||||||
include: 'Include (Keywords, Regex)',
|
include: 'Include (Keywords, Regex)',
|
||||||
includeHint: 'Include rules, supports regex',
|
includeHint: 'Include rules, supports regular expressions',
|
||||||
exclude: 'Exclude (Keywords, Regex)',
|
exclude: 'Exclude (Keywords, Regex)',
|
||||||
excludeHint: 'Exclude rules, supports regex',
|
excludeHint: 'Exclude rules, supports regular expressions',
|
||||||
filterGroups: 'Priority Rule Groups',
|
filterGroups: 'Priority Rule Groups',
|
||||||
filterGroupsHint: 'Filter subscription based on selected rule groups',
|
filterGroupsHint: 'Filter subscriptions by selected filter rule groups',
|
||||||
episodeGroup: 'Episode Group',
|
episodeGroup: 'Specify Episode Group',
|
||||||
episodeGroupHint: 'Recognize and scrape based on specific episode group',
|
episodeGroupHint: 'Recognize and scrape by specific episode group',
|
||||||
season: 'Season',
|
season: 'Specify Season',
|
||||||
seasonHint: 'Specify season for subscription',
|
seasonHint: 'Specify any season for subscription',
|
||||||
mediaCategory: 'Custom Category',
|
mediaCategory: 'Custom Category',
|
||||||
mediaCategoryHint: 'Specify category name, auto-detect if empty',
|
mediaCategoryHint: 'Specify category name, leave empty for auto-recognition',
|
||||||
customWords: 'Custom Recognition Words',
|
customWords: 'Custom Recognition Words',
|
||||||
customWordsHint: 'Recognition words only for this subscription',
|
customWordsHint: 'Recognition words only used for this subscription',
|
||||||
customWordsPlaceholder:
|
customWordsPlaceholder: 'Block word\nReplaced word => Replacement word\nPrefix <> Suffix >> Episode offset (EP)\nReplaced word => Replacement word && Prefix <> Suffix >> Episode offset (EP)\nReplacement word supports format: { tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx } to directly specify TMDBID/Douban ID recognition, where s, e are season and episode numbers (optional)',
|
||||||
'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',
|
cancelSubscribe: 'Cancel Subscription',
|
||||||
cancelSubscribeConfirm: 'Are you sure you want to cancel this subscription?',
|
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
|
cancelSubscribeConfirm: 'Are you sure you want to cancel the subscription?',
|
||||||
},
|
},
|
||||||
subscribeFiles: {
|
subscribeFiles: {
|
||||||
title: 'Downloaded Files',
|
title: 'Downloaded Files',
|
||||||
noFilesMessage: 'No files',
|
noFilesMessage: 'No files',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
downloadTab: 'Downloaded Files',
|
downloadTab: 'Download Files',
|
||||||
libraryTab: 'Media Library Files',
|
libraryTab: 'Media Library Files',
|
||||||
episodeColumn: 'Episode',
|
episodeColumn: 'Episode',
|
||||||
torrentColumn: 'Torrent',
|
torrentColumn: 'Torrent',
|
||||||
@@ -1262,28 +1275,136 @@ export default {
|
|||||||
msgCount: 'Unread Messages',
|
msgCount: 'Unread Messages',
|
||||||
inviteCount: 'Invites',
|
inviteCount: 'Invites',
|
||||||
bonus: 'Bonus Points',
|
bonus: 'Bonus Points',
|
||||||
ratio: 'Share Ratio',
|
ratio: 'Ratio',
|
||||||
joinTime: 'Join Time',
|
joinTime: 'Join Time',
|
||||||
trafficHistory: 'Traffic History',
|
trafficHistory: 'Traffic History',
|
||||||
seedingDistribution: 'Seeding Distribution',
|
seedingDistribution: 'Seeding Distribution',
|
||||||
volumeTitle: 'Volume',
|
volumeTitle: 'Volume',
|
||||||
countTitle: 'Count: ',
|
countTitle: 'Count:',
|
||||||
noData: 'None',
|
noData: 'None',
|
||||||
refreshing: 'Refreshing site data...',
|
refreshing: 'Refreshing site data...',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
},
|
},
|
||||||
siteResource: {
|
siteResource: {
|
||||||
title: 'Site Resources',
|
title: 'Site Resources',
|
||||||
searchHint: 'Search Resources',
|
searchHint: 'Search resources',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
},
|
},
|
||||||
forkSubscribe: {
|
forkSubscribe: {
|
||||||
title: 'Fork Subscription',
|
title: 'Copy Subscription',
|
||||||
selectSubscriber: 'Select Target User',
|
selectSubscriber: 'Select Copy Target',
|
||||||
overwriteExisting: 'Overwrite Existing',
|
overwriteExisting: 'Overwrite Existing Subscription',
|
||||||
overwriteExistingHint: 'Whether to overwrite if the target user already has this subscription',
|
overwriteExistingHint: 'Whether to overwrite when target user already has this subscription',
|
||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
file: {
|
||||||
|
newFolder: 'New Folder',
|
||||||
|
createFolder: 'Create Folder',
|
||||||
|
fileName: 'Filename',
|
||||||
|
fileSize: 'File Size',
|
||||||
|
fileType: 'File Type',
|
||||||
|
lastModified: 'Last Modified',
|
||||||
|
actions: 'Actions',
|
||||||
|
rename: 'Rename',
|
||||||
|
delete: 'Delete',
|
||||||
|
confirmDelete: 'Confirm Delete',
|
||||||
|
upload: 'Upload',
|
||||||
|
download: 'Download',
|
||||||
|
preview: 'Preview',
|
||||||
|
selectAll: 'Select All',
|
||||||
|
deselectAll: 'Deselect All',
|
||||||
|
moveUp: 'Go Up',
|
||||||
|
sortByName: 'Sort by Name',
|
||||||
|
sortByTime: 'Sort by Time',
|
||||||
|
currentName: 'Current Name',
|
||||||
|
newName: 'New Name',
|
||||||
|
includeSubfolders: 'Auto rename all media files in directory',
|
||||||
|
emptyFolder: 'Empty Folder',
|
||||||
|
noFilesInFolder: 'No files in this folder',
|
||||||
|
autoRecognize: 'Auto Recognize Name',
|
||||||
|
directoryTree: 'Directory Tree',
|
||||||
|
rootDirectory: 'Root Directory',
|
||||||
|
noDirectories: 'No directories available',
|
||||||
|
},
|
||||||
|
person: {
|
||||||
|
alias: 'Also Known As:',
|
||||||
|
credits: 'Credits',
|
||||||
|
biography: 'Biography',
|
||||||
|
birthday: 'Birthday',
|
||||||
|
placeOfBirth: 'Place of Birth',
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
title: 'Error!',
|
||||||
|
networkError: 'Unable to get media information, please check your network connection.',
|
||||||
|
serverError: 'Server error, please try again later.',
|
||||||
|
notFound: 'Requested resource not found.',
|
||||||
|
},
|
||||||
|
plugin: {
|
||||||
|
sort: {
|
||||||
|
popular: 'Popular',
|
||||||
|
name: 'Plugin Name',
|
||||||
|
author: 'Author',
|
||||||
|
repository: 'Plugin Repository',
|
||||||
|
latest: 'Latest Release',
|
||||||
|
},
|
||||||
|
installingPlugin: 'Installing plugin...',
|
||||||
|
installing: 'Installing {name} v{version} ...',
|
||||||
|
installSuccess: 'Plugin {name} installed successfully!',
|
||||||
|
installFailed: 'Plugin {name} installation failed: {message}',
|
||||||
|
uninstalling: 'Uninstalling {name} ...',
|
||||||
|
uninstallSuccess: 'Plugin {name} uninstalled successfully!',
|
||||||
|
uninstallFailed: 'Plugin {name} uninstallation failed: {message}',
|
||||||
|
updating: 'Updating {name} to v{version} ...',
|
||||||
|
updateSuccess: 'Plugin {name} updated successfully!',
|
||||||
|
updateFailed: 'Plugin {name} update failed: {message}',
|
||||||
|
noPlugins: 'No plugins installed',
|
||||||
|
installed: 'Installed',
|
||||||
|
notInstalled: 'Not Installed',
|
||||||
|
hasUpdate: 'Update Available',
|
||||||
|
configuring: 'Configuring',
|
||||||
|
enable: 'Enable',
|
||||||
|
disable: 'Disable',
|
||||||
|
settings: 'Settings',
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
personalInfo: 'Personal Information',
|
||||||
|
uploadNewAvatar: 'Upload New Avatar',
|
||||||
|
avatarFormatError: 'The uploaded file does not meet requirements, please select a new avatar',
|
||||||
|
avatarSizeError: 'File size must not exceed 800KB',
|
||||||
|
avatarUploadSuccess: 'New avatar uploaded successfully, will take effect after saving!',
|
||||||
|
resetAvatarSuccess: 'Reset to default avatar, will take effect after saving!',
|
||||||
|
restoreAvatarSuccess: 'Restored current avatar!',
|
||||||
|
savingInProgress: 'Saving in progress, please wait...',
|
||||||
|
usernameRequired: 'Username cannot be empty',
|
||||||
|
passwordMismatch: 'The two passwords do not match',
|
||||||
|
usernameChangeSuccess: '[{oldName}] renamed to [{newName}], user information saved successfully!',
|
||||||
|
saveSuccess: 'User information saved successfully!',
|
||||||
|
saveFailedWithNameChange: '[{oldName}] renamed to [{newName}], information save failed: {message}!',
|
||||||
|
saveFailed: 'User information save failed: {message}!',
|
||||||
|
nickname: 'Nickname',
|
||||||
|
nicknamePlaceholder: 'Display nickname, takes precedence over username',
|
||||||
|
accountBinding: 'Account Binding',
|
||||||
|
wechatUser: 'WeChat User',
|
||||||
|
telegramUser: 'Telegram User',
|
||||||
|
slackUser: 'Slack User',
|
||||||
|
vocechatUser: 'VoceChat User',
|
||||||
|
synologychatUser: 'SynologyChat User',
|
||||||
|
doubanUser: 'Douban User',
|
||||||
|
twoFactorAuthentication: 'Two-Factor Authentication',
|
||||||
|
enableTwoFactor: 'Enable Two-Factor Authentication',
|
||||||
|
disableTwoFactor: 'Disable Two-Factor Authentication',
|
||||||
|
otpGenerateFailed: 'Failed to get OTP URI: {message}!',
|
||||||
|
otpDisableSuccess: 'Two-factor authentication disabled successfully!',
|
||||||
|
otpDisableFailed: 'Failed to disable OTP: {message}!',
|
||||||
|
otpCodeRequired: 'Please enter the 6-digit verification code',
|
||||||
|
otpEnableSuccess: 'Two-factor authentication enabled successfully!',
|
||||||
|
otpEnableFailed: 'Failed to enable OTP: {message}!',
|
||||||
|
authenticatorApp: 'Authenticator App',
|
||||||
|
authenticatorAppDescription: 'Use an authenticator app like Google Authenticator, Microsoft Authenticator, Authy, or 1Password to scan the QR code. It will generate a 6-digit code for you to enter below.',
|
||||||
|
secretKeyTip: 'If you\'re having trouble with the QR code, select manual entry in your app and enter the code above.',
|
||||||
|
enterVerificationCode: 'Enter verification code to confirm enabling two-factor authentication',
|
||||||
|
avatarFormatTip: 'JPG, PNG, GIF, WEBP formats allowed, maximum size 800KB.',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
+128
-1
@@ -17,6 +17,10 @@ export default {
|
|||||||
noContent: '没有找到相关内容',
|
noContent: '没有找到相关内容',
|
||||||
all: '全部',
|
all: '全部',
|
||||||
default: '默认',
|
default: '默认',
|
||||||
|
name: '名称',
|
||||||
|
create: '新建',
|
||||||
|
saving: '保存中',
|
||||||
|
reset: '重置',
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
light: '浅色',
|
light: '浅色',
|
||||||
@@ -161,6 +165,20 @@ export default {
|
|||||||
siteAuth: '用户认证',
|
siteAuth: '用户认证',
|
||||||
helpDocs: '帮助文档',
|
helpDocs: '帮助文档',
|
||||||
restart: '重启',
|
restart: '重启',
|
||||||
|
management: '用户管理',
|
||||||
|
noUsers: '没有用户',
|
||||||
|
clickToAddUser: '点击添加用户卡片添加用户',
|
||||||
|
addUser: '添加用户',
|
||||||
|
editUser: '编辑用户',
|
||||||
|
username: '用户名',
|
||||||
|
password: '密码',
|
||||||
|
confirmPassword: '确认密码',
|
||||||
|
role: '角色',
|
||||||
|
email: '邮箱',
|
||||||
|
enabled: '启用',
|
||||||
|
disabled: '禁用',
|
||||||
|
status: '状态',
|
||||||
|
operations: '操作',
|
||||||
},
|
},
|
||||||
nav: {
|
nav: {
|
||||||
more: '更多',
|
more: '更多',
|
||||||
@@ -1187,7 +1205,7 @@ export default {
|
|||||||
customWords: '自定义识别词',
|
customWords: '自定义识别词',
|
||||||
customWordsHint: '只对该订阅使用的识别词',
|
customWordsHint: '只对该订阅使用的识别词',
|
||||||
customWordsPlaceholder:
|
customWordsPlaceholder:
|
||||||
'屏蔽词\n被替换词 => 替换词\n前定位词 <> 后定位词 >> 集偏移量(EP)\n被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)\n其中替换词支持格式:{[tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID识别,其中s、e为季数和集数(可选)',
|
'屏蔽词\n被替换词 => 替换词\n前定位词 <> 后定位词 >> 集偏移量(EP)\n被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)\n其中替换词支持格式:{ tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx } 直接指定TMDBID/豆瓣ID识别,其中s、e为季数和集数(可选)',
|
||||||
cancelSubscribe: '取消订阅',
|
cancelSubscribe: '取消订阅',
|
||||||
save: '保存',
|
save: '保存',
|
||||||
cancelSubscribeConfirm: '是否确认取消订阅?',
|
cancelSubscribeConfirm: '是否确认取消订阅?',
|
||||||
@@ -1264,4 +1282,113 @@ export default {
|
|||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
file: {
|
||||||
|
newFolder: '新建文件夹',
|
||||||
|
createFolder: '创建文件夹',
|
||||||
|
fileName: '文件名',
|
||||||
|
fileSize: '文件大小',
|
||||||
|
fileType: '文件类型',
|
||||||
|
lastModified: '修改时间',
|
||||||
|
actions: '操作',
|
||||||
|
rename: '重命名',
|
||||||
|
delete: '删除',
|
||||||
|
confirmDelete: '确认删除',
|
||||||
|
upload: '上传',
|
||||||
|
download: '下载',
|
||||||
|
preview: '预览',
|
||||||
|
selectAll: '全选',
|
||||||
|
deselectAll: '取消全选',
|
||||||
|
moveUp: '返回上一级',
|
||||||
|
sortByName: '按名称排序',
|
||||||
|
sortByTime: '按时间排序',
|
||||||
|
currentName: '当前名称',
|
||||||
|
newName: '新名称',
|
||||||
|
includeSubfolders: '自动重命名目录内所有媒体文件',
|
||||||
|
emptyFolder: '空文件夹',
|
||||||
|
noFilesInFolder: '该文件夹内没有文件',
|
||||||
|
autoRecognize: '自动识别名称',
|
||||||
|
directoryTree: '目录树',
|
||||||
|
rootDirectory: '根目录',
|
||||||
|
noDirectories: '没有可用的目录',
|
||||||
|
},
|
||||||
|
person: {
|
||||||
|
alias: '别名:',
|
||||||
|
credits: '参演作品',
|
||||||
|
biography: '个人简介',
|
||||||
|
birthday: '出生日期',
|
||||||
|
placeOfBirth: '出生地',
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
title: '出错啦!',
|
||||||
|
networkError: '无法获取到媒体信息,请检查网络连接。',
|
||||||
|
serverError: '服务器错误,请稍后重试。',
|
||||||
|
notFound: '找不到请求的资源。',
|
||||||
|
},
|
||||||
|
plugin: {
|
||||||
|
sort: {
|
||||||
|
popular: '热门',
|
||||||
|
name: '插件名称',
|
||||||
|
author: '作者',
|
||||||
|
repository: '插件仓库',
|
||||||
|
latest: '最新发布',
|
||||||
|
},
|
||||||
|
installingPlugin: '正在安装插件...',
|
||||||
|
installing: '正在安装 {name} v{version} ...',
|
||||||
|
installSuccess: '插件 {name} 安装成功!',
|
||||||
|
installFailed: '插件 {name} 安装失败:{message}',
|
||||||
|
uninstalling: '正在卸载 {name} ...',
|
||||||
|
uninstallSuccess: '插件 {name} 卸载成功!',
|
||||||
|
uninstallFailed: '插件 {name} 卸载失败:{message}',
|
||||||
|
updating: '正在更新 {name} 至 v{version} ...',
|
||||||
|
updateSuccess: '插件 {name} 更新成功!',
|
||||||
|
updateFailed: '插件 {name} 更新失败:{message}',
|
||||||
|
noPlugins: '没有安装插件',
|
||||||
|
installed: '已安装',
|
||||||
|
notInstalled: '未安装',
|
||||||
|
hasUpdate: '有更新',
|
||||||
|
configuring: '配置',
|
||||||
|
enable: '启用',
|
||||||
|
disable: '禁用',
|
||||||
|
settings: '设置',
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
personalInfo: '个人信息',
|
||||||
|
uploadNewAvatar: '上传新头像',
|
||||||
|
avatarFormatError: '上传的文件不符合要求,请重新选择头像',
|
||||||
|
avatarSizeError: '文件大小不得大于800KB',
|
||||||
|
avatarUploadSuccess: '新头像上传成功,待保存后生效!',
|
||||||
|
resetAvatarSuccess: '已重置为默认头像,待保存后生效!',
|
||||||
|
restoreAvatarSuccess: '已还原当前使用头像!',
|
||||||
|
savingInProgress: '正在保存中,请稍后...',
|
||||||
|
usernameRequired: '用户名不能为空',
|
||||||
|
passwordMismatch: '两次输入的密码不一致',
|
||||||
|
usernameChangeSuccess: '【{oldName}】更名【{newName}】,用户信息保存成功!',
|
||||||
|
saveSuccess: '用户信息保存成功!',
|
||||||
|
saveFailedWithNameChange: '【{oldName}】更名【{newName}】,信息保存失败:{message}!',
|
||||||
|
saveFailed: '用户信息保存失败:{message}!',
|
||||||
|
nickname: '昵称',
|
||||||
|
nicknamePlaceholder: '显示昵称,优先于用户名显示',
|
||||||
|
accountBinding: '账号绑定',
|
||||||
|
wechatUser: '微信用户',
|
||||||
|
telegramUser: 'Telegram用户',
|
||||||
|
slackUser: 'Slack用户',
|
||||||
|
vocechatUser: 'VoceChat用户',
|
||||||
|
synologychatUser: 'SynologyChat用户',
|
||||||
|
doubanUser: '豆瓣用户',
|
||||||
|
twoFactorAuthentication: '登录双重验证',
|
||||||
|
enableTwoFactor: '开启双重验证',
|
||||||
|
disableTwoFactor: '关闭双重验证',
|
||||||
|
otpGenerateFailed: '获取otp uri失败:{message}!',
|
||||||
|
otpDisableSuccess: '关闭登录双重验证成功!',
|
||||||
|
otpDisableFailed: '关闭otp失败:{message}!',
|
||||||
|
otpCodeRequired: '请填写6位验证码',
|
||||||
|
otpEnableSuccess: '开启登录双重验证成功!',
|
||||||
|
otpEnableFailed: '开启otp失败:{message}!',
|
||||||
|
authenticatorApp: '身份验证器',
|
||||||
|
authenticatorAppDescription:
|
||||||
|
'使用像Google Authenticator、Microsoft Authenticator、Authy或1Password这样的身份验证器应用程序,扫描二维码。它将为您生成一个6位数的代码,供您在下方输入。',
|
||||||
|
secretKeyTip: '如果您在使用二维码时遇到困难,请在您的应用程序中选择手动输入以上代码。',
|
||||||
|
enterVerificationCode: '输入验证码以确认开启双重验证',
|
||||||
|
avatarFormatTip: '允许 JPG、PNG、GIF、WEBP 格式, 最大尺寸 800KB。',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-17
@@ -1,31 +1,24 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import PersonCardListView from '@/views/discover/PersonCardListView.vue'
|
import PersonCardListView from '@/views/discover/PersonCardListView.vue'
|
||||||
|
|
||||||
// 输入参数
|
|
||||||
const props = defineProps({
|
|
||||||
// API路径
|
|
||||||
paths: Array as PropType<string[]> | PropType<string>,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 路由参数
|
// 路由参数
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const id = route.query?.id?.toString()
|
||||||
// 标题
|
|
||||||
const title = route.query?.title?.toString()
|
const title = route.query?.title?.toString()
|
||||||
|
const source = route.query?.source?.toString()
|
||||||
// 类型
|
|
||||||
const type = route.query?.type?.toString()
|
const type = route.query?.type?.toString()
|
||||||
|
const apipath = route.query?.apipath?.toString()
|
||||||
// 计算API路径
|
|
||||||
function getApiPath(paths: string[] | string) {
|
|
||||||
if (Array.isArray(paths)) return paths.join('/')
|
|
||||||
else return paths
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VPageContentTitle :title="title" />
|
<VPageContentTitle :title="title" />
|
||||||
<PersonCardListView :apipath="getApiPath(props.paths || '')" :params="route.query" :type="type" />
|
<PersonCardListView
|
||||||
|
:credits-id="id"
|
||||||
|
:credits-name="title"
|
||||||
|
:credits-source="source"
|
||||||
|
:credits-type="type"
|
||||||
|
:credits-apipath="apipath"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,5 +3,7 @@ import TransferHistoryView from '@/views/reorganize/TransferHistoryView.vue'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<TransferHistoryView />
|
<div>
|
||||||
|
<TransferHistoryView />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+13
-3
@@ -1,15 +1,25 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import MediaDetailView from '@/views/discover/MediaDetailView.vue'
|
import MediaDetailView from '@/views/discover/MediaDetailView.vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
// 国际化
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
// 路由参数
|
// 路由参数
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
// TMDBID
|
// TMDB ID
|
||||||
const mediaid = route.query?.mediaid?.toString()
|
const mediaid = route.query?.mediaid?.toString()
|
||||||
|
|
||||||
// 类型
|
// 类型:电影、电视剧
|
||||||
const type = route.query?.type?.toString()
|
const type = route.query?.type?.toString()
|
||||||
|
|
||||||
|
// 媒体信息来源:TMDB、豆瓣
|
||||||
|
const source = route.query?.source?.toString() || 'themoviedb'
|
||||||
|
|
||||||
|
// TMDB ID
|
||||||
|
const page = route.query?.page?.toString() || '1'
|
||||||
|
|
||||||
// 标题
|
// 标题
|
||||||
const title = route.query?.title?.toString()
|
const title = route.query?.title?.toString()
|
||||||
|
|
||||||
@@ -19,6 +29,6 @@ const year = route.query?.year?.toString()
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<MediaDetailView :mediaid="mediaid" :type="type" :title="title" :year="year" />
|
<MediaDetailView :mediaid="mediaid" :type="type" :source="source" :page="page" :title="title" :year="year" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SiteListView from '@/views/site/SiteCardListView.vue'
|
import SiteCardListView from '@/views/site/SiteCardListView.vue'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<SiteListView />
|
<SiteCardListView />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,5 +3,7 @@ import WorkflowListView from '@/views/workflow/WorkflowListView.vue'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<WorkflowListView />
|
<div>
|
||||||
|
<WorkflowListView />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import api from '@/api'
|
|||||||
import personIcon from '@images/misc/person.png'
|
import personIcon from '@images/misc/person.png'
|
||||||
import type { Person } from '@/api/types'
|
import type { Person } from '@/api/types'
|
||||||
import NoDataFound from '@/components/NoDataFound.vue'
|
import NoDataFound from '@/components/NoDataFound.vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
// 国际化
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const personProps = defineProps({
|
const personProps = defineProps({
|
||||||
@@ -67,7 +71,7 @@ function getPersonImage() {
|
|||||||
function getAlsoKnownAs() {
|
function getAlsoKnownAs() {
|
||||||
if (!personDetail.value?.also_known_as) return ''
|
if (!personDetail.value?.also_known_as) return ''
|
||||||
if (personProps.source === 'themoviedb') {
|
if (personProps.source === 'themoviedb') {
|
||||||
return '别名:' + personDetail.value.also_known_as.join('、')
|
return t('person.alias') + personDetail.value.also_known_as.join('、')
|
||||||
} else {
|
} else {
|
||||||
return personDetail.value.also_known_as.join(',')
|
return personDetail.value.also_known_as.join(',')
|
||||||
}
|
}
|
||||||
@@ -81,7 +85,7 @@ function getPersonCreditsPath() {
|
|||||||
} else if (personProps.source === 'bangumi') {
|
} else if (personProps.source === 'bangumi') {
|
||||||
apipath = 'bangumi'
|
apipath = 'bangumi'
|
||||||
}
|
}
|
||||||
return `/browse/${apipath}/person/credits/${personDetail.value.id}?title=参演作品`
|
return `/browse/${apipath}/person/credits/${personDetail.value.id}?title=${t('person.credits')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 参演作品API路径
|
// 参演作品API路径
|
||||||
@@ -136,7 +140,7 @@ onBeforeMount(() => {
|
|||||||
<div>
|
<div>
|
||||||
<div class="slider-header">
|
<div class="slider-header">
|
||||||
<RouterLink :to="getPersonCreditsPath()" class="slider-title">
|
<RouterLink :to="getPersonCreditsPath()" class="slider-title">
|
||||||
<span>参演作品</span>
|
<span>{{ t('person.credits') }}</span>
|
||||||
<VIcon icon="mdi-arrow-right-circle-outline" class="ms-1" />
|
<VIcon icon="mdi-arrow-right-circle-outline" class="ms-1" />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
@@ -146,7 +150,7 @@ onBeforeMount(() => {
|
|||||||
<NoDataFound
|
<NoDataFound
|
||||||
v-if="!personDetail.id && isRefreshed"
|
v-if="!personDetail.id && isRefreshed"
|
||||||
error-code="500"
|
error-code="500"
|
||||||
error-title="出错啦!"
|
:error-title="t('error.title')"
|
||||||
error-description="无法获取到媒体信息,请检查网络连接。"
|
:error-description="t('error.networkError')"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ import { isNullOrEmptyObject } from '@/@core/utils'
|
|||||||
import { getPluginTabs } from '@/router/i18n-menu'
|
import { getPluginTabs } from '@/router/i18n-menu'
|
||||||
import PluginMarketSettingDialog from '@/components/dialog/PluginMarketSettingDialog.vue'
|
import PluginMarketSettingDialog from '@/components/dialog/PluginMarketSettingDialog.vue'
|
||||||
import { useDynamicButton } from '@/composables/useDynamicButton'
|
import { useDynamicButton } from '@/composables/useDynamicButton'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
// 国际化
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
@@ -37,13 +41,13 @@ const activeSort = ref(null)
|
|||||||
const orderConfig = ref<{ id: string }[]>([])
|
const orderConfig = ref<{ id: string }[]>([])
|
||||||
|
|
||||||
// 排序选项
|
// 排序选项
|
||||||
const sortOptions = [
|
const sortOptions = computed(() => [
|
||||||
{ title: '热门', value: 'count' },
|
{ title: t('plugin.sort.popular'), value: 'count' },
|
||||||
{ title: '插件名称', value: 'plugin_name' },
|
{ title: t('plugin.sort.name'), value: 'plugin_name' },
|
||||||
{ title: '作者', value: 'plugin_author' },
|
{ title: t('plugin.sort.author'), value: 'plugin_author' },
|
||||||
{ title: '插件仓库', value: 'repo_url' },
|
{ title: t('plugin.sort.repository'), value: 'repo_url' },
|
||||||
{ title: '最新发布', value: 'add_time' },
|
{ title: t('plugin.sort.latest'), value: 'add_time' },
|
||||||
]
|
])
|
||||||
|
|
||||||
// 加载中
|
// 加载中
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -105,7 +109,7 @@ const $toast = useToast()
|
|||||||
const progressDialog = ref(false)
|
const progressDialog = ref(false)
|
||||||
|
|
||||||
// 进度框文本
|
// 进度框文本
|
||||||
const progressText = ref('正在安装插件...')
|
const progressText = ref(t('plugin.installingPlugin'))
|
||||||
|
|
||||||
// 过滤表单
|
// 过滤表单
|
||||||
const filterForm = reactive({
|
const filterForm = reactive({
|
||||||
@@ -217,7 +221,7 @@ async function installPlugin(item: Plugin) {
|
|||||||
try {
|
try {
|
||||||
// 显示等待提示框
|
// 显示等待提示框
|
||||||
progressDialog.value = true
|
progressDialog.value = true
|
||||||
progressText.value = `正在安装 ${item?.plugin_name} v${item?.plugin_version} ...`
|
progressText.value = t('plugin.installing', { name: item?.plugin_name, version: item?.plugin_version })
|
||||||
|
|
||||||
const result: { [key: string]: any } = await api.get(`plugin/install/${item?.id}`, {
|
const result: { [key: string]: any } = await api.get(`plugin/install/${item?.id}`, {
|
||||||
params: {
|
params: {
|
||||||
@@ -230,12 +234,12 @@ async function installPlugin(item: Plugin) {
|
|||||||
progressDialog.value = false
|
progressDialog.value = false
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
$toast.success(`插件 ${item?.plugin_name} 安装成功!`)
|
$toast.success(t('plugin.installSuccess', { name: item?.plugin_name }))
|
||||||
|
|
||||||
// 刷新
|
// 刷新
|
||||||
refreshData()
|
refreshData()
|
||||||
} else {
|
} else {
|
||||||
$toast.error(`插件 ${item?.plugin_name} 安装失败:${result.message}`)
|
$toast.error(t('plugin.installFailed', { name: item?.plugin_name, message: result.message }))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { FileItem, StorageConf, TransferDirectoryConf } from '@/api/types'
|
import { FileItem, StorageConf, TransferDirectoryConf } from '@/api/types'
|
||||||
import FileBrowser from '@/components/FileBrowser.vue'
|
import FileBrowser from '@/components/FileBrowser.vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
// 国际化
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const endpoints = {
|
const endpoints = {
|
||||||
list: {
|
list: {
|
||||||
@@ -179,7 +183,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.file-browser-view {
|
.file-browser-view {
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
block-size: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ import UserCard from '@/components/cards/UserCard.vue'
|
|||||||
import UserAddEditDialog from '@/components/dialog/UserAddEditDialog.vue'
|
import UserAddEditDialog from '@/components/dialog/UserAddEditDialog.vue'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import { useDynamicButton } from '@/composables/useDynamicButton'
|
import { useDynamicButton } from '@/composables/useDynamicButton'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
// 国际化
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
// APP
|
// APP
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||||
@@ -68,7 +73,7 @@ useDynamicButton({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 页面标题 -->
|
<!-- 页面标题 -->
|
||||||
<VPageContentTitle title="用户管理" />
|
<VPageContentTitle :title="t('user.management')" />
|
||||||
<div class="card-list-container">
|
<div class="card-list-container">
|
||||||
<!-- 加载中提示 -->
|
<!-- 加载中提示 -->
|
||||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||||
@@ -87,7 +92,7 @@ useDynamicButton({
|
|||||||
|
|
||||||
<!-- 无数据提示 -->
|
<!-- 无数据提示 -->
|
||||||
<div v-if="allUsers.length === 0 && isRefreshed">
|
<div v-if="allUsers.length === 0 && isRefreshed">
|
||||||
<NoDataFound error-code="404" error-title="没有用户" error-description="点击添加用户卡片添加用户" />
|
<NoDataFound error-code="404" :error-title="t('user.noUsers')" :error-description="t('user.clickToAddUser')" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 新增用户按钮 -->
|
<!-- 新增用户按钮 -->
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import type { User } from '@/api/types'
|
|||||||
import avatar1 from '@images/avatars/avatar-1.png'
|
import avatar1 from '@images/avatars/avatar-1.png'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import { useUserStore } from '@/stores'
|
import { useUserStore } from '@/stores'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
// 国际化
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
// 显示器宽度
|
// 显示器宽度
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
@@ -73,19 +77,19 @@ function changeAvatar(file: Event) {
|
|||||||
const maxSize = 800 * 1024
|
const maxSize = 800 * 1024
|
||||||
// 检查文件是否为图片
|
// 检查文件是否为图片
|
||||||
if (!allowedTypes.includes(selectedFile.type)) {
|
if (!allowedTypes.includes(selectedFile.type)) {
|
||||||
$toast.error('上传的文件不符合要求,请重新选择头像')
|
$toast.error(t('profile.avatarFormatError'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 检查文件大小
|
// 检查文件大小
|
||||||
if (selectedFile.size > maxSize) {
|
if (selectedFile.size > maxSize) {
|
||||||
$toast.error('文件大小不得大于800KB')
|
$toast.error(t('profile.avatarSizeError'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fileReader.readAsDataURL(selectedFile)
|
fileReader.readAsDataURL(selectedFile)
|
||||||
fileReader.onload = () => {
|
fileReader.onload = () => {
|
||||||
if (typeof fileReader.result === 'string') {
|
if (typeof fileReader.result === 'string') {
|
||||||
currentAvatar.value = fileReader.result
|
currentAvatar.value = fileReader.result
|
||||||
$toast.success('新头像上传成功,待保存后生效!')
|
$toast.success(t('profile.avatarUploadSuccess'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,13 +98,13 @@ function changeAvatar(file: Event) {
|
|||||||
// 重置默认头像
|
// 重置默认头像
|
||||||
function resetDefaultAvatar() {
|
function resetDefaultAvatar() {
|
||||||
currentAvatar.value = avatar1
|
currentAvatar.value = avatar1
|
||||||
$toast.success('已重置为默认头像,待保存后生效!')
|
$toast.success(t('profile.resetAvatarSuccess'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 还原当前头像
|
// 还原当前头像
|
||||||
function restoreCurrentAvatar() {
|
function restoreCurrentAvatar() {
|
||||||
currentAvatar.value = accountInfo.value.avatar
|
currentAvatar.value = accountInfo.value.avatar
|
||||||
$toast.success('已还原当前使用头像!')
|
$toast.success(t('profile.restoreAvatarSuccess'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载当前用户信息
|
// 加载当前用户信息
|
||||||
@@ -121,16 +125,16 @@ async function fetchUserInfo() {
|
|||||||
// 保存用户信息
|
// 保存用户信息
|
||||||
async function saveAccountInfo() {
|
async function saveAccountInfo() {
|
||||||
if (isSaving.value) {
|
if (isSaving.value) {
|
||||||
$toast.error('正在保存中,请稍后...')
|
$toast.error(t('profile.savingInProgress'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!currentUserName.value) {
|
if (!currentUserName.value) {
|
||||||
$toast.error('用户名不能为空')
|
$toast.error(t('profile.usernameRequired'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (newPassword.value || confirmPassword.value) {
|
if (newPassword.value || confirmPassword.value) {
|
||||||
if (newPassword.value !== confirmPassword.value) {
|
if (newPassword.value !== confirmPassword.value) {
|
||||||
$toast.error('两次输入的密码不一致')
|
$toast.error(t('profile.passwordMismatch'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
accountInfo.value.password = newPassword.value
|
accountInfo.value.password = newPassword.value
|
||||||
@@ -157,11 +161,11 @@ async function saveAccountInfo() {
|
|||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
if (oldUserName !== currentUserName.value) {
|
if (oldUserName !== currentUserName.value) {
|
||||||
$toast.success(`【${oldUserName}】更名【${currentUserName.value}】,用户信息保存成功!`)
|
$toast.success(t('profile.usernameChangeSuccess', { oldName: oldUserName, newName: currentUserName.value }))
|
||||||
// 更新本地用户名显示
|
// 更新本地用户名显示
|
||||||
userStore.setUserName(currentUserName.value)
|
userStore.setUserName(currentUserName.value)
|
||||||
} else {
|
} else {
|
||||||
$toast.success('用户信息保存成功!')
|
$toast.success(t('profile.saveSuccess'))
|
||||||
}
|
}
|
||||||
// 更新本地头像显示
|
// 更新本地头像显示
|
||||||
if (oldAvatar !== currentAvatar.value) {
|
if (oldAvatar !== currentAvatar.value) {
|
||||||
@@ -169,9 +173,15 @@ async function saveAccountInfo() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (oldAvatar !== currentAvatar.value) {
|
if (oldAvatar !== currentAvatar.value) {
|
||||||
$toast.error(`【${oldUserName}】更名【${currentUserName.value}】,信息保存失败:${result.message}!`)
|
$toast.error(
|
||||||
|
t('profile.saveFailedWithNameChange', {
|
||||||
|
oldName: oldUserName,
|
||||||
|
newName: currentUserName.value,
|
||||||
|
message: result.message,
|
||||||
|
}),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
$toast.error(`用户信息保存失败:${result.message}!`)
|
$toast.error(t('profile.saveFailed', { message: result.message }))
|
||||||
}
|
}
|
||||||
// 失败缓存值还原
|
// 失败缓存值还原
|
||||||
currentUserName.value = accountInfo.value.name
|
currentUserName.value = accountInfo.value.name
|
||||||
@@ -195,7 +205,7 @@ async function getOtpUri() {
|
|||||||
qrCode.value = result.data.uri
|
qrCode.value = result.data.uri
|
||||||
otpDialog.value = true
|
otpDialog.value = true
|
||||||
} else {
|
} else {
|
||||||
$toast.error(`获取otp uri失败:${result.message}!`)
|
$toast.error(t('profile.otpGenerateFailed', { message: result.message }))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
@@ -208,9 +218,9 @@ async function disableOtp() {
|
|||||||
const result: { [key: string]: any } = await api.post('user/otp/disable')
|
const result: { [key: string]: any } = await api.post('user/otp/disable')
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
accountInfo.value.is_otp = false
|
accountInfo.value.is_otp = false
|
||||||
$toast.success('关闭登录双重验证成功!')
|
$toast.success(t('profile.otpDisableSuccess'))
|
||||||
} else {
|
} else {
|
||||||
$toast.error(`关闭otp失败:${result.message}!`)
|
$toast.error(t('profile.otpDisableFailed', { message: result.message }))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
@@ -220,7 +230,7 @@ async function disableOtp() {
|
|||||||
// 启用Otp
|
// 启用Otp
|
||||||
async function judgeOtpPassword() {
|
async function judgeOtpPassword() {
|
||||||
if (!otpPassword.value) {
|
if (!otpPassword.value) {
|
||||||
$toast.error('请填写6位验证码')
|
$toast.error(t('profile.otpCodeRequired'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -230,11 +240,11 @@ async function judgeOtpPassword() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
$toast.success('开启登录双重验证成功!')
|
$toast.success(t('profile.otpEnableSuccess'))
|
||||||
otpDialog.value = false
|
otpDialog.value = false
|
||||||
accountInfo.value.is_otp = true
|
accountInfo.value.is_otp = true
|
||||||
} else {
|
} else {
|
||||||
$toast.error(`开启otp失败:${result.message}!`)
|
$toast.error(t('profile.otpEnableFailed', { message: result.message }))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
@@ -259,7 +269,7 @@ watch(
|
|||||||
<div>
|
<div>
|
||||||
<VRow>
|
<VRow>
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<VCard title="个人信息">
|
<VCard :title="t('profile.personalInfo')">
|
||||||
<VCardText class="flex">
|
<VCardText class="flex">
|
||||||
<!-- 👉 Avatar -->
|
<!-- 👉 Avatar -->
|
||||||
<VAvatar rounded="lg" size="100" class="me-6" :image="currentAvatar" />
|
<VAvatar rounded="lg" size="100" class="me-6" :image="currentAvatar" />
|
||||||
@@ -269,7 +279,7 @@ watch(
|
|||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<VBtn color="primary" @click="refInputEl?.click()">
|
<VBtn color="primary" @click="refInputEl?.click()">
|
||||||
<VIcon icon="mdi-cloud-upload-outline" />
|
<VIcon icon="mdi-cloud-upload-outline" />
|
||||||
<span v-if="display.mdAndUp.value" class="ms-2">上传新头像</span>
|
<span v-if="display.mdAndUp.value" class="ms-2">{{ t('profile.uploadNewAvatar') }}</span>
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
@@ -283,12 +293,12 @@ watch(
|
|||||||
|
|
||||||
<VBtn type="reset" color="info" variant="tonal" @click="restoreCurrentAvatar">
|
<VBtn type="reset" color="info" variant="tonal" @click="restoreCurrentAvatar">
|
||||||
<VIcon icon="mdi-refresh" />
|
<VIcon icon="mdi-refresh" />
|
||||||
<span v-if="display.mdAndUp.value" class="ms-2">重置</span>
|
<span v-if="display.mdAndUp.value" class="ms-2">{{ t('common.reset') }}</span>
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
|
||||||
<VBtn type="reset" color="error" variant="tonal" @click="resetDefaultAvatar">
|
<VBtn type="reset" color="error" variant="tonal" @click="resetDefaultAvatar">
|
||||||
<VIcon icon="mdi-image-sync-outline" />
|
<VIcon icon="mdi-image-sync-outline" />
|
||||||
<span v-if="display.mdAndUp.value" class="ms-2">默认</span>
|
<span v-if="display.mdAndUp.value" class="ms-2">{{ t('common.default') }}</span>
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
|
||||||
<VBtn
|
<VBtn
|
||||||
@@ -298,12 +308,12 @@ watch(
|
|||||||
>
|
>
|
||||||
<VIcon icon="mdi-account-key" />
|
<VIcon icon="mdi-account-key" />
|
||||||
<span v-if="display.mdAndUp.value" class="ms-2">{{
|
<span v-if="display.mdAndUp.value" class="ms-2">{{
|
||||||
accountInfo.is_otp ? '关闭双重验证' : '开启双重验证'
|
accountInfo.is_otp ? t('profile.disableTwoFactor') : t('profile.enableTwoFactor')
|
||||||
}}</span>
|
}}</span>
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-body-1 mb-0">允许 JPG、PNG、GIF、WEBP 格式, 最大尺寸 800KB。</p>
|
<p class="text-body-1 mb-0">{{ t('profile.avatarFormatTip') }}</p>
|
||||||
</form>
|
</form>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
|
|
||||||
@@ -312,10 +322,16 @@ watch(
|
|||||||
<VForm class="mt-6">
|
<VForm class="mt-6">
|
||||||
<VRow>
|
<VRow>
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="6">
|
||||||
<VTextField v-model="currentUserName" density="comfortable" readonly label="用户名" />
|
<VTextField v-model="currentUserName" density="comfortable" readonly :label="t('user.username')" />
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="6">
|
||||||
<VTextField v-model="accountInfo.email" density="comfortable" clearable label="邮箱" type="email" />
|
<VTextField
|
||||||
|
v-model="accountInfo.email"
|
||||||
|
density="comfortable"
|
||||||
|
clearable
|
||||||
|
:label="t('user.email')"
|
||||||
|
type="email"
|
||||||
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="6">
|
||||||
<VTextField
|
<VTextField
|
||||||
@@ -324,7 +340,7 @@ watch(
|
|||||||
:type="isNewPasswordVisible ? 'text' : 'password'"
|
:type="isNewPasswordVisible ? 'text' : 'password'"
|
||||||
:append-inner-icon="isNewPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
:append-inner-icon="isNewPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
||||||
clearable
|
clearable
|
||||||
label="密码"
|
:label="t('user.password')"
|
||||||
autocomplete=""
|
autocomplete=""
|
||||||
@click:append-inner="isNewPasswordVisible = !isNewPasswordVisible"
|
@click:append-inner="isNewPasswordVisible = !isNewPasswordVisible"
|
||||||
/>
|
/>
|
||||||
@@ -337,7 +353,7 @@ watch(
|
|||||||
:type="isConfirmPasswordVisible ? 'text' : 'password'"
|
:type="isConfirmPasswordVisible ? 'text' : 'password'"
|
||||||
:append-inner-icon="isConfirmPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
:append-inner-icon="isConfirmPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
||||||
clearable
|
clearable
|
||||||
label="确认密码"
|
:label="t('user.confirmPassword')"
|
||||||
@click:append-inner="isConfirmPasswordVisible = !isConfirmPasswordVisible"
|
@click:append-inner="isConfirmPasswordVisible = !isConfirmPasswordVisible"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
@@ -346,14 +362,14 @@ watch(
|
|||||||
v-model="accountInfo.nickname"
|
v-model="accountInfo.nickname"
|
||||||
density="comfortable"
|
density="comfortable"
|
||||||
clearable
|
clearable
|
||||||
label="昵称"
|
:label="t('profile.nickname')"
|
||||||
placeholder="显示昵称,优先于用户名显示"
|
:placeholder="t('profile.nicknamePlaceholder')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
|
|
||||||
<VDivider class="my-10">
|
<VDivider class="my-10">
|
||||||
<span>账号绑定</span>
|
<span>{{ t('profile.accountBinding') }}</span>
|
||||||
</VDivider>
|
</VDivider>
|
||||||
|
|
||||||
<VRow>
|
<VRow>
|
||||||
@@ -362,7 +378,7 @@ watch(
|
|||||||
v-model="accountInfo.settings.wechat_userid"
|
v-model="accountInfo.settings.wechat_userid"
|
||||||
density="comfortable"
|
density="comfortable"
|
||||||
clearable
|
clearable
|
||||||
label="微信用户"
|
:label="t('profile.wechatUser')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="6">
|
||||||
@@ -370,7 +386,7 @@ watch(
|
|||||||
v-model="accountInfo.settings.telegram_userid"
|
v-model="accountInfo.settings.telegram_userid"
|
||||||
density="comfortable"
|
density="comfortable"
|
||||||
clearable
|
clearable
|
||||||
label="Telegram用户"
|
:label="t('profile.telegramUser')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="6">
|
||||||
@@ -378,7 +394,7 @@ watch(
|
|||||||
v-model="accountInfo.settings.slack_userid"
|
v-model="accountInfo.settings.slack_userid"
|
||||||
density="comfortable"
|
density="comfortable"
|
||||||
clearable
|
clearable
|
||||||
label="Slack用户"
|
:label="t('profile.slackUser')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="6">
|
||||||
@@ -386,7 +402,7 @@ watch(
|
|||||||
v-model="accountInfo.settings.vocechat_userid"
|
v-model="accountInfo.settings.vocechat_userid"
|
||||||
density="comfortable"
|
density="comfortable"
|
||||||
clearable
|
clearable
|
||||||
label="VoceChat用户"
|
:label="t('profile.vocechatUser')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="6">
|
||||||
@@ -394,7 +410,7 @@ watch(
|
|||||||
v-model="accountInfo.settings.synologychat_userid"
|
v-model="accountInfo.settings.synologychat_userid"
|
||||||
density="comfortable"
|
density="comfortable"
|
||||||
clearable
|
clearable
|
||||||
label="SynologyChat用户"
|
:label="t('profile.synologychatUser')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="6">
|
||||||
@@ -402,7 +418,7 @@ watch(
|
|||||||
v-model="accountInfo.settings.douban_userid"
|
v-model="accountInfo.settings.douban_userid"
|
||||||
density="comfortable"
|
density="comfortable"
|
||||||
clearable
|
clearable
|
||||||
label="豆瓣用户"
|
:label="t('profile.doubanUser')"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
@@ -410,8 +426,8 @@ watch(
|
|||||||
<!-- 👉 Form Actions -->
|
<!-- 👉 Form Actions -->
|
||||||
<VCol cols="12" class="d-flex flex-wrap gap-4">
|
<VCol cols="12" class="d-flex flex-wrap gap-4">
|
||||||
<VBtn @click="saveAccountInfo" :disabled="isSaving">
|
<VBtn @click="saveAccountInfo" :disabled="isSaving">
|
||||||
<span v-if="isSaving">保存中...</span>
|
<span v-if="isSaving">{{ t('common.saving') }}...</span>
|
||||||
<span v-else>保存</span>
|
<span v-else>{{ t('common.save') }}</span>
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
@@ -427,40 +443,33 @@ watch(
|
|||||||
<VCard>
|
<VCard>
|
||||||
<VDialogCloseBtn @click="otpDialog = false" />
|
<VDialogCloseBtn @click="otpDialog = false" />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<h4 class="text-h4 text-center mb-6 mt-5">登录双重验证</h4>
|
<h4 class="text-h4 text-center mb-6 mt-5">{{ t('profile.twoFactorAuthentication') }}</h4>
|
||||||
<h5 class="text-h5 font-weight-medium mb-2">身份验证器</h5>
|
<h5 class="text-h5 font-weight-medium mb-2">{{ t('profile.authenticatorApp') }}</h5>
|
||||||
<p class="mb-6">
|
<p class="mb-6">
|
||||||
使用像Google Authenticator、Microsoft
|
{{ t('profile.authenticatorAppDescription') }}
|
||||||
Authenticator、Authy或1Password这样的身份验证器应用程序,扫描二维码。它将为您生成一个6位数的代码,供您在下方输入。
|
|
||||||
</p>
|
</p>
|
||||||
<div class="my-6">
|
<div class="my-6">
|
||||||
<QrcodeVue class="mx-auto" :value="qrCode" :size="200" max-width="25rem" />
|
<QrcodeVue class="mx-auto" :value="qrCode" :size="200" max-width="25rem" />
|
||||||
</div>
|
</div>
|
||||||
<VAlert
|
<VAlert :title="secret" variant="tonal" type="warning" class="my-4" :text="t('profile.secretKeyTip')">
|
||||||
:title="secret"
|
|
||||||
variant="tonal"
|
|
||||||
type="warning"
|
|
||||||
class="my-4"
|
|
||||||
text="如果您在使用二维码时遇到困难,请在您的应用程序中选择手动输入以上代码。"
|
|
||||||
>
|
|
||||||
<template #prepend />
|
<template #prepend />
|
||||||
</VAlert>
|
</VAlert>
|
||||||
<VForm>
|
<VForm>
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="otpPassword"
|
v-model="otpPassword"
|
||||||
type="text"
|
type="text"
|
||||||
label="输入验证码以确认开启双重验证"
|
:label="t('profile.enterVerificationCode')"
|
||||||
autocomplete=""
|
autocomplete=""
|
||||||
class="mb-8"
|
class="mb-8"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
<div class="d-flex justify-end flex-wrap gap-4">
|
<div class="d-flex justify-end flex-wrap gap-4">
|
||||||
<VBtn variant="outlined" color="secondary" @click="otpDialog = false"> 取消 </VBtn>
|
<VBtn variant="outlined" color="secondary" @click="otpDialog = false"> {{ t('common.cancel') }} </VBtn>
|
||||||
<VBtn @click="judgeOtpPassword">
|
<VBtn @click="judgeOtpPassword">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon icon="mdi-check" />
|
<VIcon icon="mdi-check" />
|
||||||
</template>
|
</template>
|
||||||
确定
|
{{ t('common.confirm') }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</div>
|
</div>
|
||||||
</VForm>
|
</VForm>
|
||||||
|
|||||||
+2
-2
@@ -3,8 +3,8 @@
|
|||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"module": "esnext",
|
"module": "Node16",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node16",
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
|||||||
Reference in New Issue
Block a user