Refactor settings menu and implement language changer in next
This commit is contained in:
parent
9919e0c16e
commit
3034605126
|
|
@ -1,5 +1,6 @@
|
||||||
import { Metadata } from 'next'
|
import { Metadata } from 'next'
|
||||||
|
|
||||||
|
import Settings from '/src/components/Settings/Settings'
|
||||||
import { fallbackLng } from '/src/i18n/options'
|
import { fallbackLng } from '/src/i18n/options'
|
||||||
import { useTranslation } from '/src/i18n/server'
|
import { useTranslation } from '/src/i18n/server'
|
||||||
|
|
||||||
|
|
@ -24,10 +25,11 @@ export const metadata: Metadata = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const RootLayout = async ({ children }: { children: React.ReactNode }) => {
|
const RootLayout = async ({ children }: { children: React.ReactNode }) => {
|
||||||
const { i18n } = await useTranslation([])
|
const { resolvedLanguage } = await useTranslation([])
|
||||||
|
|
||||||
return <html lang={i18n.resolvedLanguage ?? fallbackLng}>
|
return <html lang={resolvedLanguage ?? fallbackLng}>
|
||||||
<body>
|
<body>
|
||||||
|
<Settings />
|
||||||
{children}
|
{children}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||||
import Button from '/src/components/Button/Button'
|
import Button from '/src/components/Button/Button'
|
||||||
import dayjs from '/src/config/dayjs'
|
import dayjs from '/src/config/dayjs'
|
||||||
import { useTranslation } from '/src/i18n/client'
|
import { useTranslation } from '/src/i18n/client'
|
||||||
import useLocaleUpdateStore from '/src/stores/localeUpdateStore'
|
import { useStore } from '/src/stores'
|
||||||
import useSettingsStore from '/src/stores/settingsStore'
|
import useSettingsStore from '/src/stores/settingsStore'
|
||||||
import { makeClass } from '/src/utils'
|
import { makeClass } from '/src/utils'
|
||||||
|
|
||||||
|
|
@ -23,8 +23,7 @@ interface MonthProps {
|
||||||
const Month = ({ value, onChange }: MonthProps) => {
|
const Month = ({ value, onChange }: MonthProps) => {
|
||||||
const { t } = useTranslation('home')
|
const { t } = useTranslation('home')
|
||||||
|
|
||||||
const weekStart = useSettingsStore(state => state.weekStart)
|
const weekStart = useStore(useSettingsStore, state => state.weekStart) ?? 0
|
||||||
const locale = useLocaleUpdateStore(state => state.locale)
|
|
||||||
|
|
||||||
const [page, setPage] = useState({
|
const [page, setPage] = useState({
|
||||||
month: dayjs().month(),
|
month: dayjs().month(),
|
||||||
|
|
@ -45,11 +44,9 @@ const Month = ({ value, onChange }: MonthProps) => {
|
||||||
|
|
||||||
// Update month view
|
// Update month view
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dayjs.Ls?.[locale] && weekStart !== dayjs.Ls[locale].weekStart) {
|
dayjs.updateLocale(dayjs.locale(), { weekStart })
|
||||||
dayjs.updateLocale(locale, { weekStart })
|
|
||||||
}
|
|
||||||
setDates(calculateMonth(page, weekStart))
|
setDates(calculateMonth(page, weekStart))
|
||||||
}, [weekStart, page, locale])
|
}, [weekStart, page])
|
||||||
|
|
||||||
const handleFinishSelection = useCallback(() => {
|
const handleFinishSelection = useCallback(() => {
|
||||||
if (mode.current === 'add') {
|
if (mode.current === 'add') {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useCallback, useMemo, useRef, useState } from 'react'
|
||||||
|
|
||||||
import dayjs from '/src/config/dayjs'
|
import dayjs from '/src/config/dayjs'
|
||||||
import { useTranslation } from '/src/i18n/client'
|
import { useTranslation } from '/src/i18n/client'
|
||||||
|
import { useStore } from '/src/stores'
|
||||||
import useSettingsStore from '/src/stores/settingsStore'
|
import useSettingsStore from '/src/stores/settingsStore'
|
||||||
import { makeClass } from '/src/utils'
|
import { makeClass } from '/src/utils'
|
||||||
|
|
||||||
|
|
@ -21,7 +22,7 @@ interface WeekdaysProps {
|
||||||
const Weekdays = ({ value, onChange }: WeekdaysProps) => {
|
const Weekdays = ({ value, onChange }: WeekdaysProps) => {
|
||||||
const { t } = useTranslation('home')
|
const { t } = useTranslation('home')
|
||||||
|
|
||||||
const weekStart = useSettingsStore(state => state.weekStart)
|
const weekStart = useStore(useSettingsStore, state => state.weekStart) ?? 0
|
||||||
|
|
||||||
const weekdays = useMemo(() => rotateArray(dayjs.weekdaysShort().map((name, i) => ({
|
const weekdays = useMemo(() => rotateArray(dayjs.weekdaysShort().map((name, i) => ({
|
||||||
name,
|
name,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import { styled } from 'goober'
|
|
||||||
|
|
||||||
const Center = styled('div')`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default Center
|
|
||||||
|
|
@ -48,7 +48,6 @@ const CreateForm = () => {
|
||||||
const [error, setError] = useState<React.ReactNode>()
|
const [error, setError] = useState<React.ReactNode>()
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<Fields> = async values => {
|
const onSubmit: SubmitHandler<Fields> = async values => {
|
||||||
console.log({values}) // TODO:
|
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
setError(undefined)
|
setError(undefined)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import { styled } from 'goober'
|
.openButton {
|
||||||
|
|
||||||
export const OpenButton = styled('button')`
|
|
||||||
border: 0;
|
border: 0;
|
||||||
background: none;
|
background: none;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
|
|
@ -19,58 +17,63 @@ export const OpenButton = styled('button')`
|
||||||
transition: transform .15s;
|
transition: transform .15s;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
${props => props.$isOpen && `
|
|
||||||
transform: rotate(-60deg);
|
|
||||||
`}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
@media print {
|
@media print {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
`
|
}
|
||||||
|
|
||||||
export const Cover = styled('div')`
|
.open {
|
||||||
position: fixed;
|
transform: rotate(-60deg);
|
||||||
top: 0;
|
}
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 100;
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
${props => props.$isOpen && `
|
.modal {
|
||||||
display: block;
|
|
||||||
`}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const Modal = styled('div')`
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 70px;
|
inset: 0;
|
||||||
right: 12px;
|
max-width: initial;
|
||||||
background-color: var(--background);
|
min-height: initial;
|
||||||
border: 1px solid var(--surface);
|
width: initial;
|
||||||
z-index: 150;
|
height: initial;
|
||||||
padding: 10px 18px;
|
padding: 0;
|
||||||
border-radius: 3px;
|
margin: 0;
|
||||||
width: 270px;
|
background: none;
|
||||||
box-sizing: border-box;
|
border: none;
|
||||||
max-width: calc(100% - 20px);
|
overflow: visible;
|
||||||
box-shadow: 0 3px 6px 0 rgba(0,0,0,.3);
|
|
||||||
|
|
||||||
|
display: block;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(-10px);
|
transform: translateY(-10px);
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
transition: opacity .15s, transform .15s, visibility .15s;
|
transition: opacity .15s, transform .15s, visibility .15s;
|
||||||
|
|
||||||
${props => props.$isOpen && `
|
& > div {
|
||||||
|
position: absolute;
|
||||||
|
top: 70px;
|
||||||
|
right: 12px;
|
||||||
|
background-color: var(--background);
|
||||||
|
border: 1px solid var(--surface);
|
||||||
|
z-index: 150;
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: 3px;
|
||||||
|
width: 270px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-width: calc(100% - 20px);
|
||||||
|
box-shadow: 0 3px 6px 0 rgba(0,0,0,.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[open] {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
`}
|
}
|
||||||
|
|
||||||
|
&::backdrop {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
transition: none;
|
transition: none;
|
||||||
|
|
@ -78,11 +81,11 @@ export const Modal = styled('div')`
|
||||||
@media print {
|
@media print {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
`
|
}
|
||||||
|
|
||||||
export const Heading = styled('span')`
|
.heading {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
display: block;
|
display: block;
|
||||||
margin: 6px 0;
|
margin: 6px 0;
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
`
|
}
|
||||||
|
|
@ -1,136 +1,99 @@
|
||||||
import { useState, useEffect, useRef } from 'react'
|
'use client'
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import dayjs from 'dayjs'
|
import { useCallback, useRef, useState } from 'react'
|
||||||
import { Settings as SettingsIcon } from 'lucide-react'
|
import { useRouter } from 'next/navigation'
|
||||||
import { maps } from 'hue-map'
|
import { maps } from 'hue-map'
|
||||||
|
import { Settings as SettingsIcon } from 'lucide-react'
|
||||||
|
|
||||||
import { ToggleField, SelectField } from '/src/components'
|
import SelectField from '/src/components/SelectField/SelectField'
|
||||||
|
import ToggleField from '/src/components/ToggleField/ToggleField'
|
||||||
|
import dayjs from '/src/config/dayjs'
|
||||||
|
import { useTranslation } from '/src/i18n/client'
|
||||||
|
import { languageDetails } from '/src/i18n/options'
|
||||||
|
import { useStore } from '/src/stores'
|
||||||
|
import useSettingsStore from '/src/stores/settingsStore'
|
||||||
|
import { makeClass, unhyphenate } from '/src/utils'
|
||||||
|
|
||||||
import { useSettingsStore, useLocaleUpdateStore } from '/src/stores'
|
import styles from './Settings.module.scss'
|
||||||
|
|
||||||
import {
|
// TODO: add to giraugh tools
|
||||||
OpenButton,
|
const isKeyOfObject = <T extends object>(
|
||||||
Modal,
|
key: string | number | symbol,
|
||||||
Heading,
|
obj: T,
|
||||||
Cover,
|
): key is keyof T => key in obj
|
||||||
} from './Settings.styles'
|
|
||||||
|
|
||||||
import locales from '/src/i18n/locales'
|
|
||||||
import { unhyphenate } from '/src/utils'
|
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
|
|
||||||
// Language specific options
|
|
||||||
const setDefaults = (lang, store) => {
|
|
||||||
if (locales[lang]) {
|
|
||||||
store.setWeekStart(locales[lang].weekStart)
|
|
||||||
store.setTimeFormat(locales[lang].timeFormat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
const { pathname } = useRouter()
|
|
||||||
const store = useSettingsStore()
|
|
||||||
const [isOpen, _setIsOpen] = useState(false)
|
|
||||||
const { t, i18n } = useTranslation('common')
|
const { t, i18n } = useTranslation('common')
|
||||||
const setLocale = useLocaleUpdateStore(state => state.setLocale)
|
const router = useRouter()
|
||||||
const firstControlRef = useRef()
|
|
||||||
|
|
||||||
const onEsc = e => {
|
const store = useStore(useSettingsStore, state => state)
|
||||||
if (e.key === 'Escape') {
|
|
||||||
setIsOpen(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const setIsOpen = open => {
|
const modalRef = useRef<HTMLDialogElement>(null)
|
||||||
_setIsOpen(open)
|
const [isOpen, _setIsOpen] = useState(false)
|
||||||
|
const setIsOpen = useCallback((shouldOpen: boolean) => {
|
||||||
if (open) {
|
if (shouldOpen) {
|
||||||
window.setTimeout(() => firstControlRef.current?.focus(), 150)
|
modalRef.current?.showModal()
|
||||||
document.addEventListener('keyup', onEsc, true)
|
_setIsOpen(true)
|
||||||
} else {
|
} else {
|
||||||
document.removeEventListener('keyup', onEsc)
|
modalRef.current?.close()
|
||||||
|
_setIsOpen(false)
|
||||||
}
|
}
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
return <>
|
||||||
if (Object.keys(locales).includes(i18n.language)) {
|
<button
|
||||||
locales[i18n.language].import().then(() => {
|
type="button"
|
||||||
dayjs.locale(i18n.language)
|
className={makeClass(styles.openButton, isOpen && styles.open)}
|
||||||
setLocale(dayjs.locale())
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
document.documentElement.setAttribute('lang', i18n.language)
|
title={t<string>('options.name')}
|
||||||
})
|
><SettingsIcon /></button>
|
||||||
} else {
|
|
||||||
setLocale('en')
|
|
||||||
document.documentElement.setAttribute('lang', 'en')
|
|
||||||
}
|
|
||||||
}, [i18n.language, setLocale])
|
|
||||||
|
|
||||||
if (!i18n.options.storedLang) {
|
<dialog
|
||||||
setDefaults(i18n.language, store)
|
className={styles.modal}
|
||||||
i18n.options.storedLang = i18n.language
|
ref={modalRef}
|
||||||
}
|
onClose={() => _setIsOpen(false)}
|
||||||
|
onClick={() => modalRef.current?.close()}
|
||||||
i18n.on('languageChanged', lang => {
|
>
|
||||||
setDefaults(lang, store)
|
<div onClick={e => e.stopPropagation()}>
|
||||||
})
|
<span className={styles.heading}>{t('options.name')}</span>
|
||||||
|
|
||||||
// Reset scroll on navigation
|
|
||||||
useEffect(() => window.scrollTo(0, 0), [pathname])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<OpenButton
|
|
||||||
$isOpen={isOpen}
|
|
||||||
type="button"
|
|
||||||
onClick={() => setIsOpen(!isOpen)} title={t('options.name')}
|
|
||||||
><SettingsIcon /></OpenButton>
|
|
||||||
|
|
||||||
<Cover $isOpen={isOpen} onClick={() => setIsOpen(false)} />
|
|
||||||
<Modal $isOpen={isOpen}>
|
|
||||||
<Heading>{t('options.name')}</Heading>
|
|
||||||
|
|
||||||
<ToggleField
|
<ToggleField
|
||||||
label={t('options.weekStart.label')}
|
label={t('options.weekStart.label')}
|
||||||
name="weekStart"
|
name="weekStart"
|
||||||
id="weekStart"
|
|
||||||
options={{
|
options={{
|
||||||
'Sunday': t('options.weekStart.options.Sunday'),
|
'Sunday': t('options.weekStart.options.Sunday'),
|
||||||
'Monday': t('options.weekStart.options.Monday'),
|
'Monday': t('options.weekStart.options.Monday'),
|
||||||
}}
|
}}
|
||||||
value={store.weekStart === 0 ? 'Sunday' : 'Monday'}
|
value={store?.weekStart === 0 ? 'Sunday' : 'Monday'}
|
||||||
onChange={value => store.setWeekStart(value === 'Sunday' ? 0 : 1)}
|
onChange={value => store?.setWeekStart(value === 'Sunday' ? 0 : 1)}
|
||||||
inputRef={firstControlRef}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ToggleField
|
<ToggleField
|
||||||
label={t('options.timeFormat.label')}
|
label={t('options.timeFormat.label')}
|
||||||
name="timeFormat"
|
name="timeFormat"
|
||||||
id="timeFormat"
|
|
||||||
options={{
|
options={{
|
||||||
'12h': t('options.timeFormat.options.12h'),
|
'12h': t('options.timeFormat.options.12h'),
|
||||||
'24h': t('options.timeFormat.options.24h'),
|
'24h': t('options.timeFormat.options.24h'),
|
||||||
}}
|
}}
|
||||||
value={store.timeFormat}
|
value={store?.timeFormat ?? '12h'}
|
||||||
onChange={value => store.setTimeFormat(value)}
|
onChange={value => store?.setTimeFormat(value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ToggleField
|
<ToggleField
|
||||||
label={t('options.theme.label')}
|
label={t('options.theme.label')}
|
||||||
name="theme"
|
name="theme"
|
||||||
id="theme"
|
|
||||||
options={{
|
options={{
|
||||||
'System': t('options.theme.options.System'),
|
'System': t('options.theme.options.System'),
|
||||||
'Light': t('options.theme.options.Light'),
|
'Light': t('options.theme.options.Light'),
|
||||||
'Dark': t('options.theme.options.Dark'),
|
'Dark': t('options.theme.options.Dark'),
|
||||||
}}
|
}}
|
||||||
value={store.theme}
|
value={store?.theme ?? 'System'}
|
||||||
onChange={value => store.setTheme(value)}
|
onChange={value => store?.setTheme(value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectField
|
<SelectField
|
||||||
label={t('options.colormap.label')}
|
label={t('options.colormap.label')}
|
||||||
name="colormap"
|
name="colormap"
|
||||||
id="colormap"
|
|
||||||
options={{
|
options={{
|
||||||
'crabfit': t('options.colormap.classic'),
|
'crabfit': t('options.colormap.classic'),
|
||||||
...Object.fromEntries(Object.keys(maps).sort().map(palette => [
|
...Object.fromEntries(Object.keys(maps).sort().map(palette => [
|
||||||
|
|
@ -138,22 +101,21 @@ const Settings = () => {
|
||||||
unhyphenate(palette)
|
unhyphenate(palette)
|
||||||
])),
|
])),
|
||||||
}}
|
}}
|
||||||
small
|
isSmall
|
||||||
value={store.colormap}
|
value={store?.colormap}
|
||||||
onChange={event => store.setColormap(event.target.value)}
|
onChange={event => store?.setColormap(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ToggleField
|
<ToggleField
|
||||||
label={t('options.highlight.label')}
|
label={t('options.highlight.label')}
|
||||||
name="highlight"
|
name="highlight"
|
||||||
id="highlight"
|
description={t('options.highlight.title')}
|
||||||
title={t('options.highlight.title')}
|
|
||||||
options={{
|
options={{
|
||||||
'Off': t('options.highlight.options.Off'),
|
'Off': t('options.highlight.options.Off'),
|
||||||
'On': t('options.highlight.options.On'),
|
'On': t('options.highlight.options.On'),
|
||||||
}}
|
}}
|
||||||
value={store.highlight ? 'On' : 'Off'}
|
value={store?.highlight ? 'On' : 'Off'}
|
||||||
onChange={value => store.setHighlight(value === 'On')}
|
onChange={value => store?.setHighlight(value === 'On')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectField
|
<SelectField
|
||||||
|
|
@ -161,19 +123,26 @@ const Settings = () => {
|
||||||
name="language"
|
name="language"
|
||||||
id="language"
|
id="language"
|
||||||
options={{
|
options={{
|
||||||
...Object.keys(locales).reduce((ls, l) => {
|
...Object.fromEntries(Object.entries(languageDetails).map(([id, details]) => [id, details.name])),
|
||||||
ls[l] = locales[l].name
|
|
||||||
return ls
|
|
||||||
}, {}),
|
|
||||||
...process.env.NODE_ENV !== 'production' && { 'cimode': 'DEV' },
|
...process.env.NODE_ENV !== 'production' && { 'cimode': 'DEV' },
|
||||||
}}
|
}}
|
||||||
small
|
isSmall
|
||||||
value={i18n.language}
|
value={i18n.language}
|
||||||
onChange={event => i18n.changeLanguage(event.target.value)}
|
onChange={e => {
|
||||||
|
if (isKeyOfObject(e.target.value, languageDetails)) {
|
||||||
|
store?.setWeekStart(languageDetails[e.target.value].weekStart)
|
||||||
|
store?.setTimeFormat(languageDetails[e.target.value].timeFormat)
|
||||||
|
|
||||||
|
languageDetails[e.target.value]?.import().then(() => {
|
||||||
|
dayjs.locale(e.target.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
i18n.changeLanguage(e.target.value).then(() => router.refresh())
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</div>
|
||||||
</>
|
</dialog>
|
||||||
)
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Settings
|
export default Settings
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { FieldValues, useController, UseControllerProps } from 'react-hook-form'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import { Description, Label, Wrapper } from '/src/components/Field/Field'
|
import { Description, Label, Wrapper } from '/src/components/Field/Field'
|
||||||
|
import { useStore } from '/src/stores'
|
||||||
import useSettingsStore from '/src/stores/settingsStore'
|
import useSettingsStore from '/src/stores/settingsStore'
|
||||||
|
|
||||||
import styles from './TimeRangeField.module.scss'
|
import styles from './TimeRangeField.module.scss'
|
||||||
|
|
@ -67,7 +68,7 @@ interface HandleProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Handle = ({ value, onChange, labelPadding }: HandleProps) => {
|
const Handle = ({ value, onChange, labelPadding }: HandleProps) => {
|
||||||
const timeFormat = useSettingsStore(state => state.timeFormat)
|
const timeFormat = useStore(useSettingsStore, state => state.timeFormat)
|
||||||
|
|
||||||
const isMoving = useRef(false)
|
const isMoving = useRef(false)
|
||||||
const rangeRect = useRef({ left: 0, width: 0 })
|
const rangeRect = useRef({ left: 0, width: 0 })
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { initReactI18next, useTranslation as useTranslationHook } from 'react-i18next'
|
import { initReactI18next, useTranslation as useTranslationHook } from 'react-i18next'
|
||||||
|
import { cookies } from 'next/dist/client/components/headers' // risky disky (undocumented???)
|
||||||
import i18next from 'i18next'
|
import i18next from 'i18next'
|
||||||
import LanguageDetector from 'i18next-browser-languagedetector'
|
import LanguageDetector from 'i18next-browser-languagedetector'
|
||||||
import resourcesToBackend from 'i18next-resources-to-backend'
|
import resourcesToBackend from 'i18next-resources-to-backend'
|
||||||
|
|
||||||
import { getOptions } from './options'
|
import dayjs from '/src/config/dayjs'
|
||||||
|
|
||||||
|
import { cookieName, getOptions, languageDetails } from './options'
|
||||||
|
|
||||||
i18next
|
i18next
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
|
|
@ -16,10 +18,18 @@ i18next
|
||||||
))
|
))
|
||||||
.init({
|
.init({
|
||||||
...getOptions(),
|
...getOptions(),
|
||||||
lng: undefined,
|
lng: typeof window === 'undefined' ? cookies().get(cookieName)?.value : undefined,
|
||||||
detection: {
|
detection: {
|
||||||
order: ['htmlTag', 'cookie', 'navigator'],
|
order: ['htmlTag', 'cookie', 'navigator'],
|
||||||
|
caches: ['localStorage', 'cookie'],
|
||||||
|
excludeCacheFor: [],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Set dayjs locale
|
||||||
|
languageDetails[i18next.resolvedLanguage as keyof typeof languageDetails]?.import().then(() => {
|
||||||
|
dayjs.locale(i18next.resolvedLanguage)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
export const useTranslation: typeof useTranslationHook = (ns, options) => useTranslationHook(ns, options)
|
export const useTranslation: typeof useTranslationHook = (ns, options) => useTranslationHook(ns, options)
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ interface LanguageDetails {
|
||||||
/** TODO: document */
|
/** TODO: document */
|
||||||
separator?: string
|
separator?: string
|
||||||
/** Day.js locale import */
|
/** Day.js locale import */
|
||||||
import: () => unknown
|
import: () => Promise<unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const languageDetails: Record<typeof languages[number], LanguageDetails> = {
|
export const languageDetails: Record<typeof languages[number], LanguageDetails> = {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { UseTranslationOptions } from 'react-i18next'
|
|
||||||
import { cookies, headers } from 'next/headers'
|
import { cookies, headers } from 'next/headers'
|
||||||
import acceptLanguage from 'accept-language'
|
import acceptLanguage from 'accept-language'
|
||||||
import { createInstance } from 'i18next'
|
import { createInstance } from 'i18next'
|
||||||
import resourcesToBackend from 'i18next-resources-to-backend'
|
import resourcesToBackend from 'i18next-resources-to-backend'
|
||||||
|
|
||||||
import { cookieName, fallbackLng, getOptions, languages } from './options'
|
import dayjs from '/src/config/dayjs'
|
||||||
|
|
||||||
|
import { cookieName, fallbackLng, getOptions, languageDetails, languages } from './options'
|
||||||
|
|
||||||
type Mutable<T> = { -readonly [K in keyof T]: Mutable<T[K]> }
|
type Mutable<T> = { -readonly [K in keyof T]: Mutable<T[K]> }
|
||||||
|
|
||||||
|
|
@ -20,14 +21,20 @@ const initI18next = async (language: string, ns: string | string []) => {
|
||||||
return i18nInstance
|
return i18nInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTranslation = async (ns: string | string[], options: UseTranslationOptions = {}) => {
|
export const useTranslation = async (ns: string | string[], options: { keyPrefix?: string } = {}) => {
|
||||||
const language = cookies().get(cookieName)?.value
|
const language = cookies().get(cookieName)?.value
|
||||||
?? acceptLanguage.get(headers().get('Accept-Language'))
|
?? acceptLanguage.get(headers().get('Accept-Language'))
|
||||||
?? fallbackLng
|
?? fallbackLng
|
||||||
|
|
||||||
|
// Set dayjs locale
|
||||||
|
languageDetails[language as keyof typeof languageDetails]?.import().then(() => {
|
||||||
|
dayjs.locale(language)
|
||||||
|
})
|
||||||
|
|
||||||
const i18nextInstance = await initI18next(language, ns)
|
const i18nextInstance = await initI18next(language, ns)
|
||||||
return {
|
return {
|
||||||
t: i18nextInstance.getFixedT(language, Array.isArray(ns) ? ns[0] : ns, options.keyPrefix),
|
t: i18nextInstance.getFixedT(language, Array.isArray(ns) ? ns[0] : ns, options.keyPrefix),
|
||||||
i18n: i18nextInstance
|
i18n: i18nextInstance,
|
||||||
|
resolvedLanguage: language,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue