添加多语言请求接口参数
This commit is contained in:
parent
e022bc8036
commit
381e1f16d1
@ -22,6 +22,11 @@ npm run build
|
|||||||
|
|
||||||
2、单独域名部署,设置环境变量ONLY_LIVE=yes,使用正常编译即可
|
2、单独域名部署,设置环境变量ONLY_LIVE=yes,使用正常编译即可
|
||||||
|
|
||||||
|
#### 多语言部署
|
||||||
|
应用默认使用简体中文进行编译;如果需要指定其他语言需要设置环境变量:`APP_LANGUAGE`;
|
||||||
|
|
||||||
|
`APP_LANGUAGE`目前支持的值有:`zh-CN`(中文)、`en-US`(英语)。
|
||||||
|
|
||||||
**使用docker**
|
**使用docker**
|
||||||
|
|
||||||
[x] TODO
|
[x] TODO
|
||||||
|
@ -56,7 +56,7 @@ export default function ButtonBatch(
|
|||||||
if(confirmMessage){
|
if(confirmMessage){
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
wrapClassName: 'root-modal-confirm',
|
wrapClassName: 'root-modal-confirm',
|
||||||
title: title || t('confirm.title'),
|
title: <span dangerouslySetInnerHTML={{__html:title || t('confirm.title')}}></span>,
|
||||||
centered: true,
|
centered: true,
|
||||||
icon: <span className="anticon anticon-exclamation-circle"><IconWarningCircle/></span>,
|
icon: <span className="anticon anticon-exclamation-circle"><IconWarningCircle/></span>,
|
||||||
content: confirmMessage,
|
content: confirmMessage,
|
||||||
|
@ -9,6 +9,6 @@ export const DocumentTitle: React.FC<DocumentTitleProps> = ({children, title}) =
|
|||||||
if (title || children) {
|
if (title || children) {
|
||||||
document.title = title || children || '';
|
document.title = title || children || '';
|
||||||
}
|
}
|
||||||
}, []);
|
}, [title,children]);
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
13
src/hooks/useGlobalConfig.ts
Normal file
13
src/hooks/useGlobalConfig.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const globalConfig:{
|
||||||
|
i18n?:I18n
|
||||||
|
} = {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function useGlobalConfig(){
|
||||||
|
return {
|
||||||
|
globalConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useGlobalConfig;
|
@ -6,7 +6,6 @@ import LangCN from './translations/zh-CN.json';
|
|||||||
console.log('AppConfig',AppMode)
|
console.log('AppConfig',AppMode)
|
||||||
i18next.use(initReactI18next).init({
|
i18next.use(initReactI18next).init({
|
||||||
debug: true,
|
debug: true,
|
||||||
lng:'en-US',
|
|
||||||
fallbackLng: 'en-US',
|
fallbackLng: 'en-US',
|
||||||
resources: {
|
resources: {
|
||||||
'en-US': {translation:LangEN},
|
'en-US': {translation:LangEN},
|
||||||
|
@ -128,7 +128,7 @@
|
|||||||
},
|
},
|
||||||
"video": {
|
"video": {
|
||||||
"delete_confirm_title": "Are you sure you want to delete this video?",
|
"delete_confirm_title": "Are you sure you want to delete this video?",
|
||||||
"delete_confirm": "These videos will be deleted.<br.>They can be recovered from the “news” page. ",
|
"delete_confirm": "These videos will be deleted.<br/> They can be recovered from the “news” page. ",
|
||||||
"delete_description": "Are you sure you want to delete these {{count}} videos?",
|
"delete_description": "Are you sure you want to delete these {{count}} videos?",
|
||||||
"delete_empty": "Select the video you want to delete",
|
"delete_empty": "Select the video you want to delete",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
|
@ -49,7 +49,7 @@ export default function SearchForm({onSearch}: Props) {
|
|||||||
placeholder={t("history.search_key")}
|
placeholder={t("history.search_key")}
|
||||||
/>
|
/>
|
||||||
<TimeSelect
|
<TimeSelect
|
||||||
className="w-[120px] ml-1"
|
className="w-[140px] ml-1"
|
||||||
value={state.time_flag}
|
value={state.time_flag}
|
||||||
onChange={handleTimeFilter}
|
onChange={handleTimeFilter}
|
||||||
/>
|
/>
|
||||||
|
@ -106,17 +106,17 @@ export default function LibraryIndex() {
|
|||||||
<div className="live-control flex justify-between mb-2">
|
<div className="live-control flex justify-between mb-2">
|
||||||
<div className="pl-[70px]"></div>
|
<div className="pl-[70px]"></div>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Space className="text-gray-400">
|
<Space className="text-gray-400" size={20}>
|
||||||
<span>{t('select.total',{count:data?.list.length || 0})}</span>
|
<span>{t('select.total',{count:data?.list?.length || 0})}</span>
|
||||||
<span>{t('select.pushed',{count:state.pushedCount})}</span>
|
<span>{t('select.pushed',{count:state.pushedCount})}</span>
|
||||||
<span className={'text-blue-500'}>{t('select.selected_some',{count:checkedIdArray.length})}</span>
|
<span className={'text-blue-500'}>{t('select.selected_some',{count:checkedIdArray.length})}</span>
|
||||||
</Space>
|
</Space>
|
||||||
<button className="hover:text-blue-300 text-gray-400 ml-2"
|
<button className="hover:text-blue-300 text-gray-400 ml-4"
|
||||||
onClick={() => handleAllCheckedChange(checkedIdArray.length != data?.list.length)}>
|
onClick={() => handleAllCheckedChange(checkedIdArray.length != data?.list.length)}>
|
||||||
<span className="text-sm mr-2">{t("select.select_all")}</span>
|
<span className="text-sm mr-2">{t("select.select_all")}</span>
|
||||||
{/*<CheckCircleFilled className={clsx({'text-blue-500': state.checkedAll})}/>*/}
|
{/*<CheckCircleFilled className={clsx({'text-blue-500': state.checkedAll})}/>*/}
|
||||||
</button>
|
</button>
|
||||||
<Checkbox checked={checkedIdArray.length == data?.list.length}
|
<Checkbox checked={checkedIdArray.length == data?.list?.length}
|
||||||
onChange={e => handleAllCheckedChange(e.target.checked)}/>
|
onChange={e => handleAllCheckedChange(e.target.checked)}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -161,7 +161,7 @@ export default function LibraryIndex() {
|
|||||||
icon={<IconDelete className=""/>}
|
icon={<IconDelete className=""/>}
|
||||||
title={t('video.delete_description',{count:checkedIdArray.length})}
|
title={t('video.delete_description',{count:checkedIdArray.length})}
|
||||||
emptyMessage={t('video.delete_empty')}
|
emptyMessage={t('video.delete_empty')}
|
||||||
confirmMessage={t('video.delete_confirm')}
|
confirmMessage={<span dangerouslySetInnerHTML={{__html:t('video.delete_confirm')}}></span>}
|
||||||
onProcess={deleteHistories}
|
onProcess={deleteHistories}
|
||||||
>{t('delete_batch')}</ButtonBatch>}
|
>{t('delete_batch')}</ButtonBatch>}
|
||||||
{checkedIdArray?.length > 0 && <ButtonBatch
|
{checkedIdArray?.length > 0 && <ButtonBatch
|
||||||
|
@ -11,6 +11,7 @@ import routes from "@/routes/routes.tsx";
|
|||||||
import {DocumentTitle} from "@/components/document.tsx";
|
import {DocumentTitle} from "@/components/document.tsx";
|
||||||
import useConfig from "@/hooks/useConfig.ts";
|
import useConfig from "@/hooks/useConfig.ts";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
|
import useGlobalConfig from "@/hooks/useGlobalConfig.ts";
|
||||||
|
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
@ -27,6 +28,7 @@ const router = createBrowserRouter([
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const {globalConfig} = useGlobalConfig();
|
||||||
// future={{v7_startTransition: true,v7_relativeSplatPath: true}}
|
// future={{v7_startTransition: true,v7_relativeSplatPath: true}}
|
||||||
const AppRouter = () => {
|
const AppRouter = () => {
|
||||||
const {t,i18n:langConfig} = useTranslation();
|
const {t,i18n:langConfig} = useTranslation();
|
||||||
@ -37,6 +39,7 @@ const AppRouter = () => {
|
|||||||
}else{
|
}else{
|
||||||
dayjs.locale('en')
|
dayjs.locale('en')
|
||||||
}
|
}
|
||||||
|
globalConfig.i18n = i18n
|
||||||
langConfig.changeLanguage(i18n).then(()=>console.log('change lang to ',i18n))
|
langConfig.changeLanguage(i18n).then(()=>console.log('change lang to ',i18n))
|
||||||
}, [i18n])
|
}, [i18n])
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {Outlet, useLocation, useNavigate} from "react-router-dom";
|
import {Outlet, useLocation, useNavigate, useSearchParams} from "react-router-dom";
|
||||||
import {Button, Divider, Dropdown, MenuProps} from "antd";
|
import {Button, Divider, Dropdown, MenuProps} from "antd";
|
||||||
import React, {useEffect} from "react";
|
import React, {useEffect} from "react";
|
||||||
|
|
||||||
@ -78,6 +78,7 @@ const NavigationUserContainer = () => {
|
|||||||
}
|
}
|
||||||
export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
|
export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
|
||||||
const {i18n,onChangeLocalization} = useConfig();
|
const {i18n,onChangeLocalization} = useConfig();
|
||||||
|
const [params] = useSearchParams();
|
||||||
return (<div className={'dashboard-layout min-h-screen'}>
|
return (<div className={'dashboard-layout min-h-screen'}>
|
||||||
<div className="min-h-screen w-full">
|
<div className="min-h-screen w-full">
|
||||||
<div className="app-header">
|
<div className="app-header">
|
||||||
@ -86,6 +87,7 @@ export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
|
|||||||
</div>
|
</div>
|
||||||
<DashboardNavigation/>
|
<DashboardNavigation/>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
{params.get('lang') || AppConfig.APP_LANG == 'multiple' && <div>
|
||||||
{
|
{
|
||||||
i18n == 'zh-CN'?(
|
i18n == 'zh-CN'?(
|
||||||
<Button className="ml-2" onClick={()=>onChangeLocalization('en-US')}>Change To EN</Button>
|
<Button className="ml-2" onClick={()=>onChangeLocalization('en-US')}>Change To EN</Button>
|
||||||
@ -93,6 +95,7 @@ export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
|
|||||||
<Button className="ml-2" onClick={()=>onChangeLocalization('zh-CN')}>显示中文</Button>
|
<Button className="ml-2" onClick={()=>onChangeLocalization('zh-CN')}>显示中文</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
</div>}
|
||||||
<NavigationUserContainer/>
|
<NavigationUserContainer/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -116,7 +119,7 @@ const DashboardLayout: React.FC<{ children?: React.ReactNode }> = ({children}) =
|
|||||||
}
|
}
|
||||||
},[])
|
},[])
|
||||||
return <AuthGuard>
|
return <AuthGuard>
|
||||||
<div className="fixed">first path:{defaultCache.firstLoadPath}</div>
|
<div className="fixed">{defaultCache.firstLoadPath}</div>
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
{children ? children : <Outlet/>}
|
{children ? children : <Outlet/>}
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
@ -2,6 +2,7 @@ import axios from 'axios';
|
|||||||
import {stringify} from 'qs'
|
import {stringify} from 'qs'
|
||||||
import {BizError} from './types';
|
import {BizError} from './types';
|
||||||
import {getAuthToken} from "@/hooks/useAuth.ts";
|
import {getAuthToken} from "@/hooks/useAuth.ts";
|
||||||
|
import useGlobalConfig from '@/hooks/useGlobalConfig';
|
||||||
|
|
||||||
const JSON_FORMAT: string = 'application/json';
|
const JSON_FORMAT: string = 'application/json';
|
||||||
const REQUEST_TIMEOUT = 300000; // 超时时长5min
|
const REQUEST_TIMEOUT = 300000; // 超时时长5min
|
||||||
@ -11,10 +12,19 @@ const Axios = axios.create({
|
|||||||
headers: {'Content-Type': JSON_FORMAT}
|
headers: {'Content-Type': JSON_FORMAT}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const {globalConfig} = useGlobalConfig();
|
||||||
// 请求前拦截
|
// 请求前拦截
|
||||||
Axios.interceptors.request.use(config => {
|
Axios.interceptors.request.use(config => {
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
|
if (globalConfig.i18n){
|
||||||
|
let url = config.url;
|
||||||
|
if(url){
|
||||||
|
url += (url.indexOf('?') == -1?'?':'&') + `lang=${globalConfig.i18n || ''}`
|
||||||
|
config.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
//config.headers['language'] = globalConfig.i18n;
|
||||||
|
}
|
||||||
if (token) {
|
if (token) {
|
||||||
config.headers['Token'] = `${token}`;
|
config.headers['Token'] = `${token}`;
|
||||||
}
|
}
|
||||||
|
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@ -13,6 +13,7 @@ declare const AppConfig: {
|
|||||||
AUTHED_PERSON_DATA_KEY: string;
|
AUTHED_PERSON_DATA_KEY: string;
|
||||||
API_PREFIX: string;
|
API_PREFIX: string;
|
||||||
ONLY_LIVE: string;
|
ONLY_LIVE: string;
|
||||||
|
APP_LANG: string;
|
||||||
};
|
};
|
||||||
declare const AppMode: 'test' | 'production' | 'development';
|
declare const AppMode: 'test' | 'production' | 'development';
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ export default defineConfig(({mode}) => {
|
|||||||
AUTH_TOKEN_KEY: process.env.AUTH_TOKEN_KEY || AUTH_TOKEN_KEY,
|
AUTH_TOKEN_KEY: process.env.AUTH_TOKEN_KEY || AUTH_TOKEN_KEY,
|
||||||
AUTHED_PERSON_DATA_KEY: process.env.AUTHED_PERSON_DATA_KEY || 'digital-person-user-info',
|
AUTHED_PERSON_DATA_KEY: process.env.AUTHED_PERSON_DATA_KEY || 'digital-person-user-info',
|
||||||
ONLY_LIVE: process.env.ONLY_LIVE || 'no',
|
ONLY_LIVE: process.env.ONLY_LIVE || 'no',
|
||||||
|
APP_LANG: process.env.APP_LANGUAGE || 'zh-CN'
|
||||||
}),
|
}),
|
||||||
AppMode: JSON.stringify(mode),
|
AppMode: JSON.stringify(mode),
|
||||||
AppBuildVersion: JSON.stringify(AppPackage.name + '-' + AppPackage.version + '-' + dayjs().format('YYYYMMDDHH_mmss'))
|
AppBuildVersion: JSON.stringify(AppPackage.name + '-' + AppPackage.version + '-' + dayjs().format('YYYYMMDDHH_mmss'))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user