Billing菜单及页面实现多语言支持

This commit is contained in:
jingrow 2025-12-29 15:42:11 +08:00
parent 2a350e93ce
commit d911a973eb
12 changed files with 207 additions and 120 deletions

View File

@ -178,7 +178,7 @@ export default {
disabled: enforce2FA, disabled: enforce2FA,
}, },
{ {
name: '账单', name: this.$t('Billing'),
icon: () => h(WalletCards), icon: () => h(WalletCards),
route: '/billing', route: '/billing',
isActive: routeName.startsWith('Billing'), isActive: routeName.startsWith('Billing'),

View File

@ -1,5 +1,5 @@
<template> <template>
<Dialog v-model="show" :options="{ title: '余额充值' }"> <Dialog v-model="show" :options="{ title: $t('Account Recharge') }">
<template #body-content> <template #body-content>
<div <div
v-if="showMessage" v-if="showMessage"
@ -7,7 +7,7 @@
> >
<FeatherIcon class="h-4" name="info" /> <FeatherIcon class="h-4" name="info" />
<span> <span>
在更改支付方式之前请先为您的账户充值余额 {{ $t('Please recharge your account balance before changing payment method.') }}
</span> </span>
</div> </div>
<PrepaidCreditsForm <PrepaidCreditsForm

View File

@ -4,7 +4,7 @@
v-model="billingInformation.billing_name" v-model="billingInformation.billing_name"
type="text" type="text"
name="billing_name" name="billing_name"
label="账单名称" :label="$t('Billing Name')"
:required="true" :required="true"
/> />
<NewAddressForm <NewAddressForm
@ -18,7 +18,7 @@
<Button <Button
class="w-full" class="w-full"
variant="solid" variant="solid"
label="更新账单信息" :label="$t('Update Billing Information')"
:loading="addressFormRef.updateBillingInformation.loading" :loading="addressFormRef.updateBillingInformation.loading"
@click="updateBillingInformation" @click="updateBillingInformation"
/> />
@ -27,7 +27,7 @@
<script setup> <script setup>
import NewAddressForm from './NewAddressForm.vue'; import NewAddressForm from './NewAddressForm.vue';
import { FormControl, ErrorMessage, Button, createResource } from 'jingrow-ui'; import { FormControl, ErrorMessage, Button, createResource } from 'jingrow-ui';
import { reactive, ref, inject } from 'vue'; import { reactive, ref, inject, getCurrentInstance } from 'vue';
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
@ -70,15 +70,16 @@ createResource({
const errorMessage = ref(''); const errorMessage = ref('');
function updateBillingInformation() { function updateBillingInformation() {
const { $t } = getCurrentInstance().appContext.config.globalProperties;
if (!billingInformation.billing_name) { if (!billingInformation.billing_name) {
errorMessage.value = '账单名称为必填项'; errorMessage.value = $t('Billing name is required');
return; return;
} }
const billing_name = billingInformation.billing_name.trim(); const billing_name = billingInformation.billing_name.trim();
const billingNameRegex = /^[a-zA-Z0-9\-\'\,\.\s]+$/; const billingNameRegex = /^[a-zA-Z0-9\-\'\,\.\s]+$/;
const billingNameValid = billingNameRegex.test(billing_name); const billingNameValid = billingNameRegex.test(billing_name);
if (!billingNameValid) { if (!billingNameValid) {
errorMessage.value = '账单名称包含无效字符'; errorMessage.value = $t('Billing name contains invalid characters');
return; return;
} }
billingInformation.billing_name = billing_name; billingInformation.billing_name = billing_name;

View File

@ -1,12 +1,12 @@
<template> <template>
<Dialog v-model="show" :options="{ title: '账单详情' }"> <Dialog v-model="show" :options="{ title: $t('Billing Details') }">
<template #body-content> <template #body-content>
<div <div
v-if="showMessage" v-if="showMessage"
class="mb-5 inline-flex gap-1.5 text-base text-gray-700" class="mb-5 inline-flex gap-1.5 text-base text-gray-700"
> >
<FeatherIcon class="h-4" name="info" /> <FeatherIcon class="h-4" name="info" />
<span> 在继续之前请为您的账户添加账单详情</span> <span> {{ $t('Please add billing details to your account before continuing.') }}</span>
</div> </div>
<BillingDetails <BillingDetails
ref="billingRef" ref="billingRef"

View File

@ -25,7 +25,7 @@
</template> </template>
<script setup> <script setup>
import { FormControl, ErrorMessage, createResource } from 'jingrow-ui'; import { FormControl, ErrorMessage, createResource } from 'jingrow-ui';
import { ref, computed, inject, watch, onMounted } from 'vue'; import { ref, computed, inject, watch, onMounted, getCurrentInstance } from 'vue';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { DashboardError } from '../../utils/error'; import { DashboardError } from '../../utils/error';
import { countryNameMap, chinaStates } from '@/utils/billing'; import { countryNameMap, chinaStates } from '@/utils/billing';
@ -70,7 +70,9 @@ const updateBillingInformation = createResource({
if (error) throw new DashboardError(error); if (error) throw new DashboardError(error);
}, },
onSuccess: () => { onSuccess: () => {
toast.success('账单信息已更新'); const instance = getCurrentInstance();
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
toast.success($t('Billing information updated'));
emit('success'); emit('success');
}, },
}); });
@ -92,8 +94,11 @@ const codeToNameMap = computed(() => {
// - // -
const countryList = computed(() => { const countryList = computed(() => {
const instance = getCurrentInstance();
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
// //
return [{ label: '中国', value: 'China' }]; return [{ label: $t('China'), value: 'China' }];
// //
// return Object.entries({ // return Object.entries({
@ -114,14 +119,17 @@ const stateOptions = computed(() => {
}); });
const sections = computed(() => { const sections = computed(() => {
const instance = getCurrentInstance();
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
return [ return [
{ {
name: '国家和城市', name: $t('Country and City'),
columns: 2, columns: 2,
fields: [ fields: [
{ {
fieldtype: 'Select', fieldtype: 'Select',
label: '国家', label: $t('Country'),
fieldname: 'country', fieldname: 'country',
options: countryList.value, options: countryList.value,
required: true, required: true,
@ -129,32 +137,32 @@ const sections = computed(() => {
}, },
{ {
fieldtype: 'Data', fieldtype: 'Data',
label: '城市', label: $t('City'),
fieldname: 'city', fieldname: 'city',
required: true, required: true,
}, },
], ],
}, },
{ {
name: '地址', name: $t('Address'),
columns: 1, columns: 1,
fields: [ fields: [
{ {
fieldtype: 'Data', fieldtype: 'Data',
label: '地址', label: $t('Address'),
fieldname: 'address', fieldname: 'address',
required: true, required: true,
}, },
], ],
}, },
{ {
name: '州和邮政编码', name: $t('State and Postal Code'),
columns: 2, columns: 2,
fields: [ fields: [
{ {
fieldtype: fieldtype:
billingInformation.value.country === 'China' ? 'Select' : 'Data', billingInformation.value.country === 'China' ? 'Select' : 'Data',
label: '州/省/地区', label: $t('State/Province/Region'),
fieldname: 'state', fieldname: 'state',
required: true, required: true,
options: options:
@ -164,7 +172,7 @@ const sections = computed(() => {
}, },
{ {
fieldtype: 'Data', fieldtype: 'Data',
label: '邮政编码', label: $t('Postal Code'),
fieldname: 'postal_code', fieldname: 'postal_code',
required: true, required: true,
}, },
@ -186,10 +194,13 @@ function getInputType(field) {
} }
async function validate() { async function validate() {
const instance = getCurrentInstance();
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
// //
for (let field of sections.value.flatMap((s) => s.fields)) { for (let field of sections.value.flatMap((s) => s.fields)) {
if (field.required && !billingInformation.value[field.fieldname]) { if (field.required && !billingInformation.value[field.fieldname]) {
return `${field.label} 是必填项`; return $t('{field} is required', { field: field.label });
} }
} }
return null; // null return null; // null

View File

@ -3,14 +3,14 @@
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex items-center justify-between text-base text-gray-900"> <div class="flex items-center justify-between text-base text-gray-900">
<div class="flex flex-col gap-1.5"> <div class="flex flex-col gap-1.5">
<div class="text-lg font-semibold text-gray-900">账户余额</div> <div class="text-lg font-semibold text-gray-900">{{ $t('Account Balance') }}</div>
<div class="text-2xl font-bold text-blue-600 py-6"> <div class="text-2xl font-bold text-blue-600 py-6">
{{ availableCredits || currency + ' 0.00' }} {{ availableCredits || currency + ' 0.00' }}
</div> </div>
</div> </div>
<div class="shrink-0"> <div class="shrink-0">
<Button <Button
:label="'充值'" :label="$t('Recharge')"
@click=" @click="
() => { () => {
showMessage = false; showMessage = false;
@ -27,15 +27,15 @@
<div class="my-3 h-px bg-gray-100" /> <div class="my-3 h-px bg-gray-100" />
<div class="flex items-center justify-between text-base text-gray-900"> <div class="flex items-center justify-between text-base text-gray-900">
<div class="flex flex-col gap-1.5"> <div class="flex flex-col gap-1.5">
<div class="font-medium">账单地址</div> <div class="font-medium">{{ $t('Billing Address') }}</div>
<div v-if="billingDetailsSummary" class="leading-5 text-gray-700"> <div v-if="billingDetailsSummary" class="leading-5 text-gray-700">
{{ billingDetailsSummary }} {{ billingDetailsSummary }}
</div> </div>
<div v-else class="text-gray-700">无地址</div> <div v-else class="text-gray-700">{{ $t('No address') }}</div>
</div> </div>
<div class="shrink-0"> <div class="shrink-0">
<Button <Button
:label="billingDetailsSummary ? '编辑' : '添加账单地址'" :label="billingDetailsSummary ? $t('Edit') : $t('Add Billing Address')"
@click=" @click="
() => { () => {
showMessage = false; showMessage = false;
@ -73,7 +73,7 @@ import {
confirmDialog, confirmDialog,
renderDialog, renderDialog,
} from '../../utils/components'; } from '../../utils/components';
import { computed, ref, inject, h, defineAsyncComponent, onMounted, nextTick } from 'vue'; import { computed, ref, inject, h, defineAsyncComponent, onMounted, nextTick, getCurrentInstance } from 'vue';
import router from '../../router'; import router from '../../router';
// //
@ -83,6 +83,9 @@ const countryToZh = {
// //
}; };
const instance = getCurrentInstance();
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
const team = inject('team'); const team = inject('team');
const { const {
availableCredits, availableCredits,
@ -146,31 +149,31 @@ const billingDetailsSummary = computed(() => {
const paymentModeOptions = [ const paymentModeOptions = [
{ {
label: '余额支付', label: $t('Prepaid Credits'),
value: 'Prepaid Credits', value: 'Prepaid Credits',
description: '您的每月账单费用将从账户余额中扣除', description: $t('Your monthly billing charges will be deducted from your account balance'),
component: () => component: () =>
h(DropdownItem, { h(DropdownItem, {
label: '余额支付', label: $t('Prepaid Credits'),
active: team.pg.payment_mode === 'Prepaid Credits', active: team.pg.payment_mode === 'Prepaid Credits',
onClick: () => updatePaymentMode('Prepaid Credits'), onClick: () => updatePaymentMode('Prepaid Credits'),
}), }),
}, },
{ {
label: '由合作伙伴支付', label: $t('Paid By Partner'),
value: 'Paid By Partner', value: 'Paid By Partner',
condition: () => team.pg.partner_email, condition: () => team.pg.partner_email,
description: '您的合作伙伴将为您支付每月订阅费用', description: $t('Your partner will pay your monthly subscription fees'),
component: () => component: () =>
h(DropdownItem, { h(DropdownItem, {
label: '由合作伙伴支付', label: $t('Paid By Partner'),
active: team.pg.payment_mode === 'Paid by Partner', active: team.pg.payment_mode === 'Paid by Partner',
onClick: () => onClick: () =>
confirmDialog({ confirmDialog({
title: '确认支付方式', title: $t('Confirm Payment Method'),
message: `通过将支付方式更改为<strong>由合作伙伴支付</strong>,以下详细信息将与您的合作伙伴共享:<br><br><li>站点/服务器名称</li> <li>计划名称</li><li>站点/服务器激活天数</li><br>您确定要继续吗?`, message: $t('By changing the payment method to <strong>Paid By Partner</strong>, the following details will be shared with your partner:<br><br><li>Site/Server Name</li> <li>Plan Name</li><li>Site/Server Active Days</li><br>Are you sure you want to continue?'),
primaryAction: { primaryAction: {
label: '更改支付方式', label: $t('Change Payment Method'),
variant: 'solid', variant: 'solid',
onClick: ({ hide }) => { onClick: ({ hide }) => {
updatePaymentMode('Paid By Partner'); updatePaymentMode('Paid By Partner');

View File

@ -2,7 +2,7 @@
<div class="sticky top-0 z-10 shrink-0"> <div class="sticky top-0 z-10 shrink-0">
<Header> <Header>
<FBreadcrumbs <FBreadcrumbs
:items="[{ label: '账单', route: { name: 'Billing' } }]" :items="[{ label: $t('Billing'), route: { name: 'Billing' } }]"
/> />
</Header> </Header>
<TabsWithRouter <TabsWithRouter
@ -14,7 +14,7 @@
class="mx-auto mt-60 w-fit rounded border border-dashed px-12 py-8 text-center text-gray-600" class="mx-auto mt-60 w-fit rounded border border-dashed px-12 py-8 text-center text-gray-600"
> >
<i-lucide-alert-triangle class="mx-auto mb-4 h-6 w-6 text-red-600" /> <i-lucide-alert-triangle class="mx-auto mb-4 h-6 w-6 text-red-600" />
<ErrorMessage message="您无权查看账单页面" /> <ErrorMessage :message="$t('You do not have permission to view the billing page')" />
</div> </div>
</div> </div>
</template> </template>
@ -35,11 +35,11 @@ export default {
return { return {
currentTab: 0, currentTab: 0,
tabs: [ tabs: [
{ label: '概览', route: { name: 'BillingOverview' } }, { label: this.$t('Overview'), route: { name: 'BillingOverview' } },
{ label: '订单记录', route: { name: 'BillingOrders' } }, { label: this.$t('Order Records'), route: { name: 'BillingOrders' } },
{ label: '余额明细', route: { name: 'BillingBalances' } }, { label: this.$t('Balance Details'), route: { name: 'BillingBalances' } },
{ {
label: '开发者收益', label: this.$t('Developer Earnings'),
route: { name: 'BillingMarketplacePayouts' }, route: { name: 'BillingMarketplacePayouts' },
requireDeveloper: true, requireDeveloper: true,
requirePro: true requirePro: true

View File

@ -8,7 +8,7 @@
@click="exportToCsv" @click="exportToCsv"
:loading="exporting" :loading="exporting"
> >
导出 {{ $t('Export') }}
</Button> </Button>
</template> </template>
</ObjectList> </ObjectList>
@ -38,7 +38,7 @@ export default {
fields: ['type', 'source', 'invoice', 'description'], fields: ['type', 'source', 'invoice', 'description'],
columns: [ columns: [
{ {
label: '时间', label: this.$t('Time'),
fieldname: 'creation', fieldname: 'creation',
format(value) { format(value) {
return new Date(value).toLocaleString('zh-CN', { return new Date(value).toLocaleString('zh-CN', {
@ -52,7 +52,7 @@ export default {
} }
}, },
{ {
label: '描述', label: this.$t('Description'),
fieldname: 'description', fieldname: 'description',
format(value, row) { format(value, row) {
if (value !== null && value !== undefined) { if (value !== null && value !== undefined) {
@ -60,28 +60,28 @@ export default {
} }
if (row.type === 'Applied To Invoice' && row.invoice) { if (row.type === 'Applied To Invoice' && row.invoice) {
return `冲抵发票 ${row.invoice}`; return this.$t('Applied to Invoice {invoice}', { invoice: row.invoice });
} }
if (row.source === 'Prepaid Credits') { if (row.source === 'Prepaid Credits') {
return '余额充值'; return this.$t('Balance Recharge');
} }
if (row.source === 'Free Credits') { if (row.source === 'Free Credits') {
return '赠送余额'; return this.$t('Free Credits');
} }
return row.amount < 0 ? row.type : row.source; return row.amount < 0 ? row.type : row.source;
} }
}, },
{ {
label: '金额', label: this.$t('Amount'),
fieldname: 'amount', fieldname: 'amount',
align: 'right', align: 'right',
format: this.formatCurrency format: this.formatCurrency
}, },
{ {
label: '余额', label: this.$t('Balance'),
fieldname: 'ending_balance', fieldname: 'ending_balance',
align: 'right', align: 'right',
format: this.formatCurrency format: this.formatCurrency
@ -132,10 +132,10 @@ export default {
// CSV // CSV
const fields = [ const fields = [
'时间', this.$t('Time'),
'描述', this.$t('Description'),
'金额', this.$t('Amount'),
'余额' this.$t('Balance')
]; ];
// //
@ -146,11 +146,11 @@ export default {
// 使 // 使
if (!description) { if (!description) {
if (row.type === 'Applied To Invoice' && row.invoice) { if (row.type === 'Applied To Invoice' && row.invoice) {
description = `冲抵发票 ${row.invoice}`; description = this.$t('Applied to Invoice {invoice}', { invoice: row.invoice });
} else if (row.source === 'Prepaid Credits') { } else if (row.source === 'Prepaid Credits') {
description = '余额充值'; description = this.$t('Balance Recharge');
} else if (row.source === 'Free Credits') { } else if (row.source === 'Free Credits') {
description = '赠送余额'; description = this.$t('Free Credits');
} else { } else {
description = row.amount < 0 ? row.type : row.source; description = row.amount < 0 ? row.type : row.source;
} }
@ -176,7 +176,7 @@ export default {
// //
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' }); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
const today = new Date().toISOString().split('T')[0]; const today = new Date().toISOString().split('T')[0];
const filename = `余额记录-${today}.csv`; const filename = `${this.$t('Balance Records')}-${today}.csv`;
const link = document.createElement('a'); const link = document.createElement('a');
link.href = URL.createObjectURL(blob); link.href = URL.createObjectURL(blob);
link.download = filename; link.download = filename;
@ -186,7 +186,7 @@ export default {
this.exporting = false; this.exporting = false;
}, },
handleExportError(error) { handleExportError(error) {
console.error('导出数据失败:', error); console.error(this.$t('Failed to export data'), error);
this.exporting = false; this.exporting = false;
} }
} }

View File

@ -11,7 +11,7 @@
v-if="showPayout.status === 'Empty'" v-if="showPayout.status === 'Empty'"
class="text-base text-gray-600" class="text-base text-gray-600"
> >
无内容显示 {{ $t('No content to display') }}
</div> </div>
<PayoutTable v-else :payoutId="showPayout.name" /> <PayoutTable v-else :payoutId="showPayout.name" />
</template> </template>
@ -51,14 +51,14 @@ export default {
return [ return [
{ {
type: 'select', type: 'select',
label: '状态', label: this.$t('Status'),
class: !this.$isMobile ? 'w-36' : '', class: !this.$isMobile ? 'w-36' : '',
fieldname: 'status', fieldname: 'status',
options: [ options: [
{ label: '', value: '' }, { label: '', value: '' },
{ label: '待结算', value: 'Draft' }, { label: this.$t('Pending Settlement'), value: 'Draft' },
{ label: '已付款', value: 'Paid' }, { label: this.$t('Paid'), value: 'Paid' },
{ label: '已结算', value: 'Commissioned' } { label: this.$t('Settled'), value: 'Commissioned' }
] ]
} }
]; ];
@ -66,7 +66,7 @@ export default {
orderBy: 'creation desc', orderBy: 'creation desc',
columns: [ columns: [
{ {
label: '日期', label: this.$t('Date'),
fieldname: 'period_end', fieldname: 'period_end',
format(value) { format(value) {
return Intl.DateTimeFormat('en-US', { return Intl.DateTimeFormat('en-US', {
@ -76,10 +76,10 @@ export default {
}).format(new Date(value)); }).format(new Date(value));
} }
}, },
{ label: '支付方式', fieldname: 'mode_of_payment' }, { label: this.$t('Payment Method'), fieldname: 'mode_of_payment' },
{ label: '状态', fieldname: 'status', type: 'Badge' }, { label: this.$t('Status'), fieldname: 'status', type: 'Badge' },
{ {
label: '总计', label: this.$t('Total'),
fieldname: 'net_total_cny', fieldname: 'net_total_cny',
align: 'right', align: 'right',
format: (_, row) => { format: (_, row) => {

View File

@ -4,7 +4,7 @@
<div> <div>
<FInput <FInput
v-model="filters.search" v-model="filters.search"
placeholder="搜索订单..." :placeholder="$t('Search orders...')"
type="text" type="text"
class="w-60" class="w-60"
@input="debouncedSearch" @input="debouncedSearch"
@ -20,7 +20,7 @@
appearance="primary" appearance="primary"
@click="exportToCsv" @click="exportToCsv"
> >
导出 {{ $t('Export') }}
</Button> </Button>
<Button <Button
icon="refresh-cw" icon="refresh-cw"
@ -34,8 +34,8 @@
<template v-if="orders.length === 0"> <template v-if="orders.length === 0">
<div class="flex h-60 flex-col items-center justify-center space-y-2 p-4 text-center"> <div class="flex h-60 flex-col items-center justify-center space-y-2 p-4 text-center">
<i-lucide-file-text class="h-8 w-8 text-gray-400" /> <i-lucide-file-text class="h-8 w-8 text-gray-400" />
<p class="text-base font-medium">无订单记录</p> <p class="text-base font-medium">{{ $t('No Order Records') }}</p>
<p class="text-sm text-gray-600">暂无订单记录可以显示</p> <p class="text-sm text-gray-600">{{ $t('No order records to display') }}</p>
</div> </div>
</template> </template>
@ -78,7 +78,7 @@
<div class="flex items-center justify-between p-4"> <div class="flex items-center justify-between p-4">
<div class="text-sm text-gray-600"> <div class="text-sm text-gray-600">
显示 {{ orders.length }} 条订单 {{ totalCount }} {{ $t('Showing {count} orders, total {total}', { count: orders.length, total: totalCount }) }}
</div> </div>
<Button <Button
v-if="hasMoreToLoad" v-if="hasMoreToLoad"
@ -86,7 +86,7 @@
:loading="loadingMore" :loading="loadingMore"
appearance="primary" appearance="primary"
> >
加载更多 {{ $t('Load More') }}
</Button> </Button>
</div> </div>
</template> </template>
@ -95,7 +95,7 @@
</template> </template>
<script> <script>
import { ref, reactive, computed, onMounted } from 'vue'; import { ref, reactive, computed, onMounted, getCurrentInstance } from 'vue';
import { Button, Card, Input as FInput, createResource } from 'jingrow-ui'; import { Button, Card, Input as FInput, createResource } from 'jingrow-ui';
import StatusIndicator from '../components/StatusIndicator.vue'; import StatusIndicator from '../components/StatusIndicator.vue';
import { unparse } from 'papaparse'; import { unparse } from 'papaparse';
@ -109,6 +109,8 @@ export default {
StatusIndicator, StatusIndicator,
}, },
setup() { setup() {
const instance = getCurrentInstance();
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
const pageSize = 20; const pageSize = 20;
const orders = ref([]); const orders = ref([]);
const totalCount = ref(0); const totalCount = ref(0);
@ -122,15 +124,15 @@ export default {
// //
const columns = [ const columns = [
{ key: 'creation', label: '时间', class: '' }, { key: 'creation', label: $t('Time'), class: '' },
{ key: 'title', label: '标题', class: '' }, { key: 'title', label: $t('Title'), class: '' },
{ key: 'order_id', label: '订单号', class: '' }, { key: 'order_id', label: $t('Order ID'), class: '' },
{ key: 'trade_no', label: '交易号', class: '' }, { key: 'trade_no', label: $t('Transaction ID'), class: '' },
{ key: 'order_type', label: '订单类型', class: '' }, { key: 'order_type', label: $t('Order Type'), class: '' },
{ key: 'payment_method', label: '支付方式', class: '' }, { key: 'payment_method', label: $t('Payment Method'), class: '' },
{ key: 'description', label: '描述', class: '' }, { key: 'description', label: $t('Description'), class: '' },
{ key: 'total', label: '金额', class: 'text-right' }, { key: 'total', label: $t('Amount'), class: 'text-right' },
{ key: 'status', label: '状态', class: '' } { key: 'status', label: $t('Status'), class: '' }
]; ];
// //
@ -177,15 +179,15 @@ export default {
// CSV // CSV
const fields = [ const fields = [
'标题', $t('Title'),
'订单ID', $t('Order ID'),
'交易号', $t('Transaction ID'),
'订单类型', $t('Order Type'),
'支付方式', $t('Payment Method'),
'描述', $t('Description'),
'金额', $t('Amount'),
'状态', $t('Status'),
'创建时间' $t('Creation Time')
]; ];
// //
@ -213,7 +215,7 @@ export default {
// //
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' }); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
const today = new Date().toISOString().split('T')[0]; const today = new Date().toISOString().split('T')[0];
const filename = `订单记录-${today}.csv`; const filename = `${$t('Order Records')}-${today}.csv`;
const link = document.createElement('a'); const link = document.createElement('a');
link.href = URL.createObjectURL(blob); link.href = URL.createObjectURL(blob);
link.download = filename; link.download = filename;
@ -302,14 +304,14 @@ export default {
// //
function getStatusProps(status) { function getStatusProps(status) {
const statusMap = { const statusMap = {
'待支付': { label: '待支付', color: 'orange' }, '待支付': { label: $t('Pending Payment'), color: 'orange' },
'已支付': { label: '已支付', color: 'green' }, '已支付': { label: $t('Paid'), color: 'green' },
'交易成功': { label: '交易成功', color: 'green' }, '交易成功': { label: $t('Transaction Successful'), color: 'green' },
'已取消': { label: '已取消', color: 'red' }, '已取消': { label: $t('Cancelled'), color: 'red' },
'已退款': { label: '已退款', color: 'red' }, '已退款': { label: $t('Refunded'), color: 'red' },
}; };
return statusMap[status] || { label: status, color: 'blue' }; return statusMap[status] || { label: status || '', color: 'blue' };
} }
// //

View File

@ -28,14 +28,14 @@ export default {
'brand', 'brand',
'stripe_mandate_id' 'stripe_mandate_id'
], ],
emptyStateMessage: '未添加任何卡片', emptyStateMessage: this.$t('No cards added'),
columns: [ columns: [
{ {
label: '卡片姓名', label: this.$t('Card Name'),
fieldname: 'name_on_card' fieldname: 'name_on_card'
}, },
{ {
label: '卡片', label: this.$t('Card'),
fieldname: 'last_4', fieldname: 'last_4',
width: 1.5, width: 1.5,
format(value) { format(value) {
@ -51,20 +51,20 @@ export default {
{ {
theme: 'green' theme: 'green'
}, },
() => '默认' () => this.$t('Default')
); );
} }
} }
}, },
{ {
label: '有效期', label: this.$t('Expiry Date'),
width: 0.5, width: 0.5,
format(value, row) { format(value, row) {
return `${row.expiry_month}/${row.expiry_year}`; return `${row.expiry_month}/${row.expiry_year}`;
} }
}, },
{ {
label: '授权', label: this.$t('Authorization'),
type: 'Component', type: 'Component',
width: 1, width: 1,
align: 'center', align: 'center',
@ -86,7 +86,7 @@ export default {
return h( return h(
Tooltip, Tooltip,
{ {
text: '此卡上次支付失败。请使用其他卡片。' text: this.$t('This card failed to pay last time. Please use another card.')
}, },
() => () =>
h(FeatherIcon, { h(FeatherIcon, {
@ -107,7 +107,7 @@ export default {
rowActions: ({ listResource, row }) => { rowActions: ({ listResource, row }) => {
return [ return [
{ {
label: '设为默认', label: this.$t('Set as Default'),
onClick: () => { onClick: () => {
toast.promise( toast.promise(
listResource.runDocMethod.submit({ listResource.runDocMethod.submit({
@ -115,24 +115,24 @@ export default {
name: row.name name: row.name
}), }),
{ {
loading: '正在设为默认...', loading: this.$t('Setting as default...'),
success: '默认卡片已设置', success: this.$t('Default card set'),
error: '无法设置默认卡片' error: this.$t('Failed to set default card')
} }
); );
}, },
condition: () => !row.is_default condition: () => !row.is_default
}, },
{ {
label: '移除', label: this.$t('Remove'),
onClick: () => { onClick: () => {
if (row.is_default && this.$team.pg.payment_mode === 'Card') { if (row.is_default && this.$team.pg.payment_mode === 'Card') {
toast.error('无法移除默认卡片'); toast.error(this.$t('Cannot remove default card'));
return; return;
} }
confirmDialog({ confirmDialog({
title: '移除卡片', title: this.$t('Remove Card'),
message: '确定要移除此卡片吗?', message: this.$t('Are you sure you want to remove this card?'),
onSuccess: ({ hide }) => { onSuccess: ({ hide }) => {
toast.promise( toast.promise(
listResource.delete.submit(row.name, { listResource.delete.submit(row.name, {
@ -141,12 +141,12 @@ export default {
} }
}), }),
{ {
loading: '正在移除卡片...', loading: this.$t('Removing card...'),
success: '卡片已移除', success: this.$t('Card removed'),
error: error => error: error =>
error.messages?.length error.messages?.length
? error.messages.join('\n') ? error.messages.join('\n')
: error.message || '无法移除卡片' : error.message || this.$t('Failed to remove card')
} }
); );
} }
@ -158,7 +158,7 @@ export default {
orderBy: 'creation desc', orderBy: 'creation desc',
primaryAction() { primaryAction() {
return { return {
label: '添加卡片', label: this.$t('Add Card'),
slots: { slots: {
prefix: icon('plus') prefix: icon('plus')
}, },

View File

@ -302,6 +302,10 @@ Start,开始,
Start Date,开始日期, Start Date,开始日期,
Start Time,开始时间, Start Time,开始时间,
State,, State,,
State/Province/Region,州/省/地区,
City,城市,
Address,地址,
Postal Code,邮政编码,
Status,状态, Status,状态,
Step,, Step,,
Steps,脚步, Steps,脚步,
@ -459,6 +463,7 @@ Site,站点,
Duration,持续时间, Duration,持续时间,
Owner,创建者, Owner,创建者,
Account Recharge,账户充值, Account Recharge,账户充值,
Recharge,充值,
Recharge your account balance to pay for your services.,为您的账户充值余额,用于支付您的服务费用。, Recharge your account balance to pay for your services.,为您的账户充值余额,用于支付您的服务费用。,
Online Recharge,在线充值, Online Recharge,在线充值,
Bank Transfer,对公汇款, Bank Transfer,对公汇款,
@ -752,4 +757,69 @@ DNS Settings,DNS设置,
Other Information,其他信息, Other Information,其他信息,
Renew,续费, Renew,续费,
Rename,重命名, Rename,重命名,
Order Records,订单记录,
Balance Details,余额明细,
Developer Earnings,开发者收益,
You do not have permission to view the billing page,您无权查看账单页面,
Search orders...,搜索订单...,
No Order Records,无订单记录,
No order records to display,暂无订单记录可以显示,
Time,时间,
Title,标题,
Order ID,订单号,
Transaction ID,交易号,
Order Type,订单类型,
Showing {count} orders, total {total},显示 {count} 条订单,共 {total} 条,
Load More,加载更多,
Creation Time,创建时间,
Pending Payment,待支付,
Transaction Successful,交易成功,
Balance Recharge,余额充值,
Free Credits,赠送余额,
Applied to Invoice {invoice},冲抵发票 {invoice},
Balance,余额,
Balance Records,余额记录,
Failed to export data,导出数据失败,
No cards added,未添加任何卡片,
Card Name,卡片姓名,
Card,卡片,
Default,默认,
Expiry Date,有效期,
Authorization,授权,
This card failed to pay last time. Please use another card.,此卡上次支付失败。请使用其他卡片。,
Set as Default,设为默认,
Setting as default...,正在设为默认...,
Default card set,默认卡片已设置,
Failed to set default card,无法设置默认卡片,
Cannot remove default card,无法移除默认卡片,
Remove Card,移除卡片,
Are you sure you want to remove this card?,确定要移除此卡片吗?,
Removing card...,正在移除卡片...,
Card removed,卡片已移除,
Failed to remove card,无法移除卡片,
Add Card,添加卡片,
Pending Settlement,待结算,
Settled,已结算,
Account Balance,账户余额,
Billing Address,账单地址,
No address,无地址,
Add Billing Address,添加账单地址,
Edit,编辑,
Billing Name,账单名称,
Update Billing Information,更新账单信息,
Billing name is required,账单名称为必填项,
Billing name contains invalid characters,账单名称包含无效字符,
Your monthly billing charges will be deducted from your account balance,您的每月账单费用将从账户余额中扣除,
Your partner will pay your monthly subscription fees,您的合作伙伴将为您支付每月订阅费用,
Confirm Payment Method,确认支付方式,
By changing the payment method to <strong>Paid By Partner</strong>, the following details will be shared with your partner:<br><br><li>Site/Server Name</li> <li>Plan Name</li><li>Site/Server Active Days</li><br>Are you sure you want to continue?,通过将支付方式更改为<strong>由合作伙伴支付</strong>,以下详细信息将与您的合作伙伴共享:<br><br><li>站点/服务器名称</li> <li>计划名称</li><li>站点/服务器激活天数</li><br>您确定要继续吗?,
Change Payment Method,更改支付方式,
Please recharge your account balance before changing payment method.,在更改支付方式之前,请先为您的账户充值余额。,
Pay with Alipay,使用支付宝支付,
Billing Details,账单详情,
Please add billing details to your account before continuing.,在继续之前,请为您的账户添加账单详情。,
Country and City,国家和城市,
State and Postal Code,州和邮政编码,
{field} is required,{field} 是必填项,
Billing information updated,账单信息已更新,

Can't render this file because it has a wrong number of fields in line 390.