From 44f1e1e18b608916930d85b38a0c85c04d4e3d6f Mon Sep 17 00:00:00 2001 From: Rakhimzhan Rakhmaev Date: Thu, 24 Jul 2025 20:15:21 +0600 Subject: [PATCH 1/2] feat: added en locale --- frontend/src/locales/en.js | 113 +++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 frontend/src/locales/en.js diff --git a/frontend/src/locales/en.js b/frontend/src/locales/en.js new file mode 100644 index 0000000..aeb131c --- /dev/null +++ b/frontend/src/locales/en.js @@ -0,0 +1,113 @@ +const en = { + translation: { + header: { + title: 'Hexlet chat', + signedAs: 'Signed as: ', + lang: 'Change lang', + exit: 'Exit', + }, + errorPage: { + title: 'Oops!', + notFound: 'Page not found', + goMain: { + text: 'But you can ', + link: 'go to main page', + }, + }, + login: { + title: 'Login', + noAccount: 'No acc?', + signupLink: 'Registration', + form: { + username: 'Your nickname', + password: 'Password', + submit: 'Login', + errors: { + required: 'Required to fill', + invalidRequest: 'Not valid username or password', + }, + }, + }, + signup: { + title: 'Registration', + form: { + username: 'User name', + password: 'Password', + confirmPassword: 'Confirm password', + submit: 'Registration', + errors: { + required: 'Required to fill', + usernameLength: 'From 3 to 20 symbols', + passwordLength: 'More 6 symbols', + confirmPassword: 'Passwords must match', + invalidRequest: 'This user already exists', + }, + }, + }, + chat: { + channels: { + title: 'Channels', + removeButton: 'Delete', + renameButton: 'Rename', + addButton: '+', + editButton: 'Channel management', + marker: '#', + }, + messages: { + marker: '#', + count_one: '{{count}} message', + count_other: '{{count}} messages', + form: { + newMessage: 'Enter message...', + }, + }, + }, + notifications: { + add: 'Channel created', + remove: 'Channel deleted', + rename: 'Channel renamed', + errors: { + network: 'Connection error', + server: 'Data loading error', + unknown: 'Unknown error', + parsing: 'Data loading error', + }, + }, + modals: { + add: { + title: 'Add channel', + form: { + name: 'Channel name', + submit: 'Submit', + cancel: 'Cancel', + errors: { + required: 'Required field', + length: '3 to 20 characters', + uniq: 'Must be unique', + }, + }, + }, + remove: { + title: 'Remove channel', + description: 'Are you sure?', + submit: 'Remove', + cancel: 'Cancel', + }, + rename: { + title: 'Rename channel', + form: { + name: 'New channel name', + submit: 'Submit', + cancel: 'Cancel', + errors: { + required: 'Required field', + length: '3 to 20 characters', + uniq: 'Must be unique', + }, + }, + }, + }, + }, +}; + +export default en; From 0fc5b9d304482ece065b53785b30be54d398387f Mon Sep 17 00:00:00 2001 From: Rakhimzhan Rakhmaev Date: Thu, 24 Jul 2025 20:17:14 +0600 Subject: [PATCH 2/2] fix: i18n keys --- .../src/components/Channels/ChannelsBody.jsx | 31 +++++++++++++++---- frontend/src/components/Channels/index.jsx | 18 +++++------ frontend/src/components/ChatModal/index.jsx | 10 +++--- frontend/src/components/Header.jsx | 6 +++- frontend/src/components/LoginForm.jsx | 6 ++-- frontend/src/components/SignupForm.jsx | 14 ++++----- frontend/src/locales/index.js | 3 +- frontend/src/locales/ru.js | 1 + 8 files changed, 54 insertions(+), 35 deletions(-) diff --git a/frontend/src/components/Channels/ChannelsBody.jsx b/frontend/src/components/Channels/ChannelsBody.jsx index f88a631..38b352b 100644 --- a/frontend/src/components/Channels/ChannelsBody.jsx +++ b/frontend/src/components/Channels/ChannelsBody.jsx @@ -14,7 +14,9 @@ const ChannelsBody = ({ const renderButton = (name, id) => ( - + {t('chat.channels.editButton')} - {t('chat.channels.removeButton')} - {t('chat.channels.renameButton')} + + {t('chat.channels.removeButton')} + + + {t('chat.channels.renameButton')} + ); return ( -
    +
      {data?.map(({ id, name, removable }) => ( - ))} diff --git a/frontend/src/components/Channels/index.jsx b/frontend/src/components/Channels/index.jsx index 01c2e39..dd551d5 100644 --- a/frontend/src/components/Channels/index.jsx +++ b/frontend/src/components/Channels/index.jsx @@ -15,11 +15,12 @@ import SocketContext from '../../contexts'; const Channels = () => { const { data } = useGetChannelsQuery(); const dispatch = useDispatch(); - const currentChannelName = useSelector(({ - ui, - }) => ui.currentChannel.name ?? ui.defaultChannel.name); - const { name: modalName, show: modalShow } = useSelector((state) => state.ui.currentModal); - const { currentChannel, clickedChannel, defaultChannel } = useSelector((state) => state.ui); + const currentChannelName = useSelector( + ({ ui }) => ui.currentChannel.name ?? ui.defaultChannel.name, + ); + const { currentChannel, clickedChannel, defaultChannel } = useSelector( + (state) => state.ui, + ); const socket = useContext(SocketContext); const channelsNames = data?.map(({ name }) => name); @@ -93,12 +94,7 @@ const Channels = () => { onRenameChannel={handleRenameChannel} onSetCurrentChannel={handleSetCurrentChannel} /> - + ); }; diff --git a/frontend/src/components/ChatModal/index.jsx b/frontend/src/components/ChatModal/index.jsx index 4a4e7ed..1c915ec 100644 --- a/frontend/src/components/ChatModal/index.jsx +++ b/frontend/src/components/ChatModal/index.jsx @@ -1,4 +1,5 @@ import Modal from 'react-bootstrap/Modal'; +import { useSelector } from 'react-redux'; import RenameChannelModal from './RenameChannelModal'; import DeleteChannelModal from './DeleteChannelModal'; import AddChannelModal from './AddChannelModal'; @@ -9,13 +10,10 @@ const mapModal = { addChannel: AddChannelModal, }; -const ChatModal = ({ - name, - show, - onHide, - validationData, -}) => { +const ChatModal = ({ onHide, validationData }) => { + const { name, show } = useSelector((state) => state.ui.currentModal); const ModalBody = mapModal[name] ?? (() => null); + return ( { const navigate = useNavigate(); const dispatch = useDispatch(); - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const currentUserName = useSelector(({ authData }) => authData.username); const handleClick = () => { @@ -18,12 +18,16 @@ const Header = () => { dispatch(resetAuthData()); navigate('/login', { replace: false }); }; + const handleChangeLang = () => { + i18n.changeLanguage(i18n.language === 'en' ? 'ru' : 'en'); + }; return ( {t('header.title')} + {currentUserName && ( <> diff --git a/frontend/src/components/LoginForm.jsx b/frontend/src/components/LoginForm.jsx index cdd8f9f..6c07b4c 100644 --- a/frontend/src/components/LoginForm.jsx +++ b/frontend/src/components/LoginForm.jsx @@ -65,7 +65,7 @@ const LoginForm = () => { onChange={formik.handleChange} className={classNames('form-control', { 'is-invalid': !!formik.errors.username || !!formik.errors.unathorized })} /> - + {formik.errors.username &&
      {formik.errors.username}
      }
      @@ -80,12 +80,12 @@ const LoginForm = () => { onChange={formik.handleChange} className={classNames('form-control', { 'is-invalid': !!formik.errors.password || !!formik.errors.unathorized })} /> - + {formik.errors.password &&
      {formik.errors.password}
      } {formik.errors.unathorized &&
      {t('login.form.errors.invalidRequest')}
      }
      ); diff --git a/frontend/src/components/SignupForm.jsx b/frontend/src/components/SignupForm.jsx index 1e46707..2c14c6d 100644 --- a/frontend/src/components/SignupForm.jsx +++ b/frontend/src/components/SignupForm.jsx @@ -75,13 +75,13 @@ const SignupForm = () => { name="username" autoComplete="username" required="" - placeholder="Имя пользователя" + placeholder={t('signup.form.username')} id="username" value={formik.values.username} onChange={formik.handleChange} className={classNames('form-control', { 'is-invalid': !!formik.errors.username || !!formik.errors.isNotUniq })} /> - + {formik.errors.username &&
      {formik.errors.username}
      } @@ -90,14 +90,14 @@ const SignupForm = () => { name="password" autoComplete="password" required="" - placeholder="Пароль" + placeholder={t('signup.form.password')} type="password" id="password" value={formik.values.password} onChange={formik.handleChange} className={classNames('form-control', { 'is-invalid': !!formik.errors.password || !!formik.errors.isNotUniq })} /> - + {formik.errors.password &&
      {formik.errors.password}
      } @@ -106,19 +106,19 @@ const SignupForm = () => { name="confirmPassword" autoComplete="confirmPassword" required="" - placeholder="Подтвердите пароль" + placeholder={t('signup.form.confirmPassword')} type="password" id="confirmPassword" value={formik.values.confirmPassword} onChange={formik.handleChange} className={classNames('form-control', { 'is-invalid': !!formik.errors.confirmPassword || !!formik.errors.isNotUniq })} /> - + {formik.errors.confirmPassword &&
      {formik.errors.confirmPassword}
      } {formik.errors.isNotUniq &&
      {t('signup.form.errors.invalidRequest')}
      } ); diff --git a/frontend/src/locales/index.js b/frontend/src/locales/index.js index fe2af75..5564cec 100644 --- a/frontend/src/locales/index.js +++ b/frontend/src/locales/index.js @@ -1,5 +1,6 @@ import ru from './ru'; +import en from './en'; -const resources = { ru }; +const resources = { ru, en }; export default resources; diff --git a/frontend/src/locales/ru.js b/frontend/src/locales/ru.js index b38cb65..5a48399 100644 --- a/frontend/src/locales/ru.js +++ b/frontend/src/locales/ru.js @@ -4,6 +4,7 @@ const ru = { title: 'Hexlet chat', signedAs: 'Вошли как: ', exit: 'Выйти', + lang: 'Сменить язык', }, errorPage: { title: 'Упс!',