Skip to content

Commit ab5e50e

Browse files
committed
Add Chinese (Simplified) add i18n support
1 parent f0f89de commit ab5e50e

17 files changed

Lines changed: 608 additions & 104 deletions

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
开源的 OAuth2.0/OIDC 认证服务器,部署在 Cloudflare 上。
44

5+
Demo: [Click Here](https://authmaster.pages.dev/)
6+
57
## 特性
68

79
- ✅ 完整的 OAuth2.0 和 OpenID Connect 支持
@@ -66,6 +68,7 @@ cp .env.example .env
6668
```
6769

6870
默认配置:
71+
6972
```
7073
VITE_API_URL=http://localhost:8787
7174
```
@@ -108,27 +111,32 @@ npx wrangler pages deploy dist --project-name=authmaster
108111
```
109112

110113
**重要**: 部署后需要在 Cloudflare 控制台配置环境变量:
111-
- 后端: 在 `wrangler.toml` 中设置 `FRONTEND_URL`
114+
115+
- 后端: 在 `wrangler.toml` 中设置 `FRONTEND_URL`
112116
- 前端: 在 Cloudflare Pages 设置中添加 `VITE_API_URL部署 Worker API
113-
cd packages/worker-api
114-
npm run deploy
117+
cd packages/worker-api
118+
npm run deploy
115119
116120
# 部署前端到 Cloudflare Pages
121+
117122
cd packages/web-console
118123
npm run build
119124
npm run deploy
125+
120126
```
121127

122128
## 项目结构
123129

124130
```
131+
125132
authmaster/
126133
├── packages/
127134
│ ├── worker-api/ # Cloudflare Workers 后端
128135
│ ├── web-console/ # React 前端控制台
129136
│ └── shared/ # 共享类型和工具
130137
├── docs/ # 文档
131138
└── README.md
139+
132140
```
133141
134142
## 文档
@@ -145,3 +153,4 @@ MIT License - 详见 [LICENSE](./LICENSE) 文件
145153
## 贡献
146154
147155
欢迎提交 Issue 和 Pull Request!
156+
```

package-lock.json

Lines changed: 107 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/web-console/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
},
1212
"dependencies": {
1313
"@authmaster/shared": "file:../shared",
14+
"i18next": "^25.8.4",
15+
"i18next-browser-languagedetector": "^8.2.0",
1416
"react": "^18.2.0",
1517
"react-dom": "^18.2.0",
18+
"react-i18next": "^16.5.4",
1619
"react-router-dom": "^6.20.1"
1720
},
1821
"devDependencies": {

packages/web-console/src/components/ConfirmModal.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { useTranslation } from 'react-i18next';
2+
13
interface ConfirmModalProps {
24
isOpen: boolean;
35
title: string;
@@ -13,12 +15,13 @@ export function ConfirmModal({
1315
isOpen,
1416
title,
1517
message,
16-
confirmText = 'Confirm',
17-
cancelText = 'Cancel',
18+
confirmText,
19+
cancelText,
1820
onConfirm,
1921
onCancel,
2022
variant = 'danger',
2123
}: ConfirmModalProps) {
24+
const { t } = useTranslation();
2225
if (!isOpen) return null;
2326

2427
const variantStyles = {
@@ -46,13 +49,13 @@ export function ConfirmModal({
4649
onClick={onCancel}
4750
className="px-4 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300 transition-colors"
4851
>
49-
{cancelText}
52+
{cancelText || t('common.cancel')}
5053
</button>
5154
<button
5255
onClick={onConfirm}
5356
className={`px-4 py-2 text-white rounded transition-colors ${variantStyles[variant]}`}
5457
>
55-
{confirmText}
58+
{confirmText || t('common.confirm')}
5659
</button>
5760
</div>
5861
</div>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { useTranslation } from 'react-i18next';
2+
3+
export function LanguageSwitcher() {
4+
const { i18n } = useTranslation();
5+
6+
const languages = [
7+
{ code: 'en', name: 'English', flag: '🇺🇸' },
8+
{ code: 'zh', name: '中文', flag: '🇨🇳' },
9+
];
10+
11+
const currentLanguage = languages.find(lang => lang.code === i18n.language) || languages[0];
12+
13+
return (
14+
<div className="relative group">
15+
<button
16+
className="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-gray-100 transition-colors"
17+
type="button"
18+
>
19+
<span className="text-lg">{currentLanguage.flag}</span>
20+
<span className="text-sm font-medium text-gray-700">{currentLanguage.name}</span>
21+
<svg className="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
22+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
23+
</svg>
24+
</button>
25+
26+
<div className="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg border border-gray-200 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all z-50">
27+
{languages.map(lang => (
28+
<button
29+
key={lang.code}
30+
onClick={() => i18n.changeLanguage(lang.code)}
31+
className={`w-full flex items-center gap-3 px-4 py-3 hover:bg-gray-50 transition-colors first:rounded-t-lg last:rounded-b-lg ${
32+
i18n.language === lang.code ? 'bg-blue-50' : ''
33+
}`}
34+
>
35+
<span className="text-lg">{lang.flag}</span>
36+
<span className={`text-sm font-medium ${
37+
i18n.language === lang.code ? 'text-blue-600' : 'text-gray-700'
38+
}`}>
39+
{lang.name}
40+
</span>
41+
{i18n.language === lang.code && (
42+
<svg className="w-4 h-4 text-blue-600 ml-auto" fill="currentColor" viewBox="0 0 20 20">
43+
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
44+
</svg>
45+
)}
46+
</button>
47+
))}
48+
</div>
49+
</div>
50+
);
51+
}

packages/web-console/src/components/Navbar.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { Link } from 'react-router-dom';
22
import { useAuth } from '../contexts/AuthContext';
3+
import { useTranslation } from 'react-i18next';
4+
import { LanguageSwitcher } from './LanguageSwitcher';
35

46
export function Navbar() {
57
const { isAuthenticated, user, logout } = useAuth();
8+
const { t } = useTranslation();
69

710
return (
811
<nav className="bg-blue-600 text-white shadow-lg">
@@ -16,32 +19,33 @@ export function Navbar() {
1619
{isAuthenticated ? (
1720
<>
1821
<Link to="/dashboard" className="hover:text-blue-200">
19-
Dashboard
22+
{t('nav.dashboard')}
2023
</Link>
2124
<Link to="/apps" className="hover:text-blue-200">
22-
Applications
25+
{t('nav.applications')}
2326
</Link>
2427
<span className="text-blue-200">{user?.email}</span>
2528
<button
2629
onClick={logout}
2730
className="bg-blue-700 hover:bg-blue-800 px-4 py-2 rounded"
2831
>
29-
Logout
32+
{t('nav.logout')}
3033
</button>
3134
</>
3235
) : (
3336
<>
3437
<Link to="/login" className="hover:text-blue-200">
35-
Login
38+
{t('nav.login')}
3639
</Link>
3740
<Link
3841
to="/register"
3942
className="bg-blue-700 hover:bg-blue-800 px-4 py-2 rounded"
4043
>
41-
Register
44+
{t('nav.register')}
4245
</Link>
4346
</>
4447
)}
48+
<LanguageSwitcher />
4549
</div>
4650
</div>
4751
</div>

0 commit comments

Comments
 (0)