Skip to content

Commit 9f8e35c

Browse files
committed
i18n: Add language selection dropdown
1 parent e1427d0 commit 9f8e35c

File tree

4 files changed

+138
-1
lines changed

4 files changed

+138
-1
lines changed

src/components/header.module.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@
3434
}
3535
}
3636

37+
.hiddenOnDesktop {
38+
@media (min-width: $mantine-breakpoint-md) {
39+
display: none;
40+
}
41+
}
42+
3743
.burger {
3844
--burger-color: var(--ruffle-orange);
3945
}

src/components/header.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { Burger, Container, Drawer, Group } from "@mantine/core";
44
import { useDisclosure } from "@mantine/hooks";
55
import classes from "./header.module.css";
6+
import { LanguagePicker } from "./language-picker";
67
import Image from "next/image";
78
import Link from "next/link";
89
import { usePathname } from "next/navigation";
@@ -43,7 +44,7 @@ export function Header() {
4344

4445
return (
4546
<header className={classes.header}>
46-
<Container size="md" className={classes.inner}>
47+
<Container size="lg" className={classes.inner}>
4748
<Link href="/">
4849
<Image
4950
src="/logo.svg"
@@ -55,6 +56,7 @@ export function Header() {
5556
</Link>
5657
<Group gap={5} visibleFrom="md">
5758
{items}
59+
<LanguagePicker />
5860
</Group>{" "}
5961
<Drawer
6062
opened={opened}
@@ -69,6 +71,9 @@ export function Header() {
6971
>
7072
{items}
7173
</Drawer>
74+
<span className={classes.hiddenOnDesktop}>
75+
<LanguagePicker />
76+
</span>
7277
<Burger
7378
opened={opened}
7479
onClick={toggle}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
.control {
2+
width: 200px;
3+
display: flex;
4+
justify-content: space-between;
5+
align-items: center;
6+
padding: var(--mantine-spacing-xs) var(--mantine-spacing-md);
7+
border-radius: var(--mantine-radius-md);
8+
border: 1px solid
9+
light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6));
10+
transition: background-color 150ms ease;
11+
background-color: var(--ruffle-blue);
12+
}
13+
14+
.label {
15+
font-weight: 500;
16+
font-size: var(--mantine-font-size-sm);
17+
}
18+
19+
.icon {
20+
transition: transform 150ms ease;
21+
transform: rotate(0deg);
22+
23+
[data-expanded] & {
24+
transform: rotate(180deg);
25+
}
26+
}
27+
28+
.dropdown {
29+
background-color: var(--ruffle-blue);
30+
}
31+
32+
.item {
33+
@mixin hover {
34+
background-color: var(--ruffle-blue-7);
35+
}
36+
}

src/components/language-picker.tsx

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { IconChevronDown } from "@tabler/icons-react";
5+
import { Group, Menu, UnstyledButton } from "@mantine/core";
6+
import { LanguageSwitcher } from "next-export-i18n";
7+
import classes from "./language-picker.module.css";
8+
9+
const data = [
10+
{ label: "English (United States)", locale: "en" },
11+
{ label: "العربية", locale: "ar" },
12+
{ label: "Català", locale: "ca" },
13+
{ label: "简体中文", locale: "zh" },
14+
{ label: "Čeština", locale: "cs" },
15+
{ label: "Nederlands", locale: "nl" },
16+
{ label: "Français (France)", locale: "fr" },
17+
{ label: "Deutsch", locale: "de" },
18+
{ label: "עברית (ישראל)", locale: "he" },
19+
{ label: "Magyar", locale: "hu" },
20+
{ label: "Indonesian", locale: "id" },
21+
{ label: "Italiano (Italia)", locale: "it" },
22+
{ label: "日本語", locale: "ja" },
23+
{ label: "한국어", locale: "ko" },
24+
{ label: "Polski (Polska)", locale: "pl" },
25+
{ label: "Português (Portugal)", locale: "pt" },
26+
{ label: "Romanian", locale: "ro" },
27+
{ label: "Русский", locale: "ru" },
28+
{ label: "Slovenčina (Slovensko)", locale: "sk" },
29+
{ label: "Español", locale: "es" },
30+
{ label: "Svenska", locale: "sv" },
31+
{ label: "Türkçe", locale: "tr" },
32+
{ label: "Українська", locale: "uk" },
33+
];
34+
35+
export function LanguagePicker() {
36+
const [opened, setOpened] = useState(false);
37+
const [selected, setSelected] = useState(data[0]);
38+
useEffect(() => {
39+
if (typeof window !== "undefined") {
40+
const browserLang = (
41+
(window.navigator.languages && window.navigator.languages[0]) ||
42+
window.navigator.language
43+
)
44+
.split("-")[0]
45+
.toLowerCase();
46+
const currentLocale = data.some((item) => item.locale === browserLang)
47+
? browserLang
48+
: "en";
49+
const storedLang =
50+
localStorage.getItem("next-export-i18n-lang") || currentLocale;
51+
const match = data.find((item) => item.locale === storedLang);
52+
if (match) setSelected(match);
53+
}
54+
}, []);
55+
56+
const items = data.map((item) => (
57+
<LanguageSwitcher lang={item.locale} key={item.locale}>
58+
<Menu.Item
59+
component="span"
60+
className={classes.item}
61+
onClick={() => setSelected(item)}
62+
>
63+
{item.label}
64+
</Menu.Item>
65+
</LanguageSwitcher>
66+
));
67+
68+
return (
69+
<Menu
70+
onOpen={() => setOpened(true)}
71+
onClose={() => setOpened(false)}
72+
radius="md"
73+
width="target"
74+
withinPortal
75+
>
76+
<Menu.Target>
77+
<UnstyledButton
78+
className={classes.control}
79+
data-expanded={opened || undefined}
80+
>
81+
<Group gap="xs">
82+
<span className={classes.label}>{selected.label}</span>
83+
</Group>
84+
<IconChevronDown size={16} className={classes.icon} stroke={1.5} />
85+
</UnstyledButton>
86+
</Menu.Target>
87+
<Menu.Dropdown className={classes.dropdown}>{items}</Menu.Dropdown>
88+
</Menu>
89+
);
90+
}

0 commit comments

Comments
 (0)