Skip to content

Commit 60762a6

Browse files
committed
feat: enhance LanguageDetectionAlert component with improved visibility and functionality for language switching
1 parent ef07acc commit 60762a6

File tree

4 files changed

+100
-47
lines changed

4 files changed

+100
-47
lines changed
Lines changed: 91 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,56 @@
11
"use client";
22

3-
import LocaleSwitcher from "@/components/LocaleSwitcher";
4-
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
53
import { Button } from "@/components/ui/button";
6-
import { DEFAULT_LOCALE, routing } from "@/i18n/routing";
4+
import { Link as I18nLink, LOCALE_NAMES, routing } from "@/i18n/routing";
5+
import { cn } from "@/lib/utils";
76
import { useLocaleStore } from "@/stores/localeStore";
8-
import { Globe, X } from "lucide-react";
7+
import { ArrowRight, Globe, X } from "lucide-react";
98
import { useLocale } from "next-intl";
10-
import { useEffect, useState } from "react";
9+
import { useCallback, useEffect, useState } from "react";
1110

1211
export function LanguageDetectionAlert() {
1312
const [countdown, setCountdown] = useState(10); // countdown 10s and dismiss
13+
const [isVisible, setIsVisible] = useState(false);
1414
const locale = useLocale();
15-
const [currentLocale, setCurrentLocale] = useState(locale);
15+
const [detectedLocale, setDetectedLocale] = useState<string | null>(null);
1616
const {
1717
showLanguageAlert,
1818
setShowLanguageAlert,
1919
dismissLanguageAlert,
2020
getLangAlertDismissed,
2121
} = useLocaleStore();
2222

23+
const handleDismiss = useCallback(() => {
24+
setIsVisible(false);
25+
setTimeout(() => {
26+
dismissLanguageAlert();
27+
}, 300);
28+
}, [dismissLanguageAlert]);
29+
30+
const handleSwitchLanguage = useCallback(() => {
31+
dismissLanguageAlert();
32+
}, [dismissLanguageAlert]);
33+
2334
useEffect(() => {
24-
const detectedLang = navigator.language; // Get full language code, e.g., zh-HK
35+
const detectedLang = navigator.language; // Get full language code, e.g., zh_HK
2536
const storedDismiss = getLangAlertDismissed();
2637

2738
if (!storedDismiss) {
28-
// Check if the full language code is supported (e.g., zh-HK)
2939
let supportedLang = routing.locales.find((l) => l === detectedLang);
3040

31-
// If full code isn't supported, check primary language (e.g., zh)
3241
if (!supportedLang) {
33-
const mainLang = detectedLang.split("-")[0]; // Get primary language code
42+
const mainLang = detectedLang.split("-")[0];
3443
supportedLang = routing.locales.find((l) => l.startsWith(mainLang));
3544
}
3645

37-
// If language still isn't supported, default to English
38-
setCurrentLocale(supportedLang || DEFAULT_LOCALE);
39-
setShowLanguageAlert(supportedLang !== locale);
46+
if (supportedLang && supportedLang !== locale) {
47+
setDetectedLocale(supportedLang);
48+
setShowLanguageAlert(true);
49+
setTimeout(() => setIsVisible(true), 100);
50+
}
4051
}
41-
}, [locale, getLangAlertDismissed, setCurrentLocale, setShowLanguageAlert]);
52+
}, [locale, getLangAlertDismissed, setShowLanguageAlert]);
4253

43-
// countdown
4454
useEffect(() => {
4555
let timer: NodeJS.Timeout;
4656

@@ -55,40 +65,80 @@ export function LanguageDetectionAlert() {
5565
};
5666
}, [showLanguageAlert, countdown]);
5767

58-
// dismiss alert after countdown = 0
5968
useEffect(() => {
6069
if (countdown === 0 && showLanguageAlert) {
61-
dismissLanguageAlert();
70+
handleDismiss();
6271
}
63-
}, [countdown, showLanguageAlert, dismissLanguageAlert]);
72+
}, [countdown, showLanguageAlert, handleDismiss]);
6473

65-
if (!showLanguageAlert) return null;
74+
if (!showLanguageAlert || !detectedLocale) return null;
6675

67-
const messages = require(`@/i18n/messages/${currentLocale}.json`);
76+
const messages = require(`@/i18n/messages/${detectedLocale}.json`);
6877
const alertMessages = messages.LanguageDetection;
6978

7079
return (
71-
<Alert className="mb-4 relative">
72-
<Button
73-
variant="ghost"
74-
size="icon"
75-
className="absolute right-2 top-2 h-6 w-6"
76-
onClick={dismissLanguageAlert}
77-
>
78-
<X className="h-4 w-4" />
79-
</Button>
80-
<Globe className="h-4 w-4" />
81-
<AlertTitle>
82-
{alertMessages.title}{" "}
83-
<span className=" mt-2 text-sm text-muted-foreground">
84-
{alertMessages.countdown.replace("{countdown}", countdown.toString())}
85-
</span>
86-
</AlertTitle>
87-
<AlertDescription>
88-
<div className="flex items-center gap-2">
89-
{alertMessages.description} <LocaleSwitcher />
80+
<div
81+
className={cn(
82+
"fixed top-16 right-4 z-50 max-w-sm w-full mx-4 sm:mx-0 sm:w-96",
83+
"transform transition-all duration-300 ease-in-out",
84+
isVisible
85+
? "translate-x-0 translate-y-0 opacity-100"
86+
: "translate-x-full opacity-0"
87+
)}
88+
role="banner"
89+
aria-live="polite"
90+
aria-label="Language detection alert"
91+
>
92+
<div className="bg-background/95 backdrop-blur-md border border-border rounded-xl shadow-lg p-4 relative">
93+
<Button
94+
variant="ghost"
95+
size="icon"
96+
className="absolute right-2 top-2 h-6 w-6 opacity-50 hover:opacity-100"
97+
onClick={handleDismiss}
98+
aria-label="Dismiss language suggestion"
99+
>
100+
<X className="h-4 w-4" />
101+
</Button>
102+
103+
<div className="pr-8">
104+
<div className="flex items-center gap-2 mb-3">
105+
<Globe className="h-4 w-4 text-primary" />
106+
<h3 className="font-medium text-sm text-foreground">
107+
{alertMessages.title}
108+
</h3>
109+
</div>
110+
111+
<p className="text-xs text-muted-foreground mb-4 leading-relaxed">
112+
{alertMessages.description}
113+
</p>
114+
115+
<div className="flex items-center justify-between">
116+
<Button asChild onClick={handleSwitchLanguage}>
117+
<I18nLink
118+
href="/"
119+
title={`${alertMessages.switchTo} ${LOCALE_NAMES[detectedLocale]}`}
120+
locale={detectedLocale as any}
121+
className={cn(
122+
"flex items-center gap-2 px-3 py-2 rounded-lg",
123+
"bg-primary text-primary-foreground hover:bg-primary/90",
124+
"text-sm font-medium transition-colors",
125+
"group focus:outline-none focus:ring-2 focus:ring-primary/50"
126+
)}
127+
aria-label={`${alertMessages.switchTo} ${LOCALE_NAMES[detectedLocale]}`}
128+
>
129+
<span>
130+
{alertMessages.switchTo} {LOCALE_NAMES[detectedLocale]}
131+
</span>
132+
<ArrowRight className="h-3 w-3 transition-transform group-hover:translate-x-0.5" />
133+
</I18nLink>
134+
</Button>
135+
136+
<span className="text-xs text-muted-foreground">{countdown}s</span>
137+
</div>
90138
</div>
91-
</AlertDescription>
92-
</Alert>
139+
140+
<div className="absolute inset-0 rounded-xl bg-gradient-to-r from-primary/10 to-transparent pointer-events-none opacity-50" />
141+
</div>
142+
</div>
93143
);
94144
}

i18n/messages/en.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
{
22
"LanguageDetection": {
33
"title": "Language Suggestion",
4-
"description": "We noticed your browser language differs from the current site language. You can switch languages anytime 👉",
5-
"countdown": "Closing in {countdown} seconds"
4+
"description": "We noticed your browser language differs from the current site language. You can switch languages anytime.",
5+
"countdown": "Closing in {countdown} seconds",
6+
"switchTo": "Switch to"
67
},
78
"Header": {
89
"links": [

i18n/messages/ja.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
{
22
"LanguageDetection": {
33
"title": "言語の提案",
4-
"description": "ブラウザの言語設定が現在のサイト言語と異なっています。いつでも言語を切り替えることができます 👉",
5-
"countdown": "閉じるまで {countdown} 秒"
4+
"description": "ブラウザの言語設定が現在のサイト言語と異なっています。いつでも言語を切り替えることができます。",
5+
"countdown": "閉じるまで {countdown} 秒",
6+
"switchTo": "に切り替える"
67
},
78
"Header": {
89
"links": [

i18n/messages/zh.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
{
22
"LanguageDetection": {
33
"title": "语言建议",
4-
"description": "检测到您的浏览器语言和当前语言不一样,您随时都可切换语言 👉",
5-
"countdown": "将在 {countdown} 秒后关闭"
4+
"description": "检测到你的浏览器语言和当前语言不一样,你随时都可切换语言。",
5+
"countdown": "将在 {countdown} 秒后关闭",
6+
"switchTo": "切换到"
67
},
78
"Header": {
89
"links": [

0 commit comments

Comments
 (0)