2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-07 02:07:26 +08:00

feat: 添加前端翻译

This commit is contained in:
2025-04-13 01:30:26 +08:00
parent df32b3b5de
commit 81a320cd93
16 changed files with 245 additions and 198 deletions

View File

@@ -1,7 +1,9 @@
<script setup lang="ts">
import database from '@/api/panel/database'
import { NButton, NInput } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const createModel = ref({
name: '',
@@ -32,7 +34,7 @@ watch(
const handleCreate = () => {
useRequest(() => database.serverCreate(createModel.value)).onSuccess(() => {
show.value = false
window.$message.success('添加成功')
window.$message.success($gettext('Added successfully'))
window.$bus.emit('database-server:refresh')
})
}
@@ -42,7 +44,7 @@ const handleCreate = () => {
<n-modal
v-model:show="show"
preset="card"
title="添加服务器"
:title="$gettext('Add Server')"
style="width: 60vw"
size="huge"
:bordered="false"
@@ -50,72 +52,72 @@ const handleCreate = () => {
@close="show = false"
>
<n-form :model="createModel">
<n-form-item path="name" label="名称">
<n-form-item path="name" :label="$gettext('Name')">
<n-input
v-model:value="createModel.name"
type="text"
@keydown.enter.prevent
placeholder="输入数据库服务器名称"
:placeholder="$gettext('Enter database server name')"
/>
</n-form-item>
<n-form-item path="type" label="类型">
<n-form-item path="type" :label="$gettext('Type')">
<n-select
v-model:value="createModel.type"
@keydown.enter.prevent
placeholder="选择数据库类型"
:placeholder="$gettext('Select database type')"
:options="databaseType"
/>
</n-form-item>
<n-row :gutter="[0, 24]">
<n-col :span="15">
<n-form-item path="host" label="主机">
<n-form-item path="host" :label="$gettext('Host')">
<n-input
v-model:value="createModel.host"
type="text"
@keydown.enter.prevent
placeholder="输入数据库服务器主机"
:placeholder="$gettext('Enter database server host')"
/>
</n-form-item>
</n-col>
<n-col :span="2"></n-col>
<n-col :span="7">
<n-form-item path="port" label="端口">
<n-form-item path="port" :label="$gettext('Port')">
<n-input-number
w-full
v-model:value="createModel.port"
@keydown.enter.prevent
placeholder="输入数据库服务器端口"
:placeholder="$gettext('Enter database server port')"
/>
</n-form-item>
</n-col>
</n-row>
<n-form-item path="username" label="用户名">
<n-form-item path="username" :label="$gettext('Username')">
<n-input
v-model:value="createModel.username"
type="text"
@keydown.enter.prevent
placeholder="输入数据库服务器用户名"
:placeholder="$gettext('Enter database server username')"
/>
</n-form-item>
<n-form-item path="password" label="密码">
<n-form-item path="password" :label="$gettext('Password')">
<n-input
v-model:value="createModel.password"
type="password"
show-password-on="click"
@keydown.enter.prevent
placeholder="输入数据库服务器密码"
:placeholder="$gettext('Enter database server password')"
/>
</n-form-item>
<n-form-item path="remark" label="备注">
<n-form-item path="remark" :label="$gettext('Comment')">
<n-input
v-model:value="createModel.remark"
type="textarea"
@keydown.enter.prevent
placeholder="输入数据库服务器备注"
:placeholder="$gettext('Enter database server comment')"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleCreate">提交</n-button>
<n-button type="info" block @click="handleCreate">{{ $gettext('Submit') }}</n-button>
</n-modal>
</template>

View File

@@ -1,7 +1,9 @@
<script setup lang="ts">
import database from '@/api/panel/database'
import { NButton, NInput } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const createModel = ref({
server_id: null,
@@ -15,15 +17,15 @@ const createModel = ref({
const servers = ref<{ label: string; value: string }[]>([])
const hostType = [
{ label: '本地(localhost', value: 'localhost' },
{ label: '所有(%', value: '%' },
{ label: '指定', value: '' }
{ label: $gettext('Local (localhost)'), value: 'localhost' },
{ label: $gettext('All (%)'), value: '%' },
{ label: $gettext('Specific'), value: '' }
]
const handleCreate = () => {
useRequest(() => database.userCreate(createModel.value)).onSuccess(() => {
show.value = false
window.$message.success('创建成功')
window.$message.success($gettext('Created successfully'))
window.$bus.emit('database-user:refresh')
})
}
@@ -49,7 +51,7 @@ watch(
<n-modal
v-model:show="show"
preset="card"
title="创建用户"
:title="$gettext('Create User')"
style="width: 60vw"
size="huge"
:bordered="false"
@@ -57,60 +59,60 @@ watch(
@close="show = false"
>
<n-form :model="createModel">
<n-form-item path="server_id" label="服务器">
<n-form-item path="server_id" :label="$gettext('Server')">
<n-select
v-model:value="createModel.server_id"
@keydown.enter.prevent
placeholder="选择服务器"
:placeholder="$gettext('Select server')"
:options="servers"
/>
</n-form-item>
<n-form-item path="username" label="用户名">
<n-form-item path="username" :label="$gettext('Username')">
<n-input
v-model:value="createModel.username"
type="text"
@keydown.enter.prevent
placeholder="输入用户名"
:placeholder="$gettext('Enter username')"
/>
</n-form-item>
<n-form-item path="password" label="密码">
<n-form-item path="password" :label="$gettext('Password')">
<n-input
v-model:value="createModel.password"
type="password"
show-password-on="click"
@keydown.enter.prevent
placeholder="输入密码"
:placeholder="$gettext('Enter password')"
/>
</n-form-item>
<n-form-item path="host-select" label="主机(仅 MySQL">
<n-form-item path="host-select" :label="$gettext('Host (MySQL only)')">
<n-select
v-model:value="createModel.host"
@keydown.enter.prevent
placeholder="选择主机"
:placeholder="$gettext('Select host')"
:options="hostType"
/>
</n-form-item>
<n-form-item v-if="createModel.host === ''" path="host" label="指定主机">
<n-form-item v-if="createModel.host === ''" path="host" :label="$gettext('Specific Host')">
<n-input
v-model:value="createModel.host"
type="text"
@keydown.enter.prevent
placeholder="输入受支持的主机地址"
:placeholder="$gettext('Enter supported host address')"
/>
</n-form-item>
<n-form-item path="privileges" label="授权">
<n-dynamic-input v-model:value="createModel.privileges" placeholder="输入数据库名" />
<n-form-item path="privileges" :label="$gettext('Privileges')">
<n-dynamic-input v-model:value="createModel.privileges" :placeholder="$gettext('Enter database name')" />
</n-form-item>
<n-form-item path="remark" label="备注">
<n-form-item path="remark" :label="$gettext('Comment')">
<n-input
v-model:value="createModel.remark"
type="textarea"
@keydown.enter.prevent
placeholder="输入数据库用户备注"
:placeholder="$gettext('Enter database user comment')"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleCreate">提交</n-button>
<n-button type="info" block @click="handleCreate">{{ $gettext('Submit') }}</n-button>
</n-modal>
</template>

View File

@@ -151,7 +151,9 @@ const columns: any = [
},
{
default: () => {
return $gettext('Are you sure you want to synchronize database users (excluding password) to the panel?')
return $gettext(
'Are you sure you want to synchronize database users (excluding password) to the panel?'
)
},
trigger: () => {
return h(
@@ -190,7 +192,11 @@ const columns: any = [
onPositiveClick: () => {
// 防手贱
if (['local_mysql', 'local_postgresql'].includes(row.name)) {
window.$message.error($gettext('Built-in servers cannot be deleted. If you need to delete them, please uninstall the corresponding application'))
window.$message.error(
$gettext(
'Built-in servers cannot be deleted. If you need to delete them, please uninstall the corresponding app'
)
)
return
}
handleDelete(row.id)

View File

@@ -1,7 +1,9 @@
<script setup lang="ts">
import database from '@/api/panel/database'
import { NButton, NInput } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const id = defineModel<number>('id', { type: Number, required: true })
const updateModel = ref({
@@ -16,7 +18,7 @@ const updateModel = ref({
const handleUpdate = () => {
useRequest(() => database.serverUpdate(id.value, updateModel.value)).onSuccess(() => {
show.value = false
window.$message.success('修改成功')
window.$message.success($gettext('Modified successfully'))
window.$bus.emit('database-user:refresh')
})
}
@@ -42,7 +44,7 @@ watch(
<n-modal
v-model:show="show"
preset="card"
title="修改服务器"
:title="$gettext('Modify Server')"
style="width: 60vw"
size="huge"
:bordered="false"
@@ -50,64 +52,64 @@ watch(
@close="show = false"
>
<n-form :model="updateModel">
<n-form-item path="name" label="名称">
<n-form-item path="name" :label="$gettext('Name')">
<n-input
v-model:value="updateModel.name"
type="text"
@keydown.enter.prevent
placeholder="输入数据库服务器名称"
:placeholder="$gettext('Enter database server name')"
/>
</n-form-item>
<n-row :gutter="[0, 24]">
<n-col :span="15">
<n-form-item path="host" label="主机">
<n-form-item path="host" :label="$gettext('Host')">
<n-input
v-model:value="updateModel.host"
type="text"
@keydown.enter.prevent
placeholder="输入数据库服务器主机"
:placeholder="$gettext('Enter database server host')"
/>
</n-form-item>
</n-col>
<n-col :span="2"></n-col>
<n-col :span="7">
<n-form-item path="port" label="端口">
<n-form-item path="port" :label="$gettext('Port')">
<n-input-number
w-full
v-model:value="updateModel.port"
@keydown.enter.prevent
placeholder="输入数据库服务器端口"
:placeholder="$gettext('Enter database server port')"
/>
</n-form-item>
</n-col>
</n-row>
<n-form-item path="username" label="用户名">
<n-form-item path="username" :label="$gettext('Username')">
<n-input
v-model:value="updateModel.username"
type="text"
@keydown.enter.prevent
placeholder="输入数据库服务器用户名"
:placeholder="$gettext('Enter database server username')"
/>
</n-form-item>
<n-form-item path="password" label="密码">
<n-form-item path="password" :label="$gettext('Password')">
<n-input
v-model:value="updateModel.password"
type="password"
show-password-on="click"
@keydown.enter.prevent
placeholder="输入数据库服务器密码"
:placeholder="$gettext('Enter database server password')"
/>
</n-form-item>
<n-form-item path="remark" label="备注">
<n-form-item path="remark" :label="$gettext('Comment')">
<n-input
v-model:value="updateModel.remark"
type="textarea"
@keydown.enter.prevent
placeholder="输入数据库服务器备注"
:placeholder="$gettext('Enter database server comment')"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleUpdate">提交</n-button>
<n-button type="info" block @click="handleUpdate">{{ $gettext('Submit') }}</n-button>
</n-modal>
</template>

View File

@@ -1,7 +1,9 @@
<script setup lang="ts">
import database from '@/api/panel/database'
import { NButton, NInput } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const id = defineModel<number>('id', { type: Number, required: true })
const updateModel = ref({
@@ -13,7 +15,7 @@ const updateModel = ref({
const handleUpdate = () => {
useRequest(() => database.userUpdate(id.value, updateModel.value)).onSuccess(() => {
show.value = false
window.$message.success('修改成功')
window.$message.success($gettext('Modified successfully'))
window.$bus.emit('database-user:refresh')
})
}
@@ -36,7 +38,7 @@ watch(
<n-modal
v-model:show="show"
preset="card"
title="修改用户"
:title="$gettext('Modify User')"
style="width: 60vw"
size="huge"
:bordered="false"
@@ -44,28 +46,28 @@ watch(
@close="show = false"
>
<n-form :model="updateModel">
<n-form-item path="password" label="密码">
<n-form-item path="password" :label="$gettext('Password')">
<n-input
v-model:value="updateModel.password"
type="password"
show-password-on="click"
@keydown.enter.prevent
placeholder="输入密码"
:placeholder="$gettext('Enter password')"
/>
</n-form-item>
<n-form-item path="privileges" label="授权">
<n-dynamic-input v-model:value="updateModel.privileges" placeholder="输入数据库名" />
<n-form-item path="privileges" :label="$gettext('Privileges')">
<n-dynamic-input v-model:value="updateModel.privileges" :placeholder="$gettext('Enter database name')" />
</n-form-item>
<n-form-item path="remark" label="备注">
<n-form-item path="remark" :label="$gettext('Comment')">
<n-input
v-model:value="updateModel.remark"
type="textarea"
@keydown.enter.prevent
placeholder="输入数据库用户备注"
:placeholder="$gettext('Enter database user comment')"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleUpdate">提交</n-button>
<n-button type="info" block @click="handleUpdate">{{ $gettext('Submit') }}</n-button>
</n-modal>
</template>

View File

@@ -2,17 +2,19 @@
import { renderIcon } from '@/utils'
import copy2clipboard from '@vavt/copy2clipboard'
import { NButton, NFlex, NInput, NInputGroup, NPopconfirm, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import database from '@/api/panel/database'
import { formatDateTime } from '@/utils'
import UpdateUserModal from '@/views/database/UpdateUserModal.vue'
const { $gettext } = useGettext()
const updateModal = ref(false)
const updateID = ref(0)
const columns: any = [
{
title: '类型',
title: $gettext('Type'),
key: 'type',
width: 150,
render(row: any) {
@@ -35,17 +37,17 @@ const columns: any = [
}
},
{
title: '用户名',
title: $gettext('Username'),
key: 'username',
minWidth: 100,
resizable: true,
ellipsis: { tooltip: true },
render(row: any) {
return row.username || '无'
return row.username || $gettext('None')
}
},
{
title: '密码',
title: $gettext('Password'),
key: 'password',
width: 250,
render(row: any) {
@@ -56,7 +58,7 @@ const columns: any = [
type: 'password',
showPasswordOn: 'click',
readonly: true,
placeholder: '未保存'
placeholder: $gettext('Not saved')
}),
h(
NButton,
@@ -65,28 +67,28 @@ const columns: any = [
ghost: true,
onClick: () => {
copy2clipboard(row.password).then(() => {
window.$message.success('复制成功')
window.$message.success($gettext('Copied successfully'))
})
}
},
{ default: () => '复制' }
{ default: () => $gettext('Copy') }
)
]
})
}
},
{
title: '主机',
title: $gettext('Host'),
key: 'host',
width: 150,
render(row: any) {
return h(NTag, null, {
default: () => row.host || '无'
default: () => row.host || $gettext('None')
})
}
},
{
title: '服务器',
title: $gettext('Server'),
key: 'server',
width: 150,
render(row: any) {
@@ -94,7 +96,7 @@ const columns: any = [
}
},
{
title: '授权',
title: $gettext('Privileges'),
key: 'privileges',
width: 200,
render(row: any) {
@@ -109,7 +111,7 @@ const columns: any = [
}
},
{
title: '备注',
title: $gettext('Comment'),
key: 'remark',
minWidth: 250,
resizable: true,
@@ -126,19 +128,19 @@ const columns: any = [
}
},
{
title: '状态',
title: $gettext('Status'),
key: 'status',
width: 100,
render(row: any) {
return h(
NTag,
{ type: row.status === 'valid' ? 'success' : 'error' },
{ default: () => (row.status === 'valid' ? '有效' : '无效') }
{ default: () => (row.status === 'valid' ? $gettext('Valid') : $gettext('Invalid')) }
)
}
},
{
title: '更新日期',
title: $gettext('Update Date'),
key: 'updated_at',
width: 200,
ellipsis: { tooltip: true },
@@ -147,7 +149,7 @@ const columns: any = [
}
},
{
title: '操作',
title: $gettext('Actions'),
key: 'actions',
width: 200,
align: 'center',
@@ -165,7 +167,7 @@ const columns: any = [
}
},
{
default: () => '修改',
default: () => $gettext('Modify'),
icon: renderIcon('material-symbols:edit-outline', { size: 14 })
}
),
@@ -176,7 +178,7 @@ const columns: any = [
},
{
default: () => {
return '确定删除用户吗?'
return $gettext('Are you sure you want to delete the user?')
},
trigger: () => {
return h(
@@ -187,7 +189,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => '删除',
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
}
)
@@ -212,13 +214,13 @@ const { loading, data, page, total, pageSize, pageCount, refresh } = usePaginati
const handleDelete = (id: number) => {
useRequest(database.userDelete(id)).onSuccess(() => {
refresh()
window.$message.success('删除成功')
window.$message.success($gettext('Deleted successfully'))
})
}
const handleRemark = (row: any) => {
useRequest(database.userRemark(row.id, row.remark)).onSuccess(() => {
window.$message.success('修改成功')
window.$message.success($gettext('Modified successfully'))
})
}
@@ -254,7 +256,7 @@ onUnmounted(() => {
pageSizes: [20, 50, 100, 200]
}"
/>
<update-user-modal v-model:id="updateID" v-model:show="updateModal" />
<update-user-modal v-model:show="updateModal" v-model:id="updateID" />
</template>
<style scoped lang="scss"></style>

View File

@@ -1,4 +1,5 @@
import type { RouteType } from '~/types/router'
import { $gettext } from '@/utils/gettext'
const Layout = () => import('@/layout/IndexView.vue')
@@ -15,7 +16,7 @@ export default {
path: '',
component: () => import('./IndexView.vue'),
meta: {
title: '数据库',
title: $gettext('Database'),
icon: 'mdi:database',
role: ['admin'],
requireAuth: true

View File

@@ -1,9 +1,11 @@
<script setup lang="ts">
import { NButton, NInput } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import api from '@/api/panel/file'
import { generateRandomString, getBase } from '@/utils'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const path = defineModel<string>('path', { type: String, required: true })
const selected = defineModel<string[]>('selected', { type: Array, default: () => [] })
@@ -27,7 +29,7 @@ const ensureExtension = (extension: string) => {
const handleArchive = () => {
ensureExtension(format.value)
loading.value = true
const message = window.$message.loading('正在压缩中...', {
const message = window.$message.loading($gettext('Compressing...'), {
duration: 0
})
const paths = selected.value.map((item) => item.replace(path.value, '').replace(/^\//, ''))
@@ -35,7 +37,7 @@ const handleArchive = () => {
.onSuccess(() => {
show.value = false
selected.value = []
window.$message.success('压缩成功')
window.$message.success($gettext('Compressed successfully'))
})
.onComplete(() => {
message?.destroy()
@@ -59,7 +61,7 @@ onMounted(() => {
<n-modal
v-model:show="show"
preset="card"
title="压缩"
:title="$gettext('Compress')"
style="width: 60vw"
size="huge"
:bordered="false"
@@ -67,13 +69,13 @@ onMounted(() => {
>
<n-flex vertical>
<n-form>
<n-form-item label="待压缩">
<n-form-item :label="$gettext('Files to compress')">
<n-dynamic-input v-model:value="selected" :min="1" />
</n-form-item>
<n-form-item label="压缩为">
<n-form-item :label="$gettext('Compress to')">
<n-input v-model:value="file" />
</n-form-item>
<n-form-item label="格式">
<n-form-item :label="$gettext('Format')">
<n-select
v-model:value="format"
:options="[
@@ -91,7 +93,7 @@ onMounted(() => {
</n-form-item>
</n-form>
<n-button :loading="loading" :disabled="loading" type="primary" @click="handleArchive">
压缩
{{ $gettext('Compress') }}
</n-button>
</n-flex>
</n-modal>

View File

@@ -1,4 +1,7 @@
<script setup lang="ts">
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const file = defineModel<string>('file', { type: String, required: true })
const editor = ref<any>(null)
@@ -16,7 +19,7 @@ const handleSave = () => {
<n-modal
v-model:show="show"
preset="card"
:title="'编辑 - ' + file"
:title="$gettext('Edit - %{ file }', { file })"
style="width: 60vw"
size="huge"
:bordered="false"
@@ -24,8 +27,8 @@ const handleSave = () => {
>
<template #header-extra>
<n-flex>
<n-button @click="handleRefresh"> 刷新 </n-button>
<n-button type="primary" @click="handleSave"> 保存 </n-button>
<n-button @click="handleRefresh"> {{ $gettext('Refresh') }} </n-button>
<n-button type="primary" @click="handleSave"> {{ $gettext('Save') }} </n-button>
</n-flex>
</template>
<code-editor ref="editor" :path="file" :read-only="false" />

View File

@@ -9,6 +9,7 @@ import {
NPopselect,
NTag
} from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import type { DataTableColumns, DropdownOption } from 'naive-ui'
import type { RowData } from 'naive-ui/es/data-table/src/interface'
@@ -28,6 +29,7 @@ import EditModal from '@/views/file/EditModal.vue'
import PreviewModal from '@/views/file/PreviewModal.vue'
import type { Marked } from '@/views/file/types'
const { $gettext } = useGettext()
const sort = ref<string>('')
const path = defineModel<string>('path', { type: String, required: true })
const selected = defineModel<any[]>('selected', { type: Array, default: () => [] })
@@ -59,28 +61,28 @@ const options = computed<DropdownOption[]>(() => {
if (selectedRow.value == null) return []
const options = [
{
label: selectedRow.value.dir ? '打开' : isImage(selectedRow.value.name) ? '预览' : '编辑',
label: selectedRow.value.dir ? $gettext('Open') : isImage(selectedRow.value.name) ? $gettext('Preview') : $gettext('Edit'),
key: selectedRow.value.dir ? 'open' : isImage(selectedRow.value.name) ? 'preview' : 'edit'
},
{ label: '复制', key: 'copy' },
{ label: '移动', key: 'move' },
{ label: '权限', key: 'permission' },
{ label: $gettext('Copy'), key: 'copy' },
{ label: $gettext('Move'), key: 'move' },
{ label: $gettext('Permission'), key: 'permission' },
{
label: selectedRow.value.dir ? '压缩' : '下载',
label: selectedRow.value.dir ? $gettext('Compress') : $gettext('Download'),
key: selectedRow.value.dir ? 'compress' : 'download'
},
{
label: '解压',
label: $gettext('Uncompress'),
key: 'uncompress',
show: isCompress(selectedRow.value.full),
disabled: !isCompress(selectedRow.value.full)
},
{ label: '重命名', key: 'rename' },
{ label: () => h('span', { style: { color: 'red' } }, '删除'), key: 'delete' }
{ label: $gettext('Rename'), key: 'rename' },
{ label: () => h('span', { style: { color: 'red' } }, $gettext('Delete')), key: 'delete' }
]
if (marked.value.length) {
options.unshift({
label: '粘贴',
label: $gettext('Paste'),
key: 'paste'
})
}
@@ -94,7 +96,7 @@ const columns: DataTableColumns<RowData> = [
fixed: 'left'
},
{
title: '名称',
title: $gettext('Name'),
key: 'name',
minWidth: 180,
defaultSortOrder: false,
@@ -136,7 +138,7 @@ const columns: DataTableColumns<RowData> = [
}
},
{
title: '权限',
title: $gettext('Permission'),
key: 'mode',
minWidth: 80,
render(row: any): any {
@@ -148,7 +150,7 @@ const columns: DataTableColumns<RowData> = [
}
},
{
title: '所有者 / 组',
title: $gettext('Owner / Group'),
key: 'owner/group',
minWidth: 120,
render(row: any): any {
@@ -160,7 +162,7 @@ const columns: DataTableColumns<RowData> = [
}
},
{
title: '大小',
title: $gettext('Size'),
key: 'size',
minWidth: 80,
render(row: any): any {
@@ -168,7 +170,7 @@ const columns: DataTableColumns<RowData> = [
}
},
{
title: '修改时间',
title: $gettext('Modification Time'),
key: 'modify',
minWidth: 200,
render(row: any): any {
@@ -180,7 +182,7 @@ const columns: DataTableColumns<RowData> = [
}
},
{
title: '操作',
title: $gettext('Actions'),
key: 'action',
width: 340,
render(row) {
@@ -211,9 +213,9 @@ const columns: DataTableColumns<RowData> = [
{
default: () => {
if (!row.dir && !row.symlink) {
return isImage(row.name) ? '预览' : '编辑'
return isImage(row.name) ? $gettext('Preview') : $gettext('Edit')
} else {
return '打开'
return $gettext('Open')
}
}
}
@@ -236,9 +238,9 @@ const columns: DataTableColumns<RowData> = [
{
default: () => {
if (row.dir) {
return '压缩'
return $gettext('Compress')
} else {
return '下载'
return $gettext('Download')
}
}
}
@@ -255,7 +257,7 @@ const columns: DataTableColumns<RowData> = [
renameModal.value = true
}
},
{ default: () => '重命名' }
{ default: () => $gettext('Rename') }
),
h(
NPopconfirm,
@@ -263,14 +265,14 @@ const columns: DataTableColumns<RowData> = [
onPositiveClick: () => {
useRequest(file.delete(row.full)).onComplete(() => {
window.$bus.emit('file:refresh')
window.$message.success('删除成功')
window.$message.success($gettext('Deleted successfully'))
})
},
onNegativeClick: () => {}
},
{
default: () => {
return `确定删除 ${row.name} 吗?`
return $gettext('Are you sure you want to delete %{ name }?', { name: row.name })
},
trigger: () => {
return h(
@@ -280,7 +282,7 @@ const columns: DataTableColumns<RowData> = [
type: 'error',
tertiary: true
},
{ default: () => '删除' }
{ default: () => $gettext('Delete') }
)
}
}
@@ -289,11 +291,11 @@ const columns: DataTableColumns<RowData> = [
NPopselect,
{
options: [
{ label: '复制', value: 'copy' },
{ label: '移动', value: 'move' },
{ label: '权限', value: 'permission' },
{ label: '压缩', value: 'compress' },
{ label: '解压', value: 'uncompress', disabled: !isCompress(row.name) }
{ label: $gettext('Copy'), value: 'copy' },
{ label: $gettext('Move'), value: 'move' },
{ label: $gettext('Permission'), value: 'permission' },
{ label: $gettext('Compress'), value: 'compress' },
{ label: $gettext('Uncompress'), value: 'uncompress', disabled: !isCompress(row.name) }
],
onUpdateValue: (value) => {
switch (value) {
@@ -306,7 +308,7 @@ const columns: DataTableColumns<RowData> = [
force: false
}
]
window.$message.success('标记成功,请前往目标路径粘贴')
window.$message.success($gettext('Marked successfully, please navigate to the destination path to paste'))
break
case 'move':
markedType.value = 'move'
@@ -317,7 +319,7 @@ const columns: DataTableColumns<RowData> = [
force: false
}
]
window.$message.success('标记成功,请前往目标路径粘贴')
window.$message.success($gettext('Marked successfully, please navigate to the destination path to paste'))
break
case 'permission':
selected.value = [row.full]
@@ -343,7 +345,7 @@ const columns: DataTableColumns<RowData> = [
tertiary: true,
size: 'small'
},
{ default: () => '更多' }
{ default: () => $gettext('More') }
)
}
}
@@ -384,23 +386,26 @@ const handleRename = () => {
const source = path.value + '/' + renameModel.value.source
const target = path.value + '/' + renameModel.value.target
if (!checkName(renameModel.value.source) || !checkName(renameModel.value.target)) {
window.$message.error('名称不合法')
window.$message.error($gettext('Invalid name'))
return
}
useRequest(file.exist([target])).onSuccess(({ data }) => {
if (data[0]) {
window.$dialog.warning({
title: '警告',
content: `存在同名项,是否强制覆盖?`,
positiveText: '覆盖',
negativeText: '取消',
title: $gettext('Warning'),
content: $gettext('There are items with the same name. Do you want to overwrite?'),
positiveText: $gettext('Overwrite'),
negativeText: $gettext('Cancel'),
onPositiveClick: () => {
useRequest(file.move([{ source, target, force: true }]))
.onSuccess(() => {
window.$bus.emit('file:refresh')
window.$message.success(
`重命名 ${renameModel.value.source}${renameModel.value.target} 成功`
$gettext('Renamed %{ source } to %{ target } successfully', {
source: renameModel.value.source,
target: renameModel.value.target
})
)
})
.onComplete(() => {
@@ -413,7 +418,10 @@ const handleRename = () => {
.onSuccess(() => {
window.$bus.emit('file:refresh')
window.$message.success(
`重命名 ${renameModel.value.source}${renameModel.value.target} 成功`
$gettext('Renamed %{ source } to %{ target } successfully', {
source: renameModel.value.source,
target: renameModel.value.target
})
)
})
.onComplete(() => {
@@ -429,17 +437,17 @@ const handleUnCompress = () => {
!unCompressModel.value.path.startsWith('/') ||
!checkPath(unCompressModel.value.path.slice(1))
) {
window.$message.error('路径不合法')
window.$message.error($gettext('Invalid path'))
return
}
const message = window.$message.loading('正在解压中...', {
const message = window.$message.loading($gettext('Uncompressing...'), {
duration: 0
})
useRequest(file.unCompress(unCompressModel.value.file, unCompressModel.value.path))
.onSuccess(() => {
unCompressModal.value = false
window.$bus.emit('file:refresh')
window.$message.success('解压成功')
window.$message.success($gettext('Uncompressed successfully'))
})
.onComplete(() => {
message?.destroy()
@@ -448,7 +456,7 @@ const handleUnCompress = () => {
const handlePaste = () => {
if (!marked.value.length) {
window.$message.error('请先标记需要复制或移动的文件/文件夹')
window.$message.error($gettext('Please mark the files/folders to copy or move first'))
return
}
@@ -472,32 +480,36 @@ const handlePaste = () => {
}
if (flag) {
window.$dialog.warning({
title: '警告',
content: `存在同名项
${paths
.filter((item) => item.force)
.map((item) => item.name)
.join(', ')} 是否覆盖?`,
positiveText: '覆盖',
negativeText: '取消',
title: $gettext('Warning'),
content: $gettext(
'There are items with the same name. %{ items } Do you want to overwrite?',
{
items: `${paths
.filter((item) => item.force)
.map((item) => item.name)
.join(', ')}`
}
),
positiveText: $gettext('Overwrite'),
negativeText: $gettext('Cancel'),
onPositiveClick: () => {
if (markedType.value == 'copy') {
useRequest(file.copy(paths)).onSuccess(() => {
marked.value = []
window.$bus.emit('file:refresh')
window.$message.success('复制成功')
window.$message.success($gettext('Copied successfully'))
})
} else {
useRequest(file.move(paths)).onSuccess(() => {
marked.value = []
window.$bus.emit('file:refresh')
window.$message.success('移动成功')
window.$message.success($gettext('Moved successfully'))
})
}
},
onNegativeClick: () => {
marked.value = []
window.$message.info('已取消')
window.$message.info($gettext('Canceled'))
}
})
} else {
@@ -505,13 +517,13 @@ const handlePaste = () => {
useRequest(file.copy(paths)).onSuccess(() => {
marked.value = []
window.$bus.emit('file:refresh')
window.$message.success('复制成功')
window.$message.success($gettext('Copied successfully'))
})
} else {
useRequest(file.move(paths)).onSuccess(() => {
marked.value = []
window.$bus.emit('file:refresh')
window.$message.success('移动成功')
window.$message.success($gettext('Moved successfully'))
})
}
}
@@ -543,7 +555,7 @@ const handleSelect = (key: string) => {
force: false
}
]
window.$message.success('标记成功,请前往目标路径粘贴')
window.$message.success($gettext('Marked successfully, please navigate to the destination path to paste'))
break
case 'move':
markedType.value = 'move'
@@ -554,7 +566,7 @@ const handleSelect = (key: string) => {
force: false
}
]
window.$message.success('标记成功,请前往目标路径粘贴')
window.$message.success($gettext('Marked successfully, please navigate to the destination path to paste'))
break
case 'permission':
selected.value = [selectedRow.value.full]
@@ -580,7 +592,7 @@ const handleSelect = (key: string) => {
case 'delete':
useRequest(file.delete(selectedRow.value.full)).onSuccess(() => {
window.$bus.emit('file:refresh')
window.$message.success('删除成功')
window.$message.success($gettext('Deleted successfully'))
})
break
}
@@ -676,7 +688,7 @@ onUnmounted(() => {
<n-modal
v-model:show="renameModal"
preset="card"
:title="'重命名 - ' + renameModel.source"
:title="$gettext('Rename - %{ source }', { source: renameModel.source })"
style="width: 60vw"
size="huge"
:bordered="false"
@@ -684,17 +696,17 @@ onUnmounted(() => {
>
<n-flex vertical>
<n-form>
<n-form-item label="新名称">
<n-form-item :label="$gettext('New Name')">
<n-input v-model:value="renameModel.target" />
</n-form-item>
</n-form>
<n-button type="primary" @click="handleRename">保存</n-button>
<n-button type="primary" @click="handleRename">{{ $gettext('Save') }}</n-button>
</n-flex>
</n-modal>
<n-modal
v-model:show="unCompressModal"
preset="card"
:title="'解压缩 - ' + unCompressModel.file"
:title="$gettext('Uncompress - %{ file }', { file: unCompressModel.file })"
style="width: 60vw"
size="huge"
:bordered="false"
@@ -702,11 +714,11 @@ onUnmounted(() => {
>
<n-flex vertical>
<n-form>
<n-form-item label="解压到">
<n-form-item :label="$gettext('Uncompress to')">
<n-input v-model:value="unCompressModel.path" />
</n-form-item>
</n-form>
<n-button type="primary" @click="handleUnCompress">解压</n-button>
<n-button type="primary" @click="handleUnCompress">{{ $gettext('Uncompress') }}</n-button>
</n-flex>
</n-modal>
</template>

View File

@@ -1,9 +1,11 @@
<script setup lang="ts">
import type { InputInst } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import { checkPath } from '@/utils/file'
import SearchModal from '@/views/file/SearchModal.vue'
const { $gettext } = useGettext()
const path = defineModel<string>('path', { type: String, required: true })
const isInput = ref(false)
const pathInput = ref<InputInst | null>(null)
@@ -28,7 +30,7 @@ const handleInput = () => {
const handleBlur = () => {
input.value = input.value.replace(/(^\/)|(\/$)/g, '')
if (!checkPath(input.value)) {
window.$message.error('路径不合法')
window.$message.error($gettext('Invalid path'))
return
}
@@ -127,7 +129,7 @@ onUnmounted(() => {
<n-input-group flex-1>
<n-tag size="large" v-if="!isInput" flex-1 @click="handleInput">
<n-breadcrumb separator=">">
<n-breadcrumb-item @click.stop="setPath(-1)"> 根目录 </n-breadcrumb-item>
<n-breadcrumb-item @click.stop="setPath(-1)"> {{ $gettext('Root Directory') }} </n-breadcrumb-item>
<n-breadcrumb-item
v-for="(item, index) in splitPath(path, '/')"
:key="index"
@@ -147,9 +149,9 @@ onUnmounted(() => {
/>
</n-input-group>
<n-input-group w-400>
<n-input v-model:value="search.keyword" placeholder="请输入搜索内容">
<n-input v-model:value="search.keyword" :placeholder="$gettext('Enter search content')">
<template #suffix>
<n-checkbox v-model:checked="search.sub"> 包含子目录 </n-checkbox>
<n-checkbox v-model:checked="search.sub"> {{ $gettext('Include subdirectories') }} </n-checkbox>
</template>
</n-input>
<n-button type="primary" @click="handleSearch">

View File

@@ -1,8 +1,10 @@
<script setup lang="ts">
import { NButton, NInput } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import file from '@/api/panel/file'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const selected = defineModel<string[]>('selected', { type: Array, required: true })
const mode = ref('755')
@@ -24,7 +26,7 @@ const handlePermission = async () => {
show.value = false
selected.value = []
window.$bus.emit('file:refresh')
window.$message.success('修改成功')
window.$message.success($gettext('Modified successfully'))
}
const calculateOctal = (permissions: string[]) => {
@@ -59,7 +61,9 @@ const updateCheckboxes = () => {
}
const title = computed(() => {
return selected.value.length > 1 ? '批量修改权限' : `修改权限 - ${selected.value[0]}`
return selected.value.length > 1
? $gettext('Batch modify permissions')
: $gettext('Modify permissions - %{ path }', { path: selected.value[0] })
})
watch(mode, updateCheckboxes, { immediate: true })
@@ -79,44 +83,44 @@ watch(mode, updateCheckboxes, { immediate: true })
<n-form>
<n-row :gutter="[0, 24]">
<n-col :span="8">
<n-form-item label="所有者">
<n-form-item :label="$gettext('Owner')">
<n-checkbox-group v-model:value="checkbox.owner" @update:value="calculateMode">
<n-checkbox value="read" label="读取" />
<n-checkbox value="write" label="写入" />
<n-checkbox value="execute" label="执行" />
<n-checkbox value="read" :label="$gettext('Read')" />
<n-checkbox value="write" :label="$gettext('Write')" />
<n-checkbox value="execute" :label="$gettext('Execute')" />
</n-checkbox-group>
</n-form-item>
</n-col>
<n-col :span="8">
<n-form-item label="用户组">
<n-form-item :label="$gettext('Group')">
<n-checkbox-group v-model:value="checkbox.group" @update:value="calculateMode">
<n-checkbox value="read" label="读取" />
<n-checkbox value="write" label="写入" />
<n-checkbox value="execute" label="执行" />
<n-checkbox value="read" :label="$gettext('Read')" />
<n-checkbox value="write" :label="$gettext('Write')" />
<n-checkbox value="execute" :label="$gettext('Execute')" />
</n-checkbox-group>
</n-form-item>
</n-col>
<n-col :span="8">
<n-form-item label="其他">
<n-form-item :label="$gettext('Others')">
<n-checkbox-group v-model:value="checkbox.other" @update:value="calculateMode">
<n-checkbox value="read" label="读取" />
<n-checkbox value="write" label="写入" />
<n-checkbox value="execute" label="执行" />
<n-checkbox value="read" :label="$gettext('Read')" />
<n-checkbox value="write" :label="$gettext('Write')" />
<n-checkbox value="execute" :label="$gettext('Execute')" />
</n-checkbox-group>
</n-form-item>
</n-col>
</n-row>
<n-form-item label="权限">
<n-form-item :label="$gettext('Permission')">
<n-input v-model:value="mode" />
</n-form-item>
<n-form-item label="所有者">
<n-form-item :label="$gettext('Owner')">
<n-input v-model:value="owner" />
</n-form-item>
<n-form-item label="用户组">
<n-form-item :label="$gettext('Group')">
<n-input v-model:value="group" />
</n-form-item>
</n-form>
<n-button type="primary" @click="handlePermission"> 修改</n-button>
<n-button type="primary" @click="handlePermission"> {{ $gettext('Modify') }} </n-button>
</n-flex>
</n-modal>
</template>

View File

@@ -1,6 +1,8 @@
<script setup lang="ts">
import file from '@/api/panel/file'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const path = defineModel<string>('path', { type: String, required: true })
@@ -26,7 +28,7 @@ watch(
<n-modal
v-model:show="show"
preset="card"
:title="'预览 - ' + path"
:title="$gettext('Preview - %{ path }', { path })"
style="width: 60vw"
size="huge"
:bordered="false"

View File

@@ -1,11 +1,13 @@
<script setup lang="ts">
import file from '@/api/panel/file'
import { NButton, NPopconfirm, NSpace, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import copy2clipboard from '@vavt/copy2clipboard'
import type { DataTableColumns } from 'naive-ui'
import type { RowData } from 'naive-ui/es/data-table/src/interface'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const path = defineModel<string>('path', { type: String, required: true })
const keyword = defineModel<string>('keyword', { type: String, required: true })
@@ -15,7 +17,7 @@ const loading = ref(false)
const columns: DataTableColumns<RowData> = [
{
title: '名称',
title: $gettext('Name'),
key: 'full',
minWidth: 300,
ellipsis: {
@@ -23,7 +25,7 @@ const columns: DataTableColumns<RowData> = [
}
},
{
title: '大小',
title: $gettext('Size'),
key: 'size',
width: 80,
render(row: any): any {
@@ -31,7 +33,7 @@ const columns: DataTableColumns<RowData> = [
}
},
{
title: '修改时间',
title: $gettext('Modification Time'),
key: 'modify',
width: 200,
render(row: any): any {
@@ -43,7 +45,7 @@ const columns: DataTableColumns<RowData> = [
}
},
{
title: '操作',
title: $gettext('Actions'),
key: 'action',
width: 200,
render(row) {
@@ -60,13 +62,13 @@ const columns: DataTableColumns<RowData> = [
tertiary: true,
onClick: () => {
copy2clipboard(row.full).then(() => {
window.$message.success('复制成功')
window.$message.success($gettext('Copied successfully'))
})
}
},
{
default: () => {
return '复制路径'
return $gettext('Copy Path')
}
}
),
@@ -76,14 +78,14 @@ const columns: DataTableColumns<RowData> = [
onPositiveClick: () => {
useRequest(file.delete(row.full)).onSuccess(() => {
window.$bus.emit('file:refresh')
window.$message.success('删除成功')
window.$message.success($gettext('Deleted successfully'))
})
},
onNegativeClick: () => {}
},
{
default: () => {
return `确定删除 ${row.name} 吗?`
return $gettext('Are you sure you want to delete %{ name }?', { name: row.name })
},
trigger: () => {
return h(
@@ -93,7 +95,7 @@ const columns: DataTableColumns<RowData> = [
type: 'error',
tertiary: true
},
{ default: () => '删除' }
{ default: () => $gettext('Delete') }
)
}
}
@@ -149,7 +151,7 @@ watch(show, (value) => {
<n-modal
v-model:show="show"
preset="card"
:title="keyword + ' - 搜索结果'"
:title="$gettext('%{ keyword } - Search Results', { keyword })"
style="width: 60vw"
size="huge"
:bordered="false"

View File

@@ -1,8 +1,10 @@
<script setup lang="ts">
import type { UploadCustomRequestOptions } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import api from '@/api/panel/file'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const path = defineModel<string>('path', { type: String, required: true })
const upload = ref<any>(null)
@@ -15,7 +17,7 @@ const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequ
.onSuccess(() => {
onFinish()
window.$bus.emit('file:refresh')
window.$message.success(`上传 ${file.name} 成功`)
window.$message.success($gettext('Upload %{ fileName } successful', { fileName: file.name }))
})
.onError(() => {
onError()
@@ -33,7 +35,7 @@ const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequ
<n-modal
v-model:show="show"
preset="card"
title="上传"
:title="$gettext('Upload')"
style="width: 60vw"
size="huge"
:bordered="false"
@@ -45,8 +47,8 @@ const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequ
<div style="margin-bottom: 12px">
<the-icon :size="48" icon="bi:arrow-up-square" />
</div>
<NText text-18> 点击或者拖动文件到该区域来上传</NText>
<NP depth="3" m-10> 大文件建议使用 SFTP 等方式上传 </NP>
<NText text-18> {{ $gettext('Click or drag files to this area to upload') }}</NText>
<NP depth="3" m-10> {{ $gettext('For large files, it is recommended to use SFTP and other methods to upload') }} </NP>
</n-upload-dragger>
</n-upload>
</n-flex>

View File

@@ -1,3 +1,4 @@
import { $gettext } from '@/utils/gettext'
import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
@@ -15,7 +16,7 @@ export default {
path: '',
component: () => import('./IndexView.vue'),
meta: {
title: 'fileIndex.title',
title: $gettext('Files'),
icon: 'mdi:folder-open-outline',
role: ['admin'],
requireAuth: true