李金
7 months ago
15 changed files with 396 additions and 39 deletions
-
29src/App.tsx
-
1src/components/error-boundary/index.tsx
-
9src/components/error/403.tsx
-
8src/components/error/404.tsx
-
5src/components/error/error.tsx
-
152src/components/select-lang/index.tsx
-
26src/context.ts
-
1src/hooks/useTranslation.ts
-
52src/i18n.ts
-
11src/layout/RootLayout.tsx
-
2src/locales/index.ts
-
39src/locales/lang/en-US.ts
-
38src/locales/lang/zh-CN.ts
-
3src/pages/login/index.tsx
-
59src/store/system.ts
@ -0,0 +1,152 @@ |
|||
import { useAppContext } from '@/context' |
|||
import { Dropdown, DropDownProps } from 'antd' |
|||
import React, { memo, useState } from 'react' |
|||
|
|||
|
|||
interface LocalData { |
|||
key?: string |
|||
lang: string, |
|||
label?: string, |
|||
icon?: string, |
|||
title?: string, |
|||
} |
|||
|
|||
interface ClickParam { |
|||
key: string |
|||
} |
|||
|
|||
interface SelectLangProps { |
|||
globalIconClassName?: string; |
|||
postLocalesData?: (locales: LocalData[]) => LocalData[]; |
|||
onItemClick?: (params: ClickParam) => void; |
|||
className?: string; |
|||
reload?: boolean; |
|||
icon?: React.ReactNode; |
|||
style?: React.CSSProperties; |
|||
} |
|||
|
|||
const defaultLangUConfigMap = { |
|||
'en-US': { |
|||
lang: 'en-US', |
|||
label: 'English', |
|||
icon: '🇺🇸', |
|||
title: 'Language' |
|||
}, |
|||
'zh-CN': { |
|||
lang: 'zh-CN', |
|||
label: '简体中文', |
|||
icon: '🇨🇳', |
|||
title: '语言' |
|||
}, |
|||
} |
|||
|
|||
export interface HeaderDropdownProps extends DropDownProps { |
|||
overlayClassName?: string; |
|||
placement?: |
|||
| 'bottomLeft' |
|||
| 'bottomRight' |
|||
| 'topLeft' |
|||
| 'topCenter' |
|||
| 'topRight' |
|||
| 'bottomCenter'; |
|||
} |
|||
|
|||
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ |
|||
overlayClassName: cls, |
|||
...restProps |
|||
}) => ( |
|||
<Dropdown |
|||
overlayClassName={cls} |
|||
{...restProps} |
|||
/> |
|||
) |
|||
|
|||
export const SelectLang = memo((props: SelectLangProps) => { |
|||
|
|||
const ctx = useAppContext() |
|||
|
|||
const { |
|||
globalIconClassName, |
|||
postLocalesData, |
|||
onItemClick, |
|||
icon, |
|||
style, |
|||
reload, |
|||
...restProps |
|||
} = props |
|||
const [ selectedLang, setSelectedLang ] = useState(() => ctx.appData.language) |
|||
|
|||
const changeLang = ({ key }: ClickParam): void => { |
|||
ctx.changeLanguage(key, reload) |
|||
setSelectedLang(key) |
|||
} |
|||
|
|||
const defaultLangUConfig = Object.values(defaultLangUConfigMap) |
|||
|
|||
const allLangUIConfig = |
|||
postLocalesData?.(defaultLangUConfig) || defaultLangUConfig |
|||
const handleClick = onItemClick |
|||
? (params: ClickParam) => onItemClick(params) |
|||
: changeLang |
|||
|
|||
const menuItemStyle = { minWidth: '160px' } |
|||
const menuItemIconStyle = { marginRight: '8px' } |
|||
|
|||
const langMenu = { |
|||
selectedKeys: [ selectedLang ], |
|||
onClick: handleClick, |
|||
items: allLangUIConfig.map((localeObj) => ({ |
|||
key: localeObj.lang || localeObj.key, |
|||
style: menuItemStyle, |
|||
label: ( |
|||
<> |
|||
<span role="img" aria-label={localeObj?.label || 'zh-CN'} style={menuItemIconStyle}> |
|||
{localeObj?.icon || '🌐'} |
|||
</span> |
|||
{localeObj?.label || 'zh-CN'} |
|||
</> |
|||
), |
|||
})), |
|||
} |
|||
|
|||
const dropdownProps = { menu: langMenu } |
|||
|
|||
const inlineStyle = { |
|||
cursor: 'pointer', |
|||
padding: '12px', |
|||
display: 'inline-flex', |
|||
alignItems: 'center', |
|||
justifyContent: 'center', |
|||
fontSize: 18, |
|||
verticalAlign: 'middle', |
|||
...style, |
|||
} |
|||
|
|||
return ( |
|||
<HeaderDropdown {...dropdownProps} placement="bottomRight" {...restProps}> |
|||
<span className={globalIconClassName} style={inlineStyle}> |
|||
<i className="anticon" title={allLangUIConfig[selectedLang]?.title}> |
|||
{icon ? |
|||
icon : ( |
|||
<svg |
|||
viewBox="0 0 24 24" |
|||
focusable="false" |
|||
width="1em" |
|||
height="1em" |
|||
fill="currentColor" |
|||
aria-hidden="true" |
|||
> |
|||
<path d="M0 0h24v24H0z" fill="none"/> |
|||
<path |
|||
d="M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z " |
|||
className="css-c4d79v" |
|||
/> |
|||
</svg> |
|||
)} |
|||
</i> |
|||
</span> |
|||
</HeaderDropdown> |
|||
) |
|||
}) |
|||
|
|||
export default SelectLang |
@ -0,0 +1,26 @@ |
|||
import { IAppData } from '@/types' |
|||
import React, { createContext, ProviderProps, useContext } from 'react' |
|||
import { t } from 'i18next' |
|||
|
|||
export interface IAppContextValue { |
|||
get appData(): IAppData |
|||
|
|||
changeLanguage: (lang: string, reload?: boolean) => void |
|||
|
|||
t: typeof t |
|||
} |
|||
|
|||
export const AppContext = createContext<IAppContextValue>({} as unknown as any) |
|||
|
|||
export const AppContextProvider = ({ value, children }: ProviderProps<Partial<IAppContextValue>>) => { |
|||
return React.createElement(AppContext.Provider, { |
|||
value: { |
|||
...value, |
|||
t, |
|||
} as any |
|||
}, children) |
|||
} |
|||
|
|||
export const useAppContext = () => { |
|||
return useContext<IAppContextValue>(AppContext) |
|||
} |
@ -0,0 +1 @@ |
|||
export * from 'react-i18next' |
@ -0,0 +1,52 @@ |
|||
import { changeLanguage } from '@/store/system.ts' |
|||
import i18n, { InitOptions } from 'i18next' |
|||
import LanguageDetector from 'i18next-browser-languagedetector' |
|||
import { initReactI18next, useTranslation } from 'react-i18next' |
|||
import { zh, en } from './locales' |
|||
|
|||
const detectionOptions = { |
|||
// 探测器的选项
|
|||
order: [ 'querystring', 'cookie', 'localStorage', 'navigator', 'htmlTag' ], |
|||
lookupQuerystring: 'lng', |
|||
lookupCookie: 'i18next', |
|||
lookupLocalStorage: 'i18nextLng', |
|||
caches: [ 'localStorage', 'cookie' ], |
|||
excludeCacheFor: [ 'cimode' ], // 语言探测模式中排除缓存的语言
|
|||
} |
|||
|
|||
|
|||
export const initI18n = (options?: InitOptions) => { |
|||
|
|||
i18n.on('initialized', () => { |
|||
const currentLanguage = i18n.language |
|||
changeLanguage(currentLanguage) |
|||
}) |
|||
|
|||
return i18n |
|||
.use(initReactI18next) |
|||
.use(LanguageDetector) |
|||
.init({ |
|||
resources: { |
|||
en: { |
|||
translation: en.default, |
|||
}, |
|||
zh: { |
|||
translation: zh.default, |
|||
}, |
|||
}, |
|||
fallbackLng: 'zh', |
|||
debug: true, |
|||
detection: detectionOptions, |
|||
interpolation: { |
|||
escapeValue: false, |
|||
}, |
|||
...options, |
|||
}) |
|||
|
|||
|
|||
} |
|||
|
|||
export { |
|||
useTranslation |
|||
} |
|||
export default i18n |
@ -0,0 +1,2 @@ |
|||
export * as zh from './lang/zh-CN.ts' |
|||
export * as en from './lang/en-US.ts' |
@ -0,0 +1,39 @@ |
|||
import antdEN from 'antd/locale/en_US' |
|||
|
|||
export default { |
|||
...antdEN, |
|||
|
|||
error: { |
|||
'404': { |
|||
title: 'not fund', |
|||
message: 'Sorry, not found this page.' |
|||
}, |
|||
'403': { |
|||
title: 'not authorized', |
|||
message: 'Sorry, you are not authorized to access this page.' |
|||
}, |
|||
'error': { |
|||
title: 'error info', |
|||
}, |
|||
}, |
|||
route: { |
|||
goBack: 'Go Back', |
|||
}, |
|||
app: { |
|||
header: { |
|||
logout: 'logout', |
|||
} |
|||
}, |
|||
home: { |
|||
welcome: 'Welcome to' |
|||
}, |
|||
tabs: { |
|||
refresh: 'Refresh', |
|||
maximize: 'Maximize', |
|||
closeCurrent: 'Close current', |
|||
closeLeft: 'Close Left', |
|||
closeRight: 'Close Right', |
|||
closeOther: 'Close other', |
|||
closeAll: 'Close All' |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
import antdZh from 'antd/locale/zh_CN' |
|||
|
|||
export default { |
|||
...antdZh, |
|||
error: { |
|||
'404': { |
|||
title: '无法找到', |
|||
message: '找不到此页面' |
|||
}, |
|||
'403': { |
|||
title: '没有权限', |
|||
message: '对不起,您没有权限查看此页面。' |
|||
}, |
|||
'error': { |
|||
title: '错误信息', |
|||
}, |
|||
}, |
|||
route: { |
|||
goBack: '返回', |
|||
}, |
|||
app: { |
|||
header: { |
|||
logout: '退出登录', |
|||
} |
|||
}, |
|||
home: { |
|||
welcome: '欢迎使用' |
|||
}, |
|||
tabs: { |
|||
refresh: '刷新', |
|||
maximize: '最大化', |
|||
closeCurrent: '关闭当前', |
|||
closeLeft: '关闭左侧', |
|||
closeRight: '关闭右侧', |
|||
closeOther: '关闭其它', |
|||
closeAll: '关闭所有' |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue