From 1495a4b01ec1d0596a7213a3b9faab3f09c81d78 Mon Sep 17 00:00:00 2001 From: "v.lagerev" Date: Tue, 14 Apr 2026 01:20:26 +0300 Subject: [PATCH] localize direct messages --- public/locales/en.json | 104 ++++++++++++++ public/locales/ru.json | 104 ++++++++++++++ src/app/components/message/Time.tsx | 4 +- .../message/content/FallbackContent.tsx | 127 +++++++++++------- src/app/features/create-chat/CreateChat.tsx | 24 ++-- src/app/features/room/MembersDrawer.tsx | 8 +- src/app/features/room/RoomInput.tsx | 14 +- src/app/features/room/RoomTimeline.tsx | 12 +- src/app/features/room/RoomViewFollowing.tsx | 18 +-- src/app/features/room/RoomViewHeader.tsx | 27 ++-- src/app/features/room/RoomViewTyping.tsx | 20 +-- .../features/room/jump-to-time/JumpToTime.tsx | 18 +-- src/app/features/room/message/Message.tsx | 63 +++++---- .../room/room-pin-menu/RoomPinMenu.tsx | 13 +- src/app/pages/client/direct/Direct.tsx | 19 ++- src/app/pages/client/direct/DirectCreate.tsx | 6 +- src/app/pages/client/sidebar/DirectTab.tsx | 7 +- 17 files changed, 429 insertions(+), 159 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 12a228f9..aef5137a 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -347,5 +347,109 @@ "toggle_chat": "Toggle Chat", "live_count": "{{count}} Live", "open": "Open" + }, + "Direct": { + "direct_messages": "Direct Messages", + "mark_as_read": "Mark as Read", + "no_direct_messages": "No Direct Messages", + "no_direct_messages_desc": "You do not have any direct messages yet.", + "direct_message": "Direct Message", + "create_chat": "Create Chat", + "create_chat_subtitle": "Start a private, encrypted chat by entering a user ID.", + "chats": "Chats", + "user_id": "User ID", + "user_id_placeholder": "@username:server", + "invalid_user_id": "Please enter a valid User ID.", + "options": "Options", + "e2e_encryption": "End-to-End Encryption", + "e2e_encryption_desc": "Once this feature is enabled, it can't be disabled after the room is created.", + "rate_limited": "Server rate-limited your request for {{minutes}} minutes!", + "create": "Create" + }, + "Room": { + "new_messages": "New Messages", + "jump_to_unread": "Jump to Unread", + "mark_as_read": "Mark as Read", + "jump_to_latest": "Jump to Latest", + "today": "Today", + "yesterday": "Yesterday", + + "view_reactions": "View Reactions", + "read_receipts": "Read Receipts", + "view_source": "View Source", + "source_code": "Source Code", + "copy_link": "Copy Link", + "pin_message": "Pin Message", + "unpin_message": "Unpin Message", + "add_reaction": "Add Reaction", + "reply": "Reply", + "reply_in_thread": "Reply in Thread", + "edit_message": "Edit Message", + + "delete_message": "Delete Message", + "delete_confirm": "This action is irreversible! Are you sure that you want to delete this message?", + "reason": "Reason", + "optional": "optional", + "delete_error": "Failed to delete message! Please try again.", + "deleting": "Deleting...", + "delete": "Delete", + + "report_message": "Report Message", + "report_desc": "Report this message to server, which may then notify the appropriate people to take action.", + "report_reason": "Reason", + "report_error": "Failed to report message! Please try again.", + "report_success": "Message has been reported to server.", + "reporting": "Reporting...", + "report": "Report", + "no_reason": "No reason provided", + + "is_typing": " is typing...", + "and": " and ", + "are_typing": " are typing...", + "others_count": "{{count}} others", + "drop_typing": "Dismiss typing indicator", + + "members": "Members", + "members_count": "{{count}} Members", + "hide_members": "Hide Members", + "show_members": "Show Members", + "more_options": "More Options", + "close": "Close", + "search": "Search", + "notifications": "Notifications", + "invite": "Invite", + "room_settings": "Room Settings", + "jump_to_time": "Jump to Time", + "leave_room": "Leave Room", + + "send_message": "Send a message...", + "drop_files": "Drop Files in \"{{name}}\"", + "drag_drop_desc": "Drag and drop files here or click for selection dialog", + + "is_following": " is following the conversation.", + "are_following": " are following the conversation.", + "others_following_count": "{{count}} others", + + "pinned_messages": "Pinned Messages", + "no_pinned_messages": "No Pinned Messages", + "no_pinned_messages_desc": "Users with sufficient permissions can pin messages from the message context menu.", + "open": "Open", + "failed_to_load": "Failed to load message!", + + "time_label": "Time", + "date_label": "Date", + "preset": "Preset", + "beginning": "Beginning", + "open_timeline": "Open Timeline", + + "message_deleted": "This message has been deleted", + "message_deleted_reason": "This message has been deleted. {{reason}}", + "unsupported_message": "Unsupported message", + "failed_to_load_message": "Failed to load message", + "unable_to_decrypt": "Unable to decrypt message", + "not_decrypted_yet": "This message is not decrypted yet", + "broken_message": "Broken message", + "empty_message": "Empty message", + "edited": " (edited)" } } diff --git a/public/locales/ru.json b/public/locales/ru.json index 71c8e4da..31fc5a62 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -347,5 +347,109 @@ "toggle_chat": "Переключить чат", "live_count": "{{count}} В эфире", "open": "Открыть" + }, + "Direct": { + "direct_messages": "Личные сообщения", + "mark_as_read": "Отметить прочитанным", + "no_direct_messages": "Нет личных сообщений", + "no_direct_messages_desc": "У вас ещё нет личных сообщений.", + "direct_message": "Новый чат", + "create_chat": "Новый чат", + "create_chat_subtitle": "Начните приватный зашифрованный чат, указав ID пользователя.", + "chats": "Чаты", + "user_id": "ID пользователя", + "user_id_placeholder": "@username:server", + "invalid_user_id": "Введите корректный ID пользователя.", + "options": "Параметры", + "e2e_encryption": "Сквозное шифрование", + "e2e_encryption_desc": "После включения эту функцию нельзя отключить после создания комнаты.", + "rate_limited": "Сервер ограничил частоту запросов на {{minutes}} мин.!", + "create": "Создать" + }, + "Room": { + "new_messages": "Новые сообщения", + "jump_to_unread": "К непрочитанным", + "mark_as_read": "Отметить прочитанным", + "jump_to_latest": "К последним", + "today": "Сегодня", + "yesterday": "Вчера", + + "view_reactions": "Реакции", + "read_receipts": "Подтверждения прочтения", + "view_source": "Исходный код", + "source_code": "Исходный код", + "copy_link": "Копировать ссылку", + "pin_message": "Закрепить сообщение", + "unpin_message": "Открепить сообщение", + "add_reaction": "Добавить реакцию", + "reply": "Ответить", + "reply_in_thread": "Ответить в треде", + "edit_message": "Редактировать", + + "delete_message": "Удалить сообщение", + "delete_confirm": "Это действие необратимо! Вы уверены, что хотите удалить это сообщение?", + "reason": "Причина", + "optional": "необязательно", + "delete_error": "Не удалось удалить сообщение! Попробуйте снова.", + "deleting": "Удаление...", + "delete": "Удалить", + + "report_message": "Пожаловаться", + "report_desc": "Сообщить о нарушении на сервер, который может уведомить ответственных лиц для принятия мер.", + "report_reason": "Причина", + "report_error": "Не удалось отправить жалобу! Попробуйте снова.", + "report_success": "Жалоба отправлена на сервер.", + "reporting": "Отправка...", + "report": "Пожаловаться", + "no_reason": "Причина не указана", + + "is_typing": " печатает...", + "and": " и ", + "are_typing": " печатают...", + "others_count": "ещё {{count}}", + "drop_typing": "Скрыть индикатор набора", + + "members": "Участники", + "members_count": "{{count}} участников", + "hide_members": "Скрыть участников", + "show_members": "Показать участников", + "more_options": "Ещё", + "close": "Закрыть", + "search": "Поиск", + "notifications": "Уведомления", + "invite": "Пригласить", + "room_settings": "Настройки комнаты", + "jump_to_time": "Перейти к дате", + "leave_room": "Покинуть комнату", + + "send_message": "Написать сообщение...", + "drop_files": "Перетащите файлы в \"{{name}}\"", + "drag_drop_desc": "Перетащите файлы сюда или нажмите для выбора", + + "is_following": " читает чат.", + "are_following": " читают чат.", + "others_following_count": "ещё {{count}}", + + "pinned_messages": "Закреплённые сообщения", + "no_pinned_messages": "Нет закреплённых сообщений", + "no_pinned_messages_desc": "Пользователи с достаточным уровнем прав могут закреплять сообщения через контекстное меню.", + "open": "Открыть", + "failed_to_load": "Не удалось загрузить сообщение!", + + "time_label": "Время", + "date_label": "Дата", + "preset": "Пресет", + "beginning": "Начало", + "open_timeline": "Открыть ленту", + + "message_deleted": "Сообщение было удалено", + "message_deleted_reason": "Сообщение было удалено. {{reason}}", + "unsupported_message": "Неподдерживаемое сообщение", + "failed_to_load_message": "Не удалось загрузить сообщение", + "unable_to_decrypt": "Не удалось расшифровать сообщение", + "not_decrypted_yet": "Сообщение ещё не расшифровано", + "broken_message": "Повреждённое сообщение", + "empty_message": "Пустое сообщение", + "edited": " (изменено)" } } diff --git a/src/app/components/message/Time.tsx b/src/app/components/message/Time.tsx index 3eab5cc2..b15f8bf7 100644 --- a/src/app/components/message/Time.tsx +++ b/src/app/components/message/Time.tsx @@ -1,5 +1,6 @@ import React, { ComponentProps } from 'react'; import { Text, as } from 'folds'; +import { useTranslation } from 'react-i18next'; import { timeDayMonYear, timeHourMinute, today, yesterday } from '../../utils/time'; export type TimeProps = { @@ -23,6 +24,7 @@ export type TimeProps = { */ export const Time = as<'span', TimeProps & ComponentProps>( ({ compact, hour24Clock, dateFormatString, ts, ...props }, ref) => { + const { t } = useTranslation(); const formattedTime = timeHourMinute(ts, hour24Clock); let time = ''; @@ -31,7 +33,7 @@ export const Time = as<'span', TimeProps & ComponentProps>( } else if (today(ts)) { time = formattedTime; } else if (yesterday(ts)) { - time = `Yesterday ${formattedTime}`; + time = `${t('Room.yesterday')} ${formattedTime}`; } else { time = `${timeDayMonYear(ts, dateFormatString)} ${formattedTime}`; } diff --git a/src/app/components/message/content/FallbackContent.tsx b/src/app/components/message/content/FallbackContent.tsx index 9edb9678..4af26fc0 100644 --- a/src/app/components/message/content/FallbackContent.tsx +++ b/src/app/components/message/content/FallbackContent.tsx @@ -1,66 +1,91 @@ import { Box, Icon, Icons, Text, as, color, config } from 'folds'; import React from 'react'; +import { useTranslation } from 'react-i18next'; const warningStyle = { color: color.Warning.Main, opacity: config.opacity.P300 }; const criticalStyle = { color: color.Critical.Main, opacity: config.opacity.P300 }; export const MessageDeletedContent = as<'div', { children?: never; reason?: string }>( - ({ reason, ...props }, ref) => ( - - - {reason ? ( - This message has been deleted. {reason} - ) : ( - This message has been deleted - )} - - ) + ({ reason, ...props }, ref) => { + const { t } = useTranslation(); + return ( + + + {reason ? ( + {t('Room.message_deleted_reason', { reason })} + ) : ( + {t('Room.message_deleted')} + )} + + ); + } ); -export const MessageUnsupportedContent = as<'div', { children?: never }>(({ ...props }, ref) => ( - - - Unsupported message - -)); +export const MessageUnsupportedContent = as<'div', { children?: never }>(({ ...props }, ref) => { + const { t } = useTranslation(); + return ( + + + {t('Room.unsupported_message')} + + ); +}); -export const MessageFailedContent = as<'div', { children?: never }>(({ ...props }, ref) => ( - - - Failed to load message - -)); +export const MessageFailedContent = as<'div', { children?: never }>(({ ...props }, ref) => { + const { t } = useTranslation(); + return ( + + + {t('Room.failed_to_load_message')} + + ); +}); -export const MessageBadEncryptedContent = as<'div', { children?: never }>(({ ...props }, ref) => ( - - - Unable to decrypt message - -)); +export const MessageBadEncryptedContent = as<'div', { children?: never }>(({ ...props }, ref) => { + const { t } = useTranslation(); + return ( + + + {t('Room.unable_to_decrypt')} + + ); +}); -export const MessageNotDecryptedContent = as<'div', { children?: never }>(({ ...props }, ref) => ( - - - This message is not decrypted yet - -)); +export const MessageNotDecryptedContent = as<'div', { children?: never }>(({ ...props }, ref) => { + const { t } = useTranslation(); + return ( + + + {t('Room.not_decrypted_yet')} + + ); +}); -export const MessageBrokenContent = as<'div', { children?: never }>(({ ...props }, ref) => ( - - - Broken message - -)); +export const MessageBrokenContent = as<'div', { children?: never }>(({ ...props }, ref) => { + const { t } = useTranslation(); + return ( + + + {t('Room.broken_message')} + + ); +}); -export const MessageEmptyContent = as<'div', { children?: never }>(({ ...props }, ref) => ( - - - Empty message - -)); +export const MessageEmptyContent = as<'div', { children?: never }>(({ ...props }, ref) => { + const { t } = useTranslation(); + return ( + + + {t('Room.empty_message')} + + ); +}); -export const MessageEditedContent = as<'span', { children?: never }>(({ ...props }, ref) => ( - - {' (edited)'} - -)); +export const MessageEditedContent = as<'span', { children?: never }>(({ ...props }, ref) => { + const { t } = useTranslation(); + return ( + + {t('Room.edited')} + + ); +}); diff --git a/src/app/features/create-chat/CreateChat.tsx b/src/app/features/create-chat/CreateChat.tsx index faa4511b..e8939eb5 100644 --- a/src/app/features/create-chat/CreateChat.tsx +++ b/src/app/features/create-chat/CreateChat.tsx @@ -1,5 +1,6 @@ import { Box, Button, color, config, Icon, Icons, Input, Spinner, Switch, Text } from 'folds'; import React, { FormEventHandler, useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { ICreateRoomStateEvent, MatrixError, Preset, Visibility } from 'matrix-js-sdk'; import { useNavigate } from 'react-router-dom'; import { SettingTile } from '../../components/setting-tile'; @@ -17,6 +18,7 @@ type CreateChatProps = { defaultUserId?: string; }; export function CreateChat({ defaultUserId }: CreateChatProps) { + const { t } = useTranslation(); const mx = useMatrixClient(); const alive = useAlive(); const navigate = useNavigate(); @@ -75,10 +77,10 @@ export function CreateChat({ defaultUserId }: CreateChatProps) { return ( - User ID + {t('Direct.user_id')} - Please enter a valid User ID. + {t('Direct.invalid_user_id')} )} - Options + {t('Direct.options')} {error instanceof MatrixError && error.name === ErrorCode.M_LIMIT_EXCEEDED - ? `Server rate-limited your request for ${millisecondsToMinutes( - (error.data.retry_after_ms as number | undefined) ?? 0 - )} minutes!` + ? t('Direct.rate_limited', { + minutes: millisecondsToMinutes( + (error.data.retry_after_ms as number | undefined) ?? 0 + ), + }) : error.message} @@ -142,7 +146,7 @@ export function CreateChat({ defaultUserId }: CreateChatProps) { disabled={disabled} before={loading && } > - Create + {t('Direct.create')} diff --git a/src/app/features/room/MembersDrawer.tsx b/src/app/features/room/MembersDrawer.tsx index d9205076..f11de4aa 100644 --- a/src/app/features/room/MembersDrawer.tsx +++ b/src/app/features/room/MembersDrawer.tsx @@ -29,6 +29,7 @@ import { import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk'; import { useVirtualizer } from '@tanstack/react-virtual'; import classNames from 'classnames'; +import { useTranslation } from 'react-i18next'; import * as css from './MembersDrawer.css'; import { useMatrixClient } from '../../hooks/useMatrixClient'; @@ -64,14 +65,15 @@ type MemberDrawerHeaderProps = { room: Room; }; function MemberDrawerHeader({ room }: MemberDrawerHeaderProps) { + const { t } = useTranslation(); const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer'); return (
- - {`${millify(room.getJoinedMemberCount())} Members`} + + {t('Room.members_count', { count: millify(room.getJoinedMemberCount()) })} @@ -81,7 +83,7 @@ function MemberDrawerHeader({ room }: MemberDrawerHeaderProps) { offset={4} tooltip={ - Close + {t('Room.close')} } > diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index f88ccf93..2112bd3c 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -9,6 +9,7 @@ import React, { } from 'react'; import { useAtom, useAtomValue } from 'jotai'; import { isKeyHotkey } from 'is-hotkey'; +import { useTranslation } from 'react-i18next'; import { EventType, IContent, MsgType, RelationType, Room } from 'matrix-js-sdk'; import { ReactEditor } from 'slate-react'; import { Transforms, Editor } from 'slate'; @@ -126,6 +127,7 @@ interface RoomInputProps { } export const RoomInput = forwardRef( ({ editor, fileDropContainerRef, roomId, room }, ref) => { + const { t } = useTranslation(); const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const [enterForNewline] = useSetting(settingsAtom, 'enterForNewline'); @@ -497,9 +499,9 @@ export const RoomInput = forwardRef( > - {`Drop Files in "${room?.name || 'Room'}"`} + {t('Room.drop_files', { name: room?.name || 'Room' })} - Drag and drop files here or click for selection dialog + {t('Room.drag_drop_desc')} @@ -539,7 +541,7 @@ export const RoomInput = forwardRef( ( onCustomEmojiSelect={handleEmoticonSelect} onStickerSelect={handleStickerSelect} requestClose={() => { - setEmojiBoardTab((t) => { - if (t) { + setEmojiBoardTab((tab) => { + if (tab) { if (!mobileOrTablet()) ReactEditor.focus(editor); return undefined; } - return t; + return tab; }); }} /> diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 39d7e50a..d651dd11 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -1676,7 +1676,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli - New Messages + {t('Room.new_messages')} @@ -1689,8 +1689,8 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli {(() => { - if (today(mEvent.getTs())) return 'Today'; - if (yesterday(mEvent.getTs())) return 'Yesterday'; + if (today(mEvent.getTs())) return t('Room.today'); + if (yesterday(mEvent.getTs())) return t('Room.yesterday'); return timeDayMonthYear(mEvent.getTs()); })()} @@ -1726,7 +1726,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli before={} onClick={handleJumpToUnread} > - Jump to Unread + {t('Room.jump_to_unread')} } onClick={handleMarkAsRead} > - Mark as Read + {t('Room.mark_as_read')} )} @@ -1836,7 +1836,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli before={} onClick={handleJumpToLatest} > - Jump to Latest + {t('Room.jump_to_latest')} )} diff --git a/src/app/features/room/RoomViewFollowing.tsx b/src/app/features/room/RoomViewFollowing.tsx index 5a96e6ad..28069239 100644 --- a/src/app/features/room/RoomViewFollowing.tsx +++ b/src/app/features/room/RoomViewFollowing.tsx @@ -14,6 +14,7 @@ import { import { Room } from 'matrix-js-sdk'; import classNames from 'classnames'; import FocusTrap from 'focus-trap-react'; +import { useTranslation } from 'react-i18next'; import { getMemberDisplayName } from '../../utils/room'; import { getMxIdLocalPart } from '../../utils/matrix'; @@ -33,6 +34,7 @@ export type RoomViewFollowingProps = { }; export const RoomViewFollowing = as<'div', RoomViewFollowingProps>( ({ className, room, ...props }, ref) => { + const { t } = useTranslation(); const mx = useMatrixClient(); const [open, setOpen] = useState(false); const latestEvent = useRoomLatestRenderedEvent(room); @@ -83,7 +85,7 @@ export const RoomViewFollowing = as<'div', RoomViewFollowingProps>( <> {names[0]} - {' is following the conversation.'} + {t('Room.is_following')} )} @@ -91,11 +93,11 @@ export const RoomViewFollowing = as<'div', RoomViewFollowingProps>( <> {names[0]} - {' and '} + {t('Room.and')} {names[1]} - {' are following the conversation.'} + {t('Room.are_following')} )} @@ -107,11 +109,11 @@ export const RoomViewFollowing = as<'div', RoomViewFollowingProps>( {names[1]} - {' and '} + {t('Room.and')} {names[2]} - {' are following the conversation.'} + {t('Room.are_following')} )} @@ -127,11 +129,11 @@ export const RoomViewFollowing = as<'div', RoomViewFollowingProps>( {names[2]} - {' and '} + {t('Room.and')} - {names.length - 3} others + {t('Room.others_following_count', { count: names.length - 3 })} - {' are following the conversation.'} + {t('Room.are_following')} )} diff --git a/src/app/features/room/RoomViewHeader.tsx b/src/app/features/room/RoomViewHeader.tsx index a19058d2..92733083 100644 --- a/src/app/features/room/RoomViewHeader.tsx +++ b/src/app/features/room/RoomViewHeader.tsx @@ -23,6 +23,7 @@ import { Spinner, } from 'folds'; import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import { Room } from 'matrix-js-sdk'; import { useStateEvent } from '../../hooks/useStateEvent'; import { PageHeader } from '../../components/page'; @@ -74,6 +75,7 @@ type RoomMenuProps = { requestClose: () => void; }; const RoomMenu = forwardRef(({ room, requestClose }, ref) => { + const { t } = useTranslation(); const mx = useMatrixClient(); const [hideActivity] = useSetting(settingsAtom, 'hideActivity'); const unread = useRoomUnread(room.roomId, roomToUnreadAtom); @@ -131,7 +133,7 @@ const RoomMenu = forwardRef(({ room, requestClose disabled={!unread} > - Mark as Read + {t('Room.mark_as_read')} @@ -150,7 +152,7 @@ const RoomMenu = forwardRef(({ room, requestClose onClick={handleOpen} > - Notifications + {t('Room.notifications')} )} @@ -169,7 +171,7 @@ const RoomMenu = forwardRef(({ room, requestClose disabled={!canInvite} > - Invite + {t('Room.invite')} (({ room, requestClose radii="300" > - Copy Link + {t('Room.copy_link')} (({ room, requestClose radii="300" > - Room Settings + {t('Room.room_settings')} @@ -203,7 +205,7 @@ const RoomMenu = forwardRef(({ room, requestClose aria-pressed={promptJump} > - Jump to Time + {t('Room.jump_to_time')} {promptJump && ( @@ -235,7 +237,7 @@ const RoomMenu = forwardRef(({ room, requestClose aria-pressed={promptLeave} > - Leave Room + {t('Room.leave_room')} {promptLeave && ( @@ -254,6 +256,7 @@ const RoomMenu = forwardRef(({ room, requestClose }); export function RoomViewHeader({ callView }: { callView?: boolean }) { + const { t } = useTranslation(); const navigate = useNavigate(); const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); @@ -385,7 +388,7 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) { offset={4} tooltip={ - Search + {t('Room.search')} } > @@ -401,7 +404,7 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) { offset={4} tooltip={ - Pinned Messages + {t('Room.pinned_messages')} } > @@ -461,9 +464,9 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) { tooltip={ {callView ? ( - Members + {t('Room.members')} ) : ( - {peopleDrawer ? 'Hide Members' : 'Show Members'} + {peopleDrawer ? t('Room.hide_members') : t('Room.show_members')} )} } @@ -482,7 +485,7 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) { offset={4} tooltip={ - More Options + {t('Room.more_options')} } > diff --git a/src/app/features/room/RoomViewTyping.tsx b/src/app/features/room/RoomViewTyping.tsx index 1142a3a8..41962ac4 100644 --- a/src/app/features/room/RoomViewTyping.tsx +++ b/src/app/features/room/RoomViewTyping.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Box, Icon, IconButton, Icons, Text, as } from 'folds'; import { Room } from 'matrix-js-sdk'; import classNames from 'classnames'; +import { useTranslation } from 'react-i18next'; import { useSetAtom } from 'jotai'; import { roomIdToTypingMembersAtom } from '../../state/typingMembers'; import { TypingIndicator } from '../../components/typing-indicator'; @@ -16,6 +17,7 @@ export type RoomViewTypingProps = { }; export const RoomViewTyping = as<'div', RoomViewTypingProps>( ({ className, room, ...props }, ref) => { + const { t } = useTranslation(); const setTypingMembers = useSetAtom(roomIdToTypingMembersAtom); const mx = useMatrixClient(); const typingMembers = useRoomTypingMember(room.roomId); @@ -58,7 +60,7 @@ export const RoomViewTyping = as<'div', RoomViewTypingProps>( <> {typingNames[0]} - {' is typing...'} + {t('Room.is_typing')} )} @@ -66,11 +68,11 @@ export const RoomViewTyping = as<'div', RoomViewTypingProps>( <> {typingNames[0]} - {' and '} + {t('Room.and')} {typingNames[1]} - {' are typing...'} + {t('Room.are_typing')} )} @@ -82,11 +84,11 @@ export const RoomViewTyping = as<'div', RoomViewTypingProps>( {typingNames[1]} - {' and '} + {t('Room.and')} {typingNames[2]} - {' are typing...'} + {t('Room.are_typing')} )} @@ -102,16 +104,16 @@ export const RoomViewTyping = as<'div', RoomViewTypingProps>( {typingNames[2]} - {' and '} + {t('Room.and')} - {typingNames.length - 3} others + {t('Room.others_count', { count: typingNames.length - 3 })} - {' are typing...'} + {t('Room.are_typing')} )} - + diff --git a/src/app/features/room/jump-to-time/JumpToTime.tsx b/src/app/features/room/jump-to-time/JumpToTime.tsx index 223c6cf6..c8415b6c 100644 --- a/src/app/features/room/jump-to-time/JumpToTime.tsx +++ b/src/app/features/room/jump-to-time/JumpToTime.tsx @@ -20,6 +20,7 @@ import { RectCords, } from 'folds'; import { Direction, MatrixError } from 'matrix-js-sdk'; +import { useTranslation } from 'react-i18next'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { stopPropagation } from '../../../utils/keyboard'; @@ -37,6 +38,7 @@ type JumpToTimeProps = { onSubmit: (eventId: string) => void; }; export function JumpToTime({ onCancel, onSubmit }: JumpToTimeProps) { + const { t } = useTranslation(); const mx = useMatrixClient(); const room = useRoom(); const alive = useAlive(); @@ -106,7 +108,7 @@ export function JumpToTime({ onCancel, onSubmit }: JumpToTimeProps) { size="500" > - Jump to Time + {t('Room.jump_to_time')} @@ -116,7 +118,7 @@ export function JumpToTime({ onCancel, onSubmit }: JumpToTimeProps) { - Time + {t('Room.time_label')} - Date + {t('Room.date_label')} - Preset + {t('Room.preset')} {createTs < todayTs && ( - Today + {t('Room.today')} )} {createTs < yesterdayTs && ( @@ -217,7 +219,7 @@ export function JumpToTime({ onCancel, onSubmit }: JumpToTimeProps) { aria-pressed={ts === yesterdayTs} onClick={handleYesterday} > - Yesterday + {t('Room.yesterday')} )} - Beginning + {t('Room.beginning')} @@ -249,7 +251,7 @@ export function JumpToTime({ onCancel, onSubmit }: JumpToTimeProps) { } onClick={handleSubmit} > - Open Timeline + {t('Room.open_timeline')} diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx index 2d87e9bd..3733ffec 100644 --- a/src/app/features/room/message/Message.tsx +++ b/src/app/features/room/message/Message.tsx @@ -31,6 +31,7 @@ import React, { useState, } from 'react'; import FocusTrap from 'focus-trap-react'; +import { useTranslation } from 'react-i18next'; import { useHover, useFocusWithin } from 'react-aria'; import { MatrixEvent, Room } from 'matrix-js-sdk'; import { Relations } from 'matrix-js-sdk/lib/models/relations'; @@ -53,9 +54,7 @@ import { getMemberDisplayName, } from '../../../utils/room'; import { - getCanonicalAliasOrRoomId, getMxIdLocalPart, - isRoomAlias, mxcUrlToHttp, } from '../../../utils/matrix'; import { MessageLayout, MessageSpacing } from '../../../state/settings'; @@ -130,6 +129,7 @@ export const MessageAllReactionItem = as< onClose?: () => void; } >(({ room, relations, onClose, ...props }, ref) => { + const { t } = useTranslation(); const [open, setOpen] = useState(false); const handleClose = () => { @@ -176,7 +176,7 @@ export const MessageAllReactionItem = as< aria-pressed={open} > - View Reactions + {t('Room.view_reactions')} @@ -191,6 +191,7 @@ export const MessageReadReceiptItem = as< onClose?: () => void; } >(({ room, eventId, onClose, ...props }, ref) => { + const { t } = useTranslation(); const [open, setOpen] = useState(false); const handleClose = () => { @@ -226,7 +227,7 @@ export const MessageReadReceiptItem = as< aria-pressed={open} > - Read Receipts + {t('Room.read_receipts')} @@ -241,6 +242,7 @@ export const MessageSourceCodeItem = as< onClose?: () => void; } >(({ room, mEvent, onClose, ...props }, ref) => { + const { t } = useTranslation(); const [open, setOpen] = useState(false); const getContent = (evt: MatrixEvent) => @@ -290,7 +292,7 @@ export const MessageSourceCodeItem = as< > - View Source + {t('Room.view_source')} @@ -324,7 +326,7 @@ export const MessageCopyLinkItem = as< onClose?: () => void; } >(({ room, mEvent, onClose, ...props }, ref) => { - const mx = useMatrixClient(); + const { t } = useTranslation(); const handleCopy = () => { const eventId = mEvent.getId(); @@ -343,7 +345,7 @@ export const MessageCopyLinkItem = as< ref={ref} > - Copy Link + {t('Room.copy_link')} ); @@ -357,6 +359,7 @@ export const MessagePinItem = as< onClose?: () => void; } >(({ room, mEvent, onClose, ...props }, ref) => { + const { t } = useTranslation(); const mx = useMatrixClient(); const pinnedEvents = useRoomPinnedEvents(room); const isPinned = pinnedEvents.includes(mEvent.getId() ?? ''); @@ -383,7 +386,7 @@ export const MessagePinItem = as< ref={ref} > - {isPinned ? 'Unpin Message' : 'Pin Message'} + {isPinned ? t('Room.unpin_message') : t('Room.pin_message')} ); @@ -397,6 +400,7 @@ export const MessageDeleteItem = as< onClose?: () => void; } >(({ room, mEvent, onClose, ...props }, ref) => { + const { t } = useTranslation(); const mx = useMatrixClient(); const [open, setOpen] = useState(false); @@ -450,7 +454,7 @@ export const MessageDeleteItem = as< size="500" > - Delete Message + {t('Room.delete_message')} @@ -464,19 +468,19 @@ export const MessageDeleteItem = as< gap="400" > - This action is irreversible! Are you sure that you want to delete this message? + {t('Room.delete_confirm')} - Reason{' '} + {t('Room.reason')}{' '} - (optional) + ({t('Room.optional')}) {deleteState.status === AsyncStatus.Error && ( - Failed to delete message! Please try again. + {t('Room.delete_error')} )} @@ -491,7 +495,7 @@ export const MessageDeleteItem = as< aria-disabled={deleteState.status === AsyncStatus.Loading} > - {deleteState.status === AsyncStatus.Loading ? 'Deleting...' : 'Delete'} + {deleteState.status === AsyncStatus.Loading ? t('Room.deleting') : t('Room.delete')} @@ -511,7 +515,7 @@ export const MessageDeleteItem = as< ref={ref} > - Delete + {t('Room.delete')} @@ -526,6 +530,7 @@ export const MessageReportItem = as< onClose?: () => void; } >(({ room, mEvent, onClose, ...props }, ref) => { + const { t } = useTranslation(); const mx = useMatrixClient(); const [open, setOpen] = useState(false); @@ -550,7 +555,7 @@ export const MessageReportItem = as< const reasonInput = target?.reasonInput as HTMLInputElement | undefined; const reason = reasonInput && reasonInput.value.trim(); if (reasonInput) reasonInput.value = ''; - reportMessage(eventId, reason ? -100 : -50, reason || 'No reason provided'); + reportMessage(eventId, reason ? -100 : -50, reason || t('Room.no_reason')); }; const handleClose = () => { @@ -580,7 +585,7 @@ export const MessageReportItem = as< size="500" > - Report Message + {t('Room.report_message')} @@ -594,20 +599,19 @@ export const MessageReportItem = as< gap="400" > - Report this message to server, which may then notify the appropriate people to - take action. + {t('Room.report_desc')} - Reason + {t('Room.report_reason')} {reportState.status === AsyncStatus.Error && ( - Failed to report message! Please try again. + {t('Room.report_error')} )} {reportState.status === AsyncStatus.Success && ( - Message has been reported to server. + {t('Room.report_success')} )} @@ -625,7 +629,7 @@ export const MessageReportItem = as< } > - {reportState.status === AsyncStatus.Loading ? 'Reporting...' : 'Report'} + {reportState.status === AsyncStatus.Loading ? t('Room.reporting') : t('Room.report')} @@ -645,7 +649,7 @@ export const MessageReportItem = as< ref={ref} > - Report + {t('Room.report')} @@ -718,6 +722,7 @@ export const Message = as<'div', MessageProps>( }, ref ) => { + const { t } = useTranslation(); const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const senderId = mEvent.getSender() ?? ''; @@ -998,7 +1003,7 @@ export const Message = as<'div', MessageProps>( size="T300" truncate > - Add Reaction + {t('Room.add_reaction')} )} @@ -1025,7 +1030,7 @@ export const Message = as<'div', MessageProps>( size="T300" truncate > - Reply + {t('Room.reply')} {!isThreadedMessage && ( @@ -1045,7 +1050,7 @@ export const Message = as<'div', MessageProps>( size="T300" truncate > - Reply in Thread + {t('Room.reply_in_thread')} )} @@ -1066,7 +1071,7 @@ export const Message = as<'div', MessageProps>( size="T300" truncate > - Edit Message + {t('Room.edit_message')} )} diff --git a/src/app/features/room/room-pin-menu/RoomPinMenu.tsx b/src/app/features/room/room-pin-menu/RoomPinMenu.tsx index 9986849f..cd04ba61 100644 --- a/src/app/features/room/room-pin-menu/RoomPinMenu.tsx +++ b/src/app/features/room/room-pin-menu/RoomPinMenu.tsx @@ -2,6 +2,7 @@ import React, { forwardRef, MouseEventHandler, useCallback, useMemo, useRef } from 'react'; import { MatrixEvent, Room } from 'matrix-js-sdk'; import { RoomPinnedEventsEventContent } from 'matrix-js-sdk/lib/types'; +import { useTranslation } from 'react-i18next'; import { Avatar, Box, @@ -111,6 +112,7 @@ function PinnedMessage({ hour24Clock, dateFormatString, }: PinnedMessageProps) { + const { t } = useTranslation(); const pinnedEvent = useRoomEvent(room, eventId); const useAuthentication = useMediaAuthentication(); const mx = useMatrixClient(); @@ -142,7 +144,7 @@ function PinnedMessage({ const renderOptions = () => ( - Open + {t('Room.open')} {canPinEvent && ( - Failed to load message! + {t('Room.failed_to_load')} {renderOptions()} @@ -249,6 +251,7 @@ type RoomPinMenuProps = { }; export const RoomPinMenu = forwardRef( ({ room, requestClose }, ref) => { + const { t } = useTranslation(); const mx = useMatrixClient(); const userId = mx.getUserId()!; const powerLevels = usePowerLevelsContext(); @@ -454,7 +457,7 @@ export const RoomPinMenu = forwardRef(
- Pinned Messages + {t('Room.pinned_messages')} @@ -527,10 +530,10 @@ export const RoomPinMenu = forwardRef( alignItems="Center" > - No Pinned Messages + {t('Room.no_pinned_messages')} - Users with sufficient power level can pin a messages from its context menu. + {t('Room.no_pinned_messages_desc')} diff --git a/src/app/pages/client/direct/Direct.tsx b/src/app/pages/client/direct/Direct.tsx index 04edf109..a5802437 100644 --- a/src/app/pages/client/direct/Direct.tsx +++ b/src/app/pages/client/direct/Direct.tsx @@ -1,4 +1,5 @@ import React, { MouseEventHandler, forwardRef, useMemo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useAtom, useAtomValue } from 'jotai'; import { Avatar, @@ -56,6 +57,7 @@ type DirectMenuProps = { requestClose: () => void; }; const DirectMenu = forwardRef(({ requestClose }, ref) => { + const { t } = useTranslation(); const mx = useMatrixClient(); const [hideActivity] = useSetting(settingsAtom, 'hideActivity'); const orphanRooms = useDirectRooms(); @@ -78,7 +80,7 @@ const DirectMenu = forwardRef(({ requestClose } aria-disabled={!unread} > - Mark as Read + {t('Direct.mark_as_read')} @@ -87,6 +89,7 @@ const DirectMenu = forwardRef(({ requestClose } }); function DirectHeader() { + const { t } = useTranslation(); const [menuAnchor, setMenuAnchor] = useState(); const handleOpenMenu: MouseEventHandler = (evt) => { @@ -103,7 +106,7 @@ function DirectHeader() { - Direct Messages + {t('Direct.direct_messages')} @@ -139,6 +142,7 @@ function DirectHeader() { } function DirectEmpty() { + const { t } = useTranslation(); const navigate = useNavigate(); return ( @@ -147,18 +151,18 @@ function DirectEmpty() { icon={} title={ - No Direct Messages + {t('Direct.no_direct_messages')} } content={ - You do not have any direct messages yet. + {t('Direct.no_direct_messages_desc')} } options={ } @@ -169,6 +173,7 @@ function DirectEmpty() { const DEFAULT_CATEGORY_ID = makeNavCategoryId('direct', 'direct'); export function Direct() { + const { t } = useTranslation(); const mx = useMatrixClient(); useNavToActivePathMapper('direct'); const scrollRef = useRef(null); @@ -220,7 +225,7 @@ export function Direct() { - Create Chat + {t('Direct.create_chat')} @@ -235,7 +240,7 @@ export function Direct() { data-category-id={DEFAULT_CATEGORY_ID} onClick={handleCategoryClick} > - Chats + {t('Direct.chats')}
} - title="Create Chat" - subTitle="Start a private, encrypted chat by entering a user ID." + title={t('Direct.create_chat')} + subTitle={t('Direct.create_chat_subtitle')} /> diff --git a/src/app/pages/client/sidebar/DirectTab.tsx b/src/app/pages/client/sidebar/DirectTab.tsx index dcd44e2f..fdc84a61 100644 --- a/src/app/pages/client/sidebar/DirectTab.tsx +++ b/src/app/pages/client/sidebar/DirectTab.tsx @@ -1,6 +1,7 @@ import React, { MouseEventHandler, forwardRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Box, Icon, Icons, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds'; +import { useTranslation } from 'react-i18next'; import FocusTrap from 'focus-trap-react'; import { useAtomValue } from 'jotai'; import { useDirects } from '../../../state/hooks/roomList'; @@ -30,6 +31,7 @@ type DirectMenuProps = { requestClose: () => void; }; const DirectMenu = forwardRef(({ requestClose }, ref) => { + const { t } = useTranslation(); const orphanRooms = useDirectRooms(); const [hideActivity] = useSetting(settingsAtom, 'hideActivity'); const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom); @@ -52,7 +54,7 @@ const DirectMenu = forwardRef(({ requestClose } aria-disabled={!unread} > - Mark as Read + {t('Direct.mark_as_read')} @@ -61,6 +63,7 @@ const DirectMenu = forwardRef(({ requestClose } }); export function DirectTab() { + const { t } = useTranslation(); const navigate = useNavigate(); const mx = useMatrixClient(); const screenSize = useScreenSizeContext(); @@ -93,7 +96,7 @@ export function DirectTab() { }; return ( - + {(triggerRef) => (