Skip to content

Commit e3139ae

Browse files
committed
fix: server.js
1 parent 06b171e commit e3139ae

File tree

6 files changed

+100
-27
lines changed

6 files changed

+100
-27
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727
"@next/bundle-analyzer": "^12.3.1",
2828
"classnames": "^2.3.1",
2929
"clsx": "^1.1.1",
30+
"cookie-parser": "^1.4.6",
3031
"copy-to-clipboard": "^3.3.2",
3132
"dayjs": "^1.11.3",
3233
"echarts": "^5.4.2",
3334
"echarts-for-react": "^3.0.2",
3435
"express": "^4.18.1",
3536
"i18next": "^23.11.5",
37+
"js-cookie": "^3.0.5",
3638
"next": "^12.1.6",
3739
"next-i18next": "^15.3.0",
3840
"nprogress": "^0.2.0",

server.js

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,92 @@
11
/* eslint-disable @typescript-eslint/no-var-requires */
22
const express = require('express');
33
const next = require('next');
4+
const cookieParser = require('cookie-parser');
45

56
const app = next({ dev: false });
67
const handle = app.getRequestHandler();
78

89
const PORT = process.env.PORT || 3000;
10+
const DEFAULT_LOCALE = 'zh';
11+
const FALLBACK_LOCALE = 'en';
12+
const SUPPORTED_LOCALES = ['zh', 'en'];
913

1014
async function startServer() {
1115
try {
1216
await app.prepare();
1317
const server = express();
18+
server.use(cookieParser());
1419

15-
// 中间件处理尾部斜杠但不重定向
20+
// 中间件处理语言选择和路由
1621
server.use((req, res, next) => {
17-
if (req.url.length > 1 && req.url.endsWith('/')) {
18-
req.url = req.url.slice(0, -1);
22+
if (req.url.startsWith('/_next') || req.url.startsWith('/static')) {
23+
next();
24+
} else {
25+
let locale = req.cookies.locale;
26+
27+
// 解析 URL 中的语言设置
28+
const urlParts = req.path.split('/');
29+
const urlLocale = SUPPORTED_LOCALES.includes(urlParts[1])
30+
? urlParts[1]
31+
: null;
32+
33+
if (!locale) {
34+
// 如果没有 cookie,使用 URL 中的语言或浏览器偏好
35+
const browserLocale = req.acceptsLanguages(SUPPORTED_LOCALES);
36+
locale = urlLocale || browserLocale || FALLBACK_LOCALE;
37+
38+
// 如果浏览器语言不是中文,默认使用英语
39+
if (browserLocale && browserLocale !== 'zh') {
40+
locale = FALLBACK_LOCALE;
41+
}
42+
}
43+
44+
// 确保 locale 是支持的语言之一
45+
locale = SUPPORTED_LOCALES.includes(locale) ? locale : FALLBACK_LOCALE;
46+
if (urlLocale) {
47+
// URL 中有语言前缀
48+
if (urlLocale !== locale) {
49+
// URL 语言与 cookie 不匹配,更新 locale
50+
locale = urlLocale;
51+
}
52+
if (locale === DEFAULT_LOCALE) {
53+
// 默认语言不应该在 URL 中显示,重定向到无前缀的 URL
54+
const newUrl = req.url.replace(`/${DEFAULT_LOCALE}`, '') || '/';
55+
return res.redirect(307, newUrl);
56+
}
57+
} else if (locale !== DEFAULT_LOCALE) {
58+
// URL 中没有语言前缀,且不是默认语言,添加前缀并重定向
59+
let newUrl = `/${locale}${req.url}`;
60+
if (newUrl.endsWith('/')) {
61+
// 如果以 '/' 结尾,去掉末尾的 '/'
62+
newUrl = newUrl.slice(0, -1);
63+
}
64+
return res.redirect(307, newUrl);
65+
}
66+
67+
// 设置或更新 cookie
68+
res.cookie('locale', locale, {
69+
maxAge: 365 * 24 * 60 * 60 * 1000,
70+
});
71+
72+
req.locale = locale;
73+
74+
// 处理尾部斜杠并重定向
75+
if (req.url.length > 1 && req.url.endsWith('/')) {
76+
const newUrl =
77+
req.url.slice(0, -1) +
78+
(req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '');
79+
return res.redirect(301, newUrl);
80+
}
81+
82+
next();
1983
}
20-
next();
2184
});
2285

2386
// 处理所有其他请求
24-
server.all('*', (req, res) => handle(req, res));
87+
server.all('*', (req, res) => {
88+
return handle(req, res);
89+
});
2590

2691
server.listen(PORT, (err) => {
2792
if (err) throw err;

src/components/buttons/Button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as React from 'react';
1+
import React from 'react';
22
import { ImSpinner2 } from 'react-icons/im';
33

44
import clsxm from '@/lib/clsxm';

src/components/buttons/LanguageSwitcher.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// components/LanguageSwitcher.tsx
22
import { useRouter } from 'next/router';
3-
import { useEffect, useRef, useState } from 'react';
4-
import { MdTranslate } from 'react-icons/md';
3+
import { useRef, useState } from 'react';
4+
// eslint-disable-next-line @typescript-eslint/no-var-requires
5+
const Cookies = require('js-cookie');
56

67
type LanguageSwitchProps = {
78
type?: string;
@@ -11,24 +12,13 @@ const LanguageSwitcher = (props: LanguageSwitchProps) => {
1112
const router = useRouter();
1213
const { locale, asPath } = router;
1314
const [isHovered, setIsHovered] = useState(false);
15+
const [selectedLocale, setSelectedLocale] = useState(locale);
1416
const dropdownRef = useRef<HTMLDivElement>(null);
1517
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
1618

17-
useEffect(() => {
18-
const storedLocale = localStorage.getItem('locale');
19-
if (storedLocale) {
20-
router.push(asPath, asPath, { locale: storedLocale });
21-
} else {
22-
const systemLocale = navigator.language.toLowerCase().startsWith('zh')
23-
? 'zh'
24-
: 'en';
25-
localStorage.setItem('locale', systemLocale);
26-
router.push(asPath, asPath, { locale: systemLocale });
27-
}
28-
}, []);
29-
3019
const changeLanguage = (language: string) => {
31-
localStorage.setItem('locale', language);
20+
Cookies.set('locale', language);
21+
setSelectedLocale(language);
3222
setIsHovered(false);
3323
router.push(asPath, asPath, { locale: language });
3424
};
@@ -71,8 +61,8 @@ const LanguageSwitcher = (props: LanguageSwitchProps) => {
7161
aria-haspopup='true'
7262
aria-expanded={isHovered ? 'true' : 'false'}
7363
>
74-
<MdTranslate size={16} />
75-
{/* {selectedLocale === 'zh' ? '中文' : 'EN'} */}
64+
{/* <MdTranslate size={16} /> */}
65+
{selectedLocale === 'zh' ? '中文' : 'EN'}
7666
</button>
7767
</div>
7868

src/components/layout/Header.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,7 @@ const Header = () => {
8989
</button>
9090
</RepoModal>
9191
<ThemeSwitcher />
92-
<div className='hidden'>
93-
<LanguageSwitcher />
94-
</div>
92+
<LanguageSwitcher />
9593
</div>
9694
</nav>
9795
</div>

yarn.lock

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3052,11 +3052,24 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
30523052
dependencies:
30533053
safe-buffer "~5.1.1"
30543054

3055+
cookie-parser@^1.4.6:
3056+
version "1.4.6"
3057+
resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.6.tgz#3ac3a7d35a7a03bbc7e365073a26074824214594"
3058+
integrity sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==
3059+
dependencies:
3060+
cookie "0.4.1"
3061+
cookie-signature "1.0.6"
3062+
30553063
30563064
version "1.0.6"
30573065
resolved "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz"
30583066
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
30593067

3068+
3069+
version "0.4.1"
3070+
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
3071+
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
3072+
30603073
30613074
version "0.5.0"
30623075
resolved "https://registry.npmmirror.com/cookie/-/cookie-0.5.0.tgz"
@@ -5257,6 +5270,11 @@ jest@^27.5.1:
52575270
import-local "^3.0.2"
52585271
jest-cli "^27.5.1"
52595272

5273+
js-cookie@^3.0.5:
5274+
version "3.0.5"
5275+
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc"
5276+
integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
5277+
52605278
js-sdsl@^4.1.4:
52615279
version "4.1.5"
52625280
resolved "https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.1.5.tgz"

0 commit comments

Comments
 (0)