部分页面实现中文替换为支持翻译的英文

This commit is contained in:
jingrow 2025-12-28 23:27:45 +08:00
parent bcbae262b8
commit 62922ef899
7 changed files with 120 additions and 139 deletions

View File

@ -29,26 +29,26 @@
class="text-base"
:class="error ? 'text-red-500' : 'text-gray-600'"
>
{{
uploading
? `上传中 ${progress}%`
: success
? formatBytes(fileObj.size)
: error
? error
: file.description
}}
{{
uploading
? $t('Uploading {progress}%', { progress })
: success
? formatBytes(fileObj.size)
: error
? error
: file.description
}}
</span>
</template>
<template #actions>
<Button
:loading="uploading"
loadingText="上传中..."
@click="openFileSelector()"
v-if="!success"
>
上传
</Button>
<Button
:loading="uploading"
:loadingText="$t('Uploading...')"
@click="openFileSelector()"
v-if="!success"
>
{{ $t('Upload') }}
</Button>
<GreenCheckIcon class="w-5" v-if="success" />
</template>
</ListItem>
@ -72,36 +72,32 @@ export default {
icon: '<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5.33325 9.33333V22.6667C5.33325 25.6133 10.1093 28 15.9999 28C21.8906 28 26.6666 25.6133 26.6666 22.6667V9.33333M5.33325 9.33333C5.33325 12.28 10.1093 14.6667 15.9999 14.6667C21.8906 14.6667 26.6666 12.28 26.6666 9.33333M5.33325 9.33333C5.33325 6.38667 10.1093 4 15.9999 4C21.8906 4 26.6666 6.38667 26.6666 9.33333M26.6666 16C26.6666 18.9467 21.8906 21.3333 15.9999 21.3333C10.1093 21.3333 5.33325 18.9467 5.33325 16" stroke="#1F272E" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
type: 'database',
ext: 'application/x-gzip,application/sql,.sql',
title: '数据库备份',
description:
'上传数据库备份文件。通常文件名以 .sql.gz 或 .sql 结尾',
title: this.$t('Database Backup'),
description: this.$t('Upload database backup file. Usually the filename ends with .sql.gz or .sql'),
file: null
},
{
icon: '<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.39111 6.3913H26.3476V22.2174C26.3476 25.9478 23.2955 29 19.565 29H9.39111V6.3913Z" stroke="#1F272E" stroke-width="1.5" stroke-miterlimit="10"/><path d="M13.9131 13.1739H21.8261" stroke="#1F272E" stroke-width="1.5" stroke-miterlimit="10"/><path d="M13.9131 17.6957H21.8261" stroke="#1F272E" stroke-width="1.5" stroke-miterlimit="10"/><path d="M13.9131 22.2173H19.8479" stroke="#1F272E" stroke-width="1.5" stroke-miterlimit="10"/><path d="M22.9565 6.3913V3H6V25.6087H9.3913" stroke="#1F272E" stroke-width="1.5" stroke-miterlimit="10"/></svg>',
type: 'public',
ext: 'application/x-tar',
title: '公共文件',
description:
'上传公共文件备份。通常文件名以 -files.tar 结尾',
title: this.$t('Public Files'),
description: this.$t('Upload public files backup. Usually the filename ends with -files.tar'),
file: null
},
{
icon: '<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.39111 6.3913H25.3476V22.2174C25.3476 25.9478 22.2955 29 18.565 29H8.39111V6.3913Z" stroke="#1F272E" stroke-width="1.5" stroke-miterlimit="10"/><path d="M21.9565 6.3913V3H5V25.6087H8.3913" stroke="#1F272E" stroke-width="1.5" stroke-miterlimit="10"/></svg>',
type: 'private',
ext: 'application/x-tar',
title: '私有文件',
description:
'上传私有文件备份。通常文件名以 -private-files.tar 结尾',
title: this.$t('Private Files'),
description: this.$t('Upload private files backup. Usually the filename ends with -private-files.tar'),
file: null
},
{
icon: '<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.39111 6.3913H25.3476V22.2174C25.3476 25.9478 22.2955 29 18.565 29H8.39111V6.3913Z" stroke="#1F272E" stroke-width="1.5" stroke-miterlimit="10"/><path d="M21.9565 6.3913V3H5V25.6087H8.3913" stroke="#1F272E" stroke-width="1.5" stroke-miterlimit="10"/></svg>',
type: 'config',
ext: 'application/json',
title: '站点配置(如备份已加密则必需)',
description:
'上传站点配置文件。通常文件名以 -site_config_backup.json 结尾',
title: this.$t('Site Config (required if backup is encrypted)'),
description: this.$t('Upload site config file. Usually the filename ends with -site_config_backup.json'),
file: null
}
]
@ -118,7 +114,7 @@ export default {
// valid strings are "database.sql.gz", "database.sql", "database.sql (1).gz", "database.sql (2).gz"
if (!/\.sql( \(\d\))?\.gz$|\.sql$/.test(file.name)) {
throw new Error(
'数据库备份文件应以"database.sql.gz"或"database.sql"结尾'
this.$t('Database backup file should end with "database.sql.gz" or "database.sql"')
);
}
if (
@ -128,17 +124,18 @@ export default {
'application/sql'
].includes(file.type)
) {
throw new Error('无效的数据库备份文件');
throw new Error(this.$t('Invalid database backup file'));
}
}
if (['public', 'private'].includes(type)) {
if (file.type != 'application/x-tar') {
throw new Error(`无效的${type === 'public' ? '公共' : '私有'}文件备份文件`);
const fileType = type === 'public' ? this.$t('public') : this.$t('private');
throw new Error(this.$t('Invalid {fileType} files backup file', { fileType }));
}
}
if (type === 'config') {
if (file.type != 'application/json') {
throw new Error(`无效的站点配置文件`);
throw new Error(this.$t('Invalid site config file'));
}
}
}

View File

@ -1,10 +1,10 @@
<template>
<Dialog
:options="{
title: '恢复',
title: $t('Restore'),
actions: [
{
label: '恢复',
label: $t('Restore'),
variant: 'solid',
theme: 'red',
loading: $resources.restoreBackup.loading,
@ -19,19 +19,19 @@
>
<template v-slot:body-content>
<div class="space-y-4">
<p class="text-base">使用之前的备份恢复您的数据库</p>
<p class="text-base">{{ $t('Restore your database using a previous backup.') }}</p>
<div
class="flex items-center rounded border border-gray-200 bg-gray-100 p-4 text-sm text-gray-600"
>
<i-lucide-alert-triangle class="mr-4 inline-block h-6 w-6" />
<div>
此操作将用备份中的<b>数据</b><b>应用</b>替换您站点中的所有内容
{{ $t('This operation will replace all content in your site with the <b>data</b> and <b>apps</b> from the backup.') }}
</div>
</div>
<BackupFilesUploader v-model:backupFiles="selectedFiles" />
</div>
<div class="mt-3">
<!-- 跳过失败复选框 -->
<!-- Skip failing patches checkbox -->
<input
id="skip-failing"
type="checkbox"
@ -39,7 +39,7 @@
v-model="skipFailingPatches"
/>
<label for="skip-failing" class="ml-2 text-sm text-gray-900">
跳过失败的补丁如果有任何补丁失败
{{ $t('Skip failing patches (if any patch fails)') }}
</label>
</div>
<ErrorMessage class="mt-2" :message="$resources.restoreBackup.error" />
@ -81,7 +81,7 @@ export default {
validate() {
if (!this.filesUploaded) {
throw new DashboardError(
'请上传数据库、公共和私有文件以进行恢复。'
this.$t('Please upload database, public and private files to restore.')
);
}
},

View File

@ -5,46 +5,30 @@
<script>
import { isMobile } from '../../utils/device';
import { duration } from '../../utils/format';
import { t } from '../../utils/i18n';
import ObjectList from '../ObjectList.vue';
// job_type
const jobTypeI18nMap = {
'Update Site Status': '更新站点状态',
'Update Site Configuration': '更新站点配置',
'Install App on Site': '安装应用到站点',
'Uninstall App from Site': '从站点卸载应用',
'Backup Site': '备份站点',
'Restore Site': '恢复站点',
'Create Server': '创建服务器',
'Update In Place': '原地升级',
//
};
// Job type translation function
function jobTypeI18n(type) {
return jobTypeI18nMap[type] || type;
return t(type) || type;
}
//
const statusI18nMap = {
'Pending': '待处理',
'Running': '运行中',
'Success': '成功',
'Failure': '失败',
};
// Status translation function
function statusI18n(status) {
return statusI18nMap[status] || status;
return t(status) || status;
}
//
function formatTimeZh(time) {
// Relative time formatting
function formatTimeRelative(time) {
if (!time) return '';
const date = typeof time === 'string' ? new Date(time) : time;
const now = new Date();
const diff = (now.getTime() - date.getTime()) / 1000;
if (diff < 60) return '刚刚';
if (diff < 3600) return Math.floor(diff / 60) + '分钟前';
if (diff < 86400) return Math.floor(diff / 3600) + '小时前';
if (diff < 2592000) return Math.floor(diff / 86400) + '天前';
return date.toLocaleDateString('zh-CN');
if (diff < 60) return t('Just now');
if (diff < 3600) return t('{minutes} minutes ago', { minutes: Math.floor(diff / 60) });
if (diff < 86400) return t('{hours} hours ago', { hours: Math.floor(diff / 3600) });
if (diff < 2592000) return t('{days} days ago', { days: Math.floor(diff / 86400) });
return date.toLocaleDateString();
}
export default {
@ -74,20 +58,20 @@ export default {
return [
{
type: 'select',
label: '状态',
label: t('Status'),
fieldname: 'status',
class: !isMobile() ? 'w-24' : '',
options: [
{ label: '', value: '' },
{ label: '待处理', value: 'Pending' },
{ label: '运行中', value: 'Running' },
{ label: '成功', value: 'Success' },
{ label: '失败', value: 'Failure' }
{ label: t('Pending'), value: 'Pending' },
{ label: t('Running'), value: 'Running' },
{ label: t('Success'), value: 'Success' },
{ label: t('Failure'), value: 'Failure' }
]
},
{
type: 'link',
label: '类型',
label: t('Type'),
fieldname: 'job_type',
options: {
pagetype: 'Agent Job Type',
@ -99,26 +83,26 @@ export default {
},
columns: [
{
label: '任务类型',
label: t('Job Type'),
fieldname: 'job_type',
class: 'font-medium',
format: jobTypeI18n
},
{
label: '状态',
label: t('Status'),
fieldname: 'status',
type: 'Badge',
width: 0.5,
format: statusI18n
},
{
label: '站点',
label: t('Site'),
fieldname: 'site',
width: 1.2,
condition: () => pagetype !== 'Site'
},
{
label: '持续时间',
label: t('Duration'),
fieldname: 'duration',
width: 0.35,
format(value, row) {
@ -127,7 +111,7 @@ export default {
}
},
{
label: '创建者',
label: t('Owner'),
fieldname: 'owner'
},
{
@ -136,7 +120,7 @@ export default {
type: 'Timestamp',
width: 0.5,
align: 'right',
format: formatTimeZh
format: formatTimeRelative
}
].filter(c => (c.condition ? c.condition() : true))
};

View File

@ -2,11 +2,11 @@
<div class="mx-auto max-w-3xl px-4 py-8">
<div class="bg-white rounded-xl shadow-sm p-6">
<div class="mb-6">
<h1 class="text-xl font-bold text-gray-800">账户充值</h1>
<p class="text-gray-600 mt-2">为您的账户充值余额用于支付您的服务费用</p>
<h1 class="text-xl font-bold text-gray-800">{{ $t('Account Recharge') }}</h1>
<p class="text-gray-600 mt-2">{{ $t('Recharge your account balance to pay for your services.') }}</p>
</div>
<!-- 充值方式选择 (药丸式设计) -->
<!-- Payment method selection -->
<div class="mb-6">
<div class="flex w-full rounded-full bg-gray-100 p-1 text-sm text-gray-800">
<div
@ -14,19 +14,19 @@
:class="{ 'bg-white shadow-sm font-medium text-blue-600': paymentMethod === 'online' }"
@click="paymentMethod = 'online'"
>
在线充值
{{ $t('Online Recharge') }}
</div>
<div
class="w-1/2 cursor-pointer rounded-full py-2 text-center transition-all"
:class="{ 'bg-white shadow-sm font-medium text-blue-600': paymentMethod === 'transfer' }"
@click="paymentMethod = 'transfer'"
>
对公汇款
{{ $t('Bank Transfer') }}
</div>
</div>
</div>
<!-- 在线充值表单 -->
<!-- Online recharge form -->
<div v-if="paymentMethod === 'online'">
<BuyPrepaidCreditsForm
:minimumAmount="minimumAmount"
@ -35,63 +35,62 @@
/>
</div>
<!-- 对公汇款信息 - 简化版移除了表单部分 -->
<!-- Bank transfer information -->
<div v-else class="space-y-6">
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
<div class="mb-4">
<h3 class="font-medium text-gray-900">汇款账户信息</h3>
<h3 class="font-medium text-gray-900">{{ $t('Bank Account Information') }}</h3>
</div>
<div class="space-y-3 text-sm">
<div class="flex justify-between items-center py-1 border-b border-gray-200">
<span class="text-gray-600">公司名称</span>
<span class="text-gray-600">{{ $t('Company Name') }}</span>
<span class="font-medium text-gray-900">广州市向日葵网络信息科技有限公司</span>
</div>
<div class="flex justify-between items-center py-1 border-b border-gray-200">
<span class="text-gray-600">开户银行</span>
<span class="text-gray-600">{{ $t('Bank Name') }}</span>
<span class="font-medium text-gray-900">中国农业银行股份有限公司广州花都金狮支行</span>
</div>
<div class="flex justify-between items-center py-1 border-b border-gray-200">
<span class="text-gray-600">银行账号</span>
<span class="text-gray-600">{{ $t('Account Number') }}</span>
<span class="font-medium text-gray-900 font-mono">4408 6601 0400 20960</span>
</div>
</div>
</div>
<!-- 重要提示 -->
<!-- Important notice -->
<div class="rounded-lg bg-yellow-50 border border-yellow-200 p-4">
<div class="flex">
<i-lucide-alert-circle class="h-5 w-5 text-yellow-600 mr-2 flex-shrink-0" />
<div>
<h4 class="font-medium text-yellow-800">重要提示</h4>
<h4 class="font-medium text-yellow-800">{{ $t('Important Notice') }}</h4>
<ul class="mt-2 text-sm text-yellow-700 space-y-1 list-disc pl-4">
<li>汇款时请备注用户所属团队ID
<li>{{ $t('Please include your team ID in the transfer note:') }}
<strong
class="font-mono bg-yellow-100 px-1 rounded cursor-pointer inline-flex items-center gap-1 hover:bg-yellow-200 transition-colors"
@click="copyTransferNote"
title="点击复制"
:title="$t('Click to copy')"
>
{{ $team.pg.name }}
<i-lucide-copy class="h-3.5 w-3.5 text-yellow-600" />
</strong>
<span v-if="showCopiedMessage" class="text-xs text-green-600 ml-1 transition-opacity">已复制!</span>
<span v-if="showCopiedMessage" class="text-xs text-green-600 ml-1 transition-opacity">{{ $t('Copied!') }}</span>
</li>
<li>汇款成功后请联系客服进行确认财务人员会在1个工作日内处理</li>
<li>如有疑问请联系客服</li>
<li>{{ $t('After successful transfer, please contact customer service for confirmation. Our finance team will process within 1 business day.') }}</li>
<li>{{ $t('If you have any questions, please contact customer service.') }}</li>
</ul>
</div>
</div>
</div>
<!-- 添加一个说明解释资金处理流程 -->
<!-- Recharge process explanation -->
<div class="rounded-lg border border-gray-200 p-4 bg-blue-50">
<div class="flex">
<i-lucide-info class="h-5 w-5 text-blue-600 mr-2 flex-shrink-0" />
<div>
<h4 class="font-medium text-blue-800">充值流程</h4>
<h4 class="font-medium text-blue-800">{{ $t('Recharge Process') }}</h4>
<p class="mt-2 text-sm text-blue-700">
完成对公汇款后我们的财务人员会在确认收款后将资金添加到您的账户余额中
通常这个过程需要1个工作日如需加急处理请联系客服
{{ $t('After completing the bank transfer, our finance team will add the funds to your account balance after confirming receipt. This process usually takes 1 business day. For urgent processing, please contact customer service.') }}
</p>
</div>
</div>
@ -113,31 +112,31 @@ export default {
},
data() {
return {
paymentMethod: 'online', // 线
paymentMethod: 'online', // Default to online recharge
minimumAmount: 0,
showCopiedMessage: false
};
},
created() {
// URL
// Get minimum recharge amount from URL parameter if available
if (this.$route.query.amount) {
this.minimumAmount = parseFloat(this.$route.query.amount) || 0;
}
},
methods: {
onRechargeSuccess() {
this.$toast.success('充值成功');
this.$toast.success(this.$t('Recharge successful'));
//
// If redirected from another page, go back
if (this.$route.query.redirect) {
this.$router.push(this.$route.query.redirect);
} else {
//
// Otherwise navigate to billing page
this.$router.push('/billing');
}
},
onRechargeCancel() {
//
// Cancel recharge, go back
this.$router.back();
},
copyTransferNote() {
@ -190,7 +189,7 @@ export default {
if (successful) {
this.showCopiedSuccessMessage();
} else {
this.$toast.error('复制失败,请手动复制');
this.$toast.error(this.$t('Copy failed, please copy manually'));
}
},

View File

@ -1,6 +1,7 @@
import dayjs, { dayjsLocal } from './dayjs';
import { getTeam } from '../data/team';
import { format } from 'sql-formatter';
import { t } from './i18n';
export function bytes(bytes, decimals = 2, current = 0) {
if (bytes === 0) return '0 Bytes';
@ -224,36 +225,36 @@ export function formatValue(value, type) {
}
}
export const statusMap = {
'Active': '激活',
'Inactive': '未激活',
'Suspended': '已暂停',
'Broken': '损坏',
'Archived': '已归档',
'Pending': '待处理',
'Installing': '安装中',
'Running': '运行中',
'Success': '成功',
'Failure': '失败'
};
export const deployTypeMap = {
'Migrate': '迁移',
'Pull': '拉取',
'Update': '更新',
'Install': '安装',
'Uninstall': '卸载',
'Backup': '备份',
'Restore': '恢复',
'Upgrade': '升级',
'Downgrade': '降级',
'Rollback': '回滚',
};
// Status labels - use translation function
export function getStatusLabel(status) {
return statusMap[status] || status;
const statusLabels = {
'Active': t('Active'),
'Inactive': t('Inactive'),
'Suspended': t('Suspended'),
'Broken': t('Broken'),
'Archived': t('Archived'),
'Pending': t('Pending'),
'Installing': t('Installing'),
'Running': t('Running'),
'Success': t('Success'),
'Failure': t('Failure')
};
return statusLabels[status] || status;
}
// Deploy type labels - use translation function
export function getDeployTypeLabel(type) {
return deployTypeMap[type] || type;
const deployTypeLabels = {
'Migrate': t('Migrate'),
'Pull': t('Pull'),
'Update': t('Update'),
'Install': t('Install'),
'Uninstall': t('Uninstall'),
'Backup': t('Backup'),
'Restore': t('Restore'),
'Upgrade': t('Upgrade'),
'Downgrade': t('Downgrade'),
'Rollback': t('Rollback'),
};
return deployTypeLabels[type] || type;
}

View File

@ -15,7 +15,7 @@ export const notify = props => {
setTimeout(() => hideNotification(props.id), props.timeout || 5000);
};
export function getToastErrorMessage(error, fallbackMessage = '发生了错误') {
export function getToastErrorMessage(error, fallbackMessage = 'An error occurred') {
if (!error) return fallbackMessage;
try {

View File

@ -282,7 +282,7 @@ export default {
name: 'Sites',
pageMeta() {
return {
title: 'Sites - 今果 Jingrow'
title: 'Sites - Jingrow'
};
},
props: ['bench'],