 李金
					
					2 years ago
						李金
					
					2 years 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