Skip to content

Commit 34f5efd

Browse files
author
张星宇
committed
refactor: streamline navigation and localization handling in ProblemPage
- Replace Link components with onClick handlers for navigation to improve performance and reduce unnecessary re-renders. - Introduce localStorage support for locale management in I18nContext, allowing users to persist their language preference across sessions. - Update locale initialization logic to prioritize localStorage values, enhancing user experience by maintaining language settings. This refactor enhances the usability of the ProblemPage and improves the localization experience for users. Change-Id: I5cf67f93f25324b5e5b1d2b48cd8c737b2c18e80 Co-developed-by: Cursor <noreply@cursor.com> Signed-off-by: 张星宇 <neil.zxy@alibaba-inc.com>
1 parent 8292f72 commit 34f5efd

File tree

2 files changed

+104
-84
lines changed

2 files changed

+104
-84
lines changed

pages/problems/[id].tsx

Lines changed: 66 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,23 @@
11
import { useRouter } from 'next/router';
22
import { useState, useEffect, useRef, useCallback } from 'react';
33
import {
4-
Container,
5-
Grid,
64
Title,
75
Paper,
86
Text,
97
Badge,
108
Group,
119
Stack,
12-
Divider,
13-
Breadcrumbs,
14-
Anchor,
1510
Code,
1611
Button,
1712
Collapse,
1813
Loader,
1914
Alert,
2015
Center,
2116
AppShell,
22-
Box,
2317
Tabs,
2418
Select
2519
} from '@mantine/core';
2620
import dynamic from 'next/dynamic';
27-
import Link from 'next/link';
2821
import { useTranslation, useI18n } from '../../src/contexts/I18nContext';
2922
import { useTheme } from '../../src/contexts/ThemeContext';
3023
import { LanguageThemeControls } from '../../src/components/LanguageThemeControls';
@@ -130,23 +123,23 @@ export default function ProblemPage() {
130123
<AppShell.Header>
131124
<Stack gap="xs" h="100%" justify="center" px="md">
132125
<Group justify="space-between" align="flex-start">
133-
<Link href="/" style={{ textDecoration: 'none', color: 'inherit' }}>
134-
<div style={{ cursor: 'pointer' }}>
135-
<Title order={2} mb={4}>{t('homepage.title')}</Title>
136-
<Text size="sm" c="dimmed">{t('homepage.subtitle')}</Text>
137-
</div>
138-
</Link>
126+
<div
127+
style={{ cursor: 'pointer' }}
128+
onClick={() => router.push('/')}
129+
>
130+
<Title order={2} mb={4}>{t('homepage.title')}</Title>
131+
<Text size="sm" c="dimmed">{t('homepage.subtitle')}</Text>
132+
</div>
139133
<Group>
140-
<Link href="/add-problem">
141-
<Badge
142-
size="lg"
143-
variant="outline"
144-
color="blue"
145-
style={{ cursor: 'pointer', padding: '8px 16px' }}
146-
>
147-
+ {t('homepage.addProblem')}
148-
</Badge>
149-
</Link>
134+
<Badge
135+
size="lg"
136+
variant="outline"
137+
color="blue"
138+
style={{ cursor: 'pointer', padding: '8px 16px' }}
139+
onClick={() => router.push('/add-problem')}
140+
>
141+
+ {t('homepage.addProblem')}
142+
</Badge>
150143
<LanguageThemeControls />
151144
</Group>
152145
</Group>
@@ -174,23 +167,32 @@ export default function ProblemPage() {
174167
<AppShell.Header>
175168
<Stack gap="xs" h="100%" justify="center" px="md">
176169
<Group justify="space-between" align="flex-start">
177-
<Link href="/" style={{ textDecoration: 'none', color: 'inherit' }}>
178-
<div style={{ cursor: 'pointer' }}>
179-
<Title order={2} mb={4}>{t('homepage.title')}</Title>
180-
<Text size="sm" c="dimmed">{t('homepage.subtitle')}</Text>
181-
</div>
182-
</Link>
170+
<div
171+
style={{ cursor: 'pointer' }}
172+
onClick={() => router.push('/')}
173+
>
174+
<Title order={2} mb={4}>{t('homepage.title')}</Title>
175+
<Text size="sm" c="dimmed">{t('homepage.subtitle')}</Text>
176+
</div>
183177
<Group>
184-
<Link href="/add-problem">
185-
<Badge
186-
size="lg"
187-
variant="outline"
188-
color="blue"
189-
style={{ cursor: 'pointer', padding: '8px 16px' }}
190-
>
191-
+ {t('homepage.addProblem')}
192-
</Badge>
193-
</Link>
178+
<Badge
179+
size="lg"
180+
variant="outline"
181+
color="gray"
182+
style={{ cursor: 'pointer', padding: '8px 16px' }}
183+
onClick={() => router.push('/')}
184+
>
185+
{t('common.home')}
186+
</Badge>
187+
<Badge
188+
size="lg"
189+
variant="outline"
190+
color="blue"
191+
style={{ cursor: 'pointer', padding: '8px 16px' }}
192+
onClick={() => router.push('/add-problem')}
193+
>
194+
+ {t('homepage.addProblem')}
195+
</Badge>
194196
<LanguageThemeControls />
195197
</Group>
196198
</Group>
@@ -361,19 +363,6 @@ export default function ProblemPage() {
361363
);
362364
};
363365

364-
const breadcrumbItems = [
365-
{ title: t('common.home'), href: '/' },
366-
{ title: problem.title[locale as keyof typeof problem.title] || problem.title.zh, href: '#' }
367-
].map((item, index) => (
368-
item.href === '#' ? (
369-
<Text key={index}>{item.title}</Text>
370-
) : (
371-
<Anchor component={Link} href={item.href} key={index}>
372-
{item.title}
373-
</Anchor>
374-
)
375-
));
376-
377366
return (
378367
<AppShell
379368
header={{ height: 80 }}
@@ -383,33 +372,32 @@ export default function ProblemPage() {
383372
<AppShell.Header>
384373
<Stack gap="xs" h="100%" justify="center" px="md">
385374
<Group justify="space-between" align="flex-start">
386-
<Link href="/" style={{ textDecoration: 'none', color: 'inherit' }}>
387-
<div style={{ cursor: 'pointer' }}>
388-
<Title order={2} mb={4}>{t('homepage.title')}</Title>
389-
<Text size="sm" c="dimmed">{t('homepage.subtitle')}</Text>
390-
</div>
391-
</Link>
375+
<div
376+
style={{ cursor: 'pointer' }}
377+
onClick={() => router.push('/')}
378+
>
379+
<Title order={2} mb={4}>{t('homepage.title')}</Title>
380+
<Text size="sm" c="dimmed">{t('homepage.subtitle')}</Text>
381+
</div>
392382
<Group>
393-
<Link href="/">
394-
<Badge
395-
size="lg"
396-
variant="outline"
397-
color="gray"
398-
style={{ cursor: 'pointer', padding: '8px 16px' }}
399-
>
400-
{t('common.home')}
401-
</Badge>
402-
</Link>
403-
<Link href="/add-problem">
404-
<Badge
405-
size="lg"
406-
variant="outline"
407-
color="blue"
408-
style={{ cursor: 'pointer', padding: '8px 16px' }}
409-
>
410-
+ {t('homepage.addProblem')}
411-
</Badge>
412-
</Link>
383+
<Badge
384+
size="lg"
385+
variant="outline"
386+
color="gray"
387+
style={{ cursor: 'pointer', padding: '8px 16px' }}
388+
onClick={() => router.push('/')}
389+
>
390+
{t('common.home')}
391+
</Badge>
392+
<Badge
393+
size="lg"
394+
variant="outline"
395+
color="blue"
396+
style={{ cursor: 'pointer', padding: '8px 16px' }}
397+
onClick={() => router.push('/add-problem')}
398+
>
399+
+ {t('homepage.addProblem')}
400+
</Badge>
413401
<LanguageThemeControls />
414402
</Group>
415403
</Group>

src/contexts/I18nContext.tsx

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import React, { createContext, useContext, ReactNode } from 'react';
1+
import React, { createContext, useContext, ReactNode, useState, useEffect } from 'react';
22
import { useRouter } from 'next/router';
33
// Dynamically import locale files
44
import en from '../../locales/en.json';
55
import zh from '../../locales/zh.json';
66

7+
// Key for localStorage
8+
const LOCALE_STORAGE_KEY = 'app-locale';
9+
710
type Translations = {
811
common: {
912
language: string;
@@ -199,12 +202,29 @@ const translations: Record<string, Translations> = {
199202

200203
export function I18nProvider({ children }: { children: ReactNode }) {
201204
const router = useRouter();
202-
const { locale = 'zh' } = router;
205+
206+
// Initialize locale from localStorage or router, defaulting to 'zh'
207+
const [locale, setLocale] = useState<string>('zh');
208+
const [mounted, setMounted] = useState(false);
209+
210+
// Load locale from localStorage on mount
211+
useEffect(() => {
212+
setMounted(true);
213+
if (typeof window !== 'undefined') {
214+
const savedLocale = localStorage.getItem(LOCALE_STORAGE_KEY);
215+
if (savedLocale && (savedLocale === 'zh' || savedLocale === 'en')) {
216+
setLocale(savedLocale);
217+
} else if (router.locale) {
218+
setLocale(router.locale);
219+
}
220+
}
221+
}, [router.locale]);
203222

204223
const t = (key: string, params?: Record<string, string | number>): string => {
205224
try {
206225
const keys = key.split('.');
207-
let value: any = translations[locale];
226+
const currentLocale = mounted ? locale : 'zh';
227+
let value: any = translations[currentLocale];
208228

209229
for (const k of keys) {
210230
if (value && typeof value === 'object') {
@@ -227,7 +247,7 @@ export function I18nProvider({ children }: { children: ReactNode }) {
227247
}
228248

229249
// 如果找不到翻译,返回key或者使用中文作为fallback
230-
if (locale !== 'zh') {
250+
if (currentLocale !== 'zh') {
231251
let fallbackValue: any = translations.zh;
232252
for (const k of keys) {
233253
if (fallbackValue && typeof fallbackValue === 'object') {
@@ -249,8 +269,20 @@ export function I18nProvider({ children }: { children: ReactNode }) {
249269
};
250270

251271
const switchLocale = (newLocale: string) => {
252-
const { pathname, asPath, query } = router;
253-
router.push({ pathname, query }, asPath, { locale: newLocale });
272+
// Save to localStorage
273+
if (typeof window !== 'undefined') {
274+
localStorage.setItem(LOCALE_STORAGE_KEY, newLocale);
275+
}
276+
// Update state (this will trigger re-render)
277+
setLocale(newLocale);
278+
279+
// Also try to update router locale for consistency
280+
try {
281+
const { pathname, asPath, query } = router;
282+
router.push({ pathname, query }, asPath, { locale: newLocale, shallow: true });
283+
} catch (e) {
284+
// Ignore router errors, state update will handle the UI
285+
}
254286
};
255287

256288
return (

0 commit comments

Comments
 (0)