diff --git a/frontend/package.json b/frontend/package.json index c490d33..e903838 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,7 +15,6 @@ "@js-temporal/polyfill": "^0.4.4", "@microsoft/microsoft-graph-client": "^3.0.5", "accept-language": "^3.0.18", - "dayjs": "^1.11.7", "gapi-script": "^1.2.0", "goober": "^2.1.13", "hue-map": "^1.0.0", diff --git a/frontend/src/components/CalendarField/CalendarField.tsx b/frontend/src/components/CalendarField/CalendarField.tsx index 919614a..58e9286 100644 --- a/frontend/src/components/CalendarField/CalendarField.tsx +++ b/frontend/src/components/CalendarField/CalendarField.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react' import { FieldValues, useController, UseControllerProps } from 'react-hook-form' import { useTranslation } from 'react-i18next' +import { Temporal } from '@js-temporal/polyfill' import { Description, Label, Wrapper } from '/src/components/Field/Field' import ToggleField from '/src/components/ToggleField/ToggleField' @@ -27,7 +28,7 @@ const CalendarField = ({ const [innerValue, setInnerValue] = useState({ specific: [], week: [], - } satisfies Record) + } satisfies Record) useEffect(() => { setInnerValue({ ...innerValue, [type]: field.value }) diff --git a/frontend/src/components/CalendarField/components/Month/Month.tsx b/frontend/src/components/CalendarField/components/Month/Month.tsx index 1b25487..e518abe 100644 --- a/frontend/src/components/CalendarField/components/Month/Month.tsx +++ b/frontend/src/components/CalendarField/components/Month/Month.tsx @@ -1,34 +1,29 @@ -import { useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useMemo, useRef, useState } from 'react' import { rotateArray } from '@giraugh/tools' -import { Dayjs } from 'dayjs' +import { Temporal } from '@js-temporal/polyfill' import { ChevronLeft, ChevronRight } from 'lucide-react' import Button from '/src/components/Button/Button' -import { useDayjs } from '/src/config/dayjs' import { useTranslation } from '/src/i18n/client' import { useStore } from '/src/stores' import useSettingsStore from '/src/stores/settingsStore' -import { makeClass } from '/src/utils' +import { getWeekdayNames, makeClass } from '/src/utils' import styles from './Month.module.scss' interface MonthProps { - /** Array of dates in `DDMMYYYY` format */ + /** Stringified PlainDate `YYYY-MM-DD` */ value: string[] onChange: (value: string[]) => void } const Month = ({ value, onChange }: MonthProps) => { - const { t } = useTranslation('home') - const dayjs = useDayjs() + const { t, i18n } = useTranslation('home') - const weekStart = useStore(useSettingsStore, state => state.weekStart) ?? 0 + const weekStart = useStore(useSettingsStore, state => state.weekStart) ?? 1 - const [page, setPage] = useState({ - month: dayjs().month(), - year: dayjs().year(), - }) - const [dates, setDates] = useState(calculateMonth(dayjs().month(page.month).year(page.year), weekStart)) + const [page, setPage] = useState(Temporal.Now.plainDateISO().toPlainYearMonth()) + const dates = useMemo(() => calculateMonth(page, weekStart), [page, weekStart]) // Ref and state required to rerender but also access static version in callbacks const selectingRef = useRef([]) @@ -41,12 +36,6 @@ const Month = ({ value, onChange }: MonthProps) => { const startPos = useRef({ x: 0, y: 0 }) const mode = useRef<'add' | 'remove'>() - // Update month view - useEffect(() => { - dayjs.updateLocale(dayjs.locale(), { weekStart }) - setDates(calculateMonth(dayjs().month(page.month).year(page.year), weekStart)) - }, [weekStart, page]) - const handleFinishSelection = useCallback(() => { if (mode.current === 'add') { onChange([...value, ...selectingRef.current]) @@ -60,31 +49,19 @@ const Month = ({ value, onChange }: MonthProps) => {
- {(rotateArray(dayjs.weekdaysShort(), -weekStart)).map(name => + {(rotateArray(getWeekdayNames(i18n.language, 'short'), weekStart)).map(name => )}
@@ -96,27 +73,27 @@ const Month = ({ value, onChange }: MonthProps) => { className={makeClass( styles.date, date.month !== page.month && styles.otherMonth, - date.isToday && styles.today, + date.equals(Temporal.Now.plainDateISO()) && styles.today, ( - (!(mode.current === 'remove' && selecting.includes(date.str)) && value.includes(date.str)) - || (mode.current === 'add' && selecting.includes(date.str)) + (!(mode.current === 'remove' && selecting.includes(date.toString())) && value.includes(date.toString())) + || (mode.current === 'add' && selecting.includes(date.toString())) ) && styles.selected, )} - key={date.str} - title={`${date.day} ${dayjs.months()[date.month]}${date.isToday ? ` (${t('form.dates.tooltips.today')})` : ''}`} + key={date.toString()} + title={`${date.toLocaleString(i18n.language, { day: 'numeric', month: 'long' })}${date.equals(Temporal.Now.plainDateISO()) ? ` (${t('form.dates.tooltips.today')})` : ''}`} onKeyDown={e => { if (e.key === ' ' || e.key === 'Enter') { - if (value.includes(date.str)) { - onChange(value.filter(d => d !== date.str)) + if (value.includes(date.toString())) { + onChange(value.filter(d => d !== date.toString())) } else { - onChange([...value, date.str]) + onChange([...value, date.toString()]) } } }} onPointerDown={e => { startPos.current = { x, y } - mode.current = value.includes(date.str) ? 'remove' : 'add' - setSelecting([date.str]) + mode.current = value.includes(date.toString()) ? 'remove' : 'add' + setSelecting([date.toString()]) e.currentTarget.releasePointerCapture(e.pointerId) document.addEventListener('pointerup', handleFinishSelection, { once: true }) @@ -129,10 +106,10 @@ const Month = ({ value, onChange }: MonthProps) => { found.push({ y: cy, x: cx }) } } - setSelecting(found.map(d => dates[d.y][d.x].str)) + setSelecting(found.map(d => dates[d.y][d.x].toString())) } }} - >{date.day}) + >{date.toLocaleString(i18n.language, { day: 'numeric' })}) )} @@ -140,32 +117,19 @@ const Month = ({ value, onChange }: MonthProps) => { export default Month -interface Date { - str: string - day: number - month: number - isToday: boolean -} - /** Calculate the dates to show for the month in a 2d array */ -const calculateMonth = (date: Dayjs, weekStart: 0 | 1) => { - const daysInMonth = date.daysInMonth() - const daysBefore = date.date(1).day() - weekStart - const daysAfter = 6 - date.date(daysInMonth).day() + weekStart +const calculateMonth = (month: Temporal.PlainYearMonth, weekStart: 0 | 1) => { + const daysBefore = month.toPlainDate({ day: 1 }).dayOfWeek - (weekStart ? 0 : 1) + const daysAfter = 6 - month.toPlainDate({ day: month.daysInMonth }).dayOfWeek + (weekStart ? 0 : 1) - const dates: Date[][] = [] - let curDate = date.date(1).subtract(daysBefore, 'day') + const dates: Temporal.PlainDate[][] = [] + let curDate = month.toPlainDate({ day: 1 }).subtract({ days: daysBefore }) let y = 0 let x = 0 - for (let i = 0; i < daysBefore + daysInMonth + daysAfter; i++) { + for (let i = 0; i < daysBefore + month.daysInMonth + daysAfter; i++) { if (x === 0) dates[y] = [] - dates[y][x] = { - str: curDate.format('DDMMYYYY'), - day: curDate.date(), - month: curDate.month(), - isToday: curDate.isToday(), - } - curDate = curDate.add(1, 'day') + dates[y][x] = curDate + curDate = curDate.add({ days: 1 }) x++ if (x > 6) { x = 0 diff --git a/frontend/src/components/CalendarField/components/Weekdays/Weekdays.tsx b/frontend/src/components/CalendarField/components/Weekdays/Weekdays.tsx index 9d922ae..f77d375 100644 --- a/frontend/src/components/CalendarField/components/Weekdays/Weekdays.tsx +++ b/frontend/src/components/CalendarField/components/Weekdays/Weekdays.tsx @@ -1,7 +1,7 @@ import { useCallback, useMemo, useRef, useState } from 'react' -import { rotateArray } from '@giraugh/tools' +import { range, rotateArray } from '@giraugh/tools' +import { Temporal } from '@js-temporal/polyfill' -import { useDayjs } from '/src/config/dayjs' import { useTranslation } from '/src/i18n/client' import { useStore } from '/src/stores' import useSettingsStore from '/src/stores/settingsStore' @@ -11,22 +11,17 @@ import { makeClass } from '/src/utils' import styles from '../Month/Month.module.scss' interface WeekdaysProps { - /** Array of weekdays as numbers from 0-6 (as strings) */ + /** dayOfWeek 1-7 as a string */ value: string[] onChange: (value: string[]) => void } const Weekdays = ({ value, onChange }: WeekdaysProps) => { - const { t } = useTranslation('home') - const dayjs = useDayjs() + const { t, i18n } = useTranslation('home') - const weekStart = useStore(useSettingsStore, state => state.weekStart) ?? 0 + const weekStart = useStore(useSettingsStore, state => state.weekStart) ?? 1 - const weekdays = useMemo(() => rotateArray(dayjs.weekdaysShort().map((name, i) => ({ - name, - isToday: dayjs().day() === i, - str: String(i), - })), -weekStart), [weekStart]) + const weekdays = useMemo(() => rotateArray(range(1, 7).map(i => Temporal.Now.plainDateISO().add({ days: i - Temporal.Now.plainDateISO().dayOfWeek })), weekStart), [weekStart]) // Ref and state required to rerender but also access static version in callbacks const selectingRef = useRef([]) @@ -54,27 +49,27 @@ const Weekdays = ({ value, onChange }: WeekdaysProps) => { type="button" className={makeClass( styles.date, - day.isToday && styles.today, + day.equals(Temporal.Now.plainDateISO()) && styles.today, ( - (!(mode.current === 'remove' && selecting.includes(day.str)) && value.includes(day.str)) - || (mode.current === 'add' && selecting.includes(day.str)) + (!(mode.current === 'remove' && selecting.includes(day.dayOfWeek.toString())) && value.includes(day.dayOfWeek.toString())) + || (mode.current === 'add' && selecting.includes(day.dayOfWeek.toString())) ) && styles.selected, )} - key={day.name} - title={day.isToday ? t('form.dates.tooltips.today') : undefined} + key={day.toString()} + title={day.equals(Temporal.Now.plainDateISO()) ? t('form.dates.tooltips.today') : undefined} onKeyDown={e => { if (e.key === ' ' || e.key === 'Enter') { - if (value.includes(day.str)) { - onChange(value.filter(d => d !== day.str)) + if (value.includes(day.dayOfWeek.toString())) { + onChange(value.filter(d => d !== day.dayOfWeek.toString())) } else { - onChange([...value, day.str]) + onChange([...value, day.dayOfWeek.toString()]) } } }} onPointerDown={e => { startPos.current = i - mode.current = value.includes(day.str) ? 'remove' : 'add' - setSelecting([day.str]) + mode.current = value.includes(day.dayOfWeek.toString()) ? 'remove' : 'add' + setSelecting([day.dayOfWeek.toString()]) e.currentTarget.releasePointerCapture(e.pointerId) document.addEventListener('pointerup', handleFinishSelection, { once: true }) @@ -83,12 +78,12 @@ const Weekdays = ({ value, onChange }: WeekdaysProps) => { if (mode.current) { const found = [] for (let ci = Math.min(startPos.current, i); ci < Math.max(startPos.current, i) + 1; ci++) { - found.push(weekdays[ci].str) + found.push(weekdays[ci].dayOfWeek.toString()) } setSelecting(found) } }} - >{day.name} + >{day.toLocaleString(i18n.language, { weekday: 'short' })} )} } diff --git a/frontend/src/components/CreateForm/CreateForm.tsx b/frontend/src/components/CreateForm/CreateForm.tsx index 4c6ffe3..755a7b3 100644 --- a/frontend/src/components/CreateForm/CreateForm.tsx +++ b/frontend/src/components/CreateForm/CreateForm.tsx @@ -3,6 +3,7 @@ import { useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { useRouter } from 'next/navigation' +import { Temporal } from '@js-temporal/polyfill' import Button from '/src/components/Button/Button' import CalendarField from '/src/components/CalendarField/CalendarField' @@ -11,7 +12,6 @@ import SelectField from '/src/components/SelectField/SelectField' import TextField from '/src/components/TextField/TextField' import TimeRangeField from '/src/components/TimeRangeField/TimeRangeField' import { API_BASE } from '/src/config/api' -import { useDayjs } from '/src/config/dayjs' import { useTranslation } from '/src/i18n/client' import timezones from '/src/res/timezones.json' @@ -36,7 +36,6 @@ const defaultValues: Fields = { const CreateForm = () => { const { t } = useTranslation('home') - const dayjs = useDayjs() const { push } = useRouter() const { @@ -58,38 +57,38 @@ const CreateForm = () => { if (dates.length === 0) { return setError(t('form.errors.no_dates')) } - const isSpecificDates = dates[0].length === 8 if (time.start === time.end) { return setError(t('form.errors.same_times')) } - const times = dates.reduce((times, date) => { + // If format is `YYYY-MM-DD` or `d` + const isSpecificDates = dates[0].length !== 1 + + const times = dates.reduce((times, dateStr) => { const day = [] + const date = isSpecificDates + ? Temporal.PlainDate.from(dateStr) + : Temporal.Now.plainDateISO().add({ days: Number(dateStr) - Temporal.Now.plainDateISO().dayOfWeek }) + for (let i = time.start; i < (time.start > time.end ? 24 : time.end); i++) { + const dateTime = date.toZonedDateTime({ timeZone: timezone, plainTime: Temporal.PlainTime.from({ hour: i }) }).withTimeZone('UTC') if (isSpecificDates) { - day.push( - dayjs.tz(date, 'DDMMYYYY', timezone) - .hour(i).minute(0).utc().format('HHmm-DDMMYYYY') - ) + // Format as `HHmm-DDMMYYYY` + day.push(`${dateTime.hour.toString().padStart(2, '0')}${dateTime.minute.toString().padStart(2, '0')}-${dateTime.day.toString().padStart(2, '0')}${dateTime.month.toString().padStart(2, '0')}${dateTime.year.toString().padStart(4, '0')}`) } else { - day.push( - dayjs().tz(timezone) - .day(Number(date)).hour(i).minute(0).utc().format('HHmm-d') - ) + // Format as `HHmm-d` + day.push(`${dateTime.hour.toString().padStart(2, '0')}${dateTime.minute.toString().padStart(2, '0')}-${String(dateTime.dayOfWeek === 7 ? 0 : dateTime.dayOfWeek)}`) } } if (time.start > time.end) { for (let i = 0; i < time.end; i++) { + const dateTime = date.toZonedDateTime({ timeZone: timezone, plainTime: Temporal.PlainTime.from({ hour: i }) }).withTimeZone('UTC') if (isSpecificDates) { - day.push( - dayjs.tz(date, 'DDMMYYYY', timezone) - .hour(i).minute(0).utc().format('HHmm-DDMMYYYY') - ) + // Format as `HHmm-DDMMYYYY` + day.push(`${dateTime.hour.toString().padStart(2, '0')}${dateTime.minute.toString().padStart(2, '0')}-${dateTime.day.toString().padStart(2, '0')}${dateTime.month.toString().padStart(2, '0')}${dateTime.year.toString().padStart(4, '0')}`) } else { - day.push( - dayjs().tz(timezone) - .day(Number(date)).hour(i).minute(0).utc().format('HHmm-d') - ) + // Format as `HHmm-d` + day.push(`${dateTime.hour.toString().padStart(2, '0')}${dateTime.minute.toString().padStart(2, '0')}-${String(dateTime.dayOfWeek === 7 ? 0 : dateTime.dayOfWeek)}`) } } } diff --git a/frontend/src/components/Recents/Recents.tsx b/frontend/src/components/Recents/Recents.tsx index 51397ed..f831ae7 100644 --- a/frontend/src/components/Recents/Recents.tsx +++ b/frontend/src/components/Recents/Recents.tsx @@ -1,13 +1,14 @@ 'use client' import Link from 'next/link' +import { Temporal } from '@js-temporal/polyfill' import Content from '/src/components/Content/Content' import Section from '/src/components/Section/Section' -import { useDayjs } from '/src/config/dayjs' import { useTranslation } from '/src/i18n/client' import { useStore } from '/src/stores' import useRecentsStore from '/src/stores/recentsStore' +import { relativeTimeFormat } from '/src/utils' import styles from './Recents.module.scss' @@ -17,8 +18,7 @@ interface RecentsProps { const Recents = ({ target }: RecentsProps) => { const recents = useStore(useRecentsStore, state => state.recents) - const { t } = useTranslation(['home', 'common']) - const dayjs = useDayjs() + const { t, i18n } = useTranslation(['home', 'common']) return recents?.length ?
@@ -28,8 +28,8 @@ const Recents = ({ target }: RecentsProps) => { {event.name} {t('common:created', { date: dayjs.unix(event.created_at).fromNow() })} + title={Temporal.Instant.fromEpochSeconds(event.created_at).toLocaleString(i18n.language, { dateStyle: 'long' })} + >{t('common:created', { date: relativeTimeFormat(Temporal.Instant.fromEpochSeconds(event.created_at), i18n.language) })} ))} diff --git a/frontend/src/components/Settings/Settings.tsx b/frontend/src/components/Settings/Settings.tsx index 260025e..1f85dcc 100644 --- a/frontend/src/components/Settings/Settings.tsx +++ b/frontend/src/components/Settings/Settings.tsx @@ -3,6 +3,7 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { useRouter } from 'next/navigation' import { maps } from 'hue-map' +import { MapKey } from 'hue-map/dist/maps' import { Settings as SettingsIcon } from 'lucide-react' import SelectField from '/src/components/SelectField/SelectField' @@ -64,8 +65,8 @@ const Settings = () => { 'Sunday': t('options.weekStart.options.Sunday'), 'Monday': t('options.weekStart.options.Monday'), }} - value={store?.weekStart === 0 ? 'Sunday' : 'Monday'} - onChange={value => store?.setWeekStart(value === 'Sunday' ? 0 : 1)} + value={store?.weekStart === 1 ? 'Sunday' : 'Monday'} + onChange={value => store?.setWeekStart(value === 'Sunday' ? 1 : 0)} /> { }} isSmall value={store?.colormap} - onChange={event => store?.setColormap(event.target.value)} + onChange={event => store?.setColormap(event.target.value as MapKey)} /> { const timeFormat = useStore(useSettingsStore, state => state.timeFormat) + const { i18n } = useTranslation() const isMoving = useRef(false) const rangeRect = useRef({ left: 0, width: 0 }) @@ -106,7 +108,7 @@ const Handle = ({ value, onChange, labelPadding }: HandleProps) => { left: `calc(${value * 4.166}% - 11px)`, '--extra-padding': labelPadding, } as React.CSSProperties} - data-label={timeFormat === '24h' ? times[value] : dayjs().hour(Number(times[value])).format('ha')} + data-label={Temporal.PlainTime.from({ hour: Number(times[value] === '24' ? '00' : times[value]) }).toLocaleString(i18n.language, { hour: 'numeric', hour12: timeFormat === '12h' })} onMouseDown={() => { document.addEventListener('mousemove', handleMouseMove) isMoving.current = true diff --git a/frontend/src/config/dayjs.ts b/frontend/src/config/dayjs.ts deleted file mode 100644 index 4e2729f..0000000 --- a/frontend/src/config/dayjs.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { useCallback, useState } from 'react' -import { isKeyOfObject } from '@giraugh/tools' -import dayjs from 'dayjs' -import customParseFormat from 'dayjs/plugin/customParseFormat' -import isToday from 'dayjs/plugin/isToday' -import localeData from 'dayjs/plugin/localeData' -import relativeTime from 'dayjs/plugin/relativeTime' -import timezone from 'dayjs/plugin/timezone' -import updateLocale from 'dayjs/plugin/updateLocale' -import utc from 'dayjs/plugin/utc' - -import { useTranslation } from '/src/i18n/client' -import { languageDetails } from '/src/i18n/options' -import { useStore } from '/src/stores' -import useSettingsStore from '/src/stores/settingsStore' - -dayjs.extend(customParseFormat) -dayjs.extend(isToday) -dayjs.extend(localeData) -dayjs.extend(relativeTime) -dayjs.extend(timezone) -dayjs.extend(updateLocale) -dayjs.extend(utc) - -export const useDayjs = () => { - const { i18n } = useTranslation() - const store = useStore(useSettingsStore, state => state) - const [updateInstance, setUpdateInstance] = useState(0) - - const instance = useCallback(dayjs, [updateInstance, dayjs]) - - const handleLanguageChange = useCallback((lng: string) => { - if (isKeyOfObject(lng, languageDetails)) { - store?.setWeekStart(languageDetails[lng].weekStart) - store?.setTimeFormat(languageDetails[lng].timeFormat) - - languageDetails[lng]?.import().then(() => { - dayjs.locale(lng) - setUpdateInstance(updateInstance + 1) - }) - } - }, [store]) - - i18n.on('languageChanged', handleLanguageChange) - - return instance -} diff --git a/frontend/src/i18n/client.ts b/frontend/src/i18n/client.ts index f8cad7b..9f876ae 100644 --- a/frontend/src/i18n/client.ts +++ b/frontend/src/i18n/client.ts @@ -2,12 +2,11 @@ import { initReactI18next, useTranslation as useTranslationHook } from 'react-i18next' import { cookies } from 'next/dist/client/components/headers' // risky disky (undocumented???) -import dayjs from 'dayjs' import i18next from 'i18next' import LanguageDetector from 'i18next-browser-languagedetector' import resourcesToBackend from 'i18next-resources-to-backend' -import { cookieName, getOptions, languageDetails } from './options' +import { cookieName, getOptions } from './options' i18next .use(initReactI18next) @@ -24,11 +23,5 @@ i18next 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) diff --git a/frontend/src/i18n/options.ts b/frontend/src/i18n/options.ts index 0c34047..b66dabf 100644 --- a/frontend/src/i18n/options.ts +++ b/frontend/src/i18n/options.ts @@ -27,89 +27,71 @@ interface LanguageDetails { /** 0: Sunday, 1: Monday */ weekStart: 0 | 1 timeFormat: '12h' | '24h' - /** The separator to show between hours and minutes (default `:`) */ - separator?: string - /** Day.js locale import */ - import: () => Promise } export const languageDetails: Record = { 'en': { // English (US) name: 'English (US)', - import: () => import('dayjs/locale/en'), weekStart: 0, timeFormat: '12h', }, 'en-GB': { // English (UK) name: 'English (UK)', - import: () => import('dayjs/locale/en-gb'), weekStart: 1, timeFormat: '12h', }, 'de': { // German name: 'Deutsch', - import: () => import('dayjs/locale/de'), weekStart: 1, timeFormat: '24h', }, 'es': { // Spanish name: 'Español', - import: () => import('dayjs/locale/es'), weekStart: 1, timeFormat: '24h', }, 'fr': { // French name: 'Français', - import: () => import('dayjs/locale/fr'), weekStart: 1, timeFormat: '24h', }, 'hi': { // Hindi name: 'हिंदी', - import: () => import('dayjs/locale/hi'), weekStart: 1, timeFormat: '12h', }, 'id': { // Indonesian name: 'Indonesia', - import: () => import('dayjs/locale/id'), weekStart: 1, timeFormat: '24h', - separator: '.', }, 'ja': { // Japanese name: '日本語', - import: () => import('dayjs/locale/ja'), weekStart: 0, timeFormat: '12h', }, 'ko': { // Korean name: '한국어', - import: () => import('dayjs/locale/ko'), weekStart: 0, timeFormat: '24h', }, 'pl': { // Polish name: 'Polskie', - import: () => import('dayjs/locale/pl'), weekStart: 1, timeFormat: '12h', }, 'pt-BR': { // Portuguese (Brazil) name: 'Português (do Brasil)', - import: () => import('dayjs/locale/pt-br'), weekStart: 0, timeFormat: '24h', }, 'ru': { // Russian name: 'Pусский', - import: () => import('dayjs/locale/ru'), weekStart: 1, timeFormat: '24h', }, // 'zh-CN': { // Chinese // name: '中文', - // import: () => import('dayjs/locale/zh-cn'), // weekStart: 1, // timeFormat: '12h', // }, diff --git a/frontend/src/i18n/server.ts b/frontend/src/i18n/server.ts index 5704fce..abecf38 100644 --- a/frontend/src/i18n/server.ts +++ b/frontend/src/i18n/server.ts @@ -1,10 +1,9 @@ import { cookies, headers } from 'next/headers' import acceptLanguage from 'accept-language' -import dayjs from 'dayjs' import { createInstance } from 'i18next' import resourcesToBackend from 'i18next-resources-to-backend' -import { cookieName, fallbackLng, getOptions, languageDetails, languages } from './options' +import { cookieName, fallbackLng, getOptions, languages } from './options' type Mutable = { -readonly [K in keyof T]: Mutable } @@ -25,11 +24,6 @@ export const useTranslation = async (ns: string | string[], options: { keyPrefix ?? acceptLanguage.get(headers().get('Accept-Language')) ?? fallbackLng - // Set dayjs locale - languageDetails[language as keyof typeof languageDetails]?.import().then(() => { - dayjs.locale(language) - }) - const i18nextInstance = await initI18next(language, ns) return { t: i18nextInstance.getFixedT(language, Array.isArray(ns) ? ns[0] : ns, options.keyPrefix), diff --git a/frontend/src/stores/settingsStore.ts b/frontend/src/stores/settingsStore.ts index bc1ab61..1332499 100644 --- a/frontend/src/stores/settingsStore.ts +++ b/frontend/src/stores/settingsStore.ts @@ -1,4 +1,4 @@ -import { ColorMap } from 'hue-map/dist/maps' +import { MapKey } from 'hue-map/dist/maps' import { create } from 'zustand' import { persist } from 'zustand/middleware' @@ -6,17 +6,18 @@ type TimeFormat = '12h' | '24h' type Theme = 'System' | 'Light' | 'Dark' interface SettingsStore { + /** 0: Monday, 1: Sunday */ weekStart: 0 | 1 timeFormat: TimeFormat theme: Theme highlight: boolean - colormap: 'crabfit' | ColorMap + colormap: 'crabfit' | MapKey setWeekStart: (weekStart: 0 | 1) => void setTimeFormat: (timeFormat: TimeFormat) => void setTheme: (theme: Theme) => void setHighlight: (highlight: boolean) => void - setColormap: (colormap: 'crabfit' | ColorMap) => void + setColormap: (colormap: 'crabfit' | MapKey) => void } const useSettingsStore = create()(persist( @@ -33,7 +34,18 @@ const useSettingsStore = create()(persist( setHighlight: highlight => set({ highlight }), setColormap: colormap => set({ colormap }), }), - { name: 'crabfit-settings' }, + { + name: 'crabfit-settings', + version: 1, + migrate: (persistedState, version) => { + if (version === 0) { + // Weekstart used to be 0 for Sunday, but now it's been swapped + (persistedState as SettingsStore).weekStart = (persistedState as SettingsStore).weekStart === 1 ? 0 : 1 + return persistedState as SettingsStore + } + return persistedState as SettingsStore + }, + }, )) export default useSettingsStore diff --git a/frontend/src/utils/convertTimesToDates.ts b/frontend/src/utils/convertTimesToDates.ts index ec48dfa..0cfb448 100644 --- a/frontend/src/utils/convertTimesToDates.ts +++ b/frontend/src/utils/convertTimesToDates.ts @@ -1,7 +1,7 @@ import { Temporal } from '@js-temporal/polyfill' /** - * Take times as strings and convert to Dayjs objects + * Take times as strings in UTC and convert to ZonedDateTime objects in the timezone supplied * @param times An array of strings in `HHmm-d` or `HHmm-DDMMYYYY` format * @param timezone The target timezone */ diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 6cec2ec..11ccfa0 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -645,11 +645,6 @@ damerau-levenshtein@^1.0.8: resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== -dayjs@^1.11.7: - version "1.11.7" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" - integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== - debug@^3.2.6, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"