Skip to content

Commit

Permalink
fix(layout): improve language toggle
Browse files Browse the repository at this point in the history
  • Loading branch information
devshred committed Jun 14, 2024
1 parent 04436d6 commit 76b24e9
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 25 deletions.
2 changes: 1 addition & 1 deletion e2e/faq.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ test.describe('FAQ page', () => {

await expect(page).toHaveTitle(/GPS-Tools/)

await page.selectOption('[aria-label="Language Selector"]', 'DE')
await page.click('[aria-label="Language Switcher"]')

const backendVersion = page.locator('#faqSubtitle')
await expect(backendVersion).toHaveText('Warum das alles?!')
Expand Down
2 changes: 1 addition & 1 deletion e2e/home.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ test.describe('Home page', () => {

await expect(page).toHaveTitle(/GPS-Tools/)

await page.selectOption('[aria-label="Language Selector"]', 'DE')
await page.click('[aria-label="Language Switcher"]')

const backendVersion = page.locator('#introHeader')
await expect(backendVersion).toHaveText('Diese Anwendung bietet folgende Funktionalitäten:')
Expand Down
2 changes: 1 addition & 1 deletion e2e/notFound.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ test.describe('Not found page', () => {
await page.goto('/foobar')
await expect(page).toHaveTitle(/GPS-Tools/)

await page.selectOption('[aria-label="Language Selector"]', 'DE')
await page.click('[aria-label="Language Switcher"]')

const heading = page.locator('h1')
await expect(heading).toHaveText('Nicht gefunden')
Expand Down
141 changes: 141 additions & 0 deletions src/icons/EnglishIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React from 'react'

type IconProps = {
className?: string | undefined
}

const EnglishIcon: React.FC<IconProps> = ({ className }) => {
return (
<svg
id="English Language"
aria-label="English Language"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 53 53"
className={className}
>
<defs>
<style>
{
'.english-red{fill: #f30040;}.english-red,.english-space,.english-blue,.english-white{stroke-width:0px;}.english-space{fill:none;}.english-blue{fill: #14439e;}.english-white{fill:#fff;}'
}
</style>
</defs>
<g id="b">
<g id="c">
<g>
<path
className="english-red"
d="M53,26.5c0,1.69-.16,3.35-.47,4.95h-20.42v20.95c-1.81.39-3.68.6-5.61.6s-3.81-.21-5.62-.6v-20.95H.47C.16,29.85,0,28.19,0,26.5s.16-3.35.47-4.95h20.41V.6c1.81-.39,3.69-.6,5.62-.6s3.8.21,5.61.6v20.95h20.42c.31,1.6.47,3.26.47,4.95Z"
/>
<path
className="english-blue"
d="M19.25,1.09v15.68L6.99,8.56c3.21-3.48,7.33-6.1,11.99-7.47h.27Z"
/>
<path className="english-blue" d="M6.42,19.55H.93c.27-1.04.61-2.04,1.01-3l4.48,3Z" />
<path
className="english-red"
d="M17.64,19.55h-6.69l-7.94-5.3c.53-1.04,1.13-2.03,1.81-2.98l12.82,8.28Z"
/>
<path
className="english-blue"
d="M19.25,36.22v15.69h-.27c-4.66-1.38-8.79-4-11.99-7.48l12.26-8.21Z"
/>
<path className="english-blue" d="M6.42,33.45l-4.48,3c-.4-.96-.74-1.96-1.01-3h5.49Z" />
<path
className="english-red"
d="M17.64,33.45l-12.82,8.28c-.68-.95-1.28-1.95-1.81-2.99l7.94-5.29h6.69Z"
/>
<path
className="english-blue"
d="M46.01,8.56l-12.26,8.21V1.09h.27c4.66,1.37,8.78,3.99,11.99,7.47Z"
/>
<path className="english-blue" d="M52.07,19.55h-5.48l4.48-3c.39.96.73,1.96,1,3Z" />
<path
className="english-red"
d="M49.99,14.25l-7.94,5.3h-6.68l12.81-8.28c.68.95,1.28,1.94,1.81,2.98Z"
/>
<path
className="english-blue"
d="M46.01,44.43c-3.2,3.48-7.33,6.1-11.99,7.48h-.27v-15.69l12.26,8.21Z"
/>
<path
className="english-blue"
d="M52.07,33.45c-.27,1.03-.61,2.03-1,2.99l-4.48-2.99h5.48Z"
/>
<path
className="english-red"
d="M49.99,38.74c-.53,1.04-1.13,2.04-1.81,2.99l-12.81-8.28h6.68l7.94,5.29Z"
/>
<path
className="english-white"
d="M52.53,21.55h-20.42V.6c.65.14,1.29.29,1.91.49h-.27v15.68l12.26-8.21c.78.85,1.51,1.75,2.17,2.71l-12.81,8.28h6.68l7.94-5.3c.41.75.76,1.51,1.08,2.3l-4.48,3h5.48c.18.65.34,1.32.46,2Z"
/>
<path
className="english-space"
d="M46.01,8.56l-12.26,8.21V1.09h.27c4.66,1.37,8.78,3.99,11.99,7.47Z"
/>
<path
className="english-space"
d="M49.99,14.25l-7.94,5.3h-6.68l12.81-8.28c.68.95,1.28,1.94,1.81,2.98Z"
/>
<path className="english-space" d="M52.07,19.55h-5.48l4.48-3c.39.96.73,1.96,1,3Z" />
<path
className="english-white"
d="M20.88.6v20.95H.47c.12-.68.28-1.35.46-2h5.49l-4.48-3c.31-.79.66-1.55,1.07-2.3l7.94,5.3h6.69L4.82,11.27c.66-.96,1.39-1.86,2.17-2.71l12.26,8.21V1.09h-.27c.62-.2,1.25-.35,1.9-.49Z"
/>
<path className="english-space" d="M6.42,19.55H.93c.27-1.04.61-2.04,1.01-3l4.48,3Z" />
<path
className="english-space"
d="M17.64,19.55h-6.69l-7.94-5.3c.53-1.04,1.13-2.03,1.81-2.98l12.82,8.28Z"
/>
<path
className="english-space"
d="M19.25,1.09v15.68L6.99,8.56c3.21-3.48,7.33-6.1,11.99-7.47h.27Z"
/>
<path
className="english-white"
d="M20.88,31.45v20.95c-.65-.14-1.28-.29-1.9-.49h.27v-15.69l-12.26,8.21c-.78-.85-1.51-1.75-2.17-2.7l12.82-8.28h-6.69l-7.94,5.29c-.41-.74-.76-1.5-1.07-2.29l4.48-3H.93c-.18-.65-.34-1.32-.46-2h20.41Z"
/>
<path className="english-space" d="M6.42,33.45l-4.48,3c-.4-.96-.74-1.96-1.01-3h5.49Z" />
<path
className="english-space"
d="M17.64,33.45l-12.82,8.28c-.68-.95-1.28-1.95-1.81-2.99l7.94-5.29h6.69Z"
/>
<path
className="english-space"
d="M19.25,36.22v15.69h-.27c-4.66-1.38-8.79-4-11.99-7.48l12.26-8.21Z"
/>
<path
className="english-white"
d="M52.07,33.45h-5.48l4.48,2.99c-.32.8-.67,1.55-1.08,2.3l-7.94-5.29h-6.68l12.81,8.28c-.66.95-1.39,1.85-2.17,2.7l-12.26-8.21v15.69h.27c-.62.2-1.26.35-1.91.49v-20.95h20.42c-.12.68-.28,1.35-.46,2Z"
/>
<g>
<path
className="english-space"
d="M46.01,44.43c-3.2,3.48-7.33,6.1-11.99,7.48h-.27v-15.69l12.26,8.21Z"
/>
<path
className="english-space"
d="M49.99,38.74c-.53,1.04-1.13,2.04-1.81,2.99l-12.81-8.28h6.68l7.94,5.29Z"
/>
<path
className="english-space"
d="M52.07,33.45c-.27,1.03-.61,2.03-1,2.99l-4.48-2.99h5.48Z"
/>
<path
className="english-space"
d="M53,26.5c0,1.69-.16,3.35-.47,4.95h-20.42v20.95c-1.81.39-3.68.6-5.61.6s-3.81-.21-5.62-.6v-20.95H.47C.16,29.85,0,28.19,0,26.5s.16-3.35.47-4.95h20.41V.6c1.81-.39,3.69-.6,5.62-.6s3.8.21,5.61.6v20.95h20.42c.31,1.6.47,3.26.47,4.95Z"
/>
</g>
</g>
</g>
</g>
<path
className="english-white"
d="M26.5,3c12.96,0,23.5,10.54,23.5,23.5s-10.54,23.5-23.5,23.5S3,39.46,3,26.5,13.54,3,26.5,3M26.5,0C11.86,0,0,11.86,0,26.5s11.86,26.5,26.5,26.5,26.5-11.86,26.5-26.5S41.14,0,26.5,0h0Z"
/>
</svg>
)
}

export default EnglishIcon
49 changes: 49 additions & 0 deletions src/icons/GermanIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react'

type IconProps = {
className?: string | undefined
}

const DeIcon: React.FC<IconProps> = ({ className }) => {
return (
<svg
id="Deutsche Sprache"
aria-label="Deutsche Sprache"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 53 53"
className={className}
>
<defs>
<style>
{
'.german-red{fill:#f30040;}.german-red,.german-black,german-gold,.german-outline{stroke-width:0px;}.german-black{fill:#020202;}.german-gold{fill:#fd0;}.german-outline{fill: #fff;}'
}
</style>
</defs>
<g id="b">
<g id="c">
<g>
<path
className="german-black"
d="M51.15,16.76H1.85C5.72,6.94,15.3,0,26.5,0s20.78,6.94,24.65,16.76Z"
/>
<path
className="german-red"
d="M53,26.5c0,3.44-.65,6.73-1.85,9.74H1.85c-1.2-3.01-1.85-6.3-1.85-9.74s.65-6.73,1.85-9.74h49.3c1.2,3.01,1.85,6.3,1.85,9.74Z"
/>
<path
className="german-gold"
d="M51.15,36.24c-3.87,9.82-13.45,16.76-24.65,16.76S5.72,46.06,1.85,36.24h49.3Z"
/>
</g>
</g>
</g>
<path
className="german-outline"
d="M26.5,4c12.41,0,22.5,10.09,22.5,22.5s-10.09,22.5-22.5,22.5S4,38.91,4,26.5,14.09,4,26.5,4M26.5,0C11.86,0,0,11.86,0,26.5s11.86,26.5,26.5,26.5,26.5-11.86,26.5-26.5S41.14,0,26.5,0h0Z"
/>
</svg>
)
}

export default DeIcon
25 changes: 14 additions & 11 deletions src/routes/-components/LanguageSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import useLanguage from '../../hooks/useLanguage'
import { Language } from '../../@types/language'
import EnglishIcon from '../../icons/EnglishIcon'
import GermanIcon from '../../icons/GermanIcon'

const LanguageSelector = () => {
const { currentLanguage, changeLanguage } = useLanguage()
const { toggleLanguage } = useLanguage()

return (
<select
className="select bg-transparent text-white rounded-full outline-none hover:outline-1 hover:outline-white select-sm mx-1"
defaultValue={currentLanguage}
onChange={(e) => changeLanguage(e.target.value as Language)}
aria-label="Language Selector"
>
<option value="en">EN</option>
<option value="de">DE</option>
</select>
<label className="swap mx-3 swap-flip" aria-label="Language Switcher">
<input
type="checkbox"
className="theme-controller"
value="synthwave"
onClick={toggleLanguage}
/>

<GermanIcon className="swap-on w-6 h-6" />
<EnglishIcon className="swap-off w-6 h-6" />
</label>
)
}

Expand Down
2 changes: 1 addition & 1 deletion src/routes/about/-screens/AboutScreen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ vi.mock('axios', async (importActual) => {

describe('About Page', () => {
const renderWithLanguage = (children: React.ReactNode, language: Language = 'en') => {
render(<LanguageProvider language={language}>{children}</LanguageProvider>)
render(<LanguageProvider initialLanguage={language}>{children}</LanguageProvider>)
}

const version = 'v1.2.3'
Expand Down
2 changes: 1 addition & 1 deletion src/services/providers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FeedbackProvider } from './feedback/FeedbackProvider'

const Providers = ({ children }: PropsWithChildren) => {
return (
<LanguageProvider language="en">
<LanguageProvider initialLanguage="en">
<FeedbackProvider>{children}</FeedbackProvider>
</LanguageProvider>
)
Expand Down
4 changes: 2 additions & 2 deletions src/services/providers/language/LanguageContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { createContext } from 'react'
import { Language } from '../../../@types/language'

type LanguageContextType = {
currentLanguage: Language
changeLanguage: (language: Language) => void
language: Language
toggleLanguage: () => void
getMessage: (labelId: string) => string | Array<string>
}

Expand Down
17 changes: 10 additions & 7 deletions src/services/providers/language/LanguageProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,29 @@ import de from './data/de.json'
import { Language } from '../../../@types/language'

export type LanguageProviderProps = {
language: Language
initialLanguage: Language
children: ReactNode
}
type Messages = { [messageKey: string]: string | string[] }
type LanguageDictionary = { [languageKey: string]: Messages }

export const LanguageProvider: React.FC<LanguageProviderProps> = ({ language, children }) => {
const [currentLanguage, setCurrentLanguage] = useState<Language>(language)
const changeLanguage = (language: Language) => setCurrentLanguage(language)
export const LanguageProvider: React.FC<LanguageProviderProps> = ({
initialLanguage,
children,
}) => {
const [language, setLanguage] = useState<Language>(initialLanguage)
const toggleLanguage = () => setLanguage((currentState) => (currentState === 'en' ? 'de' : 'en'))

const dictionary: LanguageDictionary = { en, de }

const getMessage = (messageKey: string) => {
const message = dictionary[currentLanguage][messageKey]
if (!message) throw new Error(`MessageKey ${messageKey} not found in ${currentLanguage}.json`)
const message = dictionary[language][messageKey]
if (!message) throw new Error(`MessageKey ${messageKey} not found in ${language}.json`)
return message
}

return (
<LanguageContext.Provider value={{ currentLanguage, changeLanguage, getMessage }}>
<LanguageContext.Provider value={{ language, toggleLanguage, getMessage }}>
{children}
</LanguageContext.Provider>
)
Expand Down

0 comments on commit 76b24e9

Please sign in to comment.