Skip to content

Commit

Permalink
Updated language selector hooks and other methods
Browse files Browse the repository at this point in the history
  • Loading branch information
acharyarupak391 committed Mar 20, 2024
1 parent 68b5c1c commit c78bcf4
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 83 deletions.
6 changes: 1 addition & 5 deletions src/common/Header/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
useEffect,
useMemo,
useState
} from 'react'

Expand Down Expand Up @@ -95,10 +94,7 @@ export const Header = () => {

const { i18n } = useLingui()

const navigation = useMemo(
() => { return getNavigationLinks(router.pathname, i18n) },
[router.pathname, i18n]
)
const navigation = getNavigationLinks(router.pathname, i18n)

const handleToggleAccountPopup = () => {
setIsAccountDetailsOpen((prev) => { return !prev })
Expand Down
51 changes: 13 additions & 38 deletions src/common/Header/LanguageDropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import {
useState
} from 'react'

import { useRouter } from 'next/router'

import LeftArrow from '@/icons/LeftArrow'
import SearchLanguageIcon from '@/icons/SearchLanguageIcon'
import SelectedCircleIcon from '@/icons/SelectedCircleIcon'
Expand All @@ -16,10 +14,10 @@ import {
localesKey
} from '@/src/config/locales'
import { useDebounce } from '@/src/hooks/useDebounce'
import { useLocalStorage } from '@/src/hooks/useLocalStorage'
import { useWindowSize } from '@/src/hooks/useWindowSize'
import { useLanguageContext } from '@/src/i18n/i18n'
import { parseLocale } from '@/src/i18n/utils'
import { classNames } from '@/utils/classnames'
import { getBrowserLocale } from '@/utils/locale'
import {
Listbox,
Transition
Expand All @@ -34,69 +32,46 @@ import {
} from '@radix-ui/react-dialog'

const LANGUAGES = Object.values(languageKey)
const LANGUAGE_KEYS = Object.keys(languageKey)

/**
* @param {object} props
* @param {boolean?} [props.onOverlay]
* @returns
*/
export const LanguageDropdown = (props) => {
const router = useRouter()
const { i18n } = useLingui()
const [searchValue, setSearchValue] = useState('')

const [language, setLanguage] = useLocalStorage('locale', null)
const { locale, setLocale } = useLanguageContext()

const { width } = useWindowSize()

useEffect(() => {
const browserLocale = getBrowserLocale().replace(/-.*/, '')
if (
!language &&
LANGUAGE_KEYS.includes(browserLocale) &&
router.locale !== browserLocale
) {
router.push(router.asPath, router.asPath, { locale: browserLocale })

return
}

if (LANGUAGE_KEYS.includes(language) && router.locale !== language) {
router.push(router.asPath, router.asPath, { locale: language })
}
}, [language, router])

const [languages, setLanguages] = useState(LANGUAGES)
const [languagesToShow, setLanguagesToShow] = useState(LANGUAGES)
const debouncedSearch = useDebounce(searchValue, DEBOUNCE_TIMEOUT)

useEffect(() => {
if (!debouncedSearch) {
setLanguages(LANGUAGES)
setLanguagesToShow(LANGUAGES)

return
}
const searchedLanguages = LANGUAGES.filter((el) => { return el.toLowerCase().includes(debouncedSearch.toLowerCase()) }
)
setLanguages(searchedLanguages)
setLanguagesToShow(searchedLanguages)
}, [debouncedSearch])

const handleOnChangeLanguage = (value) => {
setLanguage(localesKey[value])
router.push(router.asPath, router.asPath, {
locale: localesKey[value]
})
setLocale(parseLocale(localesKey[value]))
}

const handleSearchLanguage = (e) => {
setSearchValue(e.target.value)
}

const { i18n } = useLingui()

return (
<div className='relative flex items-center mt-1 cursor-pointer md:mt-3'>
<Listbox
value={languageKey[router.locale]}
value={languageKey[locale]}
onChange={handleOnChangeLanguage}
>
{
Expand All @@ -111,7 +86,7 @@ export const LanguageDropdown = (props) => {
<div className={classNames('flex items-center gap-1 text-current text-md font-medium')}>
<GlobeLogo className='w-6 h-6 text-current' />
<span>
{languageKey[router.locale]?.split('-')[0]}
{languageKey[locale]?.split('-')[0]}
</span>
</div>
</Listbox.Button>
Expand Down Expand Up @@ -140,7 +115,7 @@ export const LanguageDropdown = (props) => {
<SearchLanguageIcon width={16} height={16} />
</div>
<div className='flex flex-col gap-3 overflow-y-auto max-h-400'>
{languages.map((lang, i) => {
{languagesToShow.map((lang, i) => {
return (
<Listbox.Option key={i} value={lang}>
{({ selected, active }) => {
Expand Down Expand Up @@ -171,11 +146,11 @@ export const LanguageDropdown = (props) => {

<MobileLanguageDropdown
open={open && width <= 768}
languages={languages}
languages={languagesToShow}
searchValue={searchValue}
handleSearchLanguage={handleSearchLanguage}
i18n={i18n}
currentLanguage={languageKey[router.locale]}
currentLanguage={languageKey[locale]}
/>
</>
)
Expand Down
58 changes: 27 additions & 31 deletions src/hooks/useActiveLocale.jsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,34 @@
import { useRouter } from 'next/router'
// import { useRouter } from 'next/router'

import React from 'react'

import {
DEFAULT_LOCALE,
SUPPORTED_LOCALES
} from '../config/locales'
localStorageLocale,
navigatorLocale
} from '@/src/i18n/utils'

export const parseLocale = (maybeSupportedLocale) => {
const lowerMaybeSupportedLocale = maybeSupportedLocale.toLowerCase()
import { DEFAULT_LOCALE } from '../config/locales'

return SUPPORTED_LOCALES.find(
(locale) => {
return locale.toLowerCase() === lowerMaybeSupportedLocale ||
locale.split('-')[0] === lowerMaybeSupportedLocale
export const useActiveLocale = () => {
// const router = useRouter()
const [locale, setLocale] = React.useState(DEFAULT_LOCALE)

React.useEffect(() => {
const initialLocale = localStorageLocale() || navigatorLocale() || DEFAULT_LOCALE
setLocale(initialLocale)
}, [])

// React.useEffect(() => {
// if (locale !== router.locale) {
// router.replace(router.asPath, router.asPath, { locale })
// }
// }, [locale, router])

return {
locale,
setLocale: (newLocale) => {
localStorage.setItem('locale', newLocale)
setLocale(newLocale)
}
)
}

export const navigatorLocale = () => {
if (typeof window === 'undefined' || !window || !navigator || !navigator.language) { return undefined }

const [language, region] = navigator.language.split('-')

if (region) {
return (
parseLocale(`${language}-${region.toUpperCase()}`) ??
parseLocale(language)
)
}

return parseLocale(language)
}

export const useActiveLocale = () => {
const { locale } = useRouter()

return locale || navigatorLocale() || DEFAULT_LOCALE
}
38 changes: 29 additions & 9 deletions src/i18n/i18n.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {
import React, {
useCallback,
useEffect,
useState
} from 'react'

import { DEFAULT_LOCALE } from '@/src/config/locales'
import { dynamicActivate } from '@/src/i18n/dynamic-activate'
import { i18n } from '@lingui/core'
import { I18nProvider } from '@lingui/react'
Expand All @@ -14,14 +15,30 @@ const DefaultI18n = ({ children }) => {
return <span>{children}</span>
}

/**
* @type {React.Context<{ locale: string, setLocale: React.Dispatch<React.SetStateAction<string>> }>} LanguageContext
*/
const LanguageContext = React.createContext({
locale: DEFAULT_LOCALE,
setLocale: () => {}
})

export const useLanguageContext = () => {
const context = React.useContext(LanguageContext)
if (context === undefined) {
throw new Error('useLanguageContext must be used within a LanguageProvider')
}

return context
}

export function LanguageProvider ({ children }) {
const locale = useActiveLocale()
const { locale, setLocale } = useActiveLocale()
const [loaded, setLoaded] = useState(false)
const [refresh, setRefresh] = useState(false)

useEffect(() => {
let ignore = false

dynamicActivate(locale)
.then(() => {
/* istanbul ignore next */
Expand All @@ -34,9 +51,7 @@ export function LanguageProvider ({ children }) {
ignore = true
}
}, [locale])

const updateRefresh = useCallback(() => { return setRefresh(r => { return !r }) }, [])

useEffect(() => {
// Detect network change and manually refresh
if (window && window.addEventListener) {
Expand All @@ -45,13 +60,15 @@ export function LanguageProvider ({ children }) {

return () => { return window.removeEventListener('languagechange', updateRefresh) }
}, [updateRefresh])

useEffect(() => { console.log('refreshing...') }, [refresh])

useEffect(() => {
console.log('Locale %s loaded.', i18n.locale)
}, [])

const memoizedValue = React.useMemo(() => {
return { locale, setLocale }
}, [locale, setLocale])

if (!i18n.locale) {
// only log in browser
if (typeof window !== 'undefined') {
Expand All @@ -60,11 +77,14 @@ export function LanguageProvider ({ children }) {

return null
}

if (!loaded) {
// prevent the app from rendering with placeholder text before the locale is loaded
return null
}

return <I18nProvider i18n={i18n} defaultComponent={DefaultI18n}>{children}</I18nProvider>
return (
<LanguageContext.Provider value={memoizedValue}>
<I18nProvider i18n={i18n} defaultComponent={DefaultI18n}>{children}</I18nProvider>
</LanguageContext.Provider>
)
}
58 changes: 58 additions & 0 deletions src/i18n/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { SUPPORTED_LOCALES } from '../config/locales'

export const getBrowserLocale = () => {
const fallback = null

try {
return (
navigator.userLanguage ||
(navigator.languages &&
navigator.languages.length &&
navigator.languages[0]) ||
navigator.language ||
navigator.browserLanguage ||
navigator.systemLanguage ||
fallback
)
} catch {
// `navigator` is not available
}

return fallback
}

export const parseLocale = (maybeSupportedLocale) => {
if (!maybeSupportedLocale) {
return
}

const lowerMaybeSupportedLocale = maybeSupportedLocale.toLowerCase()

return SUPPORTED_LOCALES.find(
(locale) => {
return locale.toLowerCase() === lowerMaybeSupportedLocale ||
locale.split('-')[0] === lowerMaybeSupportedLocale
}
)
}

export const navigatorLocale = () => {
if (typeof window === 'undefined' || !window) { return undefined }

const [language, region] = getBrowserLocale().split('-')

if (region) {
return (
parseLocale(`${language}-${region.toUpperCase()}`) ??
parseLocale(language)
)
}

return parseLocale(language)
}

export const localStorageLocale = () => {
if (typeof window === 'undefined' || !window || !localStorage) { return undefined }

return parseLocale(localStorage.getItem('locale'))
}

0 comments on commit c78bcf4

Please sign in to comment.