From 7bb187bce1b1c5445573dc428948e12ad0648761 Mon Sep 17 00:00:00 2001 From: Dylan Van Nielen Date: Thu, 14 Jul 2022 10:48:23 +0930 Subject: [PATCH] Implemented translation to all organisms --- public/locales/en/translation.json | 416 +++++++++++++++++- public/locales/jp/translation.json | 5 +- src/app/i18n.jsx | 1 - src/app/organisms/create-room/CreateRoom.jsx | 43 +- src/app/organisms/drag-drop/DragDrop.jsx | 8 +- .../emoji-verification/EmojiVerification.jsx | 24 +- src/app/organisms/invite-list/InviteList.jsx | 26 +- src/app/organisms/invite-user/InviteUser.jsx | 40 +- src/app/organisms/join-alias/JoinAlias.jsx | 25 +- .../organisms/navigation/DrawerBreadcrumb.jsx | 9 +- src/app/organisms/navigation/DrawerHeader.jsx | 27 +- src/app/organisms/navigation/SideBar.jsx | 22 +- .../profile-editor/ProfileEditor.jsx | 21 +- .../profile-viewer/ProfileViewer.jsx | 43 +- .../organisms/public-rooms/PublicRooms.jsx | 45 +- src/app/organisms/room/RoomSettings.jsx | 24 +- src/app/organisms/room/RoomViewContent.jsx | 36 +- src/app/organisms/room/RoomViewFloating.jsx | 39 +- src/app/organisms/room/RoomViewHeader.jsx | 14 +- src/app/organisms/room/RoomViewInput.jsx | 23 +- src/app/organisms/room/common.jsx | 120 +++-- src/app/organisms/search/Search.jsx | 9 +- src/app/organisms/settings/AuthRequest.jsx | 12 +- src/app/organisms/settings/CrossSigning.jsx | 140 +++--- src/app/organisms/settings/DeviceManage.jsx | 86 +++- src/app/organisms/settings/KeyBackup.jsx | 59 ++- .../settings/SecretStorageAccess.jsx | 16 +- src/app/organisms/settings/Settings.jsx | 85 ++-- .../shortcut-spaces/ShortcutSpaces.jsx | 25 +- .../organisms/space-manage/SpaceManage.jsx | 47 +- .../space-settings/SpaceSettings.jsx | 18 +- src/app/organisms/view-source/ViewSource.jsx | 6 +- src/app/organisms/welcome/Welcome.jsx | 4 +- 33 files changed, 1112 insertions(+), 406 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 11cdca01..60aaae8e 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -1,19 +1,80 @@ { "common" : { "close": "Close", + "open": "Open", "leave": "Leave", - "options": "Options" + "options": "Options", + "cinny": "Cinny", + "slogan": "Yet another matrix client", + "source_code": "Source code", + "sponsor": "Support", + "retry": "Retry", + "delete": "Delete", + "continue": "Continue", + "cancel": "Cancel", + "save": "Save", + "view_more": "View more", + "view_less": "View less", + "copy": "Copy", + "upload": "Upload", + "download": "Download", + "or": "Or", + "reset": "Reset", + "setup": "Setup", + "search": "Search", + "loading": "Loading...", + "joining": "Joining...", + "join": "Join", + "remove": "Remove", + "send": "Send", + "homeserver": "Homeserver", + "invite": "Invite", + "uninvite": "Uninvite", + "invited": "Invited", + "inviting": "Inviting...", + "uninviting": "Uninviting...", + "change": "Change", + "edit": "Edit", + "message_prompt": "Message" }, - "welcome": { + "errors": { + "browser_not_supported": "Not supported in this browser", + "generic": "Something went wrong!" + }, + "Welcome": { "heading": "Welcome to Cinny!", "subheading": "Yet another Matrix client" }, - "view_source":{ + "ViewSource":{ "title": "View Source", "original_source": "Original source", "decrypted_source": "Decrypted source" }, - "space_settings":{ + "SpaceManage": { + "subtitle": "manage rooms", + "load_more": "Load more", + "rooms_and_spaces": "Rooms and spaces", + "private_rooms_message": "Either the space contains private rooms or you need to join space to view it's rooms.", + "items_selected_zero": "No selected items", + "items_selected_one": "{{count}} selected item", + "items_selected_other": "{{count}} selected items", + "room_members_zero": "No room members", + "room_members_one": "{{count}} room member", + "room_members_other": "{{count}} room members", + "mark_suggested_zero": "Marking no rooms as suggested", + "mark_suggested_one": "Marking {{count}} room as suggested", + "mark_suggested_other": "Marking {{count}} rooms as suggested", + "mark_not_suggested_zero": "Marking no rooms as suggested", + "mark_not_suggested_one": "Marking {{count}} room as suggested", + "mark_not_suggested_other": "Marking {{count}} rooms as suggested", + "remove_zero": "Removing no items", + "remove_one": "Removing {{count}} item", + "remove_other": "Removing {{count}} items.", + "suggested": "Suggested", + "mark_as_suggested":"Mark as suggested", + "mark_as_not_suggested": "Mark as not suggested" + }, + "SpaceSettings":{ "subtitle": "space settings", "leave":{ "leave_space": "Leave Space", @@ -30,5 +91,352 @@ "uncategorize_subspaces": "Uncategorize subspaces", "pin_sidebar": "Pin to sidebar", "unpin_sidebar": "Unpin from sidebar" + }, + "Settings": { + "title": "Settings", + "theme": { + "follow_system": { + "title": "Follow system theme", + "description": "Use light or dark mode based on the system settings." + }, + "title": "Theme", + "theme_light": "Light", + "theme_silver": "Silver", + "theme_dark": "Dark", + "theme_butter": "Butter" + }, + "markdown": { + "title": "Markdown formatting", + "description": "Format messages with markdown before sending" + }, + "hide_membership_events": { + "title": "Hide membership events", + "description": "Hide membership change messages from room timeline. (Join, Leave, Invite, Kick and Ban)" + }, + "hide_nickname_avatar_events": { + "title": "Hide nick/avatar events", + "description": "Hide nickname and avatar change messages from the room timeline." + }, + "notifications_and_sound": { + "title": "Notifications & Sound", + "desktop": { + "title": "Desktop notifications", + "description": "Show desktop notifications when new messages arrive." + }, + "sound": { + "title": "Notification sound", + "description": "Play a sound when new messages arrive." + } + }, + "security": { + "cross_signing": { + "title": "Cross signing and backup" + }, + "export_import_encryption_keys": { + "title": "Export / Import encryption keys" + }, + "export_encryption_keys": { + "title": "Export E2E room keys", + "description": "Export end-to-end encryption room keys to decrypt old messages in other session. In order to encrypt keys you need to set a password, which will be used while importing." + }, + "import_encryption_keys": { + "title": "Import E2E room keys", + "description": "To decrypt older messages, Export E2EE room keys from Element (Settings > Security & Privacy > Encryption > Cryptography) and import them here. Imported keys are encrypted so you\\'ll have to enter the password you set in order to decrypt it." + } + }, + "logout": { + "title": "Logout", + "dialog": { + "title": "Logout", + "description": "Are you sure that you want to logout your session?", + "confirm": "Logout" + } + }, + "about":{ + "application": "Application", + "credits": "Credits" + } + }, + "ShortcutSpaces": { + "header": "Pin Spaces", + "pinned_spaces": "Pinned spaces", + "no_pinned_spaces": "No pinned spaces", + "unpinned_spaces": "Unpinned spaces", + "no_unpinned_spaces": "No unpinned spaces", + "spaces_selected_zero": "No selected spaces", + "spaces_selected_one": "{{count}} selected space", + "spaces_selected_other": "{{count}} selected spaces", + "pin_button": "Pin" + }, + "SecretStorageAccess": { + "incorrect_security_key": "Incorrect security key", + "incorrect_security_phrase": "Incorrect security phrase", + "security_phrase": "Security Phrase", + "security_key": "Security Key", + "use_security_key": "Use Security Key", + "use_security_phrase": "Use Security Phrase" + }, + "KeyBackup": { + "create_backup_title": "Create key backup", + "create_backup_tooltip": "Create backup", + "creating_backup": "Creating Backup...", + "backup_created": "Successfully created backup", + "backup_failed": "Failed to create backup", + "restoring": "Restoring backup keys...", + "restoring_progress": "Restoring backup keys... ({{progress}}/{{total}}", + "restore_backup_title": "Restore Key Backup", + "restore_backup_tooltip": "Restore Key Backup", + "restore_complete": "Successfully restored backup keys ({{progress}}/{{total}})", + "restore_failed_bad_key": "Failed to restore backup. Key is invalid!", + "restore_failed_unknown": "Failed to restore backup.", + "delete_key_backup_title": "Delete key backup", + "delete_key_backup_tooltip": "Delete backup", + "delete_key_backup_subtitle": "Deleting key backup is permanent.", + "delete_key_backup_message": "All encrypted message keys stored on the server will be permanently deleted.", + "encrypted_messages_backup_description": "Online backup your encrypted messages keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.", + "encrypted_messages_backup_title": "Encrypted messages backup", + "encrypted_messages_backup_cross_signing_disabled": "Setup cross signing to backup your encrypted messages." + }, + "DeviceManage": { + "edit_session_name_title": "Edit session name", + "edit_session_name_subtitle": "Session name", + "edit_session_name_tooltip": "Edit session name", + "current_device_label": "Current", + "verify_session_button": "Verify", + "unverified_sessions_title": "Unverified sessions", + "unverified_sessions_none": "No unverified sessions", + "unencrypted_sessions_title": "Sessions without encryption support", + "verified_sessions_title": "Verified sessions", + "verified_sessions_none": "No verified sessions", + "setup_cross_signing_message": "Setup cross signing in case you lose all your sessions", + "loading_devices": "Loading devices...", + "logout_device_title": "Logout {{device}}", + "logout_device_message": "You are about to log out the session for {{device}}", + "logout_device_confirm": "Logout", + "logout_device_tooltip": "Remove session", + "session_verification_title": "Session Verification", + "session_name_privacy_message": "Session names are visible to everyone, so do not put any private info here." + }, + "CrossSigning":{ + "title": "Cross Signing", + "setup_failed": "Failed to setup cross signing. Please try again", + "setup": "Setup cross signing", + "save_security_key_message": "Please save this security key somewhere safe", + "security_key_dialog_title": "Security Key", + "security_key_generation_message": "We will generate a Security Key, which you can use to manage message backups and session verification.", + "security_key_generation_button": "Generate Key", + "security_phrase_message": "Alternatively you can set a 'Security Phrase' so you don't have to remember the long Security Key, and optionally save the key as a backup", + "security_phrase_label": "Security Phrase", + "security_phrase_confirm_label": "Confirm Security Phrase", + "security_phrase_set_button": "Set Phrase & Generate Key", + "setup_dialog_title": "Setup cross signing", + "setup_message": "Setup to verify and keep track of all your sessions. Also required to backup encrypted message.", + "reset_keys_subtitle": "Resetting cross-signing keys is permanent.", + "reset_keys_message": "Anyone you have verified with will see security alerts and your message backup will lost. You almost certainly do not want to do this, unless you have lost Security Key or Phrase and every session you can cross-sign from." + + }, + "AuthRequest" : { + "wrong_password": "Wrong password. Please enter the correct password", + "request_failed": "Request failed!", + "password_label": "Account password" + }, + "Search": { + "description": "Type # for rooms, @ for DMs and * for spaces. Hotkey: Ctrl + k" + }, + "RoomViewInput":{ + "upload_progress": "Uploading: {{progress}}/{{total}} ({{percent}}%)", + "tombstone_replaced": "This room has been replaced, and is no longer active.", + "tombstone_permission_denied": "You do not have permission to post to this room", + "send_message_placeholder": "Send a message...", + "emoji_tooltip": "Emoji", + "file_size": "Size: {{size}}", + "cancel_reply_tooltip": "Cancel reply" + }, + "RoomViewHeader": { + "search_tooltip": "Search", + "people_tooltip": "People", + "members_tooltip": "Members" + }, + "RoomViewFloating": { + "jump_unread": "Jump to unread messages", + "mark_read": "Mark as read", + "jump_latest": "Jump to latest", + "user_typing_one": "{{user_one}} is typing...", + "user_typing_two": "{{user_one}} and {{user_two}} are typing...", + "user_typing_three": "{{user_one}}, {{user_two}} and {{user_three}} are typing...", + "user_typing_four": "{{user_one}}, {{user_two}}, {{user_three}} and {{user_four}} are typing...", + "user_typing_other": "Several people are typing..." + }, + "RoomViewContent": { + "welcome_to_room": "Welcome to {{room_name}}!", + "beginning_room": "This is the beginning of the {{room_name}} room.", + "beginning_dm": "This is the beginning of your direct message history with @{{user_name}}.", + "created_on": "Created on {{date, datetime}}", + "new_messages": "New messages" + }, + "RoomSettings" : { + "leave_room": "Leave room", + "leave_room_confirm_message": "Are you sure you want to leave {{room_name}}?", + "leave_room_confirm_button": "Leave", + "notification_header": "Notifications (Changing this will only affect you)", + "visibility_header": "Room visibility (Who can join)", + "address_header": "Room addresses", + "encryption_header": "Encryption", + "message_history_header": "Message history visibility", + "room_settings_subtitle": "room settings" + }, + "RoomCommon": { + "user_joined": "{{user_name}} joined the room", + "user_left": "{{user_name}} left the room", + "user_invited": "{{inviter_name}} invited {{user_name}}", + "invite_cancelled": "{{inviter_name}} cancelled {{user_name}}'s invite", + "invite_rejected": "{{user_name}} rejected the invitation", + "user_kicked": "{{actor}} kicked {{user_name}}: {{reason}}", + "user_banned": "{{actor}} banned {{user_name}}: {{reason}}", + "user_unbanned": "{{actor}} unbanned {{user_name}}", + "avatar_set": "{{user_name}} set an avatar", + "avatar_changed": "{{user_name}} changed their avatar", + "avatar_removed": "{{user_name}} removed their avatar", + "name_set": "{{user_name}} set their display name to {{new_name}}", + "name_changed": "{{user_name}} changed their display name to {{new_name}}", + "name_removed": "{{user_name}} removed their display name {{new_name}}" + }, + "PublicRooms": { + "could_not_join_alias": "Unable to join {{alias}}. Either the room is private or doesn't exist", + "try_joining_alias": "Try joining {{alias}}", + "joining_alias": "Joining {{alias}}...", + "no_public_rooms": "No public rooms on {{homeserver}}", + "no_result_found": "No result found for '{{input}}' on {{homeserver}}", + "title": "Public Rooms", + "search_room_name_alias": "Room name or alias", + "search_button": "Search", + "loading": "Loading public rooms from {{homeserver}}...", + "searching": "Searching for '{{query}}' on {{homeserver}}...", + "result_title": "Public rooms on {{homeserver}}", + "search_result_title": "Search result for '{{query}}' on {{homeserver}}" + }, + "ProfileViewer": { + "kick_button": "Kick", + "kick_reason_label": "Kick Reason", + "ban_button": "Ban", + "ban_reason_label": "Ban reason", + "loading_sessions" : "Loading sessions...", + "no_sessions_found": "No sessions found.", + "view_sessions_one": "View session", + "view_sessions_other": "View {{count}} sessions", + "send_direct_message_button": "Message", + "creating_dm_room": "Creating room...", + "ignore": "Ignore", + "ignoring": "Ignoring...", + "unignore": "Unignore", + "unignoring": "Unignoring...", + "change_power_level": "Change power level", + "shared_power_message": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself. Are you sure?", + "demoting_self_message": "You will not be able to undo this change as you are demoting yourself. Are you sure?" + }, + "ProfileEditor": { + "remove_avatar": "Remove avatar", + "remove_avatar_confirmation": "Are you sure that you want to remove your avatar?", + "display_name_message": "Display name of {{user_name}}" + }, + "DrawerBreadcrumb": { + "home": "Home" + }, + "DrawerHeader" : { + "add_rooms_or_spaces": "Add rooms or spaces", + "create_new_space": "Create new space", + "create_new_room": "Create new room", + "join_public_room": "Join public room", + "join_with_address": "Join with address", + "add_existing": "Add existing", + "manage_rooms": "Manage rooms", + "home": "Home", + "direct_messages": "Direct messages", + "start_dm_tooltip": "Start DM", + "add_rooms_spaces_tooltip": "Add rooms/spaces" + }, + "SideBar": { + "settings_tooltip": "Settings", + "unverified_sessions_one": "{{count}} unverified session", + "unverified_sessions_other": "{{count}} unverified sessions", + "home_tooltip": "Home", + "direct_messages_tooltip": "People", + "pin_spaces_tooltip": "Pin spaces", + "search_tooltip": "Search", + "invites_tooltip": "Invites" + }, + "JoinAlias": { + "invalid_address": "Invalid address.", + "looking_for_address": "Looking for address...", + "joining_alias": "Joining {{alias_name}}...", + "couldnt_find_room_or_space_alias": "Unable to find room/space with {{alias_name}}. Either the room/space is private or doesn't exist.", + "couldnt_find_room_or_space": "Unable to join {{alias_name}}. Either the room/space is private or doesn't exist.", + "address_label": "Address", + "title": "Join with address" + }, + "InviteUser": { + "user_not_found": "{{user_name}} not found!", + "no_matches_found": "No matches found for {{user_name}}", + "invite_result": { + "invited": "Invited", + "already_joined": "Already joined", + "already_invited": "Already invited", + "banned": "Banned" + }, + "search_label": "Name or User ID", + "search_result_title": "Search result for user {{user_name}}", + "searching_for_user": "Searching for user {{user_name}}...", + "invite_to_room": "Invite to {{room}}", + "invite_to_dm": "Direct Message" + }, + "InviteList": { + "accept_invite": "Accept", + "reject_invite": "Reject", + "direct_messages_title": "Direct Messages", + "rooms_title": "Rooms", + "spaces_title": "Spaces", + "title": "Invites" + }, + "EmojiVerification": { + "waiting_for_response": "Waiting for response from other device...", + "confirmation_prompt": "Confirm the emoji below are displayed on both devices, in the same order:", + "emojis_match_button": "They match", + "emojis_dont_match_button": "They don't match", + "accept_request_from_other_device_message": "Please accept the request from other device.", + "begin_verification_process_message": "Click accept to start the verification process.", + "begin_verification_button_text": "Accept", + "title": "Emoji Verification" + }, + "DragDrop": { + "drop_file_to_upload_prompt": "Drop file to upload" + }, + "CreateRoom": { + "private_room_short": "Private", + "restricted_room_short": "Restricted", + "public_room_short": "Public", + "private_room_long": "Private (invite only)", + "restricted_room_long": "Restricted (space member can join)", + "public_room_long": "Public (anyone can join)", + "visibility_title": "Visibility", + "visibility_message": "Visibility (who can join)", + "select_who_can_join_space": "Select who can join this space", + "select_who_can_join_room": "Select who can join this room", + "space_address": "Space address", + "room_address": "Room address", + "room_address_already_in_use": "{{room_address}} is already in use", + "e2e_title": "Enable end-to-end encryption", + "e2e_message": "You can’t disable this later. Bridges & most bots won’t work yet.", + "role_title": "Select your role", + "role_message": "Selecting 'Admin' sets your power level to 100 whereas 'Founder' sets it to 101.", + "creating_room": "Creating room...", + "creating_space": "Creating space...", + "topic_label": "Topic (optional)", + "space_name": "Space name", + "room_name": "Room name", + "role_admin": "Admin", + "role_founder": "Founder", + "create_room": "Create room", + "create_space": "Create space", + "home": "Home" } } \ No newline at end of file diff --git a/public/locales/jp/translation.json b/public/locales/jp/translation.json index 6191885d..c926fe9c 100644 --- a/public/locales/jp/translation.json +++ b/public/locales/jp/translation.json @@ -3,7 +3,8 @@ "close": "閉める", "leave": "残す" }, - "welcome":{ - "heading": "いらっしゃいませ" + "Welcome": { + "heading": "おはようございます", + "subheading": "Yet another Matrix client" } } \ No newline at end of file diff --git a/src/app/i18n.jsx b/src/app/i18n.jsx index d951279b..c62de110 100644 --- a/src/app/i18n.jsx +++ b/src/app/i18n.jsx @@ -14,7 +14,6 @@ i18n // for all options read: https://www.i18next.com/overview/configuration-options .init({ debug: true, - fallbackLng: 'en', interpolation: { escapeValue: false, // not needed for react as it escapes by default } diff --git a/src/app/organisms/create-room/CreateRoom.jsx b/src/app/organisms/create-room/CreateRoom.jsx index 15be02d2..d9d7d509 100644 --- a/src/app/organisms/create-room/CreateRoom.jsx +++ b/src/app/organisms/create-room/CreateRoom.jsx @@ -33,7 +33,14 @@ import SpaceGlobeIC from '../../../../public/res/ic/outlined/space-globe.svg'; import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; +import { t } from 'i18next'; + function CreateRoomContent({ isSpace, parentId, onRequestClose }) { + + const { t } = useTranslation(); + const [joinRule, setJoinRule] = useState(parentId ? 'restricted' : 'invite'); const [isEncrypted, setIsEncrypted] = useState(true); const [isCreatingRoom, setIsCreatingRoom] = useState(false); @@ -130,8 +137,8 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) { }; const joinRules = ['invite', 'restricted', 'public']; - const joinRuleShortText = ['Private', 'Restricted', 'Public']; - const joinRuleText = ['Private (invite only)', 'Restricted (space member can join)', 'Public (anyone can join)']; + const joinRuleShortText = [ t("CreateRoom.private_room_short"), t("CreateRoom.restricted_room_short"), t("CreateRoom.public_room_short")]; + const joinRuleText = [ t("CreateRoom.private_room_long"), t("CreateRoom.restricted_room_long"), t("CreateRoom.public_room_long")]; const jrRoomIC = [HashLockIC, HashIC, HashGlobeIC]; const jrSpaceIC = [SpaceLockIC, SpaceIC, SpaceGlobeIC]; const handleJoinRule = (evt) => { @@ -140,7 +147,7 @@ function CreateRoomContent({ isSpace, parentId, onRequestClose }) { getEventCords(evt, '.btn-surface'), (closeMenu) => ( <> - Visibility (who can join) + {t("CreateRoom.visibility_message")} { joinRules.map((rule) => (
{joinRuleShortText[joinRules.indexOf(joinRule)]} )} - content={{`Select who can join this ${isSpace ? 'space' : 'room'}.`}} + content={{isSpace ? t("CreateRoom.select_who_can_join_space") : t("CreateRoom.select_who_can_join_room")}} /> {joinRule === 'public' && (
- {isSpace ? 'Space address' : 'Room address'} + {isSpace ? t("CreateRoom.space_address") : t("CreateRoom.room_address")}
# {`:${userHs}`}
- {isValidAddress === false && {`#${addressValue}:${userHs} is already in use`}} + {isValidAddress === false && { t("CreateRoom.room_address_already_in_use", {room_address: `#${addressValue}:${userHs}`})}}
)} {!isSpace && joinRule !== 'public' && ( } - content={You can’t disable this later. Bridges & most bots won’t work yet.} + content={ {t("CreateRoom.e2e_message")}} /> )} )} content={( - Selecting Admin sets 100 power level whereas Founder sets 101. + {t("CreateRoom.role_message")} )} /> - +
- +
)} {typeof creatingError === 'string' && {creatingError}} @@ -277,13 +284,13 @@ function CreateRoom() { isOpen={create !== null} title={( - {parentId ? twemojify(room.name) : 'Home'} + {parentId ? twemojify(room.name) : t("CreateRoom.home")} - {` — create ${isSpace ? 'space' : 'room'}`} + {` — ${isSpace ? t("CreateRoom.create_space") : t("CreateRoom.create_room")}`} )} - contentOptions={} + contentOptions={} onRequestClose={onRequestClose} > { diff --git a/src/app/organisms/drag-drop/DragDrop.jsx b/src/app/organisms/drag-drop/DragDrop.jsx index e92f8a70..8576325f 100644 --- a/src/app/organisms/drag-drop/DragDrop.jsx +++ b/src/app/organisms/drag-drop/DragDrop.jsx @@ -5,14 +5,20 @@ import './DragDrop.scss'; import RawModal from '../../atoms/modal/RawModal'; import Text from '../../atoms/text/Text'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; + function DragDrop({ isOpen }) { + + const { t } = useTranslation(); + return ( - Drop file to upload + {t("DragDrop.drop_file_to_upload_prompt")} ); } diff --git a/src/app/organisms/emoji-verification/EmojiVerification.jsx b/src/app/organisms/emoji-verification/EmojiVerification.jsx index 6fe81cdd..9d54ba24 100644 --- a/src/app/organisms/emoji-verification/EmojiVerification.jsx +++ b/src/app/organisms/emoji-verification/EmojiVerification.jsx @@ -20,6 +20,10 @@ import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import { useStore } from '../../hooks/useStore'; import { accessSecretStorage } from '../settings/SecretStorageAccess'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; +import { t } from 'i18next'; + function EmojiVerificationContent({ data, requestClose }) { const [sas, setSas] = useState(null); const [process, setProcess] = useState(false); @@ -28,6 +32,8 @@ function EmojiVerificationContent({ data, requestClose }) { const mountStore = useStore(); const beginStore = useStore(); + const { t } = useTranslation(); + const beginVerification = async () => { if ( isCrossVerified(mx.deviceId) @@ -94,14 +100,14 @@ function EmojiVerificationContent({ data, requestClose }) { const renderWait = () => ( <> - Waiting for response from other device... + {t("EmojiVerification.waiting_for_response")} ); if (sas !== null) { return (
- Confirm the emoji below are displayed on both devices, in the same order: + {t("EmojiVerification.confirmation_prompt")}
{sas.sas.emoji.map((emoji, i) => ( // eslint-disable-next-line react/no-array-index-key @@ -114,8 +120,8 @@ function EmojiVerificationContent({ data, requestClose }) {
{process ? renderWait() : ( <> - - + + )}
@@ -126,7 +132,7 @@ function EmojiVerificationContent({ data, requestClose }) { if (targetDevice) { return (
- Please accept the request from other device. + {t("EmojiVerification.accept_request_from_other_device_message")}
{renderWait()}
@@ -136,12 +142,12 @@ function EmojiVerificationContent({ data, requestClose }) { return (
- Click accept to start the verification process. + {t("EmojiVerification.begin_verification_process_message")}
{ process ? renderWait() - : + : }
@@ -182,10 +188,10 @@ function EmojiVerification() { className="emoji-verification" title={( - Emoji verification + {t("EmojiVerification.title")} )} - contentOptions={} + contentOptions={} onRequestClose={requestClose} > { diff --git a/src/app/organisms/invite-list/InviteList.jsx b/src/app/organisms/invite-list/InviteList.jsx index 231928fe..c94950a5 100644 --- a/src/app/organisms/invite-list/InviteList.jsx +++ b/src/app/organisms/invite-list/InviteList.jsx @@ -16,9 +16,17 @@ import RoomTile from '../../molecules/room-tile/RoomTile'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; +import { t } from 'i18next'; + + + function InviteList({ isOpen, onRequestClose }) { const [procInvite, changeProcInvite] = useState(new Set()); + const { t } = useTranslation(); + function acceptInvite(roomId, isDM) { procInvite.add(roomId); changeProcInvite(new Set(Array.from(procInvite))); @@ -73,8 +81,8 @@ function InviteList({ isOpen, onRequestClose }) { ? () : (
- - + +
) } @@ -85,14 +93,14 @@ function InviteList({ isOpen, onRequestClose }) { return ( } + title={t("InviteList.title")} + contentOptions={} onRequestClose={onRequestClose} >
{ initMatrix.roomList.inviteDirects.size !== 0 && (
- Direct Messages + {t("InviteList.direct_messages_title")}
)} { @@ -110,8 +118,8 @@ function InviteList({ isOpen, onRequestClose }) { ? () : (
- - + +
) } @@ -121,14 +129,14 @@ function InviteList({ isOpen, onRequestClose }) { } { initMatrix.roomList.inviteSpaces.size !== 0 && (
- Spaces + {t("InviteList.spaces_title")}
)} { Array.from(initMatrix.roomList.inviteSpaces).map(renderRoomTile) } { initMatrix.roomList.inviteRooms.size !== 0 && (
- Rooms + {t("InviteList.rooms_title")}
)} { Array.from(initMatrix.roomList.inviteRooms).map(renderRoomTile) } diff --git a/src/app/organisms/invite-user/InviteUser.jsx b/src/app/organisms/invite-user/InviteUser.jsx index d0a8d9e1..56ad16fd 100644 --- a/src/app/organisms/invite-user/InviteUser.jsx +++ b/src/app/organisms/invite-user/InviteUser.jsx @@ -19,6 +19,10 @@ import RoomTile from '../../molecules/room-tile/RoomTile'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import UserIC from '../../../../public/res/ic/outlined/user.svg'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; + + function InviteUser({ isOpen, roomId, searchTerm, onRequestClose, }) { @@ -36,6 +40,8 @@ function InviteUser({ const usernameRef = useRef(null); + const { t } = useTranslation(); + const mx = initMatrix.matrixClient; function getMapCopy(myMap) { @@ -82,7 +88,7 @@ function InviteUser({ avatar_url: result.avatar_url, }]); } catch (e) { - updateSearchQuery({ error: `${inputUsername} not found!` }); + updateSearchQuery({error: t("InviteUser.user_not_found", {user_name: inputUsername})}); } } else { try { @@ -91,13 +97,13 @@ function InviteUser({ limit: 20, }); if (result.results.length === 0) { - updateSearchQuery({ error: `No matches found for "${inputUsername}"!` }); + updateSearchQuery({ error: t("InviteUser.no_matches_found", {user_name: inputUsername})}); updateIsSearching(false); return; } updateUsers(result.results); } catch (e) { - updateSearchQuery({ error: 'Something went wrong!' }); + updateSearchQuery({ error: t("errors.generic")}); } } updateIsSearching(false); @@ -135,7 +141,7 @@ function InviteUser({ } catch (e) { deleteUserFromProc(userId); if (typeof e.message === 'string') procUserError.set(userId, e.message); - else procUserError.set(userId, 'Something went wrong!'); + else procUserError.set(userId, t("errors.generic")); updateUserProcError(getMapCopy(procUserError)); } } @@ -155,7 +161,7 @@ function InviteUser({ } catch (e) { deleteUserFromProc(userId); if (typeof e.message === 'string') procUserError.set(userId, e.message); - else procUserError.set(userId, 'Something went wrong!'); + else procUserError.set(userId, t("errors.generic")); updateUserProcError(getMapCopy(procUserError)); } } @@ -173,7 +179,7 @@ function InviteUser({ return ; } if (invitedUserIds.has(userId)) { - return messageJSX('Invited', true); + return messageJSX(t("InviteUser.invite_result.invited"), true); } if (typeof roomId === 'string') { const member = mx.getRoom(roomId).getMember(userId); @@ -181,18 +187,18 @@ function InviteUser({ const userMembership = member.membership; switch (userMembership) { case 'join': - return messageJSX('Already joined', true); + return messageJSX(t("InviteUser.invite_result.already_joined"), true); case 'invite': - return messageJSX('Already Invited', true); + return messageJSX(t("InviteUser.invite_result.already_invited"), true); case 'ban': - return messageJSX('Banned', false); + return messageJSX(t("InviteUser.invite_result.banned"), false); default: } } } return (typeof roomId === 'string') - ? - : ; + ? + : ; }; const renderError = (userId) => { if (!procUserError.has(userId)) return null; @@ -239,27 +245,27 @@ function InviteUser({ return ( } + title={(typeof roomId === 'string' ? t("InviteUser.invite_to_room", {room: mx.getRoom(roomId).name}) : t("InviteUser.invite_to_dm"))} + contentOptions={} onRequestClose={onRequestClose} >
{ e.preventDefault(); searchUser(usernameRef.current.value); }}> - - + +
{ typeof searchQuery.username !== 'undefined' && isSearching && (
- {`Searching for user "${searchQuery.username}"...`} + {t("InviteUser.searching_for_user", {user_name: searchQuery.username})}
) } { typeof searchQuery.username !== 'undefined' && !isSearching && ( - {`Search result for user "${searchQuery.username}"`} + {t("InviteUser.search_result_title", {user_name: searchQuery.username})} ) } { diff --git a/src/app/organisms/join-alias/JoinAlias.jsx b/src/app/organisms/join-alias/JoinAlias.jsx index 9e7f6df1..05e033a3 100644 --- a/src/app/organisms/join-alias/JoinAlias.jsx +++ b/src/app/organisms/join-alias/JoinAlias.jsx @@ -19,6 +19,11 @@ import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import { useStore } from '../../hooks/useStore'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; +import { t } from 'i18next'; + + const ALIAS_OR_ID_REG = /^[#|!].+:.+\..+$/; function JoinAliasContent({ term, requestClose }) { @@ -29,6 +34,8 @@ function JoinAliasContent({ term, requestClose }) { const mx = initMatrix.matrixClient; const mountStore = useStore(); + const { t } = useTranslation(); + const openRoom = (roomId) => { const room = mx.getRoom(roomId); if (!room) return; @@ -54,10 +61,10 @@ function JoinAliasContent({ term, requestClose }) { const alias = e.target.alias.value; if (alias?.trim() === '') return; if (alias.match(ALIAS_OR_ID_REG) === null) { - setError('Invalid address.'); + setError(t("JoinAlias.invalid_address")); return; } - setProcess('Looking for address...'); + setProcess(t("JoinAlias.looking_for_address")); setError(undefined); let via; if (alias.startsWith('#')) { @@ -65,12 +72,12 @@ function JoinAliasContent({ term, requestClose }) { const aliasData = await mx.resolveRoomAlias(alias); via = aliasData?.servers.slice(0, 3) || []; if (mountStore.getItem()) { - setProcess(`Joining ${alias}...`); + setProcess(t("JoinAlias.joining_alias", {alias_name: alias})); } } catch (err) { if (!mountStore.getItem()) return; setProcess(false); - setError(`Unable to find room/space with ${alias}. Either room/space is private or doesn't exist.`); + setError(t("JoinAlias.couldnt_find_room_or_space_alias", {alias_name: alias})); } } try { @@ -81,14 +88,14 @@ function JoinAliasContent({ term, requestClose }) { } catch { if (!mountStore.getItem()) return; setProcess(false); - setError(`Unable to join ${alias}. Either room/space is private or doesn't exist.`); + setError(t("JoinAlias.couldnt_find_room_or_space", {alias_name: alias})); } }; return (
{process} ) - : + : }
@@ -142,9 +149,9 @@ function JoinAlias() { Join with address + {t("JoinAlias.title")} )} - contentOptions={} + contentOptions={} onRequestClose={requestClose} > { data ? :
} diff --git a/src/app/organisms/navigation/DrawerBreadcrumb.jsx b/src/app/organisms/navigation/DrawerBreadcrumb.jsx index ca9ab6a0..f67693bf 100644 --- a/src/app/organisms/navigation/DrawerBreadcrumb.jsx +++ b/src/app/organisms/navigation/DrawerBreadcrumb.jsx @@ -18,6 +18,11 @@ import NotificationBadge from '../../atoms/badge/NotificationBadge'; import ChevronRightIC from '../../../../public/res/ic/outlined/chevron-right.svg'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; +import { t } from 'i18next'; + + function DrawerBreadcrumb({ spaceId }) { const [, forceUpdate] = useState({}); const scrollRef = useRef(null); @@ -25,6 +30,8 @@ function DrawerBreadcrumb({ spaceId }) { const mx = initMatrix.matrixClient; const spacePath = navigation.selectedSpacePath; + const { t } = useTranslation(); + function onNotiChanged(roomId, total, prevTotal) { if (total === prevTotal) return; if (navigation.selectedSpacePath.includes(roomId)) { @@ -109,7 +116,7 @@ function DrawerBreadcrumb({ spaceId }) { className={index === spacePath.length - 1 ? 'drawer-breadcrumb__btn--selected' : ''} onClick={() => selectSpace(id)} > - {id === cons.tabs.HOME ? 'Home' : twemojify(mx.getRoom(id).name)} + {id === cons.tabs.HOME ? t("DrawerBreadcrumb.home") : twemojify(mx.getRoom(id).name)} { noti !== null && ( - Add rooms or spaces + {t("DrawerHeader.add_rooms_or_spaces")} { afterOptionSelect(); openCreateRoom(true, spaceId); }} disabled={!canManage} > - Create new space + {t("DrawerHeader.create_new_space")} { afterOptionSelect(); openCreateRoom(false, spaceId); }} disabled={!canManage} > - Create new room + {t("DrawerHeader.create_new_room")} { !spaceId && ( { afterOptionSelect(); openPublicRooms(); }} > - Join public room + {t("DrawerHeader.join_public_room")} )} { !spaceId && ( @@ -65,7 +72,7 @@ export function HomeSpaceOptions({ spaceId, afterOptionSelect }) { iconSrc={PlusIC} onClick={() => { afterOptionSelect(); openJoinAlias(); }} > - Join with address + {t("DrawerHeader.join_with_address")} )} { spaceId && ( @@ -74,7 +81,7 @@ export function HomeSpaceOptions({ spaceId, afterOptionSelect }) { onClick={() => { afterOptionSelect(); openSpaceAddExisting(spaceId); }} disabled={!canManage} > - Add existing + {t("DrawerHeader.add_existing")} )} { spaceId && ( @@ -82,7 +89,7 @@ export function HomeSpaceOptions({ spaceId, afterOptionSelect }) { onClick={() => { afterOptionSelect(); openSpaceManage(spaceId); }} iconSrc={HashSearchIC} > - Manage rooms + {t("DrawerHeader.manage_rooms")} )} @@ -98,7 +105,7 @@ HomeSpaceOptions.propTypes = { function DrawerHeader({ selectedTab, spaceId }) { const mx = initMatrix.matrixClient; - const tabName = selectedTab !== cons.tabs.DIRECTS ? 'Home' : 'Direct messages'; + const tabName = selectedTab !== cons.tabs.DIRECTS ? t("DrawerHeader.home") : t("DrawerHeader.direct_messages"); const isDMTab = selectedTab === cons.tabs.DIRECTS; const room = mx.getRoom(spaceId); @@ -142,8 +149,8 @@ function DrawerHeader({ selectedTab, spaceId }) { )} - { isDMTab && openInviteUser()} tooltip="Start DM" src={PlusIC} size="small" /> } - { !isDMTab && } + { isDMTab && openInviteUser()} tooltip={t("DrawerHeader.start_dm_tooltip")} src={PlusIC} size="small" /> } + { !isDMTab && } ); } diff --git a/src/app/organisms/navigation/SideBar.jsx b/src/app/organisms/navigation/SideBar.jsx index 53186965..8f587b12 100644 --- a/src/app/organisms/navigation/SideBar.jsx +++ b/src/app/organisms/navigation/SideBar.jsx @@ -34,6 +34,11 @@ import { useDeviceList } from '../../hooks/useDeviceList'; import { tabText as settingTabText } from '../settings/Settings'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; +import { t } from 'i18next'; + + function useNotificationUpdate() { const { notifications } = initMatrix; const [, forceUpdate] = useState({}); @@ -50,6 +55,9 @@ function useNotificationUpdate() { } function ProfileAvatarMenu() { + + const { t } = useTranslation(); + const mx = initMatrix.matrixClient; const [profile, setProfile] = useState({ avatarUrl: null, @@ -77,7 +85,7 @@ function ProfileAvatarMenu() { return ( openSettings(settingTabText.SECURITY)} avatar={} /> @@ -147,7 +155,7 @@ function FeaturedTab() { return ( <> selectTab(cons.tabs.HOME)} avatar={} @@ -159,7 +167,7 @@ function FeaturedTab() { ) : null} /> selectTab(cons.tabs.DIRECTS)} avatar={} @@ -355,7 +363,7 @@ function SideBar() {
openShortcutSpaces()} avatar={} /> @@ -367,13 +375,13 @@ function SideBar() {
openSearch()} avatar={} /> { totalInvites !== 0 && ( openInviteList()} avatar={} notificationBadge={} diff --git a/src/app/organisms/profile-editor/ProfileEditor.jsx b/src/app/organisms/profile-editor/ProfileEditor.jsx index 972192ef..5eaa3b24 100644 --- a/src/app/organisms/profile-editor/ProfileEditor.jsx +++ b/src/app/organisms/profile-editor/ProfileEditor.jsx @@ -16,6 +16,11 @@ import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; import './ProfileEditor.scss'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; +import { t } from 'i18next'; + + // TODO Fix bug that prevents 'Save' button from enabling up until second changed. function ProfileEditor({ userId }) { const [isEditing, setIsEditing] = useState(false); @@ -27,6 +32,8 @@ function ProfileEditor({ userId }) { const [username, setUsername] = useState(user.displayName); const [disabled, setDisabled] = useState(true); + const { t } = useTranslation(); + useEffect(() => { let isMounted = true; mx.getProfileInfo(mx.getUserId()).then((info) => { @@ -42,9 +49,9 @@ function ProfileEditor({ userId }) { const handleAvatarUpload = async (url) => { if (url === null) { const isConfirmed = await confirmDialog( - 'Remove avatar', - 'Are you sure that you want to remove avatar?', - 'Remove', + t("ProfileEditor.remove_avatar"), + t("ProfileViewer.remove_avatar_confirmation"), + t("common.remove"), 'caution', ); if (isConfirmed) { @@ -83,13 +90,13 @@ function ProfileEditor({ userId }) { onSubmit={(e) => { e.preventDefault(); saveDisplayName(); }} > - - + + ); @@ -100,7 +107,7 @@ function ProfileEditor({ userId }) { setIsEditing(true)} />
diff --git a/src/app/organisms/profile-viewer/ProfileViewer.jsx b/src/app/organisms/profile-viewer/ProfileViewer.jsx index d74629f2..731fc956 100644 --- a/src/app/organisms/profile-viewer/ProfileViewer.jsx +++ b/src/app/organisms/profile-viewer/ProfileViewer.jsx @@ -34,9 +34,16 @@ import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import { useForceUpdate } from '../../hooks/useForceUpdate'; import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; +import { t } from 'i18next'; + function ModerationTools({ roomId, userId, }) { + + const { t } = useTranslation(); + const mx = initMatrix.matrixClient; const room = mx.getRoom(roomId); const roomMember = room.getMember(userId); @@ -70,14 +77,14 @@ function ModerationTools({
{canIKick && (
- - + +
)} {canIBan && (
- - + +
)}
@@ -93,6 +100,8 @@ function SessionInfo({ userId }) { const [isVisible, setIsVisible] = useState(false); const mx = initMatrix.matrixClient; + const { t } = useTranslation(); + useEffect(() => { let isUnmounted = false; @@ -118,8 +127,8 @@ function SessionInfo({ userId }) { if (!isVisible) return null; return (
- {devices === null && Loading sessions...} - {devices?.length === 0 && No session found.} + {devices === null && {t("ProfileViewer.loading_sessions")}} + {devices?.length === 0 && {t("ProfileViewer.no_sessions_found")}} {devices !== null && (devices.map((device) => ( setIsVisible(!isVisible)} iconSrc={isVisible ? ChevronBottomIC : ChevronRightIC} > - {`View ${devices?.length > 0 ? `${devices.length} ` : ''}sessions`} + {t("ProfileViewer.view_sessions", {count: devices?.length})} {renderSessionChips()}
@@ -252,7 +261,7 @@ function ProfileFooter({ roomId, userId, onRequestClose }) { onClick={openDM} disabled={isCreatingDM} > - {isCreatingDM ? 'Creating room...' : 'Message'} + {isCreatingDM ? t("ProfileViewer.creating_dm_room") : t("ProfileViewer.send_direct_message_button")} { isBanned && canIKick && ( )} @@ -281,8 +290,8 @@ function ProfileFooter({ roomId, userId, onRequestClose }) { > { isUserIgnored - ? `${isIgnoring ? 'Unignoring...' : 'Unignore'}` - : `${isIgnoring ? 'Ignoring...' : 'Ignore'}` + ? `${isIgnoring ? t("ProfileViewer.unignoring") : t("ProfileViewer.unignore")}` + : `${isIgnoring ? t("ProfileViewer.ignoring") : t("ProfileViewer.ignore")}` }
@@ -365,16 +374,16 @@ function ProfileViewer() { const handleChangePowerLevel = async (newPowerLevel) => { if (newPowerLevel === powerLevel) return; - const SHARED_POWER_MSG = 'You will not be able to undo this change as you are promoting the user to have the same power level as yourself. Are you sure?'; - const DEMOTING_MYSELF_MSG = 'You will not be able to undo this change as you are demoting yourself. Are you sure?'; + const SHARED_POWER_MSG = t("ProfileViewer.shared_power_message"); + const DEMOTING_MYSELF_MSG = t("ProfileViewer.demoting_self_message"); const isSharedPower = newPowerLevel === myPowerLevel; const isDemotingMyself = userId === mx.getUserId(); if (isSharedPower || isDemotingMyself) { const isConfirmed = await confirmDialog( - 'Change power level', + t("ProfileViewer.change_power_level"), isSharedPower ? SHARED_POWER_MSG : DEMOTING_MYSELF_MSG, - 'Change', + t("common.change"), 'caution', ); if (!isConfirmed) return; @@ -435,7 +444,7 @@ function ProfileViewer() { title={room?.name ?? ''} onAfterClose={handleAfterClose} onRequestClose={closeDialog} - contentOptions={} + contentOptions={} > {roomId ? renderProfile() :
}
diff --git a/src/app/organisms/public-rooms/PublicRooms.jsx b/src/app/organisms/public-rooms/PublicRooms.jsx index d1674c32..970db63a 100644 --- a/src/app/organisms/public-rooms/PublicRooms.jsx +++ b/src/app/organisms/public-rooms/PublicRooms.jsx @@ -18,9 +18,15 @@ import RoomTile from '../../molecules/room-tile/RoomTile'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import HashSearchIC from '../../../../public/res/ic/outlined/hash-search.svg'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; + + const SEARCH_LIMIT = 20; function TryJoinWithAlias({ alias, onRequestClose }) { + const { t } = useTranslation(); + const [status, setStatus] = useState({ isJoining: false, error: null, @@ -53,7 +59,7 @@ function TryJoinWithAlias({ alias, onRequestClose }) { } catch (e) { setStatus({ isJoining: false, - error: `Unable to join ${alias}. Either room is private or doesn't exist.`, + error: t("PublicRooms.could_not_join_alias", {alias: alias}), roomId: null, tempRoomId: null, }); @@ -63,16 +69,16 @@ function TryJoinWithAlias({ alias, onRequestClose }) { return (
{status.roomId === null && !status.isJoining && status.error === null && ( - + )} {status.isJoining && ( <> - {`Joining ${alias}...`} + {t("PublicRooms.joining_alias", {alias: alias})} )} {status.roomId !== null && ( - + )} {status.error !== null && {status.error}}
@@ -92,6 +98,7 @@ function PublicRooms({ isOpen, searchTerm, onRequestClose }) { const [searchQuery, updateSearchQuery] = useState({}); const [joiningRooms, updateJoiningRooms] = useState(new Set()); + const { t } = useTranslation(); const roomNameRef = useRef(null); const hsRef = useRef(null); const userId = initMatrix.matrixClient.getUserId(); @@ -140,14 +147,14 @@ function PublicRooms({ isOpen, searchTerm, onRequestClose }) { if (totalRooms.length === 0) { updateSearchQuery({ error: inputRoomName === '' - ? `No public rooms on ${inputHs}` - : `No result found for "${inputRoomName}" on ${inputHs}`, + ? t("PublicRooms.no_public_rooms", {homeserver: inputHs}) + : t("PublicRooms.no_result_found", {homeserver: inputHs, input: inputRoomName}), alias: isInputAlias ? inputRoomName : null, }); } } catch (e) { updatePublicRooms([]); - let err = 'Something went wrong!'; + let err = t("errors.generic"); if (e?.httpStatus >= 400 && e?.httpStatus < 500) { err = e.message; } @@ -206,8 +213,8 @@ function PublicRooms({ isOpen, searchTerm, onRequestClose }) { desc={typeof room.topic === 'string' ? room.topic : null} options={( <> - {isJoined && } - {!isJoined && (joiningRooms.has(room.room_id) ? : )} + {isJoined && } + {!isJoined && (joiningRooms.has(room.room_id) ? : )} )} /> @@ -218,17 +225,17 @@ function PublicRooms({ isOpen, searchTerm, onRequestClose }) { return ( } + title={t("PublicRooms.title")} + contentOptions={} onRequestClose={onRequestClose} >
{ e.preventDefault(); searchRooms(); }}>
- - + +
- +
{ @@ -237,13 +244,13 @@ function PublicRooms({ isOpen, searchTerm, onRequestClose }) { ? (
- {`Loading public rooms from ${searchQuery.homeserver}...`} + {t("PublicRooms.loading", {homeserver: searchQuery.homeserver})}
) : (
- {`Searching for "${searchQuery.name}" on ${searchQuery.homeserver}...`} + {t("PublicRooms.searching", {homeserver: searchQuery.homeserver, query: searchQuery.name})}
) ) @@ -251,8 +258,8 @@ function PublicRooms({ isOpen, searchTerm, onRequestClose }) { { typeof searchQuery.name !== 'undefined' && !isSearching && ( searchQuery.name === '' - ? {`Public rooms on ${searchQuery.homeserver}.`} - : {`Search result for "${searchQuery.name}" on ${searchQuery.homeserver}.`} + ? {t("PublicRooms.result_title", {homeserver: searchQuery.homeserver})} + : {t("PublicRooms.search_result_title", {homeserver: searchQuery.homeserver, query: searchQuery.name})} ) } { searchQuery.error && ( @@ -272,7 +279,7 @@ function PublicRooms({ isOpen, searchTerm, onRequestClose }) { { publicRooms.length !== 0 && publicRooms.length % SEARCH_LIMIT === 0 && (
{ isViewMore !== true && ( - + )} { isViewMore && }
diff --git a/src/app/organisms/room/RoomSettings.jsx b/src/app/organisms/room/RoomSettings.jsx index 50c5e512..dbd597f1 100644 --- a/src/app/organisms/room/RoomSettings.jsx +++ b/src/app/organisms/room/RoomSettings.jsx @@ -38,6 +38,10 @@ import ChevronTopIC from '../../../../public/res/ic/outlined/chevron-top.svg'; import { useForceUpdate } from '../../hooks/useForceUpdate'; import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; +import { t } from 'i18next'; + const tabText = { GENERAL: 'General', SEARCH: 'Search', @@ -73,6 +77,8 @@ function GeneralSettings({ roomId }) { const room = mx.getRoom(roomId); const canInvite = room.canInvite(mx.getUserId()); + const { t } = useTranslation(); + return ( <>
@@ -88,9 +94,9 @@ function GeneralSettings({ roomId }) { variant="danger" onClick={async () => { const isConfirmed = await confirmDialog( - 'Leave room', - `Are you sure that you want to leave "${room.name}" room?`, - 'Leave', + t("RoomSettings.leave_room"), + t("RoomSettings.leave_room_confirm_message", {room_name: room.name}), + t("RoomSettings.leave_room_confirm_button"), 'danger', ); if (!isConfirmed) return; @@ -102,15 +108,15 @@ function GeneralSettings({ roomId }) {
- Notification (Changing this will only affect you) + {t("RoomSettings.notification_header")}
- Room visibility (who can join) + {t("RoomSettings.visibility_header")}
- Room addresses + {t("RoomSettings.address_header")}
@@ -125,11 +131,11 @@ function SecuritySettings({ roomId }) { return ( <>
- Encryption + {t("RoomSettings.encryption_header")}
- Message history visibility + {t("RoomSettings.message_history_header")}
@@ -181,7 +187,7 @@ function RoomSettings({ roomId }) { {`${room.name}`} - — room settings + — {t("RoomSettings.room_settings_subtitle")} diff --git a/src/app/organisms/room/RoomViewContent.jsx b/src/app/organisms/room/RoomViewContent.jsx index ab1dfbab..9a5ec5c1 100644 --- a/src/app/organisms/room/RoomViewContent.jsx +++ b/src/app/organisms/room/RoomViewContent.jsx @@ -29,6 +29,10 @@ import { parseTimelineChange } from './common'; import TimelineScroll from './TimelineScroll'; import EventLimit from './EventLimit'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; +import { Trans } from 'react-i18next'; + const PAG_LIMIT = 30; const MAX_MSG_DIFF_MINUTES = 5; const PLACEHOLDER_COUNT = 2; @@ -55,29 +59,39 @@ function RoomIntroContainer({ event, timeline }) { const [, nameForceUpdate] = useForceUpdate(); const mx = initMatrix.matrixClient; const { roomList } = initMatrix; + + const { t } = useTranslation(); + const { room } = timeline; const roomTopic = room.currentState.getStateEvents('m.room.topic')[0]?.getContent().topic; const isDM = roomList.directs.has(timeline.roomId); let avatarSrc = room.getAvatarUrl(mx.baseUrl, 80, 80, 'crop'); avatarSrc = isDM ? room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 80, 80, 'crop') : avatarSrc; - const heading = isDM ? room.name : `Welcome to ${room.name}`; + const heading = isDM ? room.name : t("RoomViewContent.welcome_to_room", {room_name: room.name}); const topic = twemojify(roomTopic || '', undefined, true); const nameJsx = twemojify(room.name); const desc = isDM ? ( <> - This is the beginning of your direct message history with @ - {nameJsx} - {'. '} - {topic} + }} + /> + {topic == "" ? "" : " - "} + {topic } ) : ( <> - {'This is the beginning of the '} - {nameJsx} - {' room. '} + }} + /> + + {topic == "" ? "" : " - "} {topic} ); @@ -98,7 +112,7 @@ function RoomIntroContainer({ event, timeline }) { name={room.name} heading={twemojify(heading)} desc={desc} - time={event ? `Created at ${dateFormat(event.getDate(), 'dd mmmm yyyy, hh:MM TT')}` : null} + time={event ? t("RoomViewContent.created_on", {date: event.getDate(), formatParams: { date: { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'}}}) : null} /> ); } @@ -386,6 +400,8 @@ let jumpToItemIndex = -1; function RoomViewContent({ eventId, roomTimeline }) { const [throttle] = useState(new Throttle()); + const { t } = useTranslation(); + const timelineSVRef = useRef(null); const timelineScrollRef = useRef(null); const eventLimitRef = useRef(null); @@ -523,7 +539,7 @@ function RoomViewContent({ eventId, roomTimeline }) { && readUptoEvent.getTs() < mEvent.getTs()); if (unreadDivider) { isNewEvent = true; - tl.push(); + tl.push(); itemCountIndex += 1; if (jumpToItemIndex === -1) jumpToItemIndex = itemCountIndex; } diff --git a/src/app/organisms/room/RoomViewFloating.jsx b/src/app/organisms/room/RoomViewFloating.jsx index d027aff2..bd8c1676 100644 --- a/src/app/organisms/room/RoomViewFloating.jsx +++ b/src/app/organisms/room/RoomViewFloating.jsx @@ -3,6 +3,8 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import './RoomViewFloating.scss'; +import { twemojify } from '../../../util/twemojify'; + import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import { markAsRead } from '../../../client/action/notifications'; @@ -14,8 +16,14 @@ import MessageIC from '../../../../public/res/ic/outlined/message.svg'; import MessageUnreadIC from '../../../../public/res/ic/outlined/message-unread.svg'; import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg'; +import { getUsername, getUsernameOfRoomMember } from '../../../util/matrixUtil'; + import { getUsersActionJsx } from './common'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; +import { Trans } from 'react-i18next'; + function useJumpToEvent(roomTimeline) { const [eventId, setEventId] = useState(null); @@ -86,32 +94,55 @@ function useScrollToBottom(roomTimeline) { function RoomViewFloating({ roomId, roomTimeline, }) { + + const { t } = useTranslation(); + const [isJumpToEvent, jumpToEvent, cancelJumpToEvent] = useJumpToEvent(roomTimeline); const [typingMembers] = useTypingMembers(roomTimeline); const [isAtBottom, setIsAtBottom] = useScrollToBottom(roomTimeline); + const room = initMatrix.matrixClient.getRoom(roomId) + + const getUserDisplayName = (userId) => { + console.log(userId); + if (room?.getMember(userId)) return getUsernameOfRoomMember(room.getMember(userId)); + return getUsername(userId); + }; + const handleScrollToBottom = () => { roomTimeline.emit(cons.events.roomTimeline.SCROLL_TO_LIVE); setIsAtBottom(true); }; + console.log(typingMembers) + + let typingMemberValues = [...typingMembers]; + + console.log(typingMemberValues) + return ( <>
0 ? ' room-view__typing--open' : ''}`}>
- {getUsersActionJsx(roomId, [...typingMembers], 'typing...')} + + }} + /> +
diff --git a/src/app/organisms/room/RoomViewHeader.jsx b/src/app/organisms/room/RoomViewHeader.jsx index 849ba14b..af4ae2c2 100644 --- a/src/app/organisms/room/RoomViewHeader.jsx +++ b/src/app/organisms/room/RoomViewHeader.jsx @@ -29,6 +29,9 @@ import BackArrowIC from '../../../../public/res/ic/outlined/chevron-left.svg'; import { useForceUpdate } from '../../hooks/useForceUpdate'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; + function RoomViewHeader({ roomId }) { const [, forceUpdate] = useForceUpdate(); const mx = initMatrix.matrixClient; @@ -37,6 +40,8 @@ function RoomViewHeader({ roomId }) { avatarSrc = isDM ? mx.getRoom(roomId).getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 36, 36, 'crop') : avatarSrc; const roomName = mx.getRoom(roomId).name; + const { t } = useTranslation(); + const roomHeaderBtnRef = useRef(null); useEffect(() => { const settingsToggle = (isVisibile) => { @@ -93,17 +98,18 @@ function RoomViewHeader({ roomId }) { - toggleRoomSettings(tabText.SEARCH)} tooltip="Search" src={SearchIC} /> - - toggleRoomSettings(tabText.MEMBERS)} tooltip="Members" src={UserIC} /> + toggleRoomSettings(tabText.SEARCH)} tooltip={t("RoomViewHeader.search_tooltip")} src={SearchIC} /> + + toggleRoomSettings(tabText.MEMBERS)} tooltip={t("RoomViewHeader.members_tooltip")} src={UserIC} /> ); } + RoomViewHeader.propTypes = { roomId: PropTypes.string.isRequired, }; diff --git a/src/app/organisms/room/RoomViewInput.jsx b/src/app/organisms/room/RoomViewInput.jsx index 37e02989..1959a472 100644 --- a/src/app/organisms/room/RoomViewInput.jsx +++ b/src/app/organisms/room/RoomViewInput.jsx @@ -30,6 +30,9 @@ import MarkdownIC from '../../../../public/res/ic/outlined/markdown.svg'; import FileIC from '../../../../public/res/ic/outlined/file.svg'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; + const CMD_REGEX = /(^\/|:|@)(\S*)$/; let isTyping = false; let isCmdActivated = false; @@ -41,6 +44,8 @@ function RoomViewInput({ const [isMarkdown, setIsMarkdown] = useState(settings.isMarkdown); const [replyTo, setReplyTo] = useState(null); + const { t } = useTranslation(); + const textAreaRef = useRef(null); const inputBaseRef = useRef(null); const uploadInputRef = useRef(null); @@ -81,7 +86,7 @@ function RoomViewInput({ function uploadingProgress(myRoomId, { loaded, total }) { if (myRoomId !== roomId) return; const progressPer = Math.round((loaded * 100) / total); - uploadProgressRef.current.textContent = `Uploading: ${bytesToSize(loaded)}/${bytesToSize(total)} (${progressPer}%)`; + uploadProgressRef.current.textContent = t("RoomViewInput.upload_progress", {progress: bytesToSize(loaded), total:bytesToSize(total), percent: progressPer}); inputBaseRef.current.style.backgroundImage = `linear-gradient(90deg, var(--bg-surface-hover) ${progressPer}%, var(--bg-surface-low) ${progressPer}%)`; } function clearAttachment(myRoomId) { @@ -311,8 +316,8 @@ function RoomViewInput({ { tombstoneEvent - ? tombstoneEvent.getContent()?.body ?? 'This room has been replaced and is no longer active.' - : 'You do not have permission to post to this room' + ? tombstoneEvent.getContent()?.body ?? t("RoomViewInput.tombstone_replaced") + : t("RoomViewInput.tombstone_permission_denied") } ); @@ -321,7 +326,7 @@ function RoomViewInput({ <>
- +
{roomTimeline.isEncrypted() && } @@ -333,7 +338,7 @@ function RoomViewInput({ onChange={handleMsgTyping} onPaste={handlePaste} onKeyDown={handleKeyDown} - placeholder="Send a message..." + placeholder={t("RoomViewInput.send_message_placeholder")} /> @@ -347,10 +352,10 @@ function RoomViewInput({ cords.y -= 250; openEmojiBoard(cords, addEmoji); }} - tooltip="Emoji" + tooltip={t("RoomViewInput.emoji_tooltip")} src={EmojiIC} /> - +
); @@ -368,7 +373,7 @@ function RoomViewInput({
{attachment.name} - {`size: ${bytesToSize(attachment.size)}`} + {t("RoomViewInput.file_size", {size: bytesToSize(attachment.size)})}
); @@ -383,7 +388,7 @@ function RoomViewInput({ setReplyTo(null); }} src={CrossIC} - tooltip="Cancel reply" + tooltip={t("RoomViewInput.cancel_reply_tooltip")} size="extra-small" /> - {twemojify(user)} - {' joined the room'} + }} + /> ); }, @@ -19,118 +28,145 @@ function getTimelineJSXMessages() { const reasonMsg = (typeof reason === 'string') ? `: ${reason}` : ''; return ( <> - {twemojify(user)} - {' left the room'} - {twemojify(reasonMsg)} + }} + /> ); }, invite(inviter, user) { return ( <> - {twemojify(inviter)} - {' invited '} - {twemojify(user)} + }} + /> ); }, cancelInvite(inviter, user) { return ( <> - {twemojify(inviter)} - {' canceled '} - {twemojify(user)} - {'\'s invite'} + }} + /> ); }, rejectInvite(user) { return ( <> - {twemojify(user)} - {' rejected the invitation'} + }} + /> ); }, kick(actor, user, reason) { - const reasonMsg = (typeof reason === 'string') ? `: ${reason}` : ''; + const reasonMsg = (typeof reason === 'string') ? `${reason}` : ''; return ( <> - {twemojify(actor)} - {' kicked '} - {twemojify(user)} - {twemojify(reasonMsg)} + }} + /> ); }, ban(actor, user, reason) { - const reasonMsg = (typeof reason === 'string') ? `: ${reason}` : ''; + const reasonMsg = (typeof reason === 'string') ? `${reason}` : ''; return ( <> - {twemojify(actor)} - {' banned '} - {twemojify(user)} - {twemojify(reasonMsg)} + }} + /> ); }, unban(actor, user) { return ( <> - {twemojify(actor)} - {' unbanned '} - {twemojify(user)} + }} + /> ); }, avatarSets(user) { return ( <> - {twemojify(user)} - {' set a avatar'} + }} + /> ); }, avatarChanged(user) { return ( <> - {twemojify(user)} - {' changed their avatar'} + }} + /> ); }, avatarRemoved(user) { return ( <> - {twemojify(user)} - {' removed their avatar'} + }} + /> ); }, nameSets(user, newName) { return ( <> - {twemojify(user)} - {' set display name to '} - {twemojify(newName)} + }} + /> ); }, nameChanged(user, newName) { return ( <> - {twemojify(user)} - {' changed their display name to '} - {twemojify(newName)} + }} + /> ); }, nameRemoved(user, lastName) { return ( <> - {twemojify(user)} - {' removed their display name '} - {twemojify(lastName)} + }} + /> ); }, diff --git a/src/app/organisms/search/Search.jsx b/src/app/organisms/search/Search.jsx index d40d8615..14e8058c 100644 --- a/src/app/organisms/search/Search.jsx +++ b/src/app/organisms/search/Search.jsx @@ -20,6 +20,9 @@ import RoomSelector from '../../molecules/room-selector/RoomSelector'; import SearchIC from '../../../../public/res/ic/outlined/search.svg'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; + function useVisiblityToggle(setResult) { const [isOpen, setIsOpen] = useState(false); @@ -81,6 +84,8 @@ function Search() { const searchRef = useRef(null); const mx = initMatrix.matrixClient; + const { t } = useTranslation(); + const handleSearchResults = (chunk, term) => { setResult({ term, @@ -212,7 +217,7 @@ function Search() { @@ -224,7 +229,7 @@ function Search() {
- Type # for rooms, @ for DMs and * for spaces. Hotkey: Ctrl + k + {t("Search.description")}
diff --git a/src/app/organisms/settings/AuthRequest.jsx b/src/app/organisms/settings/AuthRequest.jsx index ca07c2a2..3310ac30 100644 --- a/src/app/organisms/settings/AuthRequest.jsx +++ b/src/app/organisms/settings/AuthRequest.jsx @@ -12,6 +12,9 @@ import Spinner from '../../atoms/spinner/Spinner'; import { useStore } from '../../hooks/useStore'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; + let lastUsedPassword; const getAuthId = (password) => ({ type: 'm.login.password', @@ -25,6 +28,7 @@ const getAuthId = (password) => ({ function AuthRequest({ onComplete, makeRequest }) { const [status, setStatus] = useState(false); const mountStore = useStore(); + const { t } = useTranslation(); const handleForm = async (e) => { mountStore.setItem(true); @@ -41,10 +45,10 @@ function AuthRequest({ onComplete, makeRequest }) { lastUsedPassword = undefined; if (!mountStore.getItem()) return; if (err.errcode === 'M_FORBIDDEN') { - setStatus({ error: 'Wrong password. Please enter correct password.' }); + setStatus({ error: t("AuthRequest.wrong_password") }); return; } - setStatus({ error: 'Request failed!' }); + setStatus({ error: t("AuthRequest.request_failed") }); } }; @@ -57,14 +61,14 @@ function AuthRequest({ onComplete, makeRequest }) {
{status.ongoing && } {status.error && {status.error}} - {(status === false || status.error) && } + {(status === false || status.error) && }
); diff --git a/src/app/organisms/settings/CrossSigning.jsx b/src/app/organisms/settings/CrossSigning.jsx index 9213e9da..138282ac 100644 --- a/src/app/organisms/settings/CrossSigning.jsx +++ b/src/app/organisms/settings/CrossSigning.jsx @@ -19,58 +19,67 @@ import SettingTile from '../../molecules/setting-tile/SettingTile'; import { authRequest } from './AuthRequest'; import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; -const failedDialog = () => { - const renderFailure = (requestClose) => ( -
- {twemojify('❌')} - Failed to setup cross signing. Please try again. - -
- ); - openReusableDialog( - Setup cross signing, - renderFailure, - ); -}; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; -const securityKeyDialog = (key) => { - const downloadKey = () => { - const blob = new Blob([key.encodedPrivateKey], { - type: 'text/plain;charset=us-ascii', - }); - FileSaver.saveAs(blob, 'security-key.txt'); - }; - const copyKey = () => { - copyToClipboard(key.encodedPrivateKey); - }; - - const renderSecurityKey = () => ( -
- Please save this security key somewhere safe. - - {key.encodedPrivateKey} - -
- - -
-
- ); - - // Download automatically. - downloadKey(); - - openReusableDialog( - Security Key, - () => renderSecurityKey(), - ); -}; function CrossSigningSetup() { + + const { t } = useTranslation(); + const initialValues = { phrase: '', confirmPhrase: '' }; const [genWithPhrase, setGenWithPhrase] = useState(undefined); + const failedDialog = () => { + const renderFailure = (requestClose) => ( +
+ {twemojify('❌')} + {t("CrossSigning.setup_failed")} + +
+ ); + + openReusableDialog( + {t("CrossSigning.setup")}, + renderFailure, + ); + }; + + const securityKeyDialog = (key) => { + const downloadKey = () => { + const blob = new Blob([key.encodedPrivateKey], { + type: 'text/plain;charset=us-ascii', + }); + FileSaver.saveAs(blob, 'security-key.txt'); + }; + const copyKey = () => { + copyToClipboard(key.encodedPrivateKey); + }; + + const renderSecurityKey = () => ( +
+ {t("CrossSigning.save_security_key_message")} + + {key.encodedPrivateKey} + +
+ + +
+
+ ); + + // Download automatically. + downloadKey(); + + openReusableDialog( + {t("CrossSigning.security_key_dialog_title")}, + () => renderSecurityKey(), + ); + }; + + const setup = async (securityPhrase = undefined) => { const mx = initMatrix.matrixClient; setGenWithPhrase(typeof securityPhrase === 'string'); @@ -121,13 +130,12 @@ function CrossSigningSetup() {
- We will generate a Security Key, - which you can use to manage messages backup and session verification. + {t("CrossSigning.security_key_generation_message")} - {genWithPhrase !== false && } + {genWithPhrase !== false && } {genWithPhrase === false && }
- OR + {t("common.or")} setup(values.phrase)} @@ -142,15 +150,13 @@ function CrossSigningSetup() { disabled={genWithPhrase !== undefined} > - Alternatively you can also set a Security Phrase - so you don't have to remember long Security Key, - and optionally save the Key as backup. + {t("CrossSigning.security_phrase_message")} {errors.confirmPhrase && {errors.confirmPhrase}} - {genWithPhrase !== true && } + {genWithPhrase !== true && } {genWithPhrase === true && } )} @@ -180,41 +186,41 @@ const setupDialog = () => { Setup cross signing, () => , ); -}; +} function CrossSigningReset() { + const { t } = useTranslation(); return (
{twemojify('✋🧑‍🚒🤚')} - Resetting cross-signing keys is permanent. + {t("CrossSigning.reset_keys_subtitle")} - Anyone you have verified with will see security alerts and your message backup will lost. - You almost certainly do not want to do this, - unless you have lost Security Key or Phrase and - every session you can cross-sign from. + {t("CrossSigning.reset_keys_message")} - +
); } const resetDialog = () => { + openReusableDialog( Reset cross signing, () => , ); -}; +} function CrossSignin() { + const { t } = useTranslation(); const isCSEnabled = useCrossSigningStatus(); return ( Setup to verify and keep track of all your sessions. Also required to backup encrypted message.} + title={t("CrossSigning.title")} + content={{t("CrossSigning.setup_message")}} options={( isCSEnabled - ? - : + ? + : )} /> ); diff --git a/src/app/organisms/settings/DeviceManage.jsx b/src/app/organisms/settings/DeviceManage.jsx index 062ec021..175ca1b7 100644 --- a/src/app/organisms/settings/DeviceManage.jsx +++ b/src/app/organisms/settings/DeviceManage.jsx @@ -27,9 +27,15 @@ import { useDeviceList } from '../../hooks/useDeviceList'; import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; import { accessSecretStorage } from './SecretStorageAccess'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; + + const promptDeviceName = async (deviceName) => new Promise((resolve) => { let isCompleted = false; + const { t } = useTranslation(); + const renderContent = (onComplete) => { const handleSubmit = (e) => { e.preventDefault(); @@ -39,17 +45,17 @@ const promptDeviceName = async (deviceName) => new Promise((resolve) => { }; return (
- +
- - + +
); }; openReusableDialog( - Edit session name, + {t("DeviceManage.edit_session_name_title")}, (requestClose) => renderContent((name) => { isCompleted = true; resolve(name); @@ -76,6 +82,41 @@ function DeviceManage() { setProcessing([]); }, [deviceList]); + const { t } = useTranslation(); + + const promptDeviceName = async (deviceName) => new Promise((resolve) => { + let isCompleted = false; + const renderContent = (onComplete) => { + const handleSubmit = (e) => { + e.preventDefault(); + const name = e.target.session.value; + if (typeof name !== 'string') onComplete(null); + onComplete(name); + }; + return ( +
+ +
+ + +
+
+ ); + }; + + openReusableDialog( + {t("DeviceManage.edit_session_name_title")}, + (requestClose) => renderContent((name) => { + isCompleted = true; + resolve(name); + requestClose(); + }), + () => { + if (!isCompleted) resolve(null); + }, + ); + }); + const addToProcessing = (device) => { const old = [...processing]; old.push(device.device_id); @@ -91,7 +132,7 @@ function DeviceManage() {
- Loading devices... + {t("DeviceManage.loading_devices")}
); @@ -114,14 +155,14 @@ function DeviceManage() { const handleRemove = async (device) => { const isConfirmed = await confirmDialog( - `Logout ${device.display_name}`, - `You are about to logout "${device.display_name}" session.`, - 'Logout', + t("DeviceManage.logout_device_title", {device: device.display_name}), + t("DeviceManage.logout_device_message", {device: device.display_name}), + t("DeviceManage.logout_device_confirm"), 'danger', ); if (!isConfirmed) return; addToProcessing(device); - await authRequest(`Logout "${device.display_name}"`, async (auth) => { + await authRequest(t("DeviceManage.logout_device_title", {device: device.display_name}), async (auth) => { await mx.deleteDevice(device.device_id, auth); }); @@ -130,7 +171,7 @@ function DeviceManage() { }; const verifyWithKey = async (device) => { - const keyData = await accessSecretStorage('Session verification'); + const keyData = await accessSecretStorage(t("DeviceManage.session_verification_title")); if (!keyData) return; addToProcessing(device); await mx.checkOwnCrossSigningTrust(); @@ -164,7 +205,7 @@ function DeviceManage() { {displayName} {`${displayName ? ' — ' : ''}${deviceId}`} - {isCurrentDevice && Current} + {isCurrentDevice && {t("DeviceManage.current_device_label")}} )} options={ @@ -172,9 +213,9 @@ function DeviceManage() { ? : ( <> - {(isCSEnabled && canVerify) && } - handleRename(device)} src={PencilIC} tooltip="Rename" /> - handleRemove(device)} src={BinIC} tooltip="Remove session" /> + {(isCSEnabled && canVerify) && } + handleRename(device)} src={PencilIC} tooltip={t("DeviceManage.edit_session_name_tooltip")} /> + handleRemove(device)} src={BinIC} tooltip={t("DeviceManage.logout_device_tooltip")}/> ) } @@ -211,49 +252,50 @@ function DeviceManage() { noEncryption.push(device); } }); + return (
- Unverified sessions + {t("DeviceManage.unverified_sessions_title")} {!isCSEnabled && (
)} { unverified.length > 0 ? unverified.map((device) => renderDevice(device, false)) - : No unverified sessions + : {t("DeviceManage.unverified_sessions_none")} }
{noEncryption.length > 0 && (
- Sessions without encryption support + {t("DeviceManage.unencrypted_sessions_title")} {noEncryption.map((device) => renderDevice(device, null))}
)}
- Verified sessions + {t("DeviceManage.verified_sessions_title")} { verified.length > 0 ? verified.map((device, index) => { if (truncated && index >= TRUNCATED_COUNT) return null; return renderDevice(device, true); }) - : No verified sessions + : {t("DeviceManage.verified_sessions_none")} } { verified.length > TRUNCATED_COUNT && ( )} { deviceList.length > 0 && ( - Session names are visible to everyone, so do not put any private info here. + {t("DeviceManage.session_name_privacy_message")} )}
diff --git a/src/app/organisms/settings/KeyBackup.jsx b/src/app/organisms/settings/KeyBackup.jsx index 75f032bc..dd5d257b 100644 --- a/src/app/organisms/settings/KeyBackup.jsx +++ b/src/app/organisms/settings/KeyBackup.jsx @@ -24,11 +24,17 @@ import DownloadIC from '../../../../public/res/ic/outlined/download.svg'; import { useStore } from '../../hooks/useStore'; import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; + + function CreateKeyBackupDialog({ keyData }) { const [done, setDone] = useState(false); const mx = initMatrix.matrixClient; const mountStore = useStore(); + const { t } = useTranslation(); + const doBackup = async () => { setDone(false); let info; @@ -60,19 +66,19 @@ function CreateKeyBackupDialog({ keyData }) { {done === false && (
- Creating backup... + {t("KeyBackup.creating_backup")}
)} {done === true && ( <> {twemojify('✅')} - Successfully created backup + {t("KeyBackup.backup_created")} )} {done === null && ( <> - Failed to create backup - + {t("KeyBackup.backup_failed")} + )}
@@ -83,6 +89,9 @@ CreateKeyBackupDialog.propTypes = { }; function RestoreKeyBackupDialog({ keyData }) { + + const { t } = useTranslation(); + const [status, setStatus] = useState(false); const mx = initMatrix.matrixClient; const mountStore = useStore(); @@ -99,7 +108,7 @@ function RestoreKeyBackupDialog({ keyData }) { meBreath = true; }, 200); - setStatus({ message: `Restoring backup keys... (${progress.successes}/${progress.total})` }); + setStatus({ message: t("KeyBackup.restoring_progress", {progress: progress.successes, total: progress.total}) }); }; try { @@ -111,14 +120,14 @@ function RestoreKeyBackupDialog({ keyData }) { { progressCallback }, ); if (!mountStore.getItem()) return; - setStatus({ done: `Successfully restored backup keys (${info.imported}/${info.total}).` }); + setStatus({ done: t("KeyBackup.restore_complete", {progress: info.imported, total: info.total})}); } catch (e) { if (!mountStore.getItem()) return; if (e.errcode === 'RESTORE_BACKUP_ERROR_BAD_KEY') { deletePrivateKey(keyData.keyId); - setStatus({ error: 'Failed to restore backup. Key is invalid!', errorCode: 'BAD_KEY' }); + setStatus({ error: t("KeyBackup.restore_failed_bad_key"), errorCode: 'BAD_KEY' }); } else { - setStatus({ error: 'Failed to restore backup.', errCode: 'UNKNOWN' }); + setStatus({ error: t("KeyBackup.restore_failed_unknown"), errCode: 'UNKNOWN' }); } } }; @@ -133,7 +142,7 @@ function RestoreKeyBackupDialog({ keyData }) { {(status === false || status.message) && (
- {status.message ?? 'Restoring backup keys...'} + {status.message ?? t("KeyBackup.restoring")}
)} {status.done && ( @@ -145,7 +154,7 @@ function RestoreKeyBackupDialog({ keyData }) { {status.error && ( <> {status.error} - + )}
@@ -159,6 +168,7 @@ function DeleteKeyBackupDialog({ requestClose }) { const [isDeleting, setIsDeleting] = useState(false); const mx = initMatrix.matrixClient; const mountStore = useStore(); + const { t } = useTranslation(); const deleteBackup = async () => { mountStore.setItem(true); @@ -177,12 +187,12 @@ function DeleteKeyBackupDialog({ requestClose }) { return (
{twemojify('🗑')} - Deleting key backup is permanent. - All encrypted messages keys stored on server will be deleted. + {t("KeyBackup.delete_key_backup_subtitle")} + {t("KeyBackup.delete_key_backup_message")} { isDeleting ? - : + : }
); @@ -196,6 +206,7 @@ function KeyBackup() { const isCSEnabled = useCrossSigningStatus(); const [keyBackup, setKeyBackup] = useState(undefined); const mountStore = useStore(); + const { t } = useTranslation(); const fetchKeyBackupVersion = async () => { const info = await mx.getKeyBackupVersion(); @@ -220,28 +231,28 @@ function KeyBackup() { }, [isCSEnabled]); const openCreateKeyBackup = async () => { - const keyData = await accessSecretStorage('Create Key Backup'); + const keyData = await accessSecretStorage(t('KeyBackup.create_backup_title')); if (keyData === null) return; openReusableDialog( - Create Key Backup, + {t('KeyBackup.create_backup_title')}, () => , () => fetchKeyBackupVersion(), ); }; const openRestoreKeyBackup = async () => { - const keyData = await accessSecretStorage('Restore Key Backup'); + const keyData = await accessSecretStorage(t('KeyBackup.restore_backup_title')); if (keyData === null) return; openReusableDialog( - Restore Key Backup, + {t('KeyBackup.restore_backup_title')}, () => , ); }; const openDeleteKeyBackup = () => openReusableDialog( - Delete Key Backup, + {t('KeyBackup.delete_key_backup_title')}, (requestClose) => ( { @@ -254,28 +265,28 @@ function KeyBackup() { const renderOptions = () => { if (keyBackup === undefined) return ; - if (keyBackup === null) return ; + if (keyBackup === null) return ; return ( <> - - + + ); }; return ( - Online backup your encrypted messages keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key. + {t("KeyBackup.encrypted_messages_backup_description")} {!isCSEnabled && ( )} diff --git a/src/app/organisms/settings/SecretStorageAccess.jsx b/src/app/organisms/settings/SecretStorageAccess.jsx index e4fceb07..51f47cb9 100644 --- a/src/app/organisms/settings/SecretStorageAccess.jsx +++ b/src/app/organisms/settings/SecretStorageAccess.jsx @@ -13,6 +13,11 @@ import Button from '../../atoms/button/Button'; import Input from '../../atoms/input/Input'; import Spinner from '../../atoms/spinner/Spinner'; + +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; + + import { useStore } from '../../hooks/useStore'; function SecretStorageAccess({ onComplete }) { @@ -24,6 +29,7 @@ function SecretStorageAccess({ onComplete }) { const [process, setProcess] = useState(false); const [error, setError] = useState(null); const mountStore = useStore(); + const { t } = useTranslation(); const toggleWithPhrase = () => setWithPhrase(!withPhrase); @@ -39,7 +45,7 @@ function SecretStorageAccess({ onComplete }) { if (!mountStore.getItem()) return; if (!isCorrect) { - setError(`Incorrect Security ${key ? 'Key' : 'Phrase'}`); + setError(t(key ? "SecretStorageAccess.incorrect_security_key" : "SecretStorageAccess.incorrect_security_phrase")); setProcess(false); return; } @@ -51,7 +57,7 @@ function SecretStorageAccess({ onComplete }) { }); } catch (e) { if (!mountStore.getItem()) return; - setError(`Incorrect Security ${key ? 'Key' : 'Phrase'}`); + setError(t(key ? "SecretStorageAccess.incorrect_security_key" : "SecretStorageAccess.incorrect_security_phrase")); setProcess(false); } }; @@ -76,7 +82,7 @@ function SecretStorageAccess({ onComplete }) {
{error}} {!process && (
- - {isPassphrase && } + + {isPassphrase && }
)}
diff --git a/src/app/organisms/settings/Settings.jsx b/src/app/organisms/settings/Settings.jsx index 82b948ad..bc8f5b16 100644 --- a/src/app/organisms/settings/Settings.jsx +++ b/src/app/organisms/settings/Settings.jsx @@ -40,34 +40,39 @@ import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import CinnySVG from '../../../../public/res/svg/cinny.svg'; import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; + function AppearanceSection() { const [, updateState] = useState({}); + const { t } = useTranslation(); + return (
Theme { toggleSystemTheme(); updateState({}); }} /> )} - content={Use light or dark mode based on the system settings.} + content={{t("Settings.theme.follow_system.description")}} /> {!settings.useSystemTheme && ( settings.setTheme(index)} /> @@ -78,34 +83,34 @@ function AppearanceSection() {
Room messages { toggleMarkdown(); updateState({}); }} /> )} - content={Format messages with markdown syntax before sending.} + content={{t("Settings.markdown.description")}} /> { toggleMembershipEvents(); updateState({}); }} /> )} - content={Hide membership change messages from room timeline. (Join, Leave, Invite, Kick and Ban)} + content={{t("Settings.hide_membership_events.description")}} /> { toggleNickAvatarEvents(); updateState({}); }} /> )} - content={Hide nick and avatar change messages from room timeline.} + content={{t("Settings.hide_nickname_avatar_events.description")}} />
@@ -117,9 +122,11 @@ function NotificationsSection() { const [, updateState] = useState({}); + const { t } = useTranslation(); + const renderOptions = () => { if (window.Notification === undefined) { - return Not supported in this browser.; + return {t("errors.browser_not_supported")}; } if (permission === 'granted') { @@ -147,51 +154,54 @@ function NotificationsSection() { return (
- Notification & Sound + {t("Settings.notifications_and_sound.title")} Show desktop notification when new messages arrive.} + content={{t("Settings.notifications_and_sound.desktop.description")}} /> { toggleNotificationSounds(); updateState({}); }} /> )} - content={Play sound when new messages arrive.} + content={{t("Settings.notifications_and_sound.desktop.description")}} />
); } function SecuritySection() { + + const { t } = useTranslation(); + return (
- Cross signing and backup + {t("Settings.security.cross_signing.title")}
- Export/Import encryption keys + {t("Settings.security.export_import_encryption_keys.title")} - Export end-to-end encryption room keys to decrypt old messages in other session. In order to encrypt keys you need to set a password, which will be used while importing. + {t("Settings.security.export_encryption_keys.description")} )} /> - {'To decrypt older messages, Export E2EE room keys from Element (Settings > Security & Privacy > Encryption > Cryptography) and import them here. Imported keys are encrypted so you\'ll have to enter the password you set in order to decrypt it.'} + {t("Settings.security.import_encryption_keys.description")} )} @@ -202,28 +212,31 @@ function SecuritySection() { } function AboutSection() { + + const { t } = useTranslation(); + return (
- Application + {t("Settings.about.application")}
Cinny logo
- Cinny + {t("common.cinny")} {`v${cons.version}`} - Yet another matrix client + {t("common.slogan")}
- - + +
- Credits + {t("Settings.about.credits")}
  • @@ -297,9 +310,11 @@ function Settings() { const [selectedTab, setSelectedTab] = useState(tabItems[0]); const [isOpen, requestClose] = useWindowToggle(setSelectedTab); + const { t } = useTranslation(); + const handleTabChange = (tabItem) => setSelectedTab(tabItem); const handleLogout = async () => { - if (await confirmDialog('Logout', 'Are you sure that you want to logout your session?', 'Logout', 'danger')) { + if (await confirmDialog(t("Settings.logout.dialog.title"), t("Settings.logout.dialog.description"), t("Settings.logout.dialog.confirm"), 'danger')) { logout(); } }; @@ -308,13 +323,13 @@ function Settings() { Settings} + title={{t("Settings.title")}} contentOptions={( <> - + )} onRequestClose={requestClose} diff --git a/src/app/organisms/shortcut-spaces/ShortcutSpaces.jsx b/src/app/organisms/shortcut-spaces/ShortcutSpaces.jsx index 62ec76a3..6ede460d 100644 --- a/src/app/organisms/shortcut-spaces/ShortcutSpaces.jsx +++ b/src/app/organisms/shortcut-spaces/ShortcutSpaces.jsx @@ -22,6 +22,10 @@ import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import { useSpaceShortcut } from '../../hooks/useSpaceShortcut'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; + + function ShortcutSpacesContent() { const mx = initMatrix.matrixClient; const { spaces, roomIdToParents } = initMatrix.roomList; @@ -73,6 +77,8 @@ function ShortcutSpacesContent() { const toggleSelected = () => toggleSelection(spaceId); const deleteShortcut = () => deleteSpaceShortcut(spaceId); + const { t } = useTranslation(); + return ( - Pinned spaces - {spaceShortcut.length === 0 && No pinned spaces} + {t("ShortcutSpaces.pinned_spaces")} + {spaceShortcut.length === 0 && {t("ShortcutSpaces.no_pinned_spaces")}} {spaceShortcut.map((spaceId) => renderSpace(spaceId, true))} - Unpinned spaces - {spaceWithoutShortcut.length === 0 && No unpinned spaces} + {t("ShortcutSpaces.unpinned_spaces")} + {spaceWithoutShortcut.length === 0 && {t("ShortcutSpaces.no_unpinned_spaces")}} {spaceWithoutShortcut.map((spaceId) => renderSpace(spaceId, false))} {selected.length !== 0 && (
    {process && } - {process || `${selected.length} spaces selected`} + {process || t("ShortcutSpaces.spaces_selected", {count: selected.length})} { !process && ( - + )}
    )} @@ -144,6 +152,7 @@ function useVisibilityToggle() { function ShortcutSpaces() { const [isOpen, requestClose] = useVisibilityToggle(); + const { t } = useTranslation(); return ( - Pin spaces + {t("ShortcutSpaces.header")} )} - contentOptions={} + contentOptions={} onRequestClose={requestClose} > { diff --git a/src/app/organisms/space-manage/SpaceManage.jsx b/src/app/organisms/space-manage/SpaceManage.jsx index cf042da4..e2c035c1 100644 --- a/src/app/organisms/space-manage/SpaceManage.jsx +++ b/src/app/organisms/space-manage/SpaceManage.jsx @@ -32,6 +32,10 @@ import InfoIC from '../../../../public/res/ic/outlined/info.svg'; import { useForceUpdate } from '../../hooks/useForceUpdate'; import { useStore } from '../../hooks/useStore'; +import '../../i18n.jsx' +import { useTranslation } from 'react-i18next'; + + function SpaceManageBreadcrumb({ path, onSelect }) { return (
    @@ -75,6 +79,8 @@ function SpaceManageItem({ const canManage = parentRoom?.currentState.maySendStateEvent('m.space.child', mx.getUserId()) || false; const isSuggested = parentRoom?.currentState.getStateEvents('m.space.child', roomId)?.getContent().suggested === true; + const { t } = useTranslation(); + const room = mx.getRoom(roomId); const isJoined = !!(room?.getMyMembership() === 'join' || null); const name = room?.name || roomInfo.name || roomInfo.canonical_alias || roomId; @@ -114,7 +120,7 @@ function SpaceManageItem({ const roomNameJSX = ( {twemojify(name)} - {` • ${roomInfo.num_joined_members} members`} + • {t("SpaceManage.room_members", {count: roomInfo.num_joined_members})} ); @@ -142,19 +148,21 @@ function SpaceManageItem({ > {roomAvatarJSX} {roomNameJSX} - {isSuggested && Suggested} + {isSuggested && {t("SpaceManage.suggested")}} {roomInfo.topic && expandBtnJsx} { isJoined - ? - : + ? + : }
    {isExpand && roomInfo.topic && {twemojify(roomInfo.topic, undefined, true)}}
); } + + SpaceManageItem.propTypes = { parentId: PropTypes.string.isRequired, roomHierarchy: PropTypes.shape({}).isRequired, @@ -171,21 +179,23 @@ function SpaceManageFooter({ parentId, selected }) { const room = mx.getRoom(parentId); const { currentState } = room; + const { t } = useTranslation(); + const allSuggested = selected.every((roomId) => { const sEvent = currentState.getStateEvents('m.space.child', roomId); return !!sEvent?.getContent()?.suggested; }); const handleRemove = () => { - setProcess(`Removing ${selected.length} items`); + setProcess(t("SpaceManage.remove", {count: selected.length})); selected.forEach((roomId) => { mx.sendStateEvent(parentId, 'm.space.child', {}, roomId); }); }; const handleToggleSuggested = (isMark) => { - if (isMark) setProcess(`Marking as suggested ${selected.length} items`); - else setProcess(`Marking as not suggested ${selected.length} items`); + if (isMark) setProcess(t("SpaceManage.mark_suggested", {count: selected.length})); + else setProcess(t("SpaceManage.mark_not_suggested", {count: selected.length})); selected.forEach((roomId) => { const sEvent = room.currentState.getStateEvents('m.space.child', roomId); if (!sEvent) return; @@ -200,15 +210,15 @@ function SpaceManageFooter({ parentId, selected }) { return (
{process && } - {process || `${selected.length} item selected`} + {process || t("SpaceManage.items_selected", {count: selected.length})} { !process && ( <> - + )} @@ -282,6 +292,9 @@ function useChildUpdate(roomId, roomsHierarchy) { } function SpaceManageContent({ roomId, requestClose }) { + + const { t } = useTranslation(); + const mx = initMatrix.matrixClient; useUpdateOnJoin(roomId); const [, forceUpdate] = useForceUpdate(); @@ -339,11 +352,11 @@ function SpaceManageContent({ roomId, requestClose }) { {spacePath.length > 1 && ( )} - Rooms and spaces + {t("SpaceManage.rooms_and_spaces")}
{!isLoading && currentHierarchy?.rooms?.length === 1 && ( - Either the space contains private rooms or you need to join space to view it's rooms. + {t("SpaceManage.private_rooms_message")} )} {currentHierarchy && (currentHierarchy.rooms?.map((roomInfo) => ( @@ -362,15 +375,15 @@ function SpaceManageContent({ roomId, requestClose }) { /> ) )))} - {!currentHierarchy && loading...} + {!currentHierarchy && {t("common.loading")}}
{currentHierarchy?.canLoadMore && !isLoading && ( - + )} {isLoading && (
- Loading rooms... + {t("common.loading")}
)} {selected.length > 0 && ( @@ -406,6 +419,8 @@ function SpaceManage() { const [roomId, requestClose] = useWindowToggle(); const room = mx.getRoom(roomId); + const { t } = useTranslation(); + return ( {roomId && twemojify(room.name)} - — manage rooms + — {t("SpaceManage.subtitle")} )} contentOptions={} diff --git a/src/app/organisms/space-settings/SpaceSettings.jsx b/src/app/organisms/space-settings/SpaceSettings.jsx index 8ac561ee..89ba468d 100644 --- a/src/app/organisms/space-settings/SpaceSettings.jsx +++ b/src/app/organisms/space-settings/SpaceSettings.jsx @@ -83,7 +83,7 @@ function GeneralSettings({ roomId }) { }} iconSrc={isCategorized ? CategoryFilledIC : CategoryIC} > - {isCategorized ? t("space_settings.uncategorize_subspaces") : t("space_settings.categorize_subspaces")} + {isCategorized ? t("SpaceSettings.uncategorize_subspaces") : t("SpaceSettings.categorize_subspaces")} { @@ -93,30 +93,30 @@ function GeneralSettings({ roomId }) { }} iconSrc={isPinned ? PinFilledIC : PinIC} > - {isPinned ? t("space_settings.unpin_sidebar") : t("space_settings.pin_sidebar")} + {isPinned ? t("SpaceSettings.unpin_sidebar") : t("SpaceSettings.pin_sidebar")} { const isConfirmed = await confirmDialog( - t("space_settings.leave.leave_dialog_title"), - t("space_settings.leave.leave_dialog_message", {space: roomName}), - t("space_settings.leave.leave_space"), + t("SpaceSettings.leave.leave_dialog_title"), + t("SpaceSettings.leave.leave_dialog_message", {space: roomName}), + t("SpaceSettings.leave.leave_space"), 'danger', ); if (isConfirmed) leave(roomId); }} iconSrc={LeaveArrowIC} > - {t("space_settings.leave.leave_space")} + {t("SpaceSettings.leave.leave_space")}
- {t("space_settings.visibility.header")} + {t("SpaceSettings.visibility.header")}
- {t("space_settings.addresses.header")} + {t("SpaceSettings.addresses.header")}
@@ -169,7 +169,7 @@ function SpaceSettings() { title={( {isOpen && twemojify(room.name)} - — {t("space_settings.subtitle")} + — {t("SpaceSettings.subtitle")} )} contentOptions={} diff --git a/src/app/organisms/view-source/ViewSource.jsx b/src/app/organisms/view-source/ViewSource.jsx index d117d167..deb8b926 100644 --- a/src/app/organisms/view-source/ViewSource.jsx +++ b/src/app/organisms/view-source/ViewSource.jsx @@ -56,15 +56,15 @@ function ViewSource() { const renderViewSource = () => (
- {event.isEncrypted() && } - + {event.isEncrypted() && } +
); return ( setIsOpen(false)} contentOptions={ setIsOpen(false)} tooltip={t("common.close")} />} diff --git a/src/app/organisms/welcome/Welcome.jsx b/src/app/organisms/welcome/Welcome.jsx index 008345b7..1b17db60 100644 --- a/src/app/organisms/welcome/Welcome.jsx +++ b/src/app/organisms/welcome/Welcome.jsx @@ -16,8 +16,8 @@ function Welcome() {
Cinny logo - {t('welcome.heading')} - {t('welcome.subheading')} + {t('Welcome.heading')} + {t('Welcome.subheading')}
);