Add language selector

This commit is contained in:
Maria Rushkova 2021-11-22 14:54:15 +01:00
parent 844a63d085
commit 8f5ddf1f72
No known key found for this signature in database
GPG Key ID: B9B5728B991FF586
14 changed files with 304 additions and 40 deletions

View File

@ -18,8 +18,11 @@
"build": "webpack --mode production --progress"
},
"dependencies": {
"i18next": "^21.5.2",
"i18next-browser-languagedetector": "^6.1.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-i18next": "^11.14.2",
"react-is": "^17.0.1",
"react-router-dom": "^5.2.0",
"styled-components": "^5.2.1"

View File

@ -0,0 +1,3 @@
<svg width="40" height="20" viewBox="0 0 44 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M42 2L22 22L2 2" stroke="white" stroke-width="5"/>
</svg>

After

Width:  |  Height:  |  Size: 163 B

View File

@ -1,26 +1,29 @@
import React from 'react'
import { Link } from 'react-router-dom'
import styled from 'styled-components'
import { Colors } from '../constants'
import arrow from '../assets/arrow.svg'
import { Colors } from '../../constants/styles'
import arrow from '../../assets/arrow.svg'
import { useTranslation } from 'react-i18next'
export function Navigation() {
const { t, i18n } = useTranslation()
return (
<Nav>
<NavLinks>
<NavItem path="example" link="THE PANCULTURISTS Party" sublink="Copy, Read Print" subpath="example/read" />
<NavItem path="example" link="OUR MANIFESTO" />
<NavItem path="example" link="COMMON POLICIES" />
<NavItem path="example" link="BECOME A MEMBER" sublink="REGULAR OR INDIVIDUAL" subpath="example/types" />
<NavItem path="example" link="CONTRIBUTE" sublink="SEE ALL OPEN BOUNTIES" subpath="example/bounties" />
<NavItem path="example" link="THE PEOPLES FREE PRESS" sublink="Copy, Read Print" subpath="example/read" />
<NavItem path="example" link="GENERAL ASSEMBLIES" />
<NavItem path="example" link="WIKIPEDIA" />
<NavItem path="example" link="THE BOARD " />
<NavItem path="example" link="WHAT IS THE NETWORK STATE?" />
<NavItem path="example" link="DISCUSS FORUM" />
<NavItem path="example" link="THE PEOPLES SHOP" sublink="Copy, Read Print" subpath="example/read" />
<NavItem path="example" link="CALENDAR & EVENTS" />
<NavItem path="example" link={t('party')} sublink={t('nav_sub')} subpath="example/read" />
<NavItem path="example" link={t('manifesto')} />
<NavItem path="example" link={t('policies')} />
<NavItem path="example" link={t('membership')} sublink={t('membership_types')} subpath="example/types" />
<NavItem path="example" link={t('contribute')} sublink={t('contribute_bounties')} subpath="example/bounties" />
<NavItem path="example" link={t('press')} sublink={t('nav_sub')} subpath="example/read" />
<NavItem path="example" link={t('assamblies')} />
<NavItem path="example" link={t('wiki')} />
<NavItem path="example" link={t('board')} />
<NavItem path="example" link={t('network')} />
<NavItem path="example" link={t('forum')} />
<NavItem path="example" link={t('shop')} sublink={t('nav_sub')} subpath="example/read" />
<NavItem path="example" link={t('calendar')} />
</NavLinks>
</Nav>
)

View File

@ -0,0 +1,35 @@
import React from 'react'
import { Select } from './Select'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { Colors } from '../../constants/styles'
const languages = [
{ name: 'EN', code: 'en' },
{ name: 'NL', code: 'nl' },
]
export const LanguageSelector = () => {
const { t, i18n } = useTranslation()
return (
<Select
value={t('language_name')}
list={languages.map(({ name, code }) => (
<Button key={code} onClick={() => i18n.changeLanguage(code)}>
{name}
</Button>
))}
/>
)
}
const Button = styled.button`
outline: none;
color: ${Colors.White};
font-size: calc(24px + (36 - 24) * ((100vw - 320px) / (1440 - 320)));
&:hover {
color: ${Colors.Gray};
}
`

View File

@ -1,4 +1,5 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import styled from 'styled-components'
import { Colors } from '../../constants/styles'
@ -6,22 +7,25 @@ import { DiscordIcon } from '../icons/DiscordIcon'
import { GithubIcon } from '../icons/GithubIcon'
import { TwitterIcon } from '../icons/TwitterIcon'
import { MenuContent } from './Content'
import { LanguageSelector } from './LanguageSelector'
export function Menu() {
const { t, i18n } = useTranslation()
return (
<NavigationMenu>
<TopNavigation>
<TopLink to="/">Home</TopLink>
<TopLink to="/support">Support us</TopLink>
<Dropdown></Dropdown>
<TopLink to="/">{t('home')}</TopLink>
<TopLink to="/support">{t('support')}</TopLink>
<LanguageSelector />
</TopNavigation>
<Navigation>
<NavLinks>
<StyledLink to="/">WHATs ON</StyledLink>
<StyledLink to="/">NEWS & PRESS</StyledLink>
<StyledLink to="/">JOIN US</StyledLink>
<StyledLink to="/">BECOME A MEMBER</StyledLink>
<StyledLink to="/">CONTRIBUTE</StyledLink>
<StyledLink to="/">{t('whats_on')}</StyledLink>
<StyledLink to="/">{t('news')}</StyledLink>
<StyledLink to="/">{t('join')}</StyledLink>
<StyledLink to="/">{t('membership')}</StyledLink>
<StyledLink to="/">{t('contribute')}</StyledLink>
</NavLinks>
</Navigation>
<SocialLinks>
@ -39,14 +43,6 @@ export function Menu() {
)
}
export function Dropdown() {
return (
<MenuBlock>
<MenuList></MenuList>
</MenuBlock>
)
}
const NavigationMenu = styled(MenuContent)`
background-color: ${Colors.Black};
position: absolute;
@ -59,6 +55,7 @@ const NavigationMenu = styled(MenuContent)`
`
const TopNavigation = styled.div`
width: 100%;
display: flex;
color: ${Colors.White};
padding: 24px;
`
@ -107,14 +104,6 @@ const StyledLink = styled(Link)`
}
`
const MenuBlock = styled.div`
width: 100%;
`
const MenuList = styled.ul`
list-style: none;
`
const SocialLinks = styled.div`
display: flex;
justify-content: center;

View File

@ -0,0 +1,82 @@
import React, { ReactNode, useRef } from 'react'
import styled from 'styled-components'
import { Colors } from '../../constants/styles'
import arrowDown from '../../assets/arrow-down.svg'
import { useClickOutside } from '../../hooks/useClickOutside'
import { useToggler } from '../../hooks/useToggler'
export interface SelectProps {
list: ReactNode[]
value: any
}
export const Select = ({ value, list }: SelectProps) => {
const { visible, toggle } = useToggler()
const ref = useRef(null)
useClickOutside(ref, () => toggle(false))
return (
<SelectWrapper ref={ref}>
{visible && (
<SelectContent>
{list.map((item, index) => (
<SelectItem key={index}>{item}</SelectItem>
))}
</SelectContent>
)}
<SelectButton onClick={() => toggle()} className={visible ? 'open' : ''}>
{value}
</SelectButton>
</SelectWrapper>
)
}
const SelectWrapper = styled.div`
position: relative;
width: 120px;
color: ${Colors.White};
`
const SelectButton = styled.button`
position: relative;
width: 100%;
background: ${Colors.Black};
color: ${Colors.White};
font-size: calc(24px + (36 - 24) * ((100vw - 320px) / (1440 - 320)));
text-decoration: underline;
padding-right: 50px;
&::after {
content: '';
position: absolute;
bottom: 0;
right: 0;
width: 40px;
height: 20px;
transform: translateY(-50%);
background: url(${arrowDown}) center no-repeat;
background-size: contain;
}
&.open::after {
transform: translateY(-50%) rotate(180deg);
}
`
const SelectContent = styled.ul`
position: absolute;
left: 10px;
bottom: -12px;
width: 100%;
transform: translateY(100%);
overflow-y: auto;
background: ${Colors.Black};
color: ${Colors.White};
z-index: 1;
`
const SelectItem = styled.li`
& + & {
margin-top: 20px;
}
`

View File

@ -0,0 +1,17 @@
import { useEffect, RefObject } from 'react'
export const useClickOutside = (ref: RefObject<HTMLDivElement>, callback: () => void, deps?: any[]) => {
const handleClick = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as HTMLInputElement)) {
callback()
}
}
useEffect(() => {
document.addEventListener('mousedown', handleClick)
return () => {
document.removeEventListener('mousedown', handleClick)
}
}, deps)
}

14
src/hooks/useToggler.ts Normal file
View File

@ -0,0 +1,14 @@
import { useState } from 'react'
export const useToggler = () => {
const [visible, setVisibility] = useState(false)
const toggle = (value?: boolean) => {
if (value === undefined) {
setVisibility((visible) => !visible)
} else {
setVisibility(() => value)
}
}
return { visible, toggle }
}

28
src/i18n.ts Normal file
View File

@ -0,0 +1,28 @@
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
import en from './translations/en.json'
import nl from './translations/nl.json'
const resources = {
en: {
translation: en,
},
nl: {
translation: nl,
},
}
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
debug: true,
lng: 'en',
interpolation: {
escapeValue: false,
},
resources,
})
export default i18n

View File

@ -2,6 +2,7 @@ import React from 'react'
import ReactDOM from 'react-dom'
import { App } from './App'
import { Providers } from './providers'
import './i18n'
ReactDOM.render(
<Providers>

View File

@ -2,7 +2,7 @@ import React from 'react'
import styled from 'styled-components'
import { Page, MenuContent } from '../components'
import { Logo } from '../components/top/Logo'
import { Navigation } from '../components/Navigation'
import { Navigation } from '../components/home/Navigation'
export function Home() {
return (

24
src/translations/en.json Normal file
View File

@ -0,0 +1,24 @@
{
"language_name": "EN",
"home": "Home",
"support": "Support us",
"whats_on": "WHATs ON",
"news": "NEWS & PRESS",
"join": "JOIN US",
"membership":"BECOME A MEMBER",
"membership_types":"REGULAR OR INDIVIDUAL",
"contribute": "CONTRIBUTE",
"contribute_bounties": "SEE ALL OPEN BOUNTIES",
"nav_sub":"Copy, Read, Print",
"party": "THE PANCULTURISTS Party",
"manifesto": "OUR MANIFESTO",
"policies": "COMMON POLICIES",
"press": "THE PEOPLES FREE PRESS",
"assamblies": "GENERAL ASSEMBLIES",
"wiki": "WIKIPEDIA",
"board": "THE BOARD",
"network": "WHAT IS THE NETWORK STATE?",
"forum": "DISCUSS FORUM",
"shop": "THE PEOPLES SHOP",
"calendar": "CALENDAR & EVENTS"
}

24
src/translations/nl.json Normal file
View File

@ -0,0 +1,24 @@
{
"language_name": "NL",
"home": "Huis",
"support": "Steun ons",
"whats_on": "Wat is er op",
"news": "NIEUWS & PERS",
"join": "DOE MET ONS MEE",
"membership":"LID WORDEN",
"membership_types":"REGULAR OR INDIVIDUAL",
"contribute": "BIJDRAGE",
"contribute_bounties": "ZIE ALLE OPEN BUNTIES",
"nav_sub":"Kopiëren, Lezen, Afdrukken",
"party": "DE PANCULTURISTEN Partij",
"manifesto": "ONS MANIFEST",
"policies": "GEMEENSCHAPPELIJK BELEID",
"press": "DE GRATIS PERS VAN DE MENSEN",
"assamblies": "ALGEMENE VERGADERING",
"wiki": "WIKIPEDIA",
"board": "HET BORD",
"network": "WAT IS DE NETWERKSTAAT?",
"forum": "BESPREEK FORUM",
"shop": "DE MENSEN WINKEL",
"calendar": "KALENDER & EVENEMENTEN"
}

View File

@ -89,6 +89,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.12.0", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6":
version "7.16.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5"
integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.12.7":
version "7.12.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc"
@ -2977,6 +2984,13 @@ html-minifier-terser@^5.0.1:
relateurl "^0.2.7"
terser "^4.6.3"
html-parse-stringify@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
dependencies:
void-elements "3.1.0"
html-webpack-plugin@^4.5.0:
version "4.5.1"
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.1.tgz#40aaf1b5cb78f2f23a83333999625c20929cda65"
@ -3084,6 +3098,20 @@ human-signals@^2.1.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
i18next-browser-languagedetector@^6.1.2:
version "6.1.2"
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.2.tgz#68565a28b929cbc98ab6a56826ef2faf0e927ff8"
integrity sha512-YDzIGHhMRvr7M+c8B3EQUKyiMBhfqox4o1qkFvt4QXuu5V2cxf74+NCr+VEkUuU0y+RwcupA238eeolW1Yn80g==
dependencies:
"@babel/runtime" "^7.14.6"
i18next@^21.5.2:
version "21.5.2"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.5.2.tgz#ababe6fb0a769360035ebf06a36751e0b810cdfa"
integrity sha512-Iuztr2+7CPCh5SYQV0utw2HXMx1za18xfznrw/PmgX+98oIpm84bhIM7VUPODjLycwIZ299oP7sEVQ9oCgmzfg==
dependencies:
"@babel/runtime" "^7.12.0"
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@ -4731,6 +4759,14 @@ react-dom@^17.0.1:
object-assign "^4.1.1"
scheduler "^0.20.1"
react-i18next@^11.14.2:
version "11.14.2"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.14.2.tgz#2ff28f6a0ddf06eaf79435ed6c70c441d709cf34"
integrity sha512-fmDhwNA0zDmSEL3BBT5qwNMvxrKu25oXDDAZyHprfB0AHZmWXfBmRLf8MX8i1iBd2I2C2vsA2D9wxYBIwzooEQ==
dependencies:
"@babel/runtime" "^7.14.5"
html-parse-stringify "^3.0.1"
"react-is@^16.12.0 || ^17.0.0", react-is@^17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
@ -6022,6 +6058,11 @@ vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
void-elements@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
w3c-hr-time@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"