refactor(frontend): modularize i18n architecture

This commit is contained in:
elky
2026-06-30 17:01:39 +08:00
parent bf29129a4b
commit 9f138d09e6
118 changed files with 11940 additions and 5368 deletions
+21 -9
View File
@@ -1,3 +1,5 @@
import { getI18nLocale } from '@/i18n'
const COMPACT_NUMBER_UNITS = [
{ value: 1_000_000_000_000, suffix: 'T' },
{ value: 1_000_000_000, suffix: 'B' },
@@ -145,14 +147,14 @@ export function formatNumber(num: number | undefined | null): string {
if (num === undefined || num === null) {
return '0'
}
return num.toLocaleString('zh-CN')
return num.toLocaleString(getI18nLocale())
}
// Date formatting
export function formatDate(dateString: string | undefined | null): string {
if (!dateString) return '未知'
if (!dateString) return getI18nLocale() === 'en-US' ? 'Unknown' : '未知'
return new Date(dateString).toLocaleDateString('zh-CN', {
return new Date(dateString).toLocaleDateString(getI18nLocale(), {
year: 'numeric',
month: '2-digit',
day: '2-digit',
@@ -177,6 +179,15 @@ export function formatModelPrice(price: number | undefined | null): string {
// Billing type formatting
export function formatBillingType(type: string | undefined | null): string {
if (getI18nLocale() === 'en-US') {
const englishTypeMap: Record<string, string> = {
'pay_as_you_go': 'Pay as you go',
'monthly_quota': 'Monthly quota',
'free_tier': 'Free tier',
}
return englishTypeMap[type || ''] || type || 'Pay as you go'
}
const typeMap: Record<string, string> = {
'pay_as_you_go': '按量付费',
'monthly_quota': '月卡配额',
@@ -198,13 +209,14 @@ export function formatUsageCount(count: number): string {
// Format remaining time from unix timestamp
export function formatRemainingTime(expireAt: number | undefined, currentTime: number): string {
if (!expireAt) return '未知'
const isEnglish = getI18nLocale() === 'en-US'
if (!expireAt) return isEnglish ? 'Unknown' : '未知'
const remaining = expireAt - currentTime
if (remaining <= 0) return '已过期'
if (remaining <= 0) return isEnglish ? 'Expired' : '已过期'
const minutes = Math.floor(remaining / 60)
const seconds = Math.floor(remaining % 60)
return `${minutes}分${seconds}秒`
return isEnglish ? `${minutes}m ${seconds}s` : `${minutes}分${seconds}秒`
}
// Cache hit rate formatting
@@ -215,14 +227,14 @@ export function formatHitRate(rate: number | undefined): string {
// Rate limit formatting (supports "inherit" semantics: null = inherit system default)
export function formatRateLimitInheritable(rateLimit?: number | null): string {
if (rateLimit == null) return '跟随系统'
if (rateLimit === 0) return '不限速'
if (rateLimit == null) return getI18nLocale() === 'en-US' ? 'Use system default' : '跟随系统'
if (rateLimit === 0) return getI18nLocale() === 'en-US' ? 'No limit' : '不限速'
return `${rateLimit}/min`
}
// Rate limit formatting (simple: null/0 both mean unlimited)
export function formatRateLimitSimple(rateLimit?: number | null): string {
if (rateLimit == null || rateLimit === 0) return '不限速'
if (rateLimit == null || rateLimit === 0) return getI18nLocale() === 'en-US' ? 'No limit' : '不限速'
return `${rateLimit}/min`
}
+26 -11
View File
@@ -1,8 +1,15 @@
import { getI18nLocale } from '@/i18n'
import { translateLegacyText } from '@/i18n/messages'
export type PasswordPolicyLevel = 'weak' | 'medium' | 'strong'
export const PASSWORD_MAX_BYTES = 72
const textEncoder = new TextEncoder()
function tr(value: string): string {
return translateLegacyText(value, getI18nLocale())
}
function getPasswordByteLength(password: string): number {
return textEncoder.encode(password).length
}
@@ -39,24 +46,24 @@ export function normalizePasswordPolicyLevel(value: unknown): PasswordPolicyLeve
export function getPasswordPolicyHint(level: unknown): string {
switch (normalizePasswordPolicyLevel(level)) {
case 'medium':
return '至少 8 个字符,且需包含字母和数字'
return tr('至少 8 个字符,且需包含字母和数字')
case 'strong':
return '至少 8 个字符,且需包含大写字母、小写字母、数字和特殊字符'
return tr('至少 8 个字符,且需包含大写字母、小写字母、数字和特殊字符')
case 'weak':
default:
return '至少 6 个字符'
return tr('至少 6 个字符')
}
}
export function getPasswordPolicyPlaceholder(level: unknown): string {
switch (normalizePasswordPolicyLevel(level)) {
case 'medium':
return '至少 8 位,含字母和数字'
return tr('至少 8 位,含字母和数字')
case 'strong':
return '至少 8 位,含大小写字母、数字和特殊字符'
return tr('至少 8 位,含大小写字母、数字和特殊字符')
case 'weak':
default:
return '至少 6 个字符'
return tr('至少 6 个字符')
}
}
@@ -64,7 +71,7 @@ export function getPasswordPolicyPlaceholder(level: unknown): string {
* 返回所有未满足的密码策略条件。
* 空数组 = 密码合规。
*/
export function getPasswordPolicyErrors(password: string, level: unknown): string[] {
function getPasswordPolicyErrorSources(password: string, level: unknown): string[] {
if (!password) return []
const normalized = normalizePasswordPolicyLevel(level)
@@ -96,15 +103,23 @@ export function getPasswordPolicyErrors(password: string, level: unknown): strin
return errors
}
export function getPasswordPolicyErrors(password: string, level: unknown): string[] {
return getPasswordPolicyErrorSources(password, level).map(tr)
}
/**
* 兼容旧接口:返回单条错误字符串,空字符串表示通过。
* 多条未满足条件时用顿号连接。
*/
export function validatePasswordByPolicy(password: string, level: unknown): string {
const errors = getPasswordPolicyErrors(password, level)
const rawErrors = getPasswordPolicyErrorSources(password, level)
const errors = rawErrors.map(tr)
if (errors.length === 0) return ''
if (errors.length === 1 && errors[0].startsWith('长度不能超过')) {
return `密码${ errors[0]}`
if (rawErrors.length === 1 && rawErrors[0].startsWith('长度不能超过')) {
return tr(`密码${rawErrors[0]}`)
}
return `密码需要:${ errors.join('、')}`
if (getI18nLocale() === 'en-US') {
return `Password requires: ${errors.join(', ')}`
}
return `密码需要:${errors.join('、')}`
}
+104
View File
@@ -1,4 +1,5 @@
import { formatCompactNumber } from '@/utils/format'
import { getI18nLocale } from '@/i18n'
export function walletStatusLabel(status: string | null | undefined): string {
const labels: Record<string, string> = {
@@ -7,6 +8,14 @@ export function walletStatusLabel(status: string | null | undefined): string {
closed: '已关闭',
}
if (!status) return '未知'
if (getI18nLocale() === 'en-US') {
const englishLabels: Record<string, string> = {
active: 'Active',
suspended: 'Frozen',
closed: 'Closed',
}
return englishLabels[status] || status
}
return labels[status] || status
}
@@ -34,10 +43,20 @@ export function walletTransactionCategoryLabel(category: string | null | undefin
refund: '退款',
}
if (!category) return '未知'
if (getI18nLocale() === 'en-US') {
const englishLabels: Record<string, string> = {
recharge: 'Top-up',
gift: 'Grant',
adjust: 'Adjustment',
refund: 'Refund',
}
return englishLabels[category] || category
}
return labels[category] || category
}
export function dailyUsageCategoryLabel(isToday = false): string {
if (getI18nLocale() === 'en-US') return isToday ? 'Today usage' : 'Daily usage'
return isToday ? '今日消费' : '每日消费'
}
@@ -61,6 +80,21 @@ export function walletTransactionReasonLabel(reasonCode: string | null | undefin
refund_revert: '退款回补',
}
if (!reasonCode) return '未知'
if (getI18nLocale() === 'en-US') {
const englishLabels: Record<string, string> = {
topup_admin_manual: 'Manual top-up',
topup_gateway: 'Payment top-up',
topup_card_code: 'Card code top-up',
gift_initial: 'Initial grant',
gift_campaign: 'Campaign grant',
gift_expire_reclaim: 'Grant reclaim',
adjust_admin: 'Manual adjustment',
adjust_system: 'System adjustment',
refund_out: 'Refund deduction',
refund_revert: 'Refund reversal',
}
return englishLabels[reasonCode] || reasonCode
}
return labels[reasonCode] || reasonCode
}
@@ -82,6 +116,25 @@ export function paymentMethodLabel(method: string | null | undefined): string {
offline: '线下转账',
}
if (!method) return '-'
if (getI18nLocale() === 'en-US') {
const englishLabels: Record<string, string> = {
alipay: 'Alipay',
wechat: 'WeChat Pay',
wxpay: 'WeChat Pay',
wechat_pay: 'WeChat Pay',
epay: 'E-Pay',
stripe: 'Stripe',
card: 'Bank card / credit card',
link: 'Stripe Link',
admin_manual: 'Manual top-up',
card_code: 'Top-up code',
gift_code: 'Gift code',
card_recharge: 'Card code top-up',
bank_transfer: 'Bank transfer',
offline: 'Offline transfer',
}
return englishLabels[method] || method
}
return labels[method] || method
}
@@ -96,6 +149,18 @@ export function paymentStatusLabel(status: string | null | undefined): string {
refunded: '已退款',
}
if (!status) return '未知'
if (getI18nLocale() === 'en-US') {
const englishLabels: Record<string, string> = {
pending: 'Pending',
paid: 'Paid',
credited: 'Credited',
failed: 'Failed',
expired: 'Expired',
refunding: 'Refunding',
refunded: 'Refunded',
}
return englishLabels[status] || status
}
return labels[status] || status
}
@@ -109,6 +174,17 @@ export function walletLinkTypeLabel(type: string | null | undefined): string {
usage: '用量记录',
}
if (!type) return '-'
if (getI18nLocale() === 'en-US') {
const englishLabels: Record<string, string> = {
payment_order: 'Top-up order',
refund_request: 'Refund request',
admin_action: 'Admin action',
system_task: 'System task',
campaign: 'Campaign batch',
usage: 'Usage record',
}
return englishLabels[type] || 'Other'
}
return labels[type] || '其他'
}
@@ -127,6 +203,13 @@ export function refundModeLabel(mode: string | null | undefined): string {
offline_payout: '线下打款',
}
if (!mode) return '-'
if (getI18nLocale() === 'en-US') {
const englishLabels: Record<string, string> = {
original_channel: 'Original channel',
offline_payout: 'Offline payout',
}
return englishLabels[mode] || mode
}
return labels[mode] || mode
}
@@ -140,6 +223,17 @@ export function refundStatusLabel(status: string | null | undefined): string {
cancelled: '已取消',
}
if (!status) return '未知'
if (getI18nLocale() === 'en-US') {
const englishLabels: Record<string, string> = {
pending_approval: 'Pending approval',
approved: 'Approved',
processing: 'Processing',
succeeded: 'Succeeded',
failed: 'Failed',
cancelled: 'Cancelled',
}
return englishLabels[status] || status
}
return labels[status] || status
}
@@ -160,6 +254,16 @@ export function callbackStatusLabel(status: string | null | undefined): string {
error: '处理失败',
}
if (!status) return '未知'
if (getI18nLocale() === 'en-US') {
const englishLabels: Record<string, string> = {
processed: 'Processed',
duplicate: 'Duplicate callback',
ignored: 'Ignored',
invalid_signature: 'Invalid signature',
error: 'Failed',
}
return englishLabels[status] || status
}
return labels[status] || status
}