Configure web display language from profile settings, default is set to

This commit is contained in:
Indigo Tang 2025-06-09 03:36:02 +00:00
parent 261f21af55
commit 2bc4fcc77f
5 changed files with 87 additions and 16 deletions

View File

@ -1,6 +1,6 @@
'use client';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
@ -9,7 +9,8 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Save } from 'lucide-react';
import { useToast } from '@/hooks/use-toast';
import { Textarea } from '@/components/ui/textarea';
import { useI18n } from '@/locales/client';
import { useI18n, useCurrentLocale, useChangeLocale } from '@/locales/client';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
const mockUserProfile = {
name: 'Alice Wonderland',
@ -23,6 +24,8 @@ const mockUserProfile = {
export default function ProfilePage() {
const { toast } = useToast();
const t = useI18n();
const currentLocale = useCurrentLocale();
const changeLocale = useChangeLocale();
const [name, setName] = useState(mockUserProfile.name);
const [email, setEmail] = useState(mockUserProfile.email);
@ -34,38 +37,57 @@ export default function ProfilePage() {
const [newPassword, setNewPassword] = useState('');
const [confirmNewPassword, setConfirmNewPassword] = useState('');
const [selectedLanguage, setSelectedLanguage] = useState(currentLocale);
const [isLoading, setIsLoading] = useState(false);
const [isPasswordLoading, setIsPasswordLoading] = useState(false);
const [isLanguageLoading, setIsLanguageLoading] = useState(false);
useEffect(() => {
setSelectedLanguage(currentLocale);
}, [currentLocale]);
const handleProfileUpdate = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("Updating profile:", { name, avatarUrl, bio, phone, location });
toast({ title: "Profile Updated", description: "Your profile information has been saved." }); // Translate
toast({ title: t('dashboard.profile.save_button'), description: "Your profile information has been saved." });
setIsLoading(false);
};
const handlePasswordChange = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (newPassword !== confirmNewPassword) {
toast({ title: "Password Mismatch", description: "New passwords do not match.", variant: "destructive" }); // Translate
toast({ title: "Password Mismatch", description: "New passwords do not match.", variant: "destructive" });
return;
}
if (newPassword.length < 6) {
toast({ title: "Password Too Short", description: "Password must be at least 6 characters.", variant: "destructive" }); // Translate
toast({ title: "Password Too Short", description: "Password must be at least 6 characters.", variant: "destructive" });
return;
}
setIsPasswordLoading(true);
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("Changing password");
toast({ title: "Password Updated", description: "Your password has been changed successfully." }); // Translate
toast({ title: t('dashboard.profile.update_password_button'), description: "Your password has been changed successfully." });
setCurrentPassword('');
setNewPassword('');
setConfirmNewPassword('');
setIsPasswordLoading(false);
};
const handleLanguageChange = (newLang: 'en' | 'zh-TW') => {
setIsLanguageLoading(true);
changeLocale(newLang);
localStorage.setItem('userPreferredLanguage', newLang);
setSelectedLanguage(newLang);
toast({ title: t('dashboard.profile.language_settings_title'), description: t('dashboard.profile.language_updated_toast') });
// No need to manually set isLanguageLoading to false if the page reloads/re-renders due to locale change
// However, if changeLocale doesn't cause an immediate unmount, manage loading state appropriately.
// For simplicity, we assume changeLocale will lead to a re-render where currentLocale updates.
};
return (
<div className="space-y-8 max-w-3xl mx-auto">
<div>
@ -155,6 +177,32 @@ export default function ProfilePage() {
</CardFooter>
</form>
</Card>
<Card className="shadow-lg">
<CardHeader>
<CardTitle className="text-xl font-headline">{t('dashboard.profile.language_settings_title')}</CardTitle>
<CardDescription>{t('dashboard.profile.language_settings_description')}</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
<Label htmlFor="language-select">{t('dashboard.profile.display_language_label')}</Label>
<Select
value={selectedLanguage}
onValueChange={(value) => handleLanguageChange(value as 'en' | 'zh-TW')}
disabled={isLanguageLoading}
>
<SelectTrigger id="language-select">
<SelectValue placeholder={t('lang.select_language')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="en">{t('lang.english')}</SelectItem>
<SelectItem value="zh-TW">{t('lang.traditional_chinese')}</SelectItem>
</SelectContent>
</Select>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@ -15,23 +15,41 @@ import {
import { useState, useEffect } from 'react';
import { ThemeToggleButton } from '@/components/ui/theme-toggle';
import LanguageSwitcher from './LanguageSwitcher';
import { useI18n, useCurrentLocale } from '@/locales/client';
import { useI18n, useCurrentLocale, useChangeLocale } from '@/locales/client';
export default function Header() {
const pathname = usePathname();
const t = useI18n();
const locale = useCurrentLocale();
const changeLocale = useChangeLocale();
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
const authStatus = localStorage.getItem('isToyShareAuthenticated');
setIsAuthenticated(authStatus === 'true');
}, [pathname]); // Listen to pathname changes to re-check auth if needed after navigation
}, []); // Run only once on mount
useEffect(() => {
if (isMounted) { // Ensure this runs only on the client after mount
const preferredLang = localStorage.getItem('userPreferredLanguage') as 'en' | 'zh-TW' | null;
if (preferredLang && preferredLang !== locale) {
// Check if the current pathname without locale prefix exists for the preferredLang
// This avoids redirect loops if a page doesn't exist in the preferredLang
const currentPathWithoutLocale = pathname.startsWith(`/${locale}`) ? pathname.substring(`/${locale}`.length) : pathname;
// It's hard to verify if `currentPathWithoutLocale` is valid for `preferredLang` without a full route list
// For simplicity, we'll directly change locale. If a 404 occurs, user can switch back.
changeLocale(preferredLang);
}
}
}, [isMounted, locale, pathname, changeLocale]);
const handleLogout = () => {
localStorage.removeItem('isToyShareAuthenticated');
setIsAuthenticated(false);
// No need to router.push here, links will use current locale
// No need to router.push here, links will use current locale, or redirect logic will apply
};
// Helper to remove locale prefix for path comparison
@ -78,7 +96,7 @@ export default function Header() {
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleLogout}>
<LogIn className="mr-2 h-4 w-4" /> {/* Using LogIn icon for logout action for consistency */}
<LogIn className="mr-2 h-4 w-4" />
{t('header.logout')}
</DropdownMenuItem>
</DropdownMenuContent>
@ -102,7 +120,6 @@ export default function Header() {
</nav>
<LanguageSwitcher />
<ThemeToggleButton />
{/* Mobile menu trigger could be added here if needed */}
</div>
</div>
</header>

View File

@ -92,8 +92,12 @@ export default {
'dashboard.profile.confirm_new_password_label': 'Confirm New Password',
'dashboard.profile.update_password_button': 'Update Password',
'dashboard.profile.updating_password_button': 'Updating Password...',
'dashboard.profile.language_settings_title': 'Language Settings',
'dashboard.profile.language_settings_description': 'Choose your preferred display language for the application.',
'dashboard.profile.display_language_label': 'Display Language',
'dashboard.profile.language_updated_toast': 'Language updated successfully.',
'lang.english': 'English',
'lang.traditional_chinese': '繁體中文',
'lang.traditional_chinese': 'Traditional Chinese',
'lang.select_language': 'Select Language',
'general.back_to_my_toys': 'Back to My Toys',
'general.toy_not_found': 'Toy Not Found',

View File

@ -92,6 +92,10 @@ export default {
'dashboard.profile.confirm_new_password_label': '確認新密碼',
'dashboard.profile.update_password_button': '更新密碼',
'dashboard.profile.updating_password_button': '更新密碼中...',
'dashboard.profile.language_settings_title': '語言設定',
'dashboard.profile.language_settings_description': '選擇您偏好的應用程式顯示語言。',
'dashboard.profile.display_language_label': '顯示語言',
'dashboard.profile.language_updated_toast': '語言已成功更新。',
'lang.english': 'English',
'lang.traditional_chinese': '繁體中文',
'lang.select_language': '選擇語言',

View File

@ -3,9 +3,8 @@ import { createI18nMiddleware } from 'next-international/middleware';
const I18nMiddleware = createI18nMiddleware({
locales: ['en', 'zh-TW'],
defaultLocale: 'en',
urlMappingStrategy: 'rewrite', // Or 'redirect', depending on preference
// prefix: 'as-needed' // default is 'as-needed'
defaultLocale: 'zh-TW', // Changed default locale
urlMappingStrategy: 'rewrite',
});
export function middleware(request: NextRequest) {
@ -13,6 +12,5 @@ export function middleware(request: NextRequest) {
}
export const config = {
// Matcher ignoring `/_next/` and `/api/`
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};