diff --git a/frontend/app/src/components/AuthModal.tsx b/frontend/app/src/components/AuthModal.tsx index 1db1f8a..97e0899 100644 --- a/frontend/app/src/components/AuthModal.tsx +++ b/frontend/app/src/components/AuthModal.tsx @@ -10,6 +10,7 @@ import { } from "../slice/AuthApi"; import { MessageContext } from "../App"; import { store, updateToken, updateUser } from "../config/store"; +import tr from "../config/translation"; const AuthModal = (props: { open: boolean; @@ -43,7 +44,7 @@ const AuthModal = (props: { }) .then(() => refetch()) .then(() => props.setOpen(false)) - .catch(() => messageApi.error("Login failed!")); + .catch(() => messageApi.error(tr("Login failed!"))); }; const submitRegisterForm = (formData: { @@ -55,17 +56,17 @@ const AuthModal = (props: { registerUser(formData) .unwrap() .then(() => props.setOpen(false)) - .catch(() => messageApi.error("Registration failed!")); + .catch(() => messageApi.error(tr("Registration failed!"))); }; const items: MenuProps["items"] = [ { - label: "Log In", + label: tr("Log in"), key: "login", icon: , }, { - label: "Register", + label: tr("Register"), key: "register", icon: , }, @@ -79,7 +80,7 @@ const AuthModal = (props: { current === "register" && registerForm.submit(); current === "login" && loginForm.submit(); }} - okText={current === "login" ? "Log In" : "Register"} + okText={current === "login" ? tr("Log in") : tr("Register")} confirmLoading={isLoggingIn} > @@ -99,26 +100,26 @@ const AuthModal = (props: { > - + @@ -126,11 +127,11 @@ const AuthModal = (props: { ({ validator(_, value) { @@ -139,7 +140,7 @@ const AuthModal = (props: { } return Promise.reject( new Error( - "The new password that you entered do not match!" + tr("The new password that you entered do not match!") ) ); }, @@ -158,11 +159,11 @@ const AuthModal = (props: { > @@ -170,11 +171,11 @@ const AuthModal = (props: { diff --git a/frontend/app/src/components/HeaderComponent.tsx b/frontend/app/src/components/HeaderComponent.tsx index d87470e..841ee91 100644 --- a/frontend/app/src/components/HeaderComponent.tsx +++ b/frontend/app/src/components/HeaderComponent.tsx @@ -5,6 +5,7 @@ import AuthModal from "./AuthModal"; import "./styles.css"; import { StorePrototype, logOut, store } from "../config/store"; import { useSelector } from "react-redux"; +import tr from "../config/translation"; const { Header } = Layout; @@ -18,9 +19,9 @@ const HeaderComponent = () => { (state: StorePrototype) => state.auth.user ); - const userMenuItems: MenuProps["userMenuItems"] = [ + const userMenuItems: MenuProps["items"] = [ { - label: "Log out", + label: tr("Log out"), key: "logout", icon: , onClick: () => store.dispatch(logOut()), @@ -39,7 +40,7 @@ const HeaderComponent = () => { {user.username} ) : ( - "Log In" + tr("Log in") ), key: "login", icon: , diff --git a/frontend/app/src/config/store.ts b/frontend/app/src/config/store.ts index f8487d3..13048d0 100644 --- a/frontend/app/src/config/store.ts +++ b/frontend/app/src/config/store.ts @@ -17,9 +17,18 @@ const initialAuthDataState: AuthDataType = { user: null, }; +export type SettingsType = { + language: string | null; +}; + +const initialSettingsState: SettingsType = { + language: null, +}; + export type StorePrototype = { AuthApi: ReducerType; auth: AuthDataType; + settings: SettingsType; }; export const updateToken = createAction("auth/updateToken"); @@ -27,6 +36,9 @@ export const getLocalToken = createAction("auth/getLocalToken"); export const updateUser = createAction("auth/updateUser"); export const logOut = createAction("auth/logOut"); +export const setLanguage = createAction("settings/setLanguage"); +export const loadLanguage = createAction("settings/loadLanguage"); + export const store = configureStore({ reducer: { // Add the generated reducer as a specific top-level slice @@ -51,6 +63,20 @@ export const store = configureStore({ state.user = null; }); }), + settings: createReducer(initialSettingsState, (builder) => { + builder.addCase(setLanguage, (state, action) => { + state.language = action.payload || "en"; + localStorage.setItem("language", action.payload || "en"); + }); + builder.addCase(loadLanguage, (state) => { + const language: string | null = localStorage.getItem("language"); + if (language) { + state.language = language; + } else { + state.language = "en"; + } + }); + }), }, // Adding the api middleware enables caching, invalidation, polling, // and other useful features of `rtk-query`. diff --git a/frontend/app/src/config/translation.ts b/frontend/app/src/config/translation.ts new file mode 100644 index 0000000..ad6bbfb --- /dev/null +++ b/frontend/app/src/config/translation.ts @@ -0,0 +1,21 @@ +import trMapJson from "./translationMap.json"; +import { store } from "./store"; + +const trMap: unknown = trMapJson; + +const tr = (phrase: string): string => { + const currentLanguage = store.getState().settings.language; + if (!currentLanguage || currentLanguage === "en") { + return phrase; + } + return ( + ((trMap && + typeof trMap === "object" && + trMap[phrase as keyof object] && + trMap[phrase as keyof object][ + currentLanguage as keyof object + ]) as string) || phrase + ); +}; + +export default tr; diff --git a/frontend/app/src/config/translationMap.json b/frontend/app/src/config/translationMap.json new file mode 100644 index 0000000..c5394bb --- /dev/null +++ b/frontend/app/src/config/translationMap.json @@ -0,0 +1,53 @@ +{ + "Queuing has never been so simple": { + "ru": "Организация очередей еще никогда не была настолько простой" + }, + "Log in": { + "ru": "Войти" + }, + "Log out": { + "ru": "Выйти" + }, + "Register": { + "ru": "Зарегистрироваться" + }, + "Username": { + "ru": "Логин" + }, + "Password": { + "ru": "Пароль" + }, + "Display name": { + "ru": "Отображаемое имя" + }, + "Repeat password": { + "ru": "Повторите пароль" + }, + "Cancel": { + "ru": "Отмена" + }, + "Join a queue": { + "ru": "Присоединиться к очереди" + }, + "Take a tour": { + "ru": "Взглянуть на функционал" + }, + "Please input your Username!": { + "ru": "Пожалуйста, введите ваш Логин!" + }, + "Please input your Password!": { + "ru": "Пожалуйста, введите ваш Пароль!" + }, + "Please confirm your password!": { + "ru": "Пожалуйста, подтвердите ваш пароль!" + }, + "The new password that you entered do not match!": { + "ru": "Пароли не совпадают!" + }, + "Registration failed!": { + "ru": "Регистрация не удалась!" + }, + "Login failed!": { + "ru": "Вход не удался!" + } +} \ No newline at end of file diff --git a/frontend/app/src/index.tsx b/frontend/app/src/index.tsx index 2b875af..ac4bb3d 100644 --- a/frontend/app/src/index.tsx +++ b/frontend/app/src/index.tsx @@ -1,10 +1,10 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './App'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; -const root = ReactDOM.createRoot(document.getElementById('root')!); +const root = ReactDOM.createRoot(document.getElementById("root")!); root.render( - , + ); diff --git a/frontend/app/src/pages/AppRoutes.tsx b/frontend/app/src/pages/AppRoutes.tsx index 6daa0c2..7d4c966 100644 --- a/frontend/app/src/pages/AppRoutes.tsx +++ b/frontend/app/src/pages/AppRoutes.tsx @@ -1,11 +1,11 @@ -import React, { useEffect } from "react"; +import React from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import MainPage from "./MainPage"; -import { useGetUserQuery } from "../slice/AuthApi"; -import { getLocalToken, store, updateUser } from "../config/store"; +import { getLocalToken, loadLanguage, store } from "../config/store"; const AppRoutes = () => { store.dispatch(getLocalToken()); + store.dispatch(loadLanguage()); return ( diff --git a/frontend/app/src/pages/MainPage.tsx b/frontend/app/src/pages/MainPage.tsx index 9160703..865dd19 100644 --- a/frontend/app/src/pages/MainPage.tsx +++ b/frontend/app/src/pages/MainPage.tsx @@ -2,15 +2,18 @@ import React from "react"; import "./styles.css"; import logoSquare from "../../static/logo-square.png"; import { Button } from "antd"; +import tr from "../config/translation"; const MainPage = () => { return (
logo -

Queuing has never been so simple

+

+ {tr("Queuing has never been so simple")} +

- - + +
); diff --git a/frontend/app/src/pages/styles.css b/frontend/app/src/pages/styles.css index ec491b7..2eee661 100644 --- a/frontend/app/src/pages/styles.css +++ b/frontend/app/src/pages/styles.css @@ -5,6 +5,7 @@ } .main { + font-family: "Comfortaa", sans-serif; display: flex; flex-flow: column; justify-items: center;