From 9a872edc4e787c6c633bd5bc88e2c0e444d3b758 Mon Sep 17 00:00:00 2001 From: Artem Simutin Date: Mon, 24 Oct 2022 18:16:24 +0300 Subject: [PATCH 1/5] feat(translation): setup translation and translated sidebar menu (almost) --- next-i18next.config.js | 8 + next-i18next.config.mjs | 5 - next.config.mjs => next.config.js | 7 +- package.json | 2 + public/locales/en/common.json | 13 + public/locales/en/sidebar.json | 8 - public/locales/ru/common.json | 13 + .../footer/components/SmallFooter.tsx | 37 +- src/helpers/translation-page-props.ts | 19 + .../sidebar/components/top-bar/index.tsx | 10 +- .../navigation/components/sidebar/index.tsx | 4 +- src/modules/navigation/data/sidebar-items.tsx | 16 +- src/modules/navigation/models/sidebar.ts | 2 +- src/pages/dashboard/index.tsx | 6 + yarn.lock | 3019 +++++++++-------- 15 files changed, 1764 insertions(+), 1405 deletions(-) create mode 100644 next-i18next.config.js delete mode 100644 next-i18next.config.mjs rename next.config.mjs => next.config.js (76%) create mode 100644 public/locales/en/common.json delete mode 100644 public/locales/en/sidebar.json create mode 100644 public/locales/ru/common.json create mode 100644 src/helpers/translation-page-props.ts diff --git a/next-i18next.config.js b/next-i18next.config.js new file mode 100644 index 0000000..05b2bcd --- /dev/null +++ b/next-i18next.config.js @@ -0,0 +1,8 @@ +module.exports = { + i18n: { + locales: ["en", "lv", "ru"], + defaultLocale: "en", + localeDetection: true, + fallbackLng: "en", + }, +}; diff --git a/next-i18next.config.mjs b/next-i18next.config.mjs deleted file mode 100644 index ff78a35..0000000 --- a/next-i18next.config.mjs +++ /dev/null @@ -1,5 +0,0 @@ -export const i18n = { - locales: ["en", "lv", "ru"], - defaultLocale: "en", - localeDetection: true, -}; diff --git a/next.config.mjs b/next.config.js similarity index 76% rename from next.config.mjs rename to next.config.js index 3946dbc..d5c4ca5 100644 --- a/next.config.mjs +++ b/next.config.js @@ -1,7 +1,8 @@ -import { i18n } from "./next-i18next.config.mjs"; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { i18n } = require("./next-i18next.config"); /** @type {import('next').NextConfig} */ -const nextConfig = { +module.exports = { webpack(config) { config.module.rules.push({ test: /\.svg$/i, @@ -24,5 +25,3 @@ const nextConfig = { reactStrictMode: true, i18n, }; - -export default nextConfig; diff --git a/package.json b/package.json index ef2e4c7..3a946f0 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "date-fns": "^2.28.0", "framer-motion": "^7.2.1", "http-proxy": "^1.18.1", + "i18next": "^22.0.2", "lodash": "^4.17.21", "mapbox-gl": "^2.10.0", "next": "^12.2.3", @@ -39,6 +40,7 @@ "react-dom": "18.1.0", "react-dropzone": "^14.2.1", "react-hook-form": "^7.31.3", + "react-i18next": "^12.0.0", "react-icons": "^4.4.0", "react-map-gl": "^7.0.19", "react-redux": "^8.0.2", diff --git a/public/locales/en/common.json b/public/locales/en/common.json new file mode 100644 index 0000000..063e42a --- /dev/null +++ b/public/locales/en/common.json @@ -0,0 +1,13 @@ +{ + "sidebar": { + "dashboard": "Dashboard", + "messages": "Messages", + "saved": "Saved", + "search": "Search", + "help": "Help center", + "sellCarsText": "A new way to buy modern and sell old cars", + "sellCarButton": "Sell your auto", + "news": "News", + "profileSettings": "Profile settings" + } +} diff --git a/public/locales/en/sidebar.json b/public/locales/en/sidebar.json deleted file mode 100644 index c1e3b1e..0000000 --- a/public/locales/en/sidebar.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "dashboard": "Dashboard", - "saved": "Saved", - "search": "Search", - "helpCenter": "Help Center", - "sellCarsText": "A new way to buy modern and sell old cars", - "sellCarButton": "Sell Your Car" -} diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json new file mode 100644 index 0000000..bc235a1 --- /dev/null +++ b/public/locales/ru/common.json @@ -0,0 +1,13 @@ +{ + "sidebar": { + "dashboard": "Панель управления", + "messages": "Сообщения", + "saved": "Сохранённые", + "search": "Поиск", + "help": "Помощь", + "sellCarsText": "Новый способ продать или купить авто", + "sellCarButton": "Продать авто", + "news": "Новости", + "profileSettings": "Настройки" + } +} diff --git a/src/components/footer/components/SmallFooter.tsx b/src/components/footer/components/SmallFooter.tsx index ae08788..e42faf0 100644 --- a/src/components/footer/components/SmallFooter.tsx +++ b/src/components/footer/components/SmallFooter.tsx @@ -6,10 +6,38 @@ import { Text, } from "@chakra-ui/react"; import Link from "next/link"; -import React from "react"; +import { useRouter } from "next/router"; +import React, { useCallback, useMemo } from "react"; import { MdPublic, MdMoreHoriz } from "react-icons/md"; +import i18nConfig from "../../../../next-i18next.config"; +import { ArrElement } from "../../../types/arr-element.type"; const SmallFooter = () => { + const router = useRouter(); + const { pathname, asPath, query, locale } = router; + + const languageName = useMemo(() => { + switch (locale) { + case "lv": { + return "Latviesu"; + } + case "ru": { + return "Русский"; + } + default: { + return "English"; + } + } + }, []); + + const changeLanguage = useCallback( + (lang: ArrElement) => { + router.push({ pathname, query }, asPath, { locale: lang }); + return; + }, + [asPath, pathname, query, router] + ); + return ( { padding="12px 11.5px 13px 12.5px" leftIcon={} height={46} + // For testing + // TODO: Delete the line and implement popup menu + onClick={() => { + changeLanguage(languageName === "English" ? "ru" : "en"); + }} > - English + {languageName} diff --git a/src/modules/user/components/settings-content-by-tab/account.tsx b/src/modules/user/components/settings-content-by-tab/account.tsx index 1b61948..49c79d9 100644 --- a/src/modules/user/components/settings-content-by-tab/account.tsx +++ b/src/modules/user/components/settings-content-by-tab/account.tsx @@ -6,6 +6,7 @@ import { Text, useDisclosure, } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React, { useCallback } from "react"; import { Controller, useForm } from "react-hook-form"; import Avatar from "../../../../components/avatar"; @@ -30,6 +31,7 @@ type FormDateWithStringBirthdate = Omit< }; const SettingsAccountContent = () => { + const { t } = useTranslation("settings"); const { isOpen, onClose, onOpen } = useDisclosure(); const [callUpdate, { isSuccess, isError, isLoading, error }] = @@ -79,21 +81,19 @@ const SettingsAccountContent = () => { return ( - {isUserWithAvatar && ( - - - - )} + + + {isUserWithAvatar - ? "It is your current avatar and the avatar is visible for all marketplace users" - : "You don't have avatar yet, but you can add it by pressing button below"} + ? t("account.currentAvatar") + : t("account.noAvatarImage")} @@ -130,7 +130,7 @@ const SettingsAccountContent = () => { }} render={({ field: { value, onChange }, fieldState: { error } }) => ( { }} render={({ field: { value, onChange }, fieldState: { error } }) => ( { control={control} render={({ field: { value, onChange }, fieldState: { error } }) => ( onChange(value)} errors={error} @@ -192,7 +193,7 @@ const SettingsAccountContent = () => { }; return ( { control={control} render={({ field: { value, onChange }, fieldState: { error } }) => ( { control={control} render={({ field: { value, onChange }, fieldState: { error } }) => ( { control={control} render={({ field: { value, onChange }, fieldState: { error } }) => ( = ({ onReset, isDisabled, isLoading, isError, isSuccess }) => { + const { t } = useTranslation(); return ( - Update Settings + {t("common:buttons.updateSettings")} - Cancel + {t("common:buttons.cancel")} + {/* TODO: Implement as a "toast" notification */} {isSuccess && ( @@ -53,6 +56,7 @@ const DefaultProfileSettingsActionButtons: FC< )} + {/* TODO: Implement as a "toast" notification */} {isError && ( @@ -70,18 +74,21 @@ interface DeactivateAccountActionButtonProps { const DeactivateAccountActionButton: FC = ({ onDeactivate, -}) => ( - - } - onClick={onDeactivate} - display="flex" - > - Deactivate Account - - -); +}) => { + const { t } = useTranslation("settings"); + return ( + + } + onClick={onDeactivate} + display="flex" + > + {t("buttons.deactivateAccount")} + + + ); +}; export const ActionButton = chakra(Button, { baseStyle: { diff --git a/src/modules/user/components/settings-content-by-tab/components/change-avatar-modal.tsx b/src/modules/user/components/settings-content-by-tab/components/change-avatar-modal.tsx index c3d02b7..499dc69 100644 --- a/src/modules/user/components/settings-content-by-tab/components/change-avatar-modal.tsx +++ b/src/modules/user/components/settings-content-by-tab/components/change-avatar-modal.tsx @@ -17,6 +17,7 @@ import { Collapse, Stack, } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React, { FC, useCallback, @@ -49,6 +50,9 @@ const AVAILABLE_IMAGE_TYPES = [ ]; const ChangeAvatarModal: FC> = (props) => { + const { t } = useTranslation("settings", { + keyPrefix: "account.avatarModal", + }); const section = useDisclosure(); const intervalID = useRef(); const [secondBeforeClose, setSecondsBeforeClose] = useState( @@ -73,7 +77,7 @@ const ChangeAvatarModal: FC> = (props) => { const { user } = useGetMeQuery(); const [error, setError] = useState< - "max_size" | "unsupported_format" | "no_files" | null + "files_limit" | "unsupported_format" | "no_files" | null >(null); const { colors } = useTheme(); const [selectedAvatar, setSelectedAvatar] = useState( @@ -82,7 +86,7 @@ const ChangeAvatarModal: FC> = (props) => { const [step, setStep] = useState(1); const onDrop = useCallback((acceptedFiles: File[]) => { if (acceptedFiles.length > 1) { - setError("max_size"); + setError("files_limit"); return; } if (acceptedFiles.length === 0) { @@ -118,6 +122,7 @@ const ChangeAvatarModal: FC> = (props) => { } const arrayBuffer = await selectedAvatar?.arrayBuffer(); if (!arrayBuffer || !selectedAvatar?.name) { + // TODO: Change alert on our "toast" notification alert("Something went wrong"); return; } @@ -167,7 +172,7 @@ const ChangeAvatarModal: FC> = (props) => { (!isFileQueryLoading && isFileQuerySuccess && !isFileQueryError) || (!isUrlQueryLoading && isUrlQuerySuccess && !isUrlQueryError) ) { - setSecondsBeforeClose(5); + setSecondsBeforeClose(3); intervalID.current = setInterval(() => { setSecondsBeforeClose((prev) => { if (!prev) return null; @@ -209,7 +214,7 @@ const ChangeAvatarModal: FC> = (props) => { - Change avatar + {t("label")} {step === 1 && ( @@ -238,27 +243,26 @@ const ChangeAvatarModal: FC> = (props) => { largePlus: "0", }} > - Drop your avatar image file here or press on the area + {t("dropdownDescription")} - Supported formats:{" "} - {AVAILABLE_IMAGE_TYPES.map( - (item) => `".${item.slice(6)}"` - ).join(", ")} + {t("supportedFormats", { + formats: AVAILABLE_IMAGE_TYPES.map( + (item) => `".${item.slice(6)}"` + ).join(", "), + })} {!!error && ( - {error === "max_size" && - "You can upload only 1 photo for your avatar"} - {error === "no_files" && - "You must upload one file for your avatar image"} + {error === "files_limit" && t("filesLimitError")} + {error === "no_files" && t("noFilesError")} {error === "unsupported_format" && - "You must upload only supported formats"} + t("unsupportedFormatError")} )} @@ -279,7 +283,7 @@ const ChangeAvatarModal: FC> = (props) => { - Your avatars from services + {t("avatarsFromServices")} > = (props) => { > - This is your avatar image from + {t("avatarFromTheService")} {serviceInfo.name} @@ -345,8 +349,8 @@ const ChangeAvatarModal: FC> = (props) => { } > {isAvatarAlreadyInUse - ? "You already are using this avatar" - : "User this avatar"} + ? t("alreadyUsing") + : t("useThisAvatar")} @@ -359,7 +363,7 @@ const ChangeAvatarModal: FC> = (props) => { {step === 2 && ( - You are going to change your avatar + {t("youAreGoingToChangeAvatar")} > = (props) => { )} @@ -404,8 +408,11 @@ const ChangeAvatarModal: FC> = (props) => { isLoading={isFileQueryLoading || isUrlQueryLoading} isDisabled={!!secondBeforeClose} > - {secondBeforeClose === null && "Update"} - {secondBeforeClose && `Closing after ${secondBeforeClose}...`} + {secondBeforeClose === null && t("update")} + {secondBeforeClose && + t("closingAfter", { + seconds: secondBeforeClose, + })} diff --git a/src/modules/user/components/settings-head.tsx b/src/modules/user/components/settings-head.tsx index bca0907..ca85afd 100644 --- a/src/modules/user/components/settings-head.tsx +++ b/src/modules/user/components/settings-head.tsx @@ -18,12 +18,16 @@ import getTabInfoFromTabType from "../helpers/get-tab-info-from-tab-type"; import ProfileSettingsTabType from "../models/tabs"; import Avatar from "../../../components/avatar"; import { useGetMeQuery } from "../../auth/hooks/use-get-me-query"; +import { useTranslation } from "next-i18next"; interface Props { currentTab: ProfileSettingsTabType; } const ProfileSettingsHead: FC = ({ currentTab }) => { + const { t } = useTranslation("settings", { + keyPrefix: currentTab, + }); const router = useRouter(); const { user } = useGetMeQuery(); @@ -97,10 +101,10 @@ const ProfileSettingsHead: FC = ({ currentTab }) => { - {tabInfo.title} + {t("label")} - {tabInfo.description} + {t("description")} diff --git a/src/modules/user/components/settings-info-badge.tsx b/src/modules/user/components/settings-info-badge.tsx index 58d0e45..00be52f 100644 --- a/src/modules/user/components/settings-info-badge.tsx +++ b/src/modules/user/components/settings-info-badge.tsx @@ -1,6 +1,7 @@ import { Box, chakra, Flex, Text } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React, { FC } from "react"; -import getTabInfoFromTabType from "../helpers/get-tab-info-from-tab-type"; +import { getTabIconByTabType } from "../helpers/get-tab-info-from-tab-type"; import { TabButtonLinkCardType } from "./settings-link-card-button"; interface Props { @@ -8,7 +9,10 @@ interface Props { } const SettingsInfoBadge: FC = ({ tab }) => { - const { icon, info } = getTabInfoFromTabType(tab); + const { t } = useTranslation("settings", { + keyPrefix: `${tab}.information`, + }); + const icon = getTabIconByTabType(tab); return ( = ({ tab }) => { {/* Label */} - {info.label} + {t("label")} {/* Text */} - {info.body} + {t("description")} ); diff --git a/src/modules/user/helpers/get-tab-info-from-tab-type.tsx b/src/modules/user/helpers/get-tab-info-from-tab-type.tsx index 4f0f2e9..3532b84 100644 --- a/src/modules/user/helpers/get-tab-info-from-tab-type.tsx +++ b/src/modules/user/helpers/get-tab-info-from-tab-type.tsx @@ -17,6 +17,34 @@ type Return = { }; }; +export const getTabIconByTabType = ( + type: TabButtonLinkCardType +): React.ReactNode => { + switch (type) { + case "account": { + return ; + } + case "security": { + return ; + } + case "login": { + return ; + } + case "billing": { + return ; + } + case "notification": { + return ; + } + case "privacy": { + return ; + } + case "general": { + return ; + } + } +}; + const getTabInfoFromTabType = (type: TabButtonLinkCardType): Return => { switch (type) { case "account": { diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index e78ac42..316611e 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -43,9 +43,6 @@ const DashBoard = () => { ); }; -export const getStaticProps = makeTranslationStaticProps([ - "common", - "dashboard", -]); +export const getStaticProps = makeTranslationStaticProps(["dashboard"]); export default DashBoard; diff --git a/src/pages/settings/index.tsx b/src/pages/settings/index.tsx index 47197fc..4985abc 100644 --- a/src/pages/settings/index.tsx +++ b/src/pages/settings/index.tsx @@ -3,6 +3,7 @@ import { NextPage } from "next"; import { useRouter } from "next/router"; import React, { useMemo } from "react"; import Page from "../../components/page-wrapper"; +import { makeTranslationStaticProps } from "../../helpers/translation-page-props"; import { useGetMeQuery } from "../../modules/auth/hooks/use-get-me-query"; import { SettingsAccountContent, @@ -142,4 +143,6 @@ const SettingsPage: NextPage = () => { ); }; +export const getStaticProps = makeTranslationStaticProps(["settings"]); + export default SettingsPage; -- GitLab From 565ed37ff454f0906bd6555f3e72720754c62585 Mon Sep 17 00:00:00 2001 From: Artem Simutin Date: Wed, 26 Oct 2022 20:39:51 +0300 Subject: [PATCH 3/5] feat(translation): started translating security tab --- public/locales/en/common.json | 7 +++ public/locales/en/settings.json | 5 +++ .../settings-content-by-tab/account.tsx | 43 +++++++++++-------- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 46fc54c..3c9fe60 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -51,5 +51,12 @@ "updateSettings": "Update Settings", "cancel": "Cancel", "edit": "Edit" + }, + "errors": { + "form": { + "fieldRequired": "This field is required!", + "minCharacters": "The minimum amount of symbols is {{count}}!", + "maxCharacters": "The maximum amount of symbols is {{count}}!" + } } } diff --git a/public/locales/en/settings.json b/public/locales/en/settings.json index a0b1f00..c3d7f42 100644 --- a/public/locales/en/settings.json +++ b/public/locales/en/settings.json @@ -35,5 +35,10 @@ "currentAvatar": "It is your current avatar and the avatar is visible for all marketplace users", "noAvatarImage": "You don't have avatar yet, but you can add it by pressing button below", "changeAvatar": "Change Avatar" + }, + "security": { + "sessionName": "{{name}} from {{ip}}", + "currentSession": "Current session", + "removeDevice": "Remove device" } } diff --git a/src/modules/user/components/settings-content-by-tab/account.tsx b/src/modules/user/components/settings-content-by-tab/account.tsx index 49c79d9..f5ab8b8 100644 --- a/src/modules/user/components/settings-content-by-tab/account.tsx +++ b/src/modules/user/components/settings-content-by-tab/account.tsx @@ -31,7 +31,7 @@ type FormDateWithStringBirthdate = Omit< }; const SettingsAccountContent = () => { - const { t } = useTranslation("settings"); + const { t } = useTranslation(); const { isOpen, onClose, onOpen } = useDisclosure(); const [callUpdate, { isSuccess, isError, isLoading, error }] = @@ -92,8 +92,8 @@ const SettingsAccountContent = () => { {isUserWithAvatar - ? t("account.currentAvatar") - : t("account.noAvatarImage")} + ? t("settings:account.currentAvatar") + : t("settings:account.noAvatarImage")} @@ -117,20 +117,24 @@ const SettingsAccountContent = () => { rules={{ required: { value: true, - message: "This field is required", + message: t("common:errors.form.fieldRequired"), }, minLength: { value: USER_VALIDATION.name.min, - message: `The minimum amount of symbols is ${USER_VALIDATION.name.min} for the first name field`, + message: t("common:errors.form.minCharacters", { + count: USER_VALIDATION.name.min, + }), }, maxLength: { value: USER_VALIDATION.name.max, - message: `The maximum amount of symbols is ${USER_VALIDATION.name.max} for the first name field`, + message: t("common:errors.form.minCharacters", { + count: USER_VALIDATION.name.max, + }), }, }} render={({ field: { value, onChange }, fieldState: { error } }) => ( { ( { onChange(value)} errors={error} @@ -193,7 +202,7 @@ const SettingsAccountContent = () => { }; return ( { control={control} render={({ field: { value, onChange }, fieldState: { error } }) => ( { control={control} render={({ field: { value, onChange }, fieldState: { error } }) => ( { control={control} render={({ field: { value, onChange }, fieldState: { error } }) => ( Date: Thu, 27 Oct 2022 18:18:20 +0300 Subject: [PATCH 4/5] feat(translation): translate notification & privacy & login --- next-i18next.config.js | 2 +- public/locales/en/common.json | 9 +- public/locales/en/settings.json | 78 +++++++++++++++++- .../components/password-change-form.tsx | 53 ++++++------ .../components/questions-change-form.tsx | 29 ++++--- .../settings-content-by-tab/notification.tsx | 24 +++--- .../settings-content-by-tab/privacy.tsx | 82 +++++++------------ src/modules/user/components/settings-head.tsx | 2 +- .../components/social-profile-info-card.tsx | 8 +- .../user/components/user-agent-card.tsx | 33 +++++--- 10 files changed, 204 insertions(+), 116 deletions(-) diff --git a/next-i18next.config.js b/next-i18next.config.js index 05b2bcd..316e968 100644 --- a/next-i18next.config.js +++ b/next-i18next.config.js @@ -3,6 +3,6 @@ module.exports = { locales: ["en", "lv", "ru"], defaultLocale: "en", localeDetection: true, - fallbackLng: "en", + // fallbackLng: "en", }, }; diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 3c9fe60..bd9499f 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -50,10 +50,17 @@ "buttons": { "updateSettings": "Update Settings", "cancel": "Cancel", - "edit": "Edit" + "edit": "Edit", + "deactivate": "Deactivate" }, "errors": { "form": { + "password": { + "minSymbols": "Password must be longer than {{count}} symbols!", + "maxSymbols": "Password must be less in length than {{count}} symbols!", + "pattern": "Password must contain at least one number and one capital letter!", + "mustMatch": "Passwords must match!" + }, "fieldRequired": "This field is required!", "minCharacters": "The minimum amount of symbols is {{count}}!", "maxCharacters": "The maximum amount of symbols is {{count}}!" diff --git a/public/locales/en/settings.json b/public/locales/en/settings.json index c3d7f42..f284d8f 100644 --- a/public/locales/en/settings.json +++ b/public/locales/en/settings.json @@ -37,8 +37,84 @@ "changeAvatar": "Change Avatar" }, "security": { + "label": "Security", + "description": "Your credentials", + "information": { + "label": "Security credentials", + "description": "There are many things that are important to catalog design. Your images must be sharp and appealing." + }, "sessionName": "{{name}} from {{ip}}", + "sessionInformation": "Last login: {{last}} from {{location}} \nFirst login: {{first}}", "currentSession": "Current session", - "removeDevice": "Remove device" + "removeDevice": "Remove device", + "deactivateSession": "Deactivate session", + "logoutDeviceConfirmation": "Are you sure you want to logout from the device?" + }, + "login": { + "label": "Login details", + "description": "Password & account security", + "information": { + "label": "Password and questions", + "description": "Ads that sing to commercials, advertisers are now using a new type of technology called media." + }, + "changePassword": "Change password", + "currentPassword": "Current password", + "newPassword": "New password", + "newPasswordConfirmation": "New password confirmation", + "noMasterPassword": "Now you have not master password to login into your account, because you have logged in through Third Party Services (Google, Facebook, etc)", + "canAddMasterPassword": "You can add master password to login by email and password. Login through Third Party services will be available", + "addPassword": "Add password", + "question": "Question #{{index}}", + "answer": "Answer #{{index}}", + "addMore": "Add More", + "errors": { + "questions": { + "required": "Question is required", + "minSymbols": "Question must be minimum {{count}} symbols!", + "maxSymbols": "Question must be maximum {{count}} symbols!" + }, + "answers": { + "required": "Question must be answered", + "minSymbols": "Answer must be minimum {{count}} symbols!", + "maxSymbols": "Answer must be maximum {{count}} symbols!" + } + } + }, + "notification": { + "label": "Notifications", + "description": "Your email notifications", + "information": { + "label": "Updates and alerts", + "description": "Advertising is telling the world how great you are, while publicity is having tell the world how great you are." + }, + "productUpdates": "Product updates", + "productUpdatesDescription": "Receive messages from our platform", + "reminders": "Reminders", + "remindersDescription": "Receive booking reminders, pricing notices", + "promotionAndTips": "Promotions and tips", + "promotionAndTipsDescription": "Receive coupons, promotions, surveys", + "policyAndCommunity": "Policy and community", + "policyAndCommunityDescription": "Receive updates on home sharing regulations", + "accountSupport": "Account support", + "accountSupportDescription": "Receive messages about your account, your trips, legal alerts" + }, + "privacy": { + "label": "Privacy", + "description": "Linked apps and services", + "information": { + "label": "Setup profiles", + "description": "The following tips on creating a direct mail campaign have been street-tested and will bring you huge returns." + }, + "socialProfiles": "Social profiles", + "disconnect": "Disconnect", + "connect": "Connect" + }, + "general": { + "label": "Global preferences", + "description": "Branding is no longer simply about visual or the cherry in the apple pie example, as given in my earlier article.", + "information": { + "label": "Global preferences", + "body": "Branding is no longer simply about visual or the cherry in the apple pie example, as given in my earlier article." + } } } diff --git a/src/modules/user/components/settings-content-by-tab/components/password-change-form.tsx b/src/modules/user/components/settings-content-by-tab/components/password-change-form.tsx index b31d5c7..ea849b6 100644 --- a/src/modules/user/components/settings-content-by-tab/components/password-change-form.tsx +++ b/src/modules/user/components/settings-content-by-tab/components/password-change-form.tsx @@ -1,4 +1,5 @@ import { Box, Collapse, Stack, Text } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React, { memo, useCallback, useMemo, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { MdOutlineLock } from "react-icons/md"; @@ -11,6 +12,7 @@ import { Label } from "../login"; import { ActionButton, DefaultProfileSettingsActionButtons } from "./actions"; const PasswordChangeForm = () => { + const { t } = useTranslation(); const { handleSubmit, getValues, control, watch, reset } = useForm(); const [newPasswordFormVisible, setNewPasswordFormVisible] = useState(false); @@ -42,7 +44,7 @@ const PasswordChangeForm = () => { return ( - +
{isShowFullForm && ( @@ -57,7 +59,7 @@ const PasswordChangeForm = () => { value={value || ""} onChange={onChange} type="password" - placeholder="Current password" + placeholder={t("settings:login.currentPassword")} icon={} errors={error} /> @@ -69,16 +71,19 @@ const PasswordChangeForm = () => { rules={{ minLength: { value: USER_VALIDATION.password.min, - message: `Password must be longer than ${USER_VALIDATION.password.min} symbols!`, + message: t("common:errors.form.password.minSymbols", { + count: USER_VALIDATION.password.min, + }), }, maxLength: { value: USER_VALIDATION.password.max, - message: `Password must be less in length than ${USER_VALIDATION.password.max} symbols!`, + message: t("common:errors.form.password.maxSymbols", { + count: USER_VALIDATION.password.max, + }), }, pattern: { value: USER_VALIDATION.password.pattern, - message: - "Password must contain at least one number and one capital letter!", + message: t("common:errors.form.password.pattern"), }, }} render={({ @@ -89,7 +94,7 @@ const PasswordChangeForm = () => { value={value} onChange={onChange} type="password" - placeholder="New password" + placeholder={t("settings:login.newPassword")} icon={} errors={error} /> @@ -103,7 +108,7 @@ const PasswordChangeForm = () => { rules={{ validate: (value) => value === getValues().newPassword || - "Passwords must match!", + t("common:errors.form.password.mustMatch"), }} render={({ field: { value, onChange }, @@ -113,7 +118,7 @@ const PasswordChangeForm = () => { value={value} onChange={onChange} type="password" - placeholder="New password confirmation" + placeholder={t("settings:login.newPasswordConfirmation")} icon={} errors={error} /> @@ -125,18 +130,11 @@ const PasswordChangeForm = () => { {!isMasterPassword && !newPasswordFormVisible && ( - - Now you have not master password to login into your account, - because you have logged in through Third Party Services (Google, - Facebook, etc) - - - You can add master password to login by email and password. Login - through Third Party services will be available - + {t("settings:login.noMasterPassword")} + {t("settings:login.canAddMasterPassword")} setNewPasswordFormVisible(true)}> - Add password + {t("settings:login.addPassword")} @@ -150,16 +148,19 @@ const PasswordChangeForm = () => { rules={{ minLength: { value: USER_VALIDATION.password.min, - message: `Password must be longer than ${USER_VALIDATION.password.min} symbols!`, + message: t("common:errors.form.password.minSymbols", { + count: USER_VALIDATION.password.min, + }), }, maxLength: { value: USER_VALIDATION.password.max, - message: `Password must be less in length than ${USER_VALIDATION.password.max} symbols!`, + message: t("common:errors.form.password.maxSymbols", { + count: USER_VALIDATION.password.max, + }), }, pattern: { value: USER_VALIDATION.password.pattern, - message: - "Password must contain at least one number and one capital letter!", + message: t("common:errors.form.password.pattern"), }, }} render={({ @@ -170,7 +171,7 @@ const PasswordChangeForm = () => { value={value} onChange={onChange} type="password" - placeholder="New password" + placeholder={t("settings:login.newPassword")} icon={} errors={error} /> @@ -184,7 +185,7 @@ const PasswordChangeForm = () => { rules={{ validate: (value) => value === getValues().newPassword || - "Passwords must match!", + t("common:errors.form.password.mustMatch"), }} render={({ field: { value, onChange }, @@ -194,7 +195,7 @@ const PasswordChangeForm = () => { value={value} onChange={onChange} type="password" - placeholder="New password confirmation" + placeholder={t("settings:login.newPasswordConfirmation")} icon={} errors={error} /> diff --git a/src/modules/user/components/settings-content-by-tab/components/questions-change-form.tsx b/src/modules/user/components/settings-content-by-tab/components/questions-change-form.tsx index 5a6fe68..4a6270b 100644 --- a/src/modules/user/components/settings-content-by-tab/components/questions-change-form.tsx +++ b/src/modules/user/components/settings-content-by-tab/components/questions-change-form.tsx @@ -1,4 +1,5 @@ import { Box, Button, Flex, Stack } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React, { memo, useCallback, useMemo } from "react"; import { Controller, useFieldArray, useForm } from "react-hook-form"; import { @@ -32,8 +33,8 @@ type FormData = { }; const QuestionsChangeForm = () => { + const { t } = useTranslation(); const { user } = useGetMeQuery(); - const { handleSubmit, reset, control } = useForm({ defaultValues: { questions: user?.settings.security.questions, @@ -98,15 +99,19 @@ const QuestionsChangeForm = () => { rules={{ required: { value: true, - message: "Question is required", + message: t("settings:login.errors.questions.required"), }, minLength: { value: QUESTION_AND_ANSWER.question.min, - message: `Question must be minimum ${QUESTION_AND_ANSWER.question.min} symbols!`, + message: t("settings:login.errors.questions.minSymbols", { + count: QUESTION_AND_ANSWER.question.min, + }), }, maxLength: { value: QUESTION_AND_ANSWER.question.max, - message: `Question must be maximum ${QUESTION_AND_ANSWER.question.max} symbols!`, + message: t("settings:login.errors.questions.maxSymbols", { + count: QUESTION_AND_ANSWER.question.max, + }), }, }} render={({ @@ -116,7 +121,7 @@ const QuestionsChangeForm = () => { } errors={error} /> @@ -128,15 +133,19 @@ const QuestionsChangeForm = () => { rules={{ required: { value: true, - message: "Question must be answered", + message: t("settings:login.errors.answers.required"), }, minLength: { value: QUESTION_AND_ANSWER.answer.min, - message: `Answer must be minimum ${QUESTION_AND_ANSWER.answer.min} symbols!`, + message: t("settings:login.errors.answers.minSymbols", { + count: QUESTION_AND_ANSWER.answer.min, + }), }, maxLength: { value: QUESTION_AND_ANSWER.answer.max, - message: `Answer must be maximum ${QUESTION_AND_ANSWER.answer.max} symbols!`, + message: t("settings:login.errors.answers.maxSymbols", { + count: QUESTION_AND_ANSWER.answer.max, + }), }, }} render={({ @@ -146,7 +155,7 @@ const QuestionsChangeForm = () => { } errors={error} /> @@ -192,7 +201,7 @@ const QuestionsChangeForm = () => { }, }} > - Add More + {t("settings:login.addMore")} )} diff --git a/src/modules/user/components/settings-content-by-tab/notification.tsx b/src/modules/user/components/settings-content-by-tab/notification.tsx index 2e8c96c..7a1847c 100644 --- a/src/modules/user/components/settings-content-by-tab/notification.tsx +++ b/src/modules/user/components/settings-content-by-tab/notification.tsx @@ -1,4 +1,5 @@ import { Box, Flex, Stack } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React, { useCallback } from "react"; import { Controller, useForm } from "react-hook-form"; import { BooleanEditable } from "../../../../components/editable"; @@ -8,6 +9,7 @@ import { useChangeNotificationSettingsMutation } from "../../user-api"; import { DefaultProfileSettingsActionButtons } from "./components/actions"; const SettingsNotificationContent = () => { + const { t } = useTranslation("settings"); const { user } = useGetMeQuery(); const [callUpdate, { isLoading, isSuccess, isError }] = useChangeNotificationSettingsMutation(); @@ -48,8 +50,8 @@ const SettingsNotificationContent = () => { )} /> @@ -62,8 +64,8 @@ const SettingsNotificationContent = () => { )} /> @@ -76,8 +78,8 @@ const SettingsNotificationContent = () => { )} /> @@ -90,8 +92,10 @@ const SettingsNotificationContent = () => { )} /> @@ -104,8 +108,8 @@ const SettingsNotificationContent = () => { )} /> diff --git a/src/modules/user/components/settings-content-by-tab/privacy.tsx b/src/modules/user/components/settings-content-by-tab/privacy.tsx index 01d9c33..024c44c 100644 --- a/src/modules/user/components/settings-content-by-tab/privacy.tsx +++ b/src/modules/user/components/settings-content-by-tab/privacy.tsx @@ -1,18 +1,12 @@ import { Box, Flex, Stack, Text } from "@chakra-ui/react"; -import React, { useCallback, useMemo } from "react"; -import { Controller, useForm } from "react-hook-form"; +import React, { useMemo } from "react"; import SocialProfileInfoCard from "../social-profile-info-card"; -import { DefaultProfileSettingsActionButtons } from "./components/actions"; -import GoogleCalendar from "../../assets/serviceIcons/google-calendar.svg"; -import { BooleanEditable } from "../../../../components/editable"; import { useGetMeQuery } from "../../../auth/hooks/use-get-me-query"; import { UserThirdPartyServiceType } from "../../models/user/service"; - -type FormData = { - googleCalendar: boolean; -}; +import { useTranslation } from "next-i18next"; const SettingsPrivacyContent = () => { + const { t } = useTranslation("settings"); const { user } = useGetMeQuery(); const googleInformation = useMemo(() => { @@ -39,49 +33,33 @@ const SettingsPrivacyContent = () => { return info; }, [user?.services]); - const { handleSubmit, control, reset } = useForm({ - defaultValues: { - googleCalendar: false, - }, - }); - - const onSubmit = (data: FormData) => { - console.log(data); - return; - }; - - const onReset = useCallback(() => { - reset(); - return; - }, [reset]); - return ( - - {/* Social media */} - - - Social profiles - + {/* */} + {/* Social media */} + + + {t("privacy.socialProfiles")} + - - - - - - + + + + + + - {/* Services */} - + {/* Services */} + {/* Apps and services @@ -101,11 +79,11 @@ const SettingsPrivacyContent = () => { )} /> - + */} - {/* Actions */} - - + {/* Actions */} + {/* */} + {/* */} ); }; diff --git a/src/modules/user/components/settings-head.tsx b/src/modules/user/components/settings-head.tsx index ca85afd..ba27700 100644 --- a/src/modules/user/components/settings-head.tsx +++ b/src/modules/user/components/settings-head.tsx @@ -35,7 +35,7 @@ const ProfileSettingsHead: FC = ({ currentTab }) => { const isMainTab = !tabInfo; const goBack = () => { - router.back(); + router.push("/settings"); return; }; diff --git a/src/modules/user/components/social-profile-info-card.tsx b/src/modules/user/components/social-profile-info-card.tsx index 25e7f0c..7d17481 100644 --- a/src/modules/user/components/social-profile-info-card.tsx +++ b/src/modules/user/components/social-profile-info-card.tsx @@ -1,4 +1,5 @@ import { Box, Button, Flex, Text } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React, { FC } from "react"; import { MdLink, MdNotInterested } from "react-icons/md"; import { getThirdPartyServiceInfo } from "../helpers/get-third-party-service-info"; @@ -10,6 +11,9 @@ interface Props { } const SocialProfileInfoCard: FC = ({ type, isConnected }) => { + const { t } = useTranslation("settings", { + keyPrefix: "privacy", + }); const socialMediaInfo = getThirdPartyServiceInfo(type); const onRemove = () => { @@ -71,7 +75,7 @@ const SocialProfileInfoCard: FC = ({ type, isConnected }) => { fontWeight="bold" onClick={onRemove} > - Remove + {t("disconnect")} ) : ( )} diff --git a/src/modules/user/components/user-agent-card.tsx b/src/modules/user/components/user-agent-card.tsx index 0b82399..f8cdc1b 100644 --- a/src/modules/user/components/user-agent-card.tsx +++ b/src/modules/user/components/user-agent-card.tsx @@ -27,6 +27,7 @@ import { DeviceType, Session } from "../models/user/session"; import Map from "../../../components/map"; import { getCookie } from "../../auth/helpers/cookie"; import { useDeactivateSessionMutation } from "../metadata-api"; +import { useTranslation } from "next-i18next"; const UserAgentCard: FC = ({ clientID, @@ -40,6 +41,7 @@ const UserAgentCard: FC = ({ latitude, longitude, }) => { + const { t } = useTranslation(); const [deactivateSession, { isSuccess, data }] = useDeactivateSessionMutation(); const { isOpen, onClose, onToggle } = useDisclosure(); @@ -70,18 +72,25 @@ const UserAgentCard: FC = ({ const formattedIp = useMemo(() => { if (!ip) return "UNKNOWN"; - if (ip && ip === "::1") return "LOCALHOST"; - if (ip && ip.includes("::ffff:")) return ip.slice(7); + if (ip === "::1") return "LOCALHOST"; + if (ip.includes("::ffff:")) return ip.slice(7); return ip; }, [ip]); const title = useMemo(() => { - return `${formattedName} from ${formattedIp}`; - }, [formattedIp, formattedName]); + return t("settings:security.sessionName", { + name: formattedName, + ip: formattedIp, + }); + }, [formattedIp, formattedName, t]); const information = useMemo(() => { - return `Last login: ${formattedTimestamp.last} from ${location} \nFirst login: ${formattedTimestamp.first}`; - }, [formattedTimestamp.first, formattedTimestamp.last, location]); + return t("settings:security.sessionInformation", { + first: formattedTimestamp.first, + last: formattedTimestamp.last, + location, + }); + }, [formattedTimestamp.first, formattedTimestamp.last, location, t]); /** * Handlers @@ -182,7 +191,7 @@ const UserAgentCard: FC = ({ size="xs" fontWeight="bold" > - Current session + {t("settings:security.currentSession")} )} @@ -208,7 +217,7 @@ const UserAgentCard: FC = ({ leftIcon={} onClick={onToggle} > - Remove device + {t("settings:security.removeDevice")} {/* For mobile */} @@ -233,13 +242,13 @@ const UserAgentCard: FC = ({ - Deactivate session + {t("settings:security.deactivateSession")} {/* */} - Are you sure you want to logout from the device? + {t("settings:security.logoutDeviceConfirmation")} = ({ > -- GitLab From e43b027726c98ba1339c06c0777192d222c6f33f Mon Sep 17 00:00:00 2001 From: Artem S Date: Tue, 8 Nov 2022 12:34:43 +0200 Subject: [PATCH 5/5] feat(translation): refactored common i18n namespace --- .env.production | 7 ++ public/locales/en/auth.json | 68 ++++++++++++++ public/locales/en/common.json | 91 ++++++++++++------- public/locales/en/messages.json | 17 ++++ public/locales/en/settings.json | 9 +- src/components/blur/index.tsx | 12 ++- src/components/editable/dropdown-editable.tsx | 4 +- src/components/editable/input-editable.tsx | 7 +- ...ContentHeight.ts => get-content-height.ts} | 0 ...Value.ts => get-dropdown-initial-value.ts} | 0 src/components/floating-dropdown/index.tsx | 12 ++- src/components/floating-input/index.tsx | 5 +- src/components/floating-textarea/index.tsx | 8 +- .../components/language-change-modal.tsx | 86 ++++++++++++++++++ .../footer/components/large-footer.tsx | 4 +- .../footer/components/small-footer.tsx | 62 ++++++------- src/components/footer/data/footer-items.ts | 28 +++--- src/components/header/aside-menu.tsx | 3 + src/components/header/index.tsx | 3 + src/components/or-divider/index.tsx | 7 +- .../page-wrapper/information-section.tsx | 13 ++- src/components/progress-bar/index.tsx | 4 +- ...extSortState.ts => get-next-sort-state.ts} | 0 .../{getSortIcon.tsx => get-sort-icon.tsx} | 0 src/components/sort-button/index.tsx | 4 +- .../get-language-friendly-name-by-locale.ts | 21 +++++ src/helpers/get-language-friendly-names.ts | 19 ++++ src/helpers/translation-page-props.ts | 48 +++++++++- .../index.tsx => sign-in-form.tsx} | 47 +++++----- .../index.tsx => sign-in-up-image-badge.tsx} | 9 +- .../index.tsx => sign-page-wrapper.tsx} | 4 +- .../sign-up-form/ThirdSignUpStep.tsx | 13 ++- .../sign-up-form/first-sign-up-step.tsx | 25 ++--- .../sign-up-form/second-sign-up-step.tsx | 52 ++++++----- .../index.tsx => third-party-auth-button.tsx} | 16 +++- .../chats/components/chat-controls.tsx | 13 ++- src/modules/chats/components/chat-message.tsx | 8 +- .../chats/components/chat-room-content.tsx | 10 +- src/modules/chats/components/no-messages.tsx | 10 +- .../components/select-chat-placeholder.tsx | 7 +- .../settings-content-by-tab/general.tsx | 10 +- .../components/social-profile-info-card.tsx | 2 +- src/pages/auth/activation.tsx | 59 ++++++------ .../auth/{recover/index.tsx => recover.tsx} | 21 +++-- .../auth/{sign-in/index.tsx => sign-in.tsx} | 9 +- .../auth/{sign-up/index.tsx => sign-up.tsx} | 19 ++-- src/pages/messages/index.tsx | 3 + src/styles/theme/components/button.ts | 6 +- 48 files changed, 624 insertions(+), 261 deletions(-) create mode 100644 .env.production create mode 100644 public/locales/en/auth.json create mode 100644 public/locales/en/messages.json rename src/components/floating-dropdown/helpers/{getContentHeight.ts => get-content-height.ts} (100%) rename src/components/floating-dropdown/helpers/{getDropdownInitialValue.ts => get-dropdown-initial-value.ts} (100%) create mode 100644 src/components/footer/components/language-change-modal.tsx rename src/components/sort-button/helpers/{getNextSortState.ts => get-next-sort-state.ts} (100%) rename src/components/sort-button/helpers/{getSortIcon.tsx => get-sort-icon.tsx} (100%) create mode 100644 src/helpers/get-language-friendly-name-by-locale.ts create mode 100644 src/helpers/get-language-friendly-names.ts rename src/modules/auth/components/{sign-in-form/index.tsx => sign-in-form.tsx} (77%) rename src/modules/auth/components/{sign-in-up-image-badge/index.tsx => sign-in-up-image-badge.tsx} (88%) rename src/modules/auth/components/{sign-page-wrapper/index.tsx => sign-page-wrapper.tsx} (92%) rename src/modules/auth/components/{third-party-auth-button/index.tsx => third-party-auth-button.tsx} (68%) rename src/pages/auth/{recover/index.tsx => recover.tsx} (77%) rename src/pages/auth/{sign-in/index.tsx => sign-in.tsx} (58%) rename src/pages/auth/{sign-up/index.tsx => sign-up.tsx} (82%) diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..39ff2f2 --- /dev/null +++ b/.env.production @@ -0,0 +1,7 @@ +# General +NEXT_PUBLIC_CLIENT_URL="http://localhost:3000" +NEXT_PUBLIC_API_BASE_URL="http://localhost:8080" +NEXT_PUBLIC_API_VERSION="v1" + +# Services +NEXT_PUBLIC_MAPBOX_PUBLIC_TOKEN="pk.eyJ1IjoiYnloLWFzc29jaWF0aW9uIiwiYSI6ImNsN2oyY3NtZzAzeWgzdnF6OXBnNnlwOHkifQ.MiSLQ4V4iVQoL0h-9TprXw" diff --git a/public/locales/en/auth.json b/public/locales/en/auth.json new file mode 100644 index 0000000..3accedf --- /dev/null +++ b/public/locales/en/auth.json @@ -0,0 +1,68 @@ +{ + "badgeTitle": "A new way\nto buy and sell\ncars", + "signInWith": "Sign In with {{name}}", + "signUpWith": "Sign Up with {{name}}", + "recover": { + "lostYourPassword": "Lost your password? Enter your details to recover", + "enterYourDetails": "Enter your details to proceed further", + "recover": "Recover" + }, + "sign-in": { + "signInToYourAccount": "Sign In to your account", + "enterYourDetailsToProceedFurther": "Enter your details to proceed further", + "email": "Email", + "yourPassword": "Your Password", + "rememberMe": "Remember me", + "recoverPassword": "Recover password", + "signInButton": "Sign In", + "errors": { + "emailFormat": "This field doesn't match email format. Must be in \"example@example.com\" format!", + "emailIsRequired": "Email field must be filled", + "passwordIsRequired": "Password is required" + } + }, + "sign-up": { + "signUpToGettingStarted": "Sign Up to getting started", + "enterDetailsToProceedFurther": "Enter your details to proceed further", + "email": "Email", + "firstName": "First name", + "lastName": "Lastname", + "password": "Password", + "confirmPassword": "Confirm password", + "iAgreeWithTerms": "I agree with terms & conditions", + "signUpButton": "Sign Up", + "continue": "Continue", + "tellUsAboutYourself": "Tell us about yourself", + "thankYou": "Thank you!", + "weSentAnEmailTo": "We sent an email to {{email}} \nClick confirmation link in the email to verify your account", + "goToDashboard": "Go to Dashboard", + "errors": { + "emailFormat": "This field doesn't match email format. Must be in \"example@example.com\" format!", + "emailIsRequired": "Email field must be filled", + "youMustAgreeWithTerms": "You must agree with our Terms & Conditions", + "emailIsNotAvailable": "The email is not available", + "firstNameRequired": "First name is required!", + "lastNameRequired": "Lastname is required!", + "passwordIsRequired": "Password is required", + "passwordMinLength": "Password must be longer than {{count}} symbols!", + "passwordMaxLength": "Password must be less in length than {{count}} symbols!", + "passwordPattern": "Password must contain at least one number and one capital letter!", + "passwordConfirmationIsRequired": "Password confirmation is required!", + "passwordsMustMatch": "Passwords must match!" + } + }, + "activation": { + "emailConfirmation": "Email confirmation", + "profileWIthConfirmedEmail": "Profile with confirmed email allows you to use full power of GreatCar service", + "thisWillTakeJustAFewSeconds": "This will take just a few seconds", + "thankYou": "Thank you! You have successfully confirmed your email", + "confirmationEmailWasSuccessfullySent": "Confirmation email was successfully sent! Check your email and follow the instructions in the letter.", + "goToDashboard": "Go to Dashboard", + "resentConfirmationEmail": "Resend confirmation email", + "goHomePage": "Go home page", + "errors": { + "wentWrongConfirmEmail": "Something went wrong. Resend confirmation email and try to reconfirm from a new one.", + "wentWrongResending": "Something went wrong while resending confirmation email" + } + } +} diff --git a/public/locales/en/common.json b/public/locales/en/common.json index bd9499f..1cb11df 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -1,8 +1,64 @@ { + "components": { + "blur": { + "notAuthorized": "Authorize to unlock", + "soon": "Soon", + "locked": "Locked" + }, + "editable": { + "notProvided": "Not provided", + "edit": "Edit" + }, + "floating-dropdown": { + "noResults": "No results" + }, + "footer": { + "privacyPolicy": "Privacy Policy", + "termsOfUse": "Terms of Use", + "allRightsReserved": "© {{year}} All rights reserved", + "languageModal": { + "selectLanguage": "Select language" + }, + "about": { + "label": "About", + "howItWorks": "How it works", + "careers": "Careers", + "aboutUs": "About us", + "media": "Media" + }, + "community": { + "label": "Community", + "againstDiscrimination": "Against Discrimination", + "inviteFriends": "Invite Friends", + "giftCards": "Gift Cards" + }, + "seller": { + "label": "Become Seller", + "addYourVehicle": "Add your vehicle", + "businessAccount": "Business Account", + "resourceCenter": "Resource Center", + "community": "Community" + }, + "support": { + "label": "Support", + "covid": "Updates for COVID-19", + "helpCenter": "Help Center", + "trustSafety": "Trust & Safety" + }, + "languageSelector": {} + }, + "or-divider": { + "or": "Or" + }, + "page-wrapper": { + "information-section": { + "emailConfirmation": "Please confirm your fucking email {{email}} to get full service features" + } + } + }, "settings": "Profile Settings", "helpCenter": "Help Center", "logout": "Log Out", - "notProvided": "Not provided", "sidebar": { "dashboard": "Dashboard", "messages": "Messages", @@ -14,39 +70,6 @@ "news": "News", "profileSettings": "Profile settings" }, - "footer": { - "mini": { - "privacyPolicy": "Privacy Policy", - "termsOfUse": "Terms of Use", - "allRightsReserved": "© {{year}} All rights reserved" - }, - "about": { - "label": "About", - "howItWorks": "How it works", - "careers": "Careers", - "aboutUs": "About us", - "media": "Media" - }, - "community": { - "label": "Community", - "againstDiscrimination": "Against Discrimination", - "inviteFriends": "Invite Friends", - "giftCards": "Gift Cards" - }, - "seller": { - "label": "Become Seller", - "addYourVehicle": "Add your vehicle", - "businessAccount": "Business Account", - "resourceCenter": "Resource Center", - "community": "Community" - }, - "support": { - "label": "Support", - "covid": "Updates for COVID-19", - "helpCenter": "Help Center", - "trustSafety": "Trust & Safety" - } - }, "buttons": { "updateSettings": "Update Settings", "cancel": "Cancel", diff --git a/public/locales/en/messages.json b/public/locales/en/messages.json new file mode 100644 index 0000000..a7697a9 --- /dev/null +++ b/public/locales/en/messages.json @@ -0,0 +1,17 @@ +{ + "selectConversation": "Select conversation", + "selectConversationDescription": "Select a specific conversation from the left column to view full conversation", + "noConversations": "No conversations yet", + "noConversationsDescription": "To start a conversation with the seller on the advert page press the \"Start conversation\" button and send the first message. The further messages with the seller will be displayed here and you can continue your dialog from this page", + "chatWith": "Chat with", + "previousMessages": "Previous messages", + "newMessages": "New messages", + "repliedOn": "Replied on", + "reply": "Reply", + "seen": "Seen", + "youAreGoingToReplyOn": "You are going to reply on:", + "messageText": "Message text", + "sendMessage": "Send message", + "newLine": "new line", + "backToTheChatRooms": "Back to the chat rooms" +} diff --git a/public/locales/en/settings.json b/public/locales/en/settings.json index f284d8f..9ead591 100644 --- a/public/locales/en/settings.json +++ b/public/locales/en/settings.json @@ -106,6 +106,8 @@ "description": "The following tips on creating a direct mail campaign have been street-tested and will bring you huge returns." }, "socialProfiles": "Social profiles", + "connected": "Connected", + "notConnected": "Not connected", "disconnect": "Disconnect", "connect": "Connect" }, @@ -114,7 +116,10 @@ "description": "Branding is no longer simply about visual or the cherry in the apple pie example, as given in my earlier article.", "information": { "label": "Global preferences", - "body": "Branding is no longer simply about visual or the cherry in the apple pie example, as given in my earlier article." - } + "description": "Branding is no longer simply about visual or the cherry in the apple pie example, as given in my earlier article." + }, + "preferredCurrency": "Preferred currency", + "timezone": "Timezone", + "language": "Language" } } diff --git a/src/components/blur/index.tsx b/src/components/blur/index.tsx index 86aba5c..ae900ec 100644 --- a/src/components/blur/index.tsx +++ b/src/components/blur/index.tsx @@ -1,4 +1,5 @@ import { Box, Flex, Text } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React, { FC, memo, PropsWithChildren, useMemo, useRef } from "react"; import { MdAccessTimeFilled, MdLock } from "react-icons/md"; import { useGetMeQuery } from "../../modules/auth/hooks/use-get-me-query"; @@ -21,6 +22,9 @@ const Blur: FC> = ({ backgroundOpacity = BACKGROUND_OPACITY, children, }) => { + const { t } = useTranslation("common", { + keyPrefix: "components.blur", + }); const ref = useRef(null); const { user } = useGetMeQuery(); @@ -44,13 +48,13 @@ const Blur: FC> = ({ // Different condition treatment switch (condition) { case "not_authenticated": - return "Authorize to unlock"; + return t("notAuthorized"); case "not_ready": - return "Soon"; + return t("soon"); } // Default - return "Locked"; - }, [condition, reason]); + return t("locked"); + }, [condition, reason, t]); const icon = useMemo(() => { if (condition === "not_ready") return ; diff --git a/src/components/editable/dropdown-editable.tsx b/src/components/editable/dropdown-editable.tsx index 747f75e..e56065c 100644 --- a/src/components/editable/dropdown-editable.tsx +++ b/src/components/editable/dropdown-editable.tsx @@ -57,7 +57,7 @@ const DropdownEditable: FC = ({ errors, ...props }) => { - const { t } = useTranslation("common"); + const { t } = useTranslation("common", { keyPrefix: "components.editable" }); const { isOpen, onClose, onToggle } = useDisclosure({ defaultIsOpen: autoInput, @@ -179,7 +179,7 @@ const DropdownEditable: FC = ({ size="xs" color="grays.500" > - {t("buttons.edit")} + {t("edit")} diff --git a/src/components/editable/input-editable.tsx b/src/components/editable/input-editable.tsx index dc7c801..70a274e 100644 --- a/src/components/editable/input-editable.tsx +++ b/src/components/editable/input-editable.tsx @@ -52,7 +52,7 @@ const InputEditable: FC = ({ inputType = "text", ...props }) => { - const { t } = useTranslation("common"); + const { t } = useTranslation("common", { keyPrefix: "components.editable" }); const { isOpen, onClose, onToggle } = useDisclosure({ defaultIsOpen: autoInput, @@ -76,8 +76,9 @@ const InputEditable: FC = ({ const valueForPlainText = useMemo(() => { if (!value) return NOT_PROVIDED; - if (isDate) + if (isDate) { return new Date(value).toLocaleDateString().replaceAll("/", "-"); + } return value as string; }, [isDate, value]); @@ -139,7 +140,7 @@ const InputEditable: FC = ({ size="xs" color="grays.500" > - {t("buttons.edit")} + {t("edit")} diff --git a/src/components/floating-dropdown/helpers/getContentHeight.ts b/src/components/floating-dropdown/helpers/get-content-height.ts similarity index 100% rename from src/components/floating-dropdown/helpers/getContentHeight.ts rename to src/components/floating-dropdown/helpers/get-content-height.ts diff --git a/src/components/floating-dropdown/helpers/getDropdownInitialValue.ts b/src/components/floating-dropdown/helpers/get-dropdown-initial-value.ts similarity index 100% rename from src/components/floating-dropdown/helpers/getDropdownInitialValue.ts rename to src/components/floating-dropdown/helpers/get-dropdown-initial-value.ts diff --git a/src/components/floating-dropdown/index.tsx b/src/components/floating-dropdown/index.tsx index d10d41e..4292969 100644 --- a/src/components/floating-dropdown/index.tsx +++ b/src/components/floating-dropdown/index.tsx @@ -6,6 +6,7 @@ import { Input, Text, } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React, { HTMLAttributes, memo, @@ -21,8 +22,8 @@ import useCombinedRefs from "../../hooks/use-combine-refs"; import useValidationErrorFromProps from "../../hooks/use-validation-error-from-props"; import { ComponentValidationProps } from "../../models/validation/interfaces"; import DropdownItem, { DropdownItemType } from "./components/dropdown-item"; -import getContentHeight from "./helpers/getContentHeight"; -import getDropdownValue from "./helpers/getDropdownInitialValue"; +import getContentHeight from "./helpers/get-content-height"; +import getDropdownValue from "./helpers/get-dropdown-initial-value"; export const CONTENT_HEIGHT_WITHOUT_RESULTS = 100; // in px export const MAX_CONTENT_ITEMS_COUNT = 4; @@ -90,6 +91,9 @@ const FloatingDropdown = React.forwardRef( }, forwardedRef ) => { + const { t } = useTranslation("common", { + keyPrefix: "components.floating-dropdown", + }); const innerRef = useRef(null); const combinedRef = useCombinedRefs(forwardedRef, innerRef); const [focused, setFocused] = useState(false); @@ -216,7 +220,6 @@ const FloatingDropdown = React.forwardRef( const onKeyPressHandler = (e: React.KeyboardEvent) => { if (searchQuery.length === 0) return; - if (e.key === "Enter" && visibleItems.length > 0) { onItemSelect(visibleItems[0]); } @@ -287,7 +290,6 @@ const FloatingDropdown = React.forwardRef( {/* Validation error */} - {error && error?.message && ( {error?.message} @@ -324,7 +326,7 @@ const FloatingDropdown = React.forwardRef( )) ) : ( - No results + {t("noResults")} )} diff --git a/src/components/floating-input/index.tsx b/src/components/floating-input/index.tsx index baf0089..ab97fc5 100644 --- a/src/components/floating-input/index.tsx +++ b/src/components/floating-input/index.tsx @@ -153,15 +153,12 @@ const FloatingInput: FC = ({ const inputCalculatedWidthMinus = useMemo(() => { let value = 0; - if (!!icon) { value += 34; } - if (!!controls) { value += 12; } - return value; }, [controls, icon]); @@ -228,6 +225,8 @@ const FloatingInput: FC = ({ )} + + {/* Validation error */} {error && error?.message && ( {error?.message} diff --git a/src/components/floating-textarea/index.tsx b/src/components/floating-textarea/index.tsx index c96496d..b8f4d37 100644 --- a/src/components/floating-textarea/index.tsx +++ b/src/components/floating-textarea/index.tsx @@ -2,7 +2,6 @@ import { Box, FormControl, FormLabel, - Text, Textarea, TextareaProps, } from "@chakra-ui/react"; @@ -10,6 +9,7 @@ import React, { HTMLAttributes, memo, useMemo, useRef, useState } from "react"; import useCombinedRefs from "../../hooks/use-combine-refs"; import useValidationErrorFromProps from "../../hooks/use-validation-error-from-props"; import { ComponentValidationProps } from "../../models/validation/interfaces"; +import Error from "../../components/error"; const TEXTAREA_MAX_HEIGHT = 400; @@ -121,10 +121,12 @@ const FloatingTextArea = React.forwardRef( {/* Input icon */} {icon} + + {/* Validation error */} {error && error?.message && ( - + {error?.message} - + )} ); diff --git a/src/components/footer/components/language-change-modal.tsx b/src/components/footer/components/language-change-modal.tsx new file mode 100644 index 0000000..9508495 --- /dev/null +++ b/src/components/footer/components/language-change-modal.tsx @@ -0,0 +1,86 @@ +import { + Flex, + Modal, + ModalBody, + Text, + ModalContent, + ModalHeader, + ModalOverlay, + IconButton, + Box, + Stack, +} from "@chakra-ui/react"; +import { useRouter } from "next/router"; +import React, { FC, useCallback } from "react"; +import { ArrElement } from "../../../types/arr-element.type"; +import RadioWithText from "../../radio-with-text"; +import i18nConfig from "../../../../next-i18next.config"; +import { getLanguageFriendlyNameByLocale } from "../../../helpers/get-language-friendly-name-by-locale"; +import { MdClose } from "react-icons/md"; +import { useTranslation } from "next-i18next"; + +interface Props { + isOpen: boolean; + onClose: () => void; +} + +const LanguageChangeModal: FC = ({ isOpen, onClose }) => { + const { t } = useTranslation("common", { + keyPrefix: "components.footer.languageModal", + }); + const router = useRouter(); + const { pathname, asPath, query, locale } = router; + const locales = router.locales; + + const changeLanguage = useCallback( + (lang: ArrElement) => { + router.push({ pathname, query }, asPath, { locale: lang }); + return; + }, + [asPath, pathname, query, router] + ); + + return ( + + + + + + + + {t("selectLanguage")} + + } + onClick={onClose} + /> + + + + + {locales?.map((arrayLocale) => { + const name = getLanguageFriendlyNameByLocale(arrayLocale); + return ( + + changeLanguage(arrayLocale)} + > + {name} + + + ); + })} + + + + + ); +}; + +export default LanguageChangeModal; diff --git a/src/components/footer/components/large-footer.tsx b/src/components/footer/components/large-footer.tsx index 2372c5f..26206b8 100644 --- a/src/components/footer/components/large-footer.tsx +++ b/src/components/footer/components/large-footer.tsx @@ -7,7 +7,7 @@ import FooterListItem from "./footer-list/footer-list-item"; const LargeFooter = () => { const { t } = useTranslation("common", { - keyPrefix: "footer", + keyPrefix: "components.footer", }); return ( { key={`footer-list-item_${footerListItem.name}+${footerListItem.href}`} href="/" > - {t(footerListItem.name)} + {t(`${item.title}.${footerListItem.name}`)} ))} diff --git a/src/components/footer/components/small-footer.tsx b/src/components/footer/components/small-footer.tsx index 0494971..8cd6678 100644 --- a/src/components/footer/components/small-footer.tsx +++ b/src/components/footer/components/small-footer.tsx @@ -4,41 +4,33 @@ import { Link as ChakraLink, Stack, Text, + useDisclosure, } from "@chakra-ui/react"; import { useTranslation } from "next-i18next"; import Link from "next/link"; import { useRouter } from "next/router"; -import React, { useCallback, useMemo } from "react"; +import React, { useMemo } from "react"; import { MdPublic, MdMoreHoriz } from "react-icons/md"; -import i18nConfig from "../../../../next-i18next.config"; -import { ArrElement } from "../../../types/arr-element.type"; +import { getLanguageFriendlyNameByLocale } from "../../../helpers/get-language-friendly-name-by-locale"; +import LanguageChangeModal from "./language-change-modal"; const SmallFooter = () => { - const { t } = useTranslation("common"); - const router = useRouter(); - const { pathname, asPath, query, locale } = router; + const { locale } = useRouter(); + const { t } = useTranslation("common", { + keyPrefix: "components.footer", + }); const languageName = useMemo(() => { - switch (locale) { - case "lv": { - return "Latviesu"; - } - case "ru": { - return "Русский"; - } - default: { - return "English"; - } - } - }, []); + if (!locale) return "unknown"; + return getLanguageFriendlyNameByLocale(locale); + }, [locale]); - const changeLanguage = useCallback( - (lang: ArrElement) => { - router.push({ pathname, query }, asPath, { locale: lang }); - return; - }, - [asPath, pathname, query, router] - ); + // Modal states + const { + isOpen: isLanguageOpen, + onClose: onLanguageClose, + onOpen: onLanguageOpen, + } = useDisclosure(); return ( { > - {t("footer.mini.privacyPolicy")} + {t("privacyPolicy")} - {t("footer.mini.termsOfUse")} + {t("termsOfUse")} { alignItems="center" color="grays.500" > - {t("footer.mini.allRightsReserved", { + {t("allRightsReserved", { year: new Date().getFullYear(), })} @@ -104,11 +96,7 @@ const SmallFooter = () => { padding="12px 11.5px 13px 12.5px" leftIcon={} height={46} - // For testing - // TODO: Delete the line and implement popup menu - onClick={() => { - changeLanguage(languageName === "English" ? "ru" : "en"); - }} + onClick={onLanguageOpen} > {languageName} @@ -122,6 +110,14 @@ const SmallFooter = () => { USD + + {/* Language Selection Modal */} + {isLanguageOpen && ( + + )} ); }; diff --git a/src/components/footer/data/footer-items.ts b/src/components/footer/data/footer-items.ts index 1e16457..bf3c7ce 100644 --- a/src/components/footer/data/footer-items.ts +++ b/src/components/footer/data/footer-items.ts @@ -5,19 +5,19 @@ const footerItems: FooterList[] = [ title: "about", items: [ { - name: "about.howItWorks", + name: "howItWorks", href: "/", }, { - name: "about.careers", + name: "careers", href: "/", }, { - name: "about.aboutUs", + name: "aboutUs", href: "/", }, { - name: "about.media", + name: "media", href: "/", }, ], @@ -26,15 +26,15 @@ const footerItems: FooterList[] = [ title: "community", items: [ { - name: "community.againstDiscrimination", + name: "againstDiscrimination", href: "/", }, { - name: "community.inviteFriends", + name: "inviteFriends", href: "/", }, { - name: "community.giftCards", + name: "giftCards", href: "/", }, ], @@ -43,19 +43,19 @@ const footerItems: FooterList[] = [ title: "seller", items: [ { - name: "seller.addYourVehicle", + name: "addYourVehicle", href: "/", }, { - name: "seller.businessAccount", + name: "businessAccount", href: "/", }, { - name: "seller.resourceCenter", + name: "resourceCenter", href: "/", }, { - name: "seller.community", + name: "community", href: "/", }, ], @@ -64,15 +64,15 @@ const footerItems: FooterList[] = [ title: "support", items: [ { - name: "support.covid", + name: "covid", href: "/", }, { - name: "support.helpCenter", + name: "helpCenter", href: "/", }, { - name: "support.trustSafety", + name: "trustSafety", href: "/", }, ], diff --git a/src/components/header/aside-menu.tsx b/src/components/header/aside-menu.tsx index 8128ced..bfabfc7 100644 --- a/src/components/header/aside-menu.tsx +++ b/src/components/header/aside-menu.tsx @@ -18,6 +18,9 @@ interface Props { onClose: () => void; } +// TODO: Delete the component +// 'Cause it's useless, use new aside menu instead + const AsideMenu: FC = ({ isOpen, onClose }) => { return ( diff --git a/src/components/header/index.tsx b/src/components/header/index.tsx index 0bf505e..bc13765 100644 --- a/src/components/header/index.tsx +++ b/src/components/header/index.tsx @@ -14,6 +14,9 @@ import { MdMenu, MdPerson } from "react-icons/md"; import AsideMenu from "./aside-menu"; import { FC, memo } from "react"; +// TODO: Translate the component +// Haven't translated yet 'cause the component's content isn't final + const Header: FC = () => { const { isOpen, onOpen, onClose } = useDisclosure(); diff --git a/src/components/or-divider/index.tsx b/src/components/or-divider/index.tsx index b909364..2f1d33b 100644 --- a/src/components/or-divider/index.tsx +++ b/src/components/or-divider/index.tsx @@ -1,12 +1,17 @@ import { Box, Flex, Text } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React from "react"; const OrDivider = () => { + const { t } = useTranslation("common", { + keyPrefix: "components.or-divider", + }); + return ( - Or + {t("or")} diff --git a/src/components/page-wrapper/information-section.tsx b/src/components/page-wrapper/information-section.tsx index edf64b9..c7ae892 100644 --- a/src/components/page-wrapper/information-section.tsx +++ b/src/components/page-wrapper/information-section.tsx @@ -11,6 +11,7 @@ import { MdClose } from "react-icons/md"; import { emailShorter } from "../../helpers/email-shorter"; import { useGetMeQuery } from "../../modules/auth/hooks/use-get-me-query"; import { hexIntoRGBA } from "../../helpers/hex-into-rgba"; +import { Trans } from "next-i18next"; const InformationSection = () => { const theme = useTheme(); @@ -32,9 +33,15 @@ const InformationSection = () => { }, }} > - Please confirm your email{" "} - ({emailShorter(user?.email as string)}) to get full - service features + }} + /> ); } diff --git a/src/components/progress-bar/index.tsx b/src/components/progress-bar/index.tsx index 531dd71..1cb5990 100644 --- a/src/components/progress-bar/index.tsx +++ b/src/components/progress-bar/index.tsx @@ -1,10 +1,12 @@ import React from "react"; import NextProgressBar from "nextjs-progressbar"; +import { useTheme } from "@chakra-ui/react"; const ProgressBar = () => { + const theme = useTheme(); return ( { + switch (locale) { + case "lv": { + return "Latviesu"; + } + case "ru": { + return "Русский"; + } + case "en": { + return "English"; + } + default: { + return `${locale} (unspecified)`; + } + } +}; diff --git a/src/helpers/get-language-friendly-names.ts b/src/helpers/get-language-friendly-names.ts new file mode 100644 index 0000000..191a1ce --- /dev/null +++ b/src/helpers/get-language-friendly-names.ts @@ -0,0 +1,19 @@ +/** + * Function return friendly locales names from defined locales + * @param locales locales from next.js router + * @returns friendly locales names + */ +export const getLanguageFriendlyNames = (locales: string[]) => { + return locales?.map((locale) => { + switch (locale) { + case "en": + return "English"; + case "ru": + return "Русский"; + case "lv": + return "Latviešu"; + default: + return `(specify): ${locale}`; + } + }); +}; diff --git a/src/helpers/translation-page-props.ts b/src/helpers/translation-page-props.ts index 86443d4..3b9c5ec 100644 --- a/src/helpers/translation-page-props.ts +++ b/src/helpers/translation-page-props.ts @@ -1,8 +1,13 @@ -import { GetStaticPropsContext } from "next"; +import { + GetStaticPropsContext, + GetStaticProps, + GetServerSideProps, + GetServerSidePropsContext, +} from "next"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; export const getI18nProps = async ( - ctx: GetStaticPropsContext, + ctx: GetStaticPropsContext | GetServerSidePropsContext, ns: string[] ) => { const locale = ctx?.locale as string; @@ -13,10 +18,45 @@ export const getI18nProps = async ( }; export const makeTranslationStaticProps = - (ns: string[] = []) => + (ns: string[] = [], callback?: GetStaticProps) => async (ctx: GetStaticPropsContext) => { const extended = ["common", ...ns]; + const i18Props = await getI18nProps(ctx, extended); + const callbackReturn = !!callback ? await callback(ctx) : null; + if (!!callbackReturn) { + return { + ...callbackReturn, + props: { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + ...(!!callbackReturn.props ? callbackReturn.props : {}), + ...i18Props, + }, + }; + } return { - props: await getI18nProps(ctx, extended), + props: i18Props, + }; + }; + +export const makeTranslationServerProps = + (ns: string[] = [], callback?: GetServerSideProps) => + async (ctx: GetServerSidePropsContext) => { + const extended = ["common", ...ns]; + const i18Props = await getI18nProps(ctx, extended); + const callbackReturn = !!callback ? await callback(ctx) : null; + if (!!callbackReturn) { + return { + ...callbackReturn, + props: { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + ...(!!callbackReturn.props ? callbackReturn.props : {}), + ...i18Props, + }, + }; + } + return { + props: i18Props, }; }; diff --git a/src/modules/auth/components/sign-in-form/index.tsx b/src/modules/auth/components/sign-in-form.tsx similarity index 77% rename from src/modules/auth/components/sign-in-form/index.tsx rename to src/modules/auth/components/sign-in-form.tsx index 6a3b393..19027c0 100644 --- a/src/modules/auth/components/sign-in-form/index.tsx +++ b/src/modules/auth/components/sign-in-form.tsx @@ -1,21 +1,25 @@ import { Box, Button, Flex, Link, Stack, Text } from "@chakra-ui/react"; import React, { FC, useEffect } from "react"; import { MdAddAlarm, MdOutlineLock } from "react-icons/md"; -import FloatingInput from "../../../../components/floating-input"; -import Logo from "../../../../components/logo"; +import FloatingInput from "../../../components/floating-input"; +import Logo from "../../../components/logo"; import { Controller, useForm } from "react-hook-form"; -import { SignInFormData } from "../../../../pages/auth/sign-in"; -import RadioWithText from "../../../../components/radio-with-text"; -import OrDivider from "../../../../components/or-divider"; -import { USER_VALIDATION } from "../../data/user-validation"; -import { useLoginMutation } from "../../auth-api"; +import { SignInFormData } from "../../../pages/auth/sign-in"; +import RadioWithText from "../../../components/radio-with-text"; +import OrDivider from "../../../components/or-divider"; +import { USER_VALIDATION } from "../data/user-validation"; +import { useLoginMutation } from "../auth-api"; import { useRouter } from "next/router"; -import { ErrorResponse } from "../../../../models/error-response"; -import Error from "../../../../components/error"; -import ThirdPartyAuthButton from "../third-party-auth-button"; -import { UserThirdPartyServiceType } from "../../../user/models/user/service"; +import { ErrorResponse } from "../../../models/error-response"; +import Error from "../../../components/error"; +import ThirdPartyAuthButton from "./third-party-auth-button"; +import { UserThirdPartyServiceType } from "../../user/models/user/service"; +import { useTranslation } from "next-i18next"; const SignInForm: FC = () => { + const { t } = useTranslation("auth", { + keyPrefix: "sign-in", + }); const router = useRouter(); const { handleSubmit, control } = useForm(); const [callLogin, { isLoading, data, error }] = useLoginMutation(); @@ -37,10 +41,10 @@ const SignInForm: FC = () => { - Sign In to your account + {t("signInToYourAccount")} - Enter your details to proceed further + {t("enterYourDetailsToProceedFurther")}
@@ -50,19 +54,18 @@ const SignInForm: FC = () => { rules={{ pattern: { value: USER_VALIDATION.email, - message: - 'This field doesn\'t match email format. Must be in "example@example.com" format!', + message: t("errors.emailFormat"), }, required: { value: true, - message: "Email field must be filled", + message: t("errors.emailIsRequired"), }, }} render={({ field: { value, onChange }, fieldState: { error } }) => ( } isRequired errors={error} @@ -77,7 +80,7 @@ const SignInForm: FC = () => { rules={{ required: { value: true, - message: "Password is required", + message: t("passwordIsRequired"), }, }} render={({ @@ -88,7 +91,7 @@ const SignInForm: FC = () => { value={value} onChange={onChange} type="password" - placeholder="Your Password" + placeholder={t("yourPassword")} icon={} isRequired errors={error} @@ -109,7 +112,7 @@ const SignInForm: FC = () => { onValueChange={onChange} color="black.500" > - Remember me + {t("rememberMe")} )} /> @@ -120,7 +123,7 @@ const SignInForm: FC = () => { color="brand.500" fontWeight="semibold" > - Recover password + {t("recoverPassword")} @@ -138,7 +141,7 @@ const SignInForm: FC = () => { mb="28px" type="submit" > - Sign In + {t("signInButton")} diff --git a/src/modules/auth/components/sign-in-up-image-badge/index.tsx b/src/modules/auth/components/sign-in-up-image-badge.tsx similarity index 88% rename from src/modules/auth/components/sign-in-up-image-badge/index.tsx rename to src/modules/auth/components/sign-in-up-image-badge.tsx index 9ba9b03..fadb8b4 100644 --- a/src/modules/auth/components/sign-in-up-image-badge/index.tsx +++ b/src/modules/auth/components/sign-in-up-image-badge.tsx @@ -6,8 +6,9 @@ import { Text, } from "@chakra-ui/react"; import React, { FC } from "react"; -import { SignType } from "../sign-page-wrapper"; -import image from "../../../../../public/static/assets/landing/car-market-landing-hero-bg.png"; +import { SignType } from "./sign-page-wrapper"; +import image from "../../../../public/static/assets/landing/car-market-landing-hero-bg.png"; +import { useTranslation } from "next-i18next"; interface Props { type: SignType; @@ -16,6 +17,8 @@ interface Props { } const SignInUpImageBadge: FC = ({ type, onSignIn, onSignUp }) => { + const { t } = useTranslation("auth"); + // Mini selectors const shouldGoToSignIn = type === "sign-up"; const shouldGoToSignUp = type === "sign-in"; @@ -40,7 +43,7 @@ const SignInUpImageBadge: FC = ({ type, onSignIn, onSignUp }) => { lineHeight="78px" userSelect="none" > - A new way
to buy and sell
cars + {t("badgeTitle")}
diff --git a/src/modules/auth/components/sign-page-wrapper/index.tsx b/src/modules/auth/components/sign-page-wrapper.tsx similarity index 92% rename from src/modules/auth/components/sign-page-wrapper/index.tsx rename to src/modules/auth/components/sign-page-wrapper.tsx index cc01b3f..43d3ce1 100644 --- a/src/modules/auth/components/sign-page-wrapper/index.tsx +++ b/src/modules/auth/components/sign-page-wrapper.tsx @@ -1,8 +1,8 @@ import { Box, Flex } from "@chakra-ui/react"; import { useRouter } from "next/router"; import React, { FC, PropsWithChildren } from "react"; -import Page, { SeoDataProps } from "../../../../components/page-wrapper"; -import SignInUpImageBadge from "../sign-in-up-image-badge"; +import Page, { SeoDataProps } from "../../../components/page-wrapper"; +import SignInUpImageBadge from "./sign-in-up-image-badge"; export type SignType = "sign-up" | "sign-in"; diff --git a/src/modules/auth/components/sign-up-form/ThirdSignUpStep.tsx b/src/modules/auth/components/sign-up-form/ThirdSignUpStep.tsx index b43f977..e74697b 100644 --- a/src/modules/auth/components/sign-up-form/ThirdSignUpStep.tsx +++ b/src/modules/auth/components/sign-up-form/ThirdSignUpStep.tsx @@ -1,4 +1,5 @@ import { Box, Button, Flex, Text } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React, { FC } from "react"; import DoneBadge from "../../assets/done.svg"; @@ -8,20 +9,24 @@ interface Props { } const ThirdSignUpStep: FC = ({ email, onSubmit }) => { + const { t } = useTranslation("auth", { + keyPrefix: "sign-up", + }); return ( - Thank you! + {t("thankYou")} - We sent an email to {email}
- Click confirmation link in the email to verify your account + {t("weSentAnEmailTo", { + email, + })}
); diff --git a/src/modules/auth/components/sign-up-form/first-sign-up-step.tsx b/src/modules/auth/components/sign-up-form/first-sign-up-step.tsx index 10dcfdb..0f0e2a2 100644 --- a/src/modules/auth/components/sign-up-form/first-sign-up-step.tsx +++ b/src/modules/auth/components/sign-up-form/first-sign-up-step.tsx @@ -11,6 +11,7 @@ import { RegisterDto } from "../../models/dto/register"; import ThirdPartyAuthButton from "../third-party-auth-button"; import { useLazyCheckEmailAvailabilityQuery } from "../../auth-api"; import { UserThirdPartyServiceType } from "../../../user/models/user/service"; +import { useTranslation } from "next-i18next"; export type FirstSignUpCollectableData = Pick< RegisterDto, @@ -22,6 +23,9 @@ interface Props { } const FirstSignUpStep: FC = ({ changeStep }) => { + const { t } = useTranslation("auth", { + keyPrefix: "sign-up", + }); const { control, trigger, setError, getValues, clearErrors } = useFormContext(); @@ -31,7 +35,7 @@ const FirstSignUpStep: FC = ({ changeStep }) => { useEffect(() => { if (!data && isSuccess) { setError("email", { - message: "The email is not available", + message: t("errors.emailIsNotAvailable"), type: "validate", }); } @@ -48,6 +52,7 @@ const FirstSignUpStep: FC = ({ changeStep }) => { isLoading, isSuccess, setError, + t, ]); const onContinue = useCallback(async () => { @@ -62,10 +67,10 @@ const FirstSignUpStep: FC = ({ changeStep }) => { - Sign Up to getting started + {t("signUpToGettingStarted")} - Enter your details to proceed further + {t("enterDetailsToProceedFurther")} = ({ changeStep }) => { rules={{ pattern: { value: USER_VALIDATION.email, - message: - 'This field doesn\'t match email format. Must be in "example@example.com" format!', + message: t("errors.emailFormat"), }, required: { value: true, - message: "Email field must be filled", + message: t("errors.emailIsRequired"), }, }} render={({ field: { value, onChange }, fieldState: { error } }) => ( } isRequired id="Email" @@ -100,8 +104,7 @@ const FirstSignUpStep: FC = ({ changeStep }) => { control={control} defaultValue={false} rules={{ - validate: (v) => - v || "You must agree with our Terms & Conditions", + validate: (v) => v || t("errors.youMustAgreeWithTerms"), }} render={({ field: { onChange, value }, fieldState }) => ( = ({ changeStep }) => { onValueChange={onChange} errors={fieldState.error} > - I agree with terms & conditions + {t("iAgreeWithTerms")} )} /> @@ -122,7 +125,7 @@ const FirstSignUpStep: FC = ({ changeStep }) => { onClick={onContinue} isLoading={isLoading} > - Sign Up + {t("signUpButton")} diff --git a/src/modules/auth/components/sign-up-form/second-sign-up-step.tsx b/src/modules/auth/components/sign-up-form/second-sign-up-step.tsx index e870746..45b9622 100644 --- a/src/modules/auth/components/sign-up-form/second-sign-up-step.tsx +++ b/src/modules/auth/components/sign-up-form/second-sign-up-step.tsx @@ -10,6 +10,7 @@ import { USER_VALIDATION } from "../../data/user-validation"; import { RegisterDto } from "../../models/dto/register"; import { ErrorResponse } from "../../../../models/error-response"; import Error from "../../../../components/error"; +import { useTranslation } from "next-i18next"; interface Props { isLoading: boolean; @@ -18,6 +19,9 @@ interface Props { } const SecondSignUpStep: FC = ({ isDisabled, isLoading, error }) => { + const { t } = useTranslation("auth", { + keyPrefix: "sign-up", + }); const { control, getValues } = useFormContext(); return ( @@ -26,10 +30,10 @@ const SecondSignUpStep: FC = ({ isDisabled, isLoading, error }) => { - Tell us about yourself + {t("tellUsAboutYourself")} - Enter your details to proceed further + {t("enterDetailsToProceedFurther")} @@ -39,12 +43,11 @@ const SecondSignUpStep: FC = ({ isDisabled, isLoading, error }) => { rules={{ required: { value: true, - message: "Email field must be filled", + message: t("errors.emailIsRequired"), }, pattern: { value: USER_VALIDATION.email, - message: - 'This field doesn\'t match email format. Must be in "example@example.com" format!', + message: t("errors.emailFormat"), }, }} render={({ field: { value, onChange }, fieldState: { error } }) => ( @@ -52,7 +55,7 @@ const SecondSignUpStep: FC = ({ isDisabled, isLoading, error }) => { value={value} onChange={onChange} id="email" - placeholder="Email" + placeholder={t("email")} icon={} isRequired errors={error} @@ -71,7 +74,7 @@ const SecondSignUpStep: FC = ({ isDisabled, isLoading, error }) => { name="firstName" control={control} rules={{ - required: "First name is required!", + required: t("errors.firstNameRequired"), }} render={({ field: { value, onChange }, @@ -81,7 +84,7 @@ const SecondSignUpStep: FC = ({ isDisabled, isLoading, error }) => { value={value} onChange={onChange} id="name" - placeholder="First name" + placeholder={t("firstName")} icon={} isRequired errors={error} @@ -93,7 +96,7 @@ const SecondSignUpStep: FC = ({ isDisabled, isLoading, error }) => { name="lastName" control={control} rules={{ - required: "Lastname is required!", + required: t("errors.lastNameRequired"), }} render={({ field: { value, onChange }, @@ -103,7 +106,7 @@ const SecondSignUpStep: FC = ({ isDisabled, isLoading, error }) => { value={value} onChange={onChange} id="lastname" - placeholder="Lastname" + placeholder={t("lastName")} icon={} isRequired errors={error} @@ -117,20 +120,23 @@ const SecondSignUpStep: FC = ({ isDisabled, isLoading, error }) => { rules={{ required: { value: true, - message: "Password is required", + message: t("errors.passwordIsRequired"), }, minLength: { value: USER_VALIDATION.password.min, - message: `Password must be longer than ${USER_VALIDATION.password.min} symbols!`, + message: t("errors.passwordMinLength", { + count: USER_VALIDATION.password.min, + }), }, maxLength: { value: USER_VALIDATION.password.max, - message: `Password must be less in length than ${USER_VALIDATION.password.max} symbols!`, + message: t("errors.passwordMaxLength", { + count: USER_VALIDATION.password.max, + }), }, pattern: { value: USER_VALIDATION.password.pattern, - message: - "Password must contain at least one number and one capital letter!", + message: t("errors.passwordPattern"), }, }} render={({ field: { value, onChange }, fieldState: { error } }) => ( @@ -139,7 +145,7 @@ const SecondSignUpStep: FC = ({ isDisabled, isLoading, error }) => { onChange={onChange} type="password" id="password" - placeholder="Password" + placeholder={t("password")} icon={} isRequired errors={error} @@ -152,10 +158,11 @@ const SecondSignUpStep: FC = ({ isDisabled, isLoading, error }) => { rules={{ required: { value: true, - message: "Password confirmation is required!", + message: t("errors.passwordConfirmationIsRequired"), }, validate: (value) => - value === getValues().password || "Passwords must match!", + value === getValues().password || + t("errors.passwordsMustMatch"), }} render={({ field: { value, onChange }, fieldState: { error } }) => ( = ({ isDisabled, isLoading, error }) => { onChange={onChange} type="password" id="confirmationPassword" - placeholder="Confirm password" + placeholder={t("confirmPassword")} icon={} isRequired errors={error} @@ -178,8 +185,7 @@ const SecondSignUpStep: FC = ({ isDisabled, isLoading, error }) => { control={control} defaultValue={getValues().isAgreedTerms} rules={{ - validate: (v) => - v || "You must agree with our Terms & Conditions", + validate: (v) => v || t("errors.youMustAgreeWithTerms"), }} render={({ field: { value, onChange }, fieldState }) => { return ( @@ -188,7 +194,7 @@ const SecondSignUpStep: FC = ({ isDisabled, isLoading, error }) => { onValueChange={onChange} errors={fieldState.error} > - I agree with terms & conditions + {t("iAgreeWithTerms")} ); }} @@ -205,7 +211,7 @@ const SecondSignUpStep: FC = ({ isDisabled, isLoading, error }) => { isLoading={isLoading} isDisabled={isDisabled} > - Continue + {t("continue")} diff --git a/src/modules/auth/components/third-party-auth-button/index.tsx b/src/modules/auth/components/third-party-auth-button.tsx similarity index 68% rename from src/modules/auth/components/third-party-auth-button/index.tsx rename to src/modules/auth/components/third-party-auth-button.tsx index 8f4fcb6..1e7b72e 100644 --- a/src/modules/auth/components/third-party-auth-button/index.tsx +++ b/src/modules/auth/components/third-party-auth-button.tsx @@ -1,7 +1,8 @@ import { Button, chakra } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React, { FC, useMemo } from "react"; -import { getThirdPartyServiceInfo } from "../../../user/helpers/get-third-party-service-info"; -import { UserThirdPartyServiceType } from "../../../user/models/user/service"; +import { getThirdPartyServiceInfo } from "../../user/helpers/get-third-party-service-info"; +import { UserThirdPartyServiceType } from "../../user/models/user/service"; interface Props { type: "sign-in" | "sign-up"; @@ -9,18 +10,23 @@ interface Props { } const ThirdPartyAuthButton: FC = ({ type, service }) => { + const { t } = useTranslation("auth"); const thirdPartyInfo = getThirdPartyServiceInfo(service); const text = useMemo(() => { switch (type) { case "sign-in": { - return `Sign In with ${thirdPartyInfo.name}`; + return t("signInWith", { + name: thirdPartyInfo.name, + }); } case "sign-up": { - return `Sign Up with ${thirdPartyInfo.name}`; + return t("signUpWith", { + name: thirdPartyInfo.name, + }); } } - }, [thirdPartyInfo.name, type]); + }, [t, thirdPartyInfo.name, type]); return ( diff --git a/src/modules/chats/components/chat-controls.tsx b/src/modules/chats/components/chat-controls.tsx index 3c9bdfd..eceebba 100644 --- a/src/modules/chats/components/chat-controls.tsx +++ b/src/modules/chats/components/chat-controls.tsx @@ -1,4 +1,5 @@ import { Box, Flex, IconButton, Kbd, Text } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React, { FC, useCallback } from "react"; import { UseFormReturn } from "react-hook-form"; import { MdClose, MdSend } from "react-icons/md"; @@ -15,6 +16,8 @@ interface Props { } const ChatControls: FC = ({ room, methods }) => { + const { t } = useTranslation("messages"); + const replyOn = methods.watch("replyOn"); const onReplyClear = () => { @@ -73,7 +76,7 @@ const ChatControls: FC = ({ room, methods }) => { alignItems="center" mb="10px" > - You are going to reply on: + {t("youAreGoingToReplyOn")} } @@ -115,7 +118,7 @@ const ChatControls: FC = ({ room, methods }) => { = ({ room, methods }) => { } boxSize="44px" isDisabled={methods.watch("body").length === 0} @@ -142,10 +145,10 @@ const ChatControls: FC = ({ room, methods }) => { {/* Send message tip */} {isMacPlatform ? "⌘ COMMAND" : "CTRL"} +{" "} - Enter - send message + Enter - {t("sendMessage").toLowerCase()} - Enter - new line + Enter - {t("newLine")} )} diff --git a/src/modules/chats/components/chat-message.tsx b/src/modules/chats/components/chat-message.tsx index 84347fa..c004c6c 100644 --- a/src/modules/chats/components/chat-message.tsx +++ b/src/modules/chats/components/chat-message.tsx @@ -11,6 +11,7 @@ import { Text, } from "@chakra-ui/react"; import { format } from "date-fns"; +import { useTranslation } from "next-i18next"; import React, { FC, memo, @@ -45,6 +46,7 @@ const ChatMessage: FC = ({ isPrivateConversation, onReply, }) => { + const { t } = useTranslation("messages"); const { user } = useGetMeQuery(); const [isSeenState, setIsSeenState] = useState(false); const ref = useRef(null); @@ -141,7 +143,7 @@ const ChatMessage: FC = ({ mb="1px" /> - Replied on + {t("repliedOn")} = ({ handler={() => onReply(message)} onClose={onClose} > - Reply + {t("reply")}
@@ -255,7 +257,7 @@ const ChatMessage: FC = ({ mr="4px" /> - Seen + {t("seen")} )} diff --git a/src/modules/chats/components/chat-room-content.tsx b/src/modules/chats/components/chat-room-content.tsx index 576519c..71490ee 100644 --- a/src/modules/chats/components/chat-room-content.tsx +++ b/src/modules/chats/components/chat-room-content.tsx @@ -7,6 +7,7 @@ import { Stack, Text, } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React, { FC, memo, @@ -46,6 +47,7 @@ export type MessagesControlsFormType = { }; const ChatRoomContent: FC = ({ room, onBackClick }) => { + const { t } = useTranslation("messages"); const viewboxRef = useRef(null); const totalCount = useRef(); const messagesPerPage = useRef(); @@ -241,7 +243,7 @@ const ChatRoomContent: FC = ({ room, onBackClick }) => { }} > } mr="12px" onClick={onBackClick} @@ -249,7 +251,7 @@ const ChatRoomContent: FC = ({ room, onBackClick }) => { {membersWithoutMe && ( <> - Chat with + {t("chatWith")} {membersWithoutMe.avatars.map((url, index, arr) => ( @@ -321,13 +323,13 @@ const ChatRoomContent: FC = ({ room, onBackClick }) => { {unseenMessages.length === 0 && ( <> - Previous messages + {t("previousMessages")} )} {unseenMessages.length > 0 && ( <> - New messages + {t("newMessages")} )} diff --git a/src/modules/chats/components/no-messages.tsx b/src/modules/chats/components/no-messages.tsx index d4f7a59..9f186e0 100644 --- a/src/modules/chats/components/no-messages.tsx +++ b/src/modules/chats/components/no-messages.tsx @@ -1,8 +1,10 @@ import { Box, Flex, Text } from "@chakra-ui/react"; +import { useTranslation } from "next-i18next"; import React, { FC } from "react"; import NoDataIcon from "../assets/no-data.svg"; const NoMessages: FC = () => { + const { t } = useTranslation("messages"); return ( @@ -10,14 +12,10 @@ const NoMessages: FC = () => { - No conversations yet + {t("noConversations")} - {`To start a conversation with the seller on the advert page press the "Start - conversation" button and send the first message. The further messages with - the seller will be displayed here and you can continue your dialog - from this page -`} + {t("noConversationsDescription")} diff --git a/src/modules/chats/components/select-chat-placeholder.tsx b/src/modules/chats/components/select-chat-placeholder.tsx index ae9d9d9..03b194b 100644 --- a/src/modules/chats/components/select-chat-placeholder.tsx +++ b/src/modules/chats/components/select-chat-placeholder.tsx @@ -1,8 +1,10 @@ import React from "react"; import { Flex, Text } from "@chakra-ui/react"; import SelectChatIcon from "../assets/select-chat.svg"; +import { useTranslation } from "next-i18next"; const SelectChatPlaceholder = () => { + const { t } = useTranslation("messages"); return ( { - Select conversation + {t("selectConversation")} - Select a specific conversation from the left column to view full - conversation + {t("selectConversationDescription")} ); diff --git a/src/modules/user/components/settings-content-by-tab/general.tsx b/src/modules/user/components/settings-content-by-tab/general.tsx index 226a209..7514db7 100644 --- a/src/modules/user/components/settings-content-by-tab/general.tsx +++ b/src/modules/user/components/settings-content-by-tab/general.tsx @@ -26,6 +26,7 @@ import { import { useChangeBaseSettingsMutation } from "../../user-api"; import { useFormValidationError } from "../../../../hooks/set-form-validation-errors"; import { useGetMeQuery } from "../../../auth/hooks/use-get-me-query"; +import { useTranslation } from "next-i18next"; type FormData = { currency: DropdownItemTraitObject; @@ -34,6 +35,9 @@ type FormData = { }; const SettingsGeneralContent = () => { + const { t } = useTranslation("settings", { + keyPrefix: "general", + }); const { user } = useGetMeQuery(); const [callUpdate, { error, isLoading, isSuccess, isError }] = useChangeBaseSettingsMutation(); @@ -100,7 +104,7 @@ const SettingsGeneralContent = () => { )} @@ -112,7 +116,7 @@ const SettingsGeneralContent = () => { )} @@ -124,7 +128,7 @@ const SettingsGeneralContent = () => { )} diff --git a/src/modules/user/components/social-profile-info-card.tsx b/src/modules/user/components/social-profile-info-card.tsx index 7d17481..bb85276 100644 --- a/src/modules/user/components/social-profile-info-card.tsx +++ b/src/modules/user/components/social-profile-info-card.tsx @@ -59,7 +59,7 @@ const SocialProfileInfoCard: FC = ({ type, isConnected }) => { {socialMediaInfo.name} - {isConnected ? "Connected" : "Not connected"} + {isConnected ? t("connected") : t("notConnected")} diff --git a/src/pages/auth/activation.tsx b/src/pages/auth/activation.tsx index 91e716f..2962c10 100644 --- a/src/pages/auth/activation.tsx +++ b/src/pages/auth/activation.tsx @@ -1,9 +1,11 @@ import { Box, Button, Flex, Spinner, Text } from "@chakra-ui/react"; -import { GetServerSideProps, NextPage } from "next"; +import { NextPage } from "next"; +import { useTranslation } from "next-i18next"; import { useRouter } from "next/router"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { MdCheckCircle, MdError } from "react-icons/md"; import Logo from "../../components/logo"; +import { makeTranslationServerProps } from "../../helpers/translation-page-props"; import { useLazyConfirmEmailQuery, useLazyResendConfirmationEmailQuery, @@ -34,6 +36,9 @@ interface Props { } const EmailActivationPage: NextPage = ({ activationHash }) => { + const { t } = useTranslation("auth", { + keyPrefix: "activation", + }); const [state, setState] = useState( !activationHash ? "error" : "loading" ); @@ -143,7 +148,7 @@ const EmailActivationPage: NextPage = ({ activationHash }) => { - Email confirmation + {t("emailConfirmation")} = ({ activationHash }) => { color="grays.500" align="center" > - Profile with confirmed email allows you to use full power of GreatCar - service + {t("profileWIthConfirmedEmail")} {(state === "loading" || state === "sending") && ( @@ -192,7 +196,7 @@ const EmailActivationPage: NextPage = ({ activationHash }) => { color="grays.500" align="center" > - This will take just a few seconds + {t("thisWillTakeJustAFewSeconds")} )} {state === "success" && ( @@ -203,7 +207,7 @@ const EmailActivationPage: NextPage = ({ activationHash }) => { color="green.700" align="center" > - Thank you! You have successfully confirmed your email + {t("thankYou")} )} {state === "sent" && ( @@ -214,8 +218,7 @@ const EmailActivationPage: NextPage = ({ activationHash }) => { color="green.700" align="center" > - Confirmation email was successfully sent! Check your email and - follow the instructions in the letter. + {t("confirmationEmailWasSuccessfullySent")} )} {state === "error" && ( @@ -226,8 +229,7 @@ const EmailActivationPage: NextPage = ({ activationHash }) => { color="red.500" align="center" > - Something went wrong. Resend confirmation email and try to reconfirm - from a new one. + {t("errors.wentWrongConfirmEmail")} )} {state === "send_fail" && ( @@ -238,7 +240,7 @@ const EmailActivationPage: NextPage = ({ activationHash }) => { color="red.500" align="center" > - Something went wrong while resending confirmation email + {t("errors.wentWrongResending")} )} {state !== "error" && state !== "sent" && ( @@ -248,17 +250,17 @@ const EmailActivationPage: NextPage = ({ activationHash }) => { isDisabled={state === "loading"} onClick={goToDashboard} > - Go to Dashboard + {t("goToDashboard")} )} {state === "error" && ( )} {state === "sent" && ( )} @@ -266,24 +268,27 @@ const EmailActivationPage: NextPage = ({ activationHash }) => { ); }; -export const getServerSideProps: GetServerSideProps = async (ctx) => { - const activation_hash = ctx.query[ACTIVATION_HASH_QUERY_STRING]; +export const getServerSideProps = makeTranslationServerProps( + ["auth"], + async (ctx) => { + const activation_hash = ctx.query[ACTIVATION_HASH_QUERY_STRING]; + + if (!activation_hash) { + return { + props: { + activationHash: null, + }, + }; + } - if (!activation_hash) { return { props: { - activationHash: null, + activationHash: Array.isArray(activation_hash) + ? activation_hash[0] + : activation_hash, }, }; } - - return { - props: { - activationHash: Array.isArray(activation_hash) - ? activation_hash[0] - : activation_hash, - }, - }; -}; +); export default EmailActivationPage; diff --git a/src/pages/auth/recover/index.tsx b/src/pages/auth/recover.tsx similarity index 77% rename from src/pages/auth/recover/index.tsx rename to src/pages/auth/recover.tsx index 59a331b..152d036 100644 --- a/src/pages/auth/recover/index.tsx +++ b/src/pages/auth/recover.tsx @@ -1,11 +1,13 @@ import { Box, Button, Flex, Text } from "@chakra-ui/react"; import React from "react"; -import SignPageWrapper from "../../../modules/auth/components/sign-page-wrapper"; -import LockBadge from "../../../modules/auth/assets/lock.svg"; -import FloatingInput from "../../../components/floating-input"; +import SignPageWrapper from "../../modules/auth/components/sign-page-wrapper"; +import LockBadge from "../../modules/auth/assets/lock.svg"; +import FloatingInput from "../../components/floating-input"; import { Controller, useForm } from "react-hook-form"; import { MdAddAlarm } from "react-icons/md"; -import { USER_VALIDATION } from "../../../modules/auth/data/user-validation"; +import { USER_VALIDATION } from "../../modules/auth/data/user-validation"; +import { useTranslation } from "next-i18next"; +import { makeTranslationStaticProps } from "../../helpers/translation-page-props"; const TITLE = "Recover password"; const DESCRIPTION = "Recover forgotten password"; @@ -16,6 +18,9 @@ interface RecoverData { } const RecoverPage = () => { + const { t } = useTranslation("auth", { + keyPrefix: "recover", + }); const { handleSubmit, control } = useForm(); const onSubmit = (data: RecoverData) => { @@ -35,10 +40,10 @@ const RecoverPage = () => { - Lost your password? Enter your details to recover + {t("lostYourPassword")} - Enter your details to proceed further + {t("enterYourDetails")}
@@ -74,7 +79,7 @@ const RecoverPage = () => {
@@ -82,4 +87,6 @@ const RecoverPage = () => { ); }; +export const getStaticProps = makeTranslationStaticProps(["auth"]); + export default RecoverPage; diff --git a/src/pages/auth/sign-in/index.tsx b/src/pages/auth/sign-in.tsx similarity index 58% rename from src/pages/auth/sign-in/index.tsx rename to src/pages/auth/sign-in.tsx index bcd6130..d5dd612 100644 --- a/src/pages/auth/sign-in/index.tsx +++ b/src/pages/auth/sign-in.tsx @@ -1,7 +1,8 @@ import React from "react"; -import SignInForm from "../../../modules/auth/components/sign-in-form"; -import SingPageWrapper from "../../../modules/auth/components/sign-page-wrapper"; -import { LoginDto } from "../../../modules/auth/models/dto/login"; +import { makeTranslationStaticProps } from "../../helpers/translation-page-props"; +import SignInForm from "../../modules/auth/components/sign-in-form"; +import SingPageWrapper from "../../modules/auth/components/sign-page-wrapper"; +import { LoginDto } from "../../modules/auth/models/dto/login"; type AdditionalSignInFormData = { rememberMe: boolean; @@ -26,4 +27,6 @@ const SignInPage = () => { ); }; +export const getStaticProps = makeTranslationStaticProps(["auth"]); + export default SignInPage; diff --git a/src/pages/auth/sign-up/index.tsx b/src/pages/auth/sign-up.tsx similarity index 82% rename from src/pages/auth/sign-up/index.tsx rename to src/pages/auth/sign-up.tsx index 226e59c..e75de53 100644 --- a/src/pages/auth/sign-up/index.tsx +++ b/src/pages/auth/sign-up.tsx @@ -2,16 +2,17 @@ import { NextPage } from "next"; import { useRouter } from "next/router"; import React, { useCallback, useEffect, useMemo } from "react"; import { FormProvider, useForm } from "react-hook-form"; -import { useFormValidationError } from "../../../hooks/set-form-validation-errors"; -import { ErrorResponse } from "../../../models/error-response"; -import { useRegisterMutation } from "../../../modules/auth/auth-api"; -import SignPageWrapper from "../../../modules/auth/components/sign-page-wrapper"; +import { makeTranslationStaticProps } from "../../helpers/translation-page-props"; +import { useFormValidationError } from "../../hooks/set-form-validation-errors"; +import { ErrorResponse } from "../../models/error-response"; +import { useRegisterMutation } from "../../modules/auth/auth-api"; +import SignPageWrapper from "../../modules/auth/components/sign-page-wrapper"; import { FirstSignUpStep, SecondSignUpStep, ThirdSignUpStep, -} from "../../../modules/auth/components/sign-up-form"; -import { RegisterDto } from "../../../modules/auth/models/dto/register"; +} from "../../modules/auth/components/sign-up-form"; +import { RegisterDto } from "../../modules/auth/models/dto/register"; const TOTAL_STEPS = 3; @@ -38,17 +39,13 @@ const SignUpPage: NextPage = ({}) => { const currentStep = useMemo(() => { const { step } = router.query; - const numericStep = Number(step); - if (isNaN(numericStep)) { return 1; } - if (numericStep > TOTAL_STEPS) { return 1; } - return numericStep; }, [router.query]); @@ -128,4 +125,6 @@ const SignUpPage: NextPage = ({}) => { ); }; +export const getStaticProps = makeTranslationStaticProps(["auth"]); + export default SignUpPage; diff --git a/src/pages/messages/index.tsx b/src/pages/messages/index.tsx index 8dbd8e1..2d12453 100644 --- a/src/pages/messages/index.tsx +++ b/src/pages/messages/index.tsx @@ -8,6 +8,7 @@ import ChatRoomListItem from "../../modules/chats/components/chat-room-list-item import NoMessages from "../../modules/chats/components/no-messages"; import { useGetChatRoomsQuery } from "../../modules/chats/chat-api"; import SelectChatPlaceholder from "../../modules/chats/components/select-chat-placeholder"; +import { makeTranslationStaticProps } from "../../helpers/translation-page-props"; const TITLE = "My messages"; const DESCRIPTION = "My messages"; @@ -105,4 +106,6 @@ const MessagesPage: NextPage = () => { ); }; +export const getStaticProps = makeTranslationStaticProps(["messages"]); + export default MessagesPage; diff --git a/src/styles/theme/components/button.ts b/src/styles/theme/components/button.ts index ebefff4..791e1e6 100644 --- a/src/styles/theme/components/button.ts +++ b/src/styles/theme/components/button.ts @@ -46,15 +46,15 @@ const ThemedButton: ComponentStyleConfig = { }, ghost: { - backgroundColor: "grays.50", + backgroundColor: "grays.100", color: "grays.500", _hover: { - backgroundColor: "grays.300", + backgroundColor: "grays.50", }, _active: { - backgroundColor: "grays.50", + backgroundColor: "grays.200", }, }, -- GitLab