Skip to content

Commit 566c364

Browse files
Expand language selector and remove translation banner (#7)
Add 194 searchable language and locale options, including French, and suppress the intrusive translation overlay.
1 parent 85eb5c8 commit 566c364

3 files changed

Lines changed: 382 additions & 71 deletions

File tree

components/language-selector.js

Lines changed: 126 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,32 @@ import { useEffect, useState } from 'react'
22
import {
33
Box,
44
Button,
5-
Menu,
6-
MenuButton,
7-
MenuItem,
8-
MenuList,
5+
Divider,
6+
Input,
7+
Link,
8+
Popover,
9+
PopoverBody,
10+
PopoverContent,
11+
PopoverHeader,
12+
PopoverTrigger,
913
Text,
10-
useColorModeValue
14+
useColorModeValue,
15+
VStack
1116
} from '@chakra-ui/react'
1217
import { CheckIcon, ChevronDownIcon } from '@chakra-ui/icons'
1318
import { IoLanguageOutline } from 'react-icons/io5'
19+
import { getShortLanguageLabel, languages } from '../lib/languages'
1420

1521
const ORIGINAL_ORIGIN = 'https://matthewvaishnav.github.io'
1622
const TRANSLATED_ORIGIN = 'https://matthewvaishnav-github-io.translate.goog'
17-
18-
const languages = [
19-
{ code: 'en', shortLabel: 'EN', nativeLabel: 'English', englishLabel: 'English' },
20-
{ code: 'zh-CN', shortLabel: '中', nativeLabel: '简体中文', englishLabel: 'Chinese' },
21-
{ code: 'ja', shortLabel: '日', nativeLabel: '日本語', englishLabel: 'Japanese' },
22-
{ code: 'ko', shortLabel: '한', nativeLabel: '한국어', englishLabel: 'Korean' }
23-
]
24-
23+
const featuredCodes = ['en', 'fr', 'fr-CA', 'zh-CN', 'zh-TW', 'ja', 'ko', 'es', 'de']
2524
const supportedCodes = new Set(languages.map(language => language.code))
2625

26+
const orderedLanguages = [
27+
...featuredCodes.map(code => languages.find(language => language.code === code)),
28+
...languages.filter(language => !featuredCodes.includes(language.code))
29+
].filter(Boolean)
30+
2731
const getActiveLanguage = () => {
2832
if (typeof window === 'undefined') return 'en'
2933

@@ -58,10 +62,11 @@ const getLanguageUrl = languageCode => {
5862

5963
const LanguageSelector = () => {
6064
const [activeLanguage, setActiveLanguage] = useState('en')
65+
const [search, setSearch] = useState('')
6166
const buttonVariant = useColorModeValue('solid', 'outline')
6267
const buttonBorder = useColorModeValue('transparent', 'orange.300')
63-
const menuBg = useColorModeValue('white', 'gray.800')
64-
const menuBorder = useColorModeValue('blackAlpha.200', 'whiteAlpha.200')
68+
const popoverBg = useColorModeValue('white', 'gray.800')
69+
const popoverBorder = useColorModeValue('blackAlpha.200', 'whiteAlpha.200')
6570
const activeBg = useColorModeValue('teal.50', 'whiteAlpha.100')
6671
const muted = useColorModeValue('gray.500', 'whiteAlpha.600')
6772

@@ -71,74 +76,124 @@ const LanguageSelector = () => {
7176

7277
const activeOption =
7378
languages.find(language => language.code === activeLanguage) || languages[0]
79+
const normalizedSearch = search.trim().toLocaleLowerCase()
80+
const visibleLanguages = normalizedSearch
81+
? languages.filter(language =>
82+
`${language.englishLabel} ${language.nativeLabel} ${language.code}`
83+
.toLocaleLowerCase()
84+
.includes(normalizedSearch)
85+
)
86+
: orderedLanguages
7487

7588
const selectLanguage = languageCode => {
7689
if (languageCode === activeLanguage) return
7790
window.location.assign(getLanguageUrl(languageCode))
7891
}
7992

8093
return (
81-
<Menu placement="bottom-end" isLazy>
82-
<MenuButton
83-
as={Button}
84-
aria-label={`Select language. Current language: ${activeOption.englishLabel}`}
85-
colorScheme={useColorModeValue('teal', 'orange')}
86-
variant={buttonVariant}
87-
borderColor={buttonBorder}
88-
minW={{ base: '40px', md: '84px' }}
89-
px={{ base: 0, md: 3 }}
90-
>
91-
<Box as="span" display={{ base: 'none', md: 'inline-flex' }} mr={2}>
92-
<IoLanguageOutline />
93-
</Box>
94-
<Box as="span">{activeOption.shortLabel}</Box>
95-
<ChevronDownIcon display={{ base: 'none', md: 'inline-block' }} ml={1.5} />
96-
</MenuButton>
97-
98-
<MenuList
99-
minW="210px"
100-
p={2}
101-
mt={1}
102-
bg={menuBg}
103-
borderColor={menuBorder}
94+
<Popover placement="bottom-end" isLazy>
95+
<PopoverTrigger>
96+
<Button
97+
aria-label={`Select language. Current language: ${activeOption.englishLabel}`}
98+
colorScheme={useColorModeValue('teal', 'orange')}
99+
variant={buttonVariant}
100+
borderColor={buttonBorder}
101+
minW={{ base: '40px', md: '84px' }}
102+
px={{ base: 0, md: 3 }}
103+
className="notranslate"
104+
translate="no"
105+
>
106+
<Box as="span" display={{ base: 'none', md: 'inline-flex' }} mr={2}>
107+
<IoLanguageOutline />
108+
</Box>
109+
<Box as="span">{getShortLanguageLabel(activeOption.code)}</Box>
110+
<ChevronDownIcon display={{ base: 'none', md: 'inline-block' }} ml={1.5} />
111+
</Button>
112+
</PopoverTrigger>
113+
114+
<PopoverContent
115+
w={{ base: 'calc(100vw - 24px)', sm: '330px' }}
116+
maxW="330px"
117+
bg={popoverBg}
118+
borderColor={popoverBorder}
104119
borderRadius="xl"
105120
boxShadow="xl"
121+
className="notranslate"
122+
translate="no"
106123
>
107-
{languages.map(language => {
108-
const isActive = language.code === activeLanguage
109-
110-
return (
111-
<MenuItem
112-
key={language.code}
113-
onClick={() => selectLanguage(language.code)}
114-
bg={isActive ? activeBg : 'transparent'}
115-
borderRadius="lg"
116-
px={3}
117-
py={2.5}
118-
_hover={{ bg: activeBg }}
119-
_focus={{ bg: activeBg }}
120-
>
121-
<Box w="18px" mr={3} color="teal.400">
122-
{isActive && <CheckIcon boxSize={3} />}
123-
</Box>
124-
<Box flex="1">
125-
<Text fontWeight={isActive ? 700 : 600} lineHeight="short">
126-
{language.nativeLabel}
127-
</Text>
128-
{language.nativeLabel !== language.englishLabel && (
129-
<Text mt={1} color={muted} fontSize="xs" lineHeight="short">
130-
{language.englishLabel}
124+
<PopoverHeader border="0" px={4} pt={4} pb={2}>
125+
<Text fontWeight={800}>Choose a language</Text>
126+
<Text mt={1} color={muted} fontSize="xs">
127+
{languages.length} translation options
128+
</Text>
129+
</PopoverHeader>
130+
<PopoverBody px={3} pt={2} pb={3}>
131+
<Input
132+
value={search}
133+
onChange={event => setSearch(event.target.value)}
134+
placeholder="Search languages"
135+
aria-label="Search languages"
136+
mb={2}
137+
borderRadius="lg"
138+
/>
139+
140+
<VStack align="stretch" spacing={1} maxH="320px" overflowY="auto" pr={1}>
141+
{visibleLanguages.map(language => {
142+
const isActive = language.code === activeLanguage
143+
144+
return (
145+
<Button
146+
key={language.code}
147+
onClick={() => selectLanguage(language.code)}
148+
variant="ghost"
149+
bg={isActive ? activeBg : 'transparent'}
150+
justifyContent="flex-start"
151+
h="auto"
152+
minH="44px"
153+
px={3}
154+
py={2}
155+
borderRadius="lg"
156+
fontWeight="normal"
157+
_hover={{ bg: activeBg }}
158+
_focusVisible={{ boxShadow: 'outline' }}
159+
>
160+
<Box w="18px" mr={3} color="teal.400" flexShrink={0}>
161+
{isActive && <CheckIcon boxSize={3} />}
162+
</Box>
163+
<Box flex="1" minW={0} textAlign="left">
164+
<Text fontWeight={isActive ? 700 : 600} lineHeight="short" noOfLines={1}>
165+
{language.nativeLabel}
166+
</Text>
167+
{language.nativeLabel !== language.englishLabel && (
168+
<Text mt={1} color={muted} fontSize="xs" lineHeight="short" noOfLines={1}>
169+
{language.englishLabel}
170+
</Text>
171+
)}
172+
</Box>
173+
<Text ml={3} color={muted} fontSize="xs" fontWeight={700} flexShrink={0}>
174+
{language.code}
131175
</Text>
132-
)}
133-
</Box>
134-
<Text color={muted} fontSize="xs" fontWeight={700}>
135-
{language.shortLabel}
176+
</Button>
177+
)
178+
})}
179+
180+
{visibleLanguages.length === 0 && (
181+
<Text px={3} py={5} color={muted} fontSize="sm" textAlign="center">
182+
No matching language
136183
</Text>
137-
</MenuItem>
138-
)
139-
})}
140-
</MenuList>
141-
</Menu>
184+
)}
185+
</VStack>
186+
187+
<Divider my={3} />
188+
<Text color={muted} fontSize="xs" textAlign="center">
189+
Translations powered by{' '}
190+
<Link href="https://translate.google.com" target="_blank" rel="noopener noreferrer">
191+
Google Translate
192+
</Link>
193+
</Text>
194+
</PopoverBody>
195+
</PopoverContent>
196+
</Popover>
142197
)
143198
}
144199

0 commit comments

Comments
 (0)