Fix date formatting on language change
This commit is contained in:
parent
b9b63625f1
commit
4be1c49975
@ -46,6 +46,7 @@
|
|||||||
"react-dev-utils": "^10.2.1",
|
"react-dev-utils": "^10.2.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"react-i18next": "^11.7.0",
|
"react-i18next": "^11.7.0",
|
||||||
|
"react-moment": "^0.9.7",
|
||||||
"react-redux": "^7.2.0",
|
"react-redux": "^7.2.0",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
|
42
src/app.tsx
42
src/app.tsx
@ -1,23 +1,27 @@
|
|||||||
import React, { Dispatch, HTMLProps, useEffect } from 'react';
|
import React, { HTMLProps, useEffect } from 'react';
|
||||||
import { Link, Route, Switch } from "react-router-dom"
|
import { Link, Route, Switch } from "react-router-dom"
|
||||||
import moment from "moment";
|
|
||||||
import { route, routes } from "@/routing";
|
import { route, routes } from "@/routing";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { AppState } from "@/state/reducer";
|
import { AppState, isReady } from "@/state/reducer";
|
||||||
import { StudentAction, StudentActions } from "@/state/actions/student";
|
import { StudentActions } from "@/state/actions/student";
|
||||||
import { sampleStudent } from "@/provider/dummy/student";
|
import { sampleStudent } from "@/provider/dummy/student";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { Student } from "@/data";
|
import { Student } from "@/data";
|
||||||
import '@/styles/overrides.scss'
|
import '@/styles/overrides.scss'
|
||||||
import '@/styles/header.scss'
|
import '@/styles/header.scss'
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { EditionAction, EditionActions } from "@/state/actions/edition";
|
import { EditionActions } from "@/state/actions/edition";
|
||||||
import { sampleEdition } from "@/provider/dummy/edition";
|
import { sampleEdition } from "@/provider/dummy/edition";
|
||||||
import { Edition } from "@/data/edition";
|
import { Edition } from "@/data/edition";
|
||||||
|
import { SettingActions } from "@/state/actions/settings";
|
||||||
|
import { useDispatch } from "@/state/actions";
|
||||||
|
import { getLocale, Locale } from "@/state/reducer/settings";
|
||||||
|
import i18n from "@/i18n";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
const UserMenu = (props: HTMLProps<HTMLUListElement>) => {
|
const UserMenu = (props: HTMLProps<HTMLUListElement>) => {
|
||||||
const student = useSelector<AppState, Student>(state => state.student as Student);
|
const student = useSelector<AppState, Student>(state => state.student as Student);
|
||||||
const dispatch = useDispatch<Dispatch<StudentAction>>();
|
const dispatch = useDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleUserLogin = () => {
|
const handleUserLogin = () => {
|
||||||
@ -47,17 +51,17 @@ const UserMenu = (props: HTMLProps<HTMLUListElement>) => {
|
|||||||
const LanguageSwitcher = ({ className, ...props }: HTMLProps<HTMLUListElement>) => {
|
const LanguageSwitcher = ({ className, ...props }: HTMLProps<HTMLUListElement>) => {
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
|
|
||||||
const handleLanguageChange = (language: string) => () => {
|
const dispatch = useDispatch();
|
||||||
i18n.changeLanguage(language);
|
|
||||||
document.documentElement.lang = language;
|
const handleLanguageChange = (language: Locale) => () => {
|
||||||
moment.locale(language)
|
dispatch({ type: SettingActions.SetLocale, locale: language })
|
||||||
}
|
}
|
||||||
|
|
||||||
const isActive = (language: string) => language.toLowerCase() === i18n.language.toLowerCase();
|
const isActive = (language: string) => language.toLowerCase() === i18n.language.toLowerCase();
|
||||||
|
|
||||||
return <ul className={ classNames(className, "language-switcher") } { ...props }>
|
return <ul className={ classNames(className, "language-switcher") } { ...props }>
|
||||||
{ ['pl', 'en'].map(language => <li key={ language }>
|
{ ['pl', 'en'].map(language => <li key={ language }>
|
||||||
<Link to="#" onClick={ handleLanguageChange(language) }
|
<Link to="#" onClick={ handleLanguageChange(language as Locale) }
|
||||||
className={ classNames("language-switcher__language", isActive(language) && "language-switcher__language--active") }>
|
className={ classNames("language-switcher__language", isActive(language) && "language-switcher__language--active") }>
|
||||||
{ language }
|
{ language }
|
||||||
</Link>
|
</Link>
|
||||||
@ -66,16 +70,24 @@ const LanguageSwitcher = ({ className, ...props }: HTMLProps<HTMLUListElement>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const dispatch = useDispatch<Dispatch<EditionAction>>();
|
const dispatch = useDispatch();
|
||||||
const edition = useSelector<AppState, Edition | null>(state => state.edition);
|
const edition = useSelector<AppState, Edition | null>(state => state.edition);
|
||||||
|
|
||||||
|
const locale = useSelector<AppState, Locale>(state => getLocale(state.settings));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!edition) {
|
if (!edition) {
|
||||||
dispatch({ type: EditionActions.Set, edition: sampleEdition });
|
dispatch({ type: EditionActions.Set, edition: sampleEdition });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const isReady = !!edition;
|
useEffect(() => {
|
||||||
|
i18n.changeLanguage(locale);
|
||||||
|
document.documentElement.lang = locale;
|
||||||
|
moment.locale(locale)
|
||||||
|
})
|
||||||
|
|
||||||
|
const ready = useSelector(isReady);
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<header className="header">
|
<header className="header">
|
||||||
@ -96,7 +108,7 @@ function App() {
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
{ isReady && <Switch>{ routes.map(({ name, content, ...route }) => <Route { ...route } key={ name }>{ content() }</Route>) }</Switch> }
|
{ ready && <Switch>{ routes.map(({ name, content, ...route }) => <Route { ...route } key={ name }>{ content() }</Route>) }</Switch> }
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
18
src/i18n.ts
18
src/i18n.ts
@ -2,10 +2,9 @@ import i18n from "i18next";
|
|||||||
import { initReactI18next } from "react-i18next";
|
import { initReactI18next } from "react-i18next";
|
||||||
import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector";
|
import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector";
|
||||||
|
|
||||||
import moment from "moment";
|
|
||||||
|
|
||||||
import "moment/locale/pl"
|
import "moment/locale/pl"
|
||||||
import "moment/locale/en-gb"
|
import "moment/locale/en-gb"
|
||||||
|
import moment, { isDuration, isMoment } from "moment";
|
||||||
|
|
||||||
const resources = {
|
const resources = {
|
||||||
en: {
|
en: {
|
||||||
@ -21,9 +20,20 @@ i18n
|
|||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
resources,
|
resources,
|
||||||
fallbackLng: "en",
|
fallbackLng: "pl",
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false
|
escapeValue: false,
|
||||||
|
format: (value, format, lng) => {
|
||||||
|
if (isMoment(value)) {
|
||||||
|
return value.locale(lng || "pl").format(format || "DD MMM YYYY");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDuration(value)) {
|
||||||
|
return value.locale(lng || "pl").humanize();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -31,10 +31,10 @@ const Step = ({ until, label, completedOn, children, completed, ...props }: Step
|
|||||||
{ label }
|
{ label }
|
||||||
{ until && <Box>
|
{ until && <Box>
|
||||||
<Typography variant="subtitle2" color="textSecondary">
|
<Typography variant="subtitle2" color="textSecondary">
|
||||||
{ t('until', { date: until.format("DD MMMM YYYY") }) }
|
{ t('until', { date: until }) }
|
||||||
{ isLate && <Typography color="error" display="inline"
|
{ isLate && <Typography color="error" display="inline"
|
||||||
variant="body2"> - { t('late', { by: moment.duration(now.diff(until)).humanize() }) }</Typography> }
|
variant="body2"> - { t('late', { by: moment.duration(now.diff(until)) }) }</Typography> }
|
||||||
{ !isLate && !completed && <Typography display="inline" variant="body2"> - { t('left', { left: left.humanize() }) }</Typography> }
|
{ !isLate && !completed && <Typography display="inline" variant="body2"> - { t('left', { left: left }) }</Typography> }
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box> }
|
</Box> }
|
||||||
</StepLabel>
|
</StepLabel>
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import { StudentAction, StudentActions } from "@/state/actions/student";
|
||||||
|
import { EditionAction, EditionActions } from "@/state/actions/edition";
|
||||||
|
import { SettingActions, SettingsAction } from "@/state/actions/settings";
|
||||||
|
import { Dispatch } from "react";
|
||||||
|
|
||||||
|
import { useDispatch as useReduxDispatch } from "react-redux";
|
||||||
|
|
||||||
|
export * from "./base"
|
||||||
|
export * from "./edition"
|
||||||
|
export * from "./settings"
|
||||||
|
export * from "./student"
|
||||||
|
|
||||||
|
export type Action = StudentAction | EditionAction | SettingsAction;
|
||||||
|
|
||||||
|
export const Actions = { ...StudentActions, ...EditionActions, ...SettingActions }
|
||||||
|
export type Actions = typeof Actions;
|
||||||
|
|
||||||
|
export const useDispatch = () => useReduxDispatch<Dispatch<Action>>()
|
||||||
|
|
||||||
|
export default Actions;
|
12
src/state/actions/settings.ts
Normal file
12
src/state/actions/settings.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Action } from "@/state/actions/base";
|
||||||
|
import { Locale } from "@/state/reducer/settings";
|
||||||
|
|
||||||
|
export enum SettingActions {
|
||||||
|
SetLocale = "SET_LOCALE",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetLocaleAction extends Action<SettingActions.SetLocale> {
|
||||||
|
locale: Locale
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SettingsAction = SetLocaleAction;
|
@ -2,12 +2,16 @@ import { combineReducers } from "redux";
|
|||||||
|
|
||||||
import studentReducer from "./student"
|
import studentReducer from "./student"
|
||||||
import editionReducer from "@/state/reducer/edition";
|
import editionReducer from "@/state/reducer/edition";
|
||||||
|
import settingsReducer from "@/state/reducer/settings";
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
student: studentReducer,
|
student: studentReducer,
|
||||||
edition: editionReducer,
|
edition: editionReducer,
|
||||||
|
settings: settingsReducer,
|
||||||
})
|
})
|
||||||
|
|
||||||
export type AppState = ReturnType<typeof rootReducer>;
|
export type AppState = ReturnType<typeof rootReducer>;
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|
||||||
|
export const isReady = (state: AppState) => !!state.edition;
|
||||||
|
24
src/state/reducer/settings.ts
Normal file
24
src/state/reducer/settings.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { SettingActions, SettingsAction } from "@/state/actions/settings";
|
||||||
|
|
||||||
|
export type Locale = "pl" | "en"
|
||||||
|
|
||||||
|
export type SettingsState = {
|
||||||
|
locale: Locale
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultSettingsState: SettingsState = {
|
||||||
|
locale: "pl",
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingsReducer = (state: SettingsState = defaultSettingsState, action: SettingsAction): SettingsState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case SettingActions.SetLocale:
|
||||||
|
return { ...state, locale: action.locale }
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default settingsReducer;
|
||||||
|
|
||||||
|
export const getLocale = (state: SettingsState): Locale => state.locale;
|
@ -3,9 +3,9 @@ login: login
|
|||||||
logout: logout
|
logout: logout
|
||||||
logged-in-as: logged in as <1>{{ name }}</1>
|
logged-in-as: logged in as <1>{{ name }}</1>
|
||||||
|
|
||||||
until: until {{ date }}
|
until: until {{ date, DD MMMM YYYY }}
|
||||||
late: late by {{ by }}
|
late: late by {{ by, humanize }}
|
||||||
left: '{{ left }} left'
|
left: '{{ left, humanize }} left'
|
||||||
|
|
||||||
dropzone: "Drag and drop a file here or click to choose"
|
dropzone: "Drag and drop a file here or click to choose"
|
||||||
|
|
||||||
|
@ -3,9 +3,9 @@ login: zaloguj się
|
|||||||
logout: wyloguj się
|
logout: wyloguj się
|
||||||
logged-in-as: zalogowany jako <1>{{ name }}</1>
|
logged-in-as: zalogowany jako <1>{{ name }}</1>
|
||||||
|
|
||||||
until: do {{ date }}
|
until: do {{ date, DD MMMM YYYY }}
|
||||||
late: '{{ by }} spóźnienia'
|
late: '{{ by, humanize }} spóźnienia'
|
||||||
left: jeszcze {{ left }}
|
left: jeszcze {{ left, humanize }}
|
||||||
|
|
||||||
confirm: zatwierdź
|
confirm: zatwierdź
|
||||||
go-back: wstecz
|
go-back: wstecz
|
||||||
|
@ -7408,6 +7408,11 @@ react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is
|
|||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
|
||||||
|
react-moment@^0.9.7:
|
||||||
|
version "0.9.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-moment/-/react-moment-0.9.7.tgz#ca570466595b1aa4f7619e62da18b3bb2de8b6f3"
|
||||||
|
integrity sha512-ifzUrUGF6KRsUN2pRG5k56kO0mJBr8kRkWb0wNvtFIsBIxOuPxhUpL1YlXwpbQCbHq23hUu6A0VEk64HsFxk9g==
|
||||||
|
|
||||||
react-redux@^7.2.0:
|
react-redux@^7.2.0:
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"
|
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"
|
||||||
|
Loading…
Reference in New Issue
Block a user