feat:新增多语言

This commit is contained in:
JiaJun
2026-04-14 17:27:52 +08:00
parent 8fcf0355b3
commit b2c7c8d362
31 changed files with 1952 additions and 378 deletions

34
src/lib/i18n.ts Normal file
View File

@@ -0,0 +1,34 @@
import i18n from 'i18next'
import {initReactI18next} from 'react-i18next'
import en from '@/message/en'
import zh from '@/message/zh'
import {useUserStore} from '@/store/user.ts'
export function normalizeLanguage(language?: string) {
const value = language?.trim().toLowerCase()
if (!value) {
return 'zh'
}
return value.startsWith('zh') ? 'zh' : 'en'
}
const initialLanguage = normalizeLanguage(useUserStore.getState().language)
void i18n
.use(initReactI18next)
.init({
resources: {
zh: {translation: zh},
en: {translation: en},
},
lng: initialLanguage,
fallbackLng: 'zh',
interpolation: {
escapeValue: false,
},
})
export default i18n

View File

@@ -1,7 +1,7 @@
import type {goodsType} from '@/types/business.type.ts'
export const queryKeys = {
authBootstrap: ['auth-bootstrap'] as const,
authBootstrap: (username: string) => ['auth-bootstrap', username] as const,
goodsCatalog: (types?: readonly goodsType[]) => ['goods-catalog', ...(types ?? ['all'])] as const,
assets: (sessionId: string) => ['assets', sessionId] as const,
addressList: (sessionId: string) => ['address-list', sessionId] as const,

View File

@@ -1,6 +1,7 @@
import ky, {HTTPError, type AfterResponseHook, type Input, type KyInstance, type Options} from 'ky'
import {notifyError, resolveToastMessage} from '@/features/notifications'
import i18n from '@/lib/i18n'
import {useUserStore} from '@/store/user.ts'
import type {ValidateTokenData} from '@/types/auth.type.ts'
import {objectToFormData} from './tool'
@@ -44,7 +45,17 @@ const isApiEnvelope = (value: unknown): value is ApiEnvelope =>
'code' in value &&
typeof (value as {code?: unknown}).code === 'number'
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? '/api/'
const resolveApiBaseUrl = () => {
const configuredBaseUrl = import.meta.env.VITE_API_BASE_URL?.trim()
if (configuredBaseUrl) {
return `${configuredBaseUrl.replace(/\/+$/, '')}/api/`
}
return import.meta.env.PROD ? 'https://playx-api.cjdhr.top/api/' : '/api/'
}
const API_BASE_URL = resolveApiBaseUrl()
const REQUEST_TIMEOUT = 10_000
const AUTH_RETRY_HEADER = 'x-auth-retried'
const VERIFY_TOKEN_PATH = '/v1/mall/verifyToken'
@@ -56,12 +67,18 @@ export const setAccessTokenFormatter = (formatter?: TokenFormatter) => {
accessTokenFormatter = formatter ?? ((token) => `Bearer ${token}`)
}
const getRequestLanguage = () => useUserStore.getState().language?.trim() || 'zh'
const authRefreshClient = ky.create({
baseUrl: API_BASE_URL,
timeout: REQUEST_TIMEOUT,
retry: 0,
headers: {
lang: 'zh',
hooks: {
beforeRequest: [
({request}) => {
request.headers.set('lang', getRequestLanguage())
},
],
},
})
@@ -88,7 +105,7 @@ async function refreshAuthInfo() {
refreshAuthInfoPromise = (async () => {
const token = useUserStore.getState().userInfo?.token?.trim()
if (!token) {
throw new RequestError('Unauthorized')
throw new RequestError(i18n.t('errors.unauthorized'))
}
const response = await authRefreshClient.post('v1/mall/verifyToken', {
@@ -100,7 +117,7 @@ async function refreshAuthInfo() {
}>()
if (!response || (response.code !== 1 && response.code !== 200) || !response.data?.session_id) {
throw new RequestError(resolveToastMessage(response, 'Unauthorized'))
throw new RequestError(resolveToastMessage(response, i18n.t('errors.unauthorized')))
}
useUserStore.getState().setAuthInfo(response.data)
@@ -141,12 +158,11 @@ const requestClient = ky.create({
baseUrl: API_BASE_URL,
timeout: REQUEST_TIMEOUT,
retry: 0,
headers: {
lang: 'zh',
},
hooks: {
beforeRequest: [
({request}) => {
request.headers.set('lang', getRequestLanguage())
const token = useUserStore.getState().userInfo?.token
if (!token) {
return
@@ -165,7 +181,7 @@ const requestClient = ky.create({
error.data,
typeof error.data === 'string'
? error.data
: error.response.statusText || 'Request failed',
: error.response.statusText || i18n.t('errors.requestFailed'),
)
notifyError(message)
@@ -177,7 +193,7 @@ const requestClient = ky.create({
})
}
const message = error.message || 'Network request failed'
const message = error.message || i18n.t('errors.networkRequestFailed')
notifyError(message)
return new RequestError(message, {
cause: error,
@@ -219,7 +235,7 @@ async function parseResponse<T, R extends ResponseType>(
const payload = await response.json()
if (isApiEnvelope(payload) && payload.code !== 1 && payload.code !== 200) {
const message = payload.msg?.trim() || 'Request failed'
const message = payload.msg?.trim() || i18n.t('errors.requestFailed')
notifyError(message)
throw new RequestError(message, {
status: response.status,