Skip to content

Commit 6eb43f1

Browse files
committed
Refactor: Introduce LanguageButtons component and remove LanguageSelector; update i18n initialization to support URL language detection
1 parent 2cf29ea commit 6eb43f1

4 files changed

Lines changed: 101 additions & 36 deletions

File tree

src/App.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import ViewToggle from './components/ViewToggle';
55
import { useTranslation } from 'react-i18next';
66
import Tooth from './components/Tooth';
77
import { FEATURES } from './config';
8+
import LanguageButtons from './components/LanguageButtons';
89

910

1011
const teethLayout = [
@@ -331,7 +332,8 @@ const ElasticPlacer = () => {
331332
{t('footer.copyright', { year: new Date().getFullYear() })} 
332333
<a href="https://kokokoding.nl" target="_blank" rel="noopener noreferrer" className="text-jort hover:underline">Koko Koding</a>
333334
</small>
334-
335+
336+
<LanguageButtons />
335337
</div>
336338
);
337339
};

src/components/LanguageButtons.tsx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { useTranslation } from 'react-i18next';
2+
import { Monitor, Check } from 'lucide-react';
3+
4+
const LanguageButtons = () => {
5+
const { i18n } = useTranslation();
6+
7+
const handleLanguageChange = (lang: string | null) => {
8+
const params = new URLSearchParams(window.location.search);
9+
10+
if (lang) {
11+
params.set('lng', lang);
12+
i18n.changeLanguage(lang);
13+
} else {
14+
params.delete('lng');
15+
const userLang = navigator.language.split('-')[0];
16+
i18n.changeLanguage(userLang);
17+
}
18+
19+
// Update URL without reloading the page
20+
const newUrl = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}`;
21+
window.history.pushState({ path: newUrl }, '', newUrl);
22+
};
23+
24+
// Get current language from URL, fallback to i18n.language
25+
const getCurrentLanguage = () => {
26+
const params = new URLSearchParams(window.location.search);
27+
return params.get('lng') || i18n.language;
28+
};
29+
30+
// Check if system language is being used (no lng param in URL)
31+
const isUsingSystemLanguage = () => {
32+
const params = new URLSearchParams(window.location.search);
33+
return !params.has('lng');
34+
};
35+
36+
return (
37+
<div className="fixed bottom-4 right-4 flex gap-2 bg-white rounded-full shadow-lg p-1 z-50">
38+
<button
39+
onClick={() => handleLanguageChange(null)}
40+
className={`p-2 rounded-full transition-colors hover:bg-gray-100 relative group ${
41+
isUsingSystemLanguage() ? 'text-blue-500' : 'text-gray-600'
42+
}`}
43+
title="Use system language"
44+
>
45+
<Monitor size={20} />
46+
{isUsingSystemLanguage() && (
47+
<Check className="absolute -top-1 -right-1 text-blue-500" size={12} />
48+
)}
49+
<span className="absolute right-0 bottom-full mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded whitespace-nowrap">
50+
System ({navigator.language.split('-')[0].toUpperCase()})
51+
</span>
52+
</button>
53+
<button
54+
onClick={() => handleLanguageChange('nl')}
55+
className={`p-2 rounded-full transition-colors hover:bg-gray-100 font-semibold relative group ${
56+
getCurrentLanguage() === 'nl' && !isUsingSystemLanguage() ? 'text-blue-500' : 'text-gray-600'
57+
}`}
58+
title="Nederlands"
59+
>
60+
NL
61+
{getCurrentLanguage() === 'nl' && !isUsingSystemLanguage() && (
62+
<Check className="absolute -top-1 -right-1 text-blue-500" size={12} />
63+
)}
64+
<span className="absolute right-0 bottom-full mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded">
65+
Nederlands
66+
</span>
67+
</button>
68+
<button
69+
onClick={() => handleLanguageChange('en')}
70+
className={`p-2 rounded-full transition-colors hover:bg-gray-100 font-semibold relative group ${
71+
getCurrentLanguage() === 'en' && !isUsingSystemLanguage() ? 'text-blue-500' : 'text-gray-600'
72+
}`}
73+
title="English"
74+
>
75+
EN
76+
{getCurrentLanguage() === 'en' && !isUsingSystemLanguage() && (
77+
<Check className="absolute -top-1 -right-1 text-blue-500" size={12} />
78+
)}
79+
<span className="absolute right-0 bottom-full mb-2 hidden group-hover:block bg-gray-800 text-white text-xs px-2 py-1 rounded">
80+
English
81+
</span>
82+
</button>
83+
</div>
84+
);
85+
};
86+
87+
export default LanguageButtons;

src/components/LanguageSelector.tsx

Lines changed: 0 additions & 35 deletions
This file was deleted.

src/i18n/i18n.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import i18n from 'i18next';
22
import { initReactI18next } from 'react-i18next';
33
import LanguageDetector from 'i18next-browser-languagedetector';
44

5+
// Get language from URL if present
6+
const getInitialLanguage = () => {
7+
const params = new URLSearchParams(window.location.search);
8+
return params.get('lng') || navigator.language.split('-')[0];
9+
};
10+
511
const resources = {
612
en: {
713
translation: {
@@ -98,7 +104,12 @@ i18n
98104
.use(initReactI18next)
99105
.init({
100106
resources,
107+
lng: getInitialLanguage(), // Set initial language
101108
fallbackLng: 'en',
109+
detection: {
110+
order: ['querystring', 'navigator'],
111+
lookupQuerystring: 'lng',
112+
},
102113
interpolation: {
103114
escapeValue: false,
104115
},

0 commit comments

Comments
 (0)