diff --git a/public/locales/de.json b/public/locales/de.json index 98020e116..8e337938d 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -2,6 +2,8 @@ "common": { "close": "Schließen", "options": "Optionen", + "source_code": "Quellcode", + "sponsor": "Unterstützen", "delete": "Löschen", "continue": "Weiter", "view_more": "Mehr ansehen", @@ -36,7 +38,8 @@ "invited": " hat eingeladen", "reject_invite": " hat die Einladung abgelehnt ", "requested_join": " hat angefragt dem Raum bei zu tretten ", - "joined": " ist dem Raum beigetreten" + "joined": " ist dem Raum beigetreten", + "left_room": " hat den Raum verlassen " }, "RoomIntro": { "created_by_on": "Erstellt von am {{time}}", @@ -44,6 +47,15 @@ "invite_member": "Nutzer einladen", "open_old_room": "Alten Raum öffnen", "join_old_room": "Altem Raum beitreten" + }, + "MessageContentFallback": { + "message_deleted": "Diese Nachricht wurde gelöscht", + "unsupported_message": "Nicht unterstützte Nachricht", + "failed_to_load_message": "Meldung konnte nicht geladen werden", + "unable_to_decrypt": "Nachricht kann nicht entschlüsselt werden", + "not_decrypted_yet": "Diese Nachricht ist noch nicht entschlüsselt", + "broken_message": "Kaputte Nachricht", + "empty_message": "Leere Nachricht" } }, "Organisms": { @@ -106,6 +118,45 @@ "emoji": "Emoji", "security": "Sicherheit", "about": "Über" + }, + "notifications_and_sound": { + "title": "Benachrichtungen & Töne", + "desktop": { + "title": "Desktop-Benachrichtigungen", + "description": "Bei Eintreffen neuer Nachrichten Desktop-Benachrichtigung anzeigen." + }, + "sound": { + "title": "Benachrichtigungstöne", + "description": "Bei Eintreffen neuer Nachrichten Ton abspielen." + } + }, + "security": { + "cross_signing": { + "title": "Quersignaturen und Sicherung" + }, + "export_import_encryption_keys": { + "title": "Schlüssel exportieren/importieren" + }, + "export_encryption_keys": { + "title": "Raumschlüssel exportieren", + "description": "Raumschlüssel für die Entschlüsselung alter Nachrichten einer anderen Sitzung exportieren. Um Raumschlüssel zu verschlüsseln muss ein Kennwort vergeben werden, das während des Importierens verwendet wird." + }, + "import_encryption_keys": { + "title": "Raumschlüssel importieren", + "description": "Raumschlüssel für die Entschlüsselung alter Nachrichten einer anderen Sitzung importieren. Um Raumschlüssel zu entschlüsseln muss das Kennwort eingegeben werden, das während des Exportierens verwendet wurde." + } + }, + "logout": { + "title": "Abmelden", + "dialog": { + "title": "Abmelden", + "description": "Bist du sicher, dass du dich von dieser Sitzung abmelden möchtest?", + "confirm": "Abmelden" + } + }, + "about": { + "application": "Anwendung", + "credits": "Credits" } }, "SecretStorageAccess": { @@ -195,7 +246,8 @@ "name_set": " hat den Anzeigenamen gewählt", "name_changed": " hat den Anzeigenamen in geändert", "name_removed": " hat den Anzeigenamen entfernt", - "changed_room_name": " hat den Raum Name geändert" + "changed_room_name": " hat den Raum Name geändert", + "new_messages": "Neue Nachrichten" }, "DrawerBreadcrumb": { "home": "Home" @@ -444,5 +496,10 @@ "logout_prompt": "Abmeldung", "clear_cache": "Zwischenspeicher löschen & neuladen" } + }, + "Time": { + "today": "Heute", + "yesterday": "Gestern", + "timeHourMinute": "HH:mm" } } diff --git a/public/locales/en.json b/public/locales/en.json index 671955836..499273005 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -2,6 +2,8 @@ "common": { "close": "Close", "options": "Options", + "source_code": "Source code", + "sponsor": "Support", "delete": "Delete", "continue": "Continue", "view_more": "View more", @@ -36,7 +38,8 @@ "invited": " invited ", "reject_invite": " rejected the invitation ", "requested_join": " request to join room ", - "joined": " joined the room" + "joined": " joined the room", + "left_room": " left the room " }, "RoomIntro": { "created_by_on": "Created by on {{time}}", @@ -44,6 +47,15 @@ "invite_member": "Invite Member", "open_old_room": "Open Old Room", "join_old_room": "Join Old Room" + }, + "MessageContentFallback": { + "message_deleted": "This message has been deleted", + "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" } }, "Organisms": { @@ -106,6 +118,45 @@ "emoji": "Emoji", "security": "Security", "about": "About" + }, + "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" } }, "SecretStorageAccess": { @@ -195,7 +246,8 @@ "name_set": " set their display name to ", "name_changed": " changed their display name to ", "name_removed": " removed their display name ", - "changed_room_name": " changed room name" + "changed_room_name": " changed room name", + "new_messages": "New Messages" }, "DrawerBreadcrumb": { "home": "Home" @@ -444,5 +496,10 @@ "logout_prompt": "Logout", "clear_cache": "Clear cache & reload" } + }, + "Time": { + "today": "Today", + "yesterday": "Yesterday", + "timeHourMinute": "hh:mm A" } } diff --git a/src/app/components/message/MessageContentFallback.tsx b/src/app/components/message/MessageContentFallback.tsx index 9edb96784..9a34e118f 100644 --- a/src/app/components/message/MessageContentFallback.tsx +++ b/src/app/components/message/MessageContentFallback.tsx @@ -1,5 +1,6 @@ import { Box, Icon, Icons, Text, as, color, config } from 'folds'; import React from 'react'; +import { Trans } from 'react-i18next'; const warningStyle = { color: color.Warning.Main, opacity: config.opacity.P300 }; const criticalStyle = { color: color.Critical.Main, opacity: config.opacity.P300 }; @@ -9,9 +10,9 @@ export const MessageDeletedContent = as<'div', { children?: never; reason?: stri {reason ? ( - This message has been deleted. {reason} + . {reason} ) : ( - This message has been deleted + )} ) @@ -20,42 +21,42 @@ export const MessageDeletedContent = as<'div', { children?: never; reason?: stri export const MessageUnsupportedContent = as<'div', { children?: never }>(({ ...props }, ref) => ( - Unsupported message + )); export const MessageFailedContent = as<'div', { children?: never }>(({ ...props }, ref) => ( - Failed to load message + )); export const MessageBadEncryptedContent = as<'div', { children?: never }>(({ ...props }, ref) => ( - Unable to decrypt message + )); export const MessageNotDecryptedContent = as<'div', { children?: never }>(({ ...props }, ref) => ( - This message is not decrypted yet + )); export const MessageBrokenContent = as<'div', { children?: never }>(({ ...props }, ref) => ( - Broken message + )); export const MessageEmptyContent = as<'div', { children?: never }>(({ ...props }, ref) => ( - Empty message + )); diff --git a/src/app/components/message/Time.tsx b/src/app/components/message/Time.tsx index de11cf896..fef28cc45 100644 --- a/src/app/components/message/Time.tsx +++ b/src/app/components/message/Time.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { Text, as } from 'folds'; -import { timeDayMonYear, timeHourMinute, today, yesterday } from '../../utils/time'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import { timeDayMonYear, today, yesterday } from '../../utils/time'; export type TimeProps = { compact?: boolean; @@ -8,15 +10,17 @@ export type TimeProps = { }; export const Time = as<'span', TimeProps>(({ compact, ts, ...props }, ref) => { + const { t } = useTranslation(); + + const timeHourMinute = dayjs(ts).format(t('Time.timeHourMinute')); + let time = ''; - if (compact) { - time = timeHourMinute(ts); - } else if (today(ts)) { - time = timeHourMinute(ts); + if (compact || today(ts)) { + time = timeHourMinute; } else if (yesterday(ts)) { - time = `Yesterday ${timeHourMinute(ts)}`; + time = `${t('Time.yesterday')} ${timeHourMinute}`; } else { - time = `${timeDayMonYear(ts)} ${timeHourMinute(ts)}`; + time = `${timeDayMonYear(ts)} ${timeHourMinute}`; } return ( diff --git a/src/app/components/room-intro/RoomIntro.tsx b/src/app/components/room-intro/RoomIntro.tsx index a54af1965..a9963cf10 100644 --- a/src/app/components/room-intro/RoomIntro.tsx +++ b/src/app/components/room-intro/RoomIntro.tsx @@ -2,6 +2,7 @@ import React, { useCallback } from 'react'; import { Avatar, AvatarFallback, AvatarImage, Box, Button, Spinner, Text, as, color } from 'folds'; import { Room } from 'matrix-js-sdk'; import { useTranslation, Trans } from 'react-i18next'; +import dayjs from 'dayjs'; import { openInviteUser, selectRoom } from '../../../client/action/navigation'; import { useStateEvent } from '../../hooks/useStateEvent'; import { IRoomCreateContent, Membership, StateEvent } from '../../../types/matrix/room'; @@ -9,7 +10,7 @@ import { getMemberDisplayName, getStateEvent } from '../../utils/room'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { getMxIdLocalPart } from '../../utils/matrix'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; -import { timeDayMonthYear, timeHourMinute } from '../../utils/time'; +import { timeDayMonthYear } from '../../utils/time'; export type RoomIntroProps = { room: Room; @@ -69,7 +70,7 @@ export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) => @{creatorName} }} - values={{ time: `${timeDayMonthYear(ts)} ${timeHourMinute(ts)}` }} + values={{ time: `${timeDayMonthYear(ts)} ${dayjs(ts).format(t('Time.timeHourMinute'))}` }} /> )} diff --git a/src/app/hooks/useMemberEventParser.tsx b/src/app/hooks/useMemberEventParser.tsx index fc2c5318d..d5fc7740b 100644 --- a/src/app/hooks/useMemberEventParser.tsx +++ b/src/app/hooks/useMemberEventParser.tsx @@ -152,7 +152,7 @@ export const useMemberEventParser = (): MemberEventParser => { senderId === userId ? ( <> {userName} - {' left the room '} + {t('Components.MemberEvent.left_room')} {content.reason} ) : ( diff --git a/src/app/organisms/room/RoomTimeline.tsx b/src/app/organisms/room/RoomTimeline.tsx index 989d50440..7e2475dd2 100644 --- a/src/app/organisms/room/RoomTimeline.tsx +++ b/src/app/organisms/room/RoomTimeline.tsx @@ -1693,7 +1693,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli - New Messages + {t('Organisms.RoomCommon.new_messages')} @@ -1706,8 +1706,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('Time.today'); + if (yesterday(mEvent.getTs())) return t('Time.yesterday'); return timeDayMonthYear(mEvent.getTs()); })()} diff --git a/src/app/organisms/settings/Settings.jsx b/src/app/organisms/settings/Settings.jsx index a81764723..53f6f88d9 100644 --- a/src/app/organisms/settings/Settings.jsx +++ b/src/app/organisms/settings/Settings.jsx @@ -233,6 +233,8 @@ function NotificationsSection() { const [, updateState] = useState({}); + const { t } = useTranslation(); + const renderOptions = () => { if (window.Notification === undefined) { return Not supported in this browser.; @@ -264,21 +266,21 @@ function NotificationsSection() { return ( <>
- Notification & Sound + {t('Organisms.Settings.notifications_and_sound.title')} Show desktop notification when new messages arrive.} + content={{t('Organisms.Settings.notifications_and_sound.desktop.description')}} /> { toggleNotificationSounds(); updateState({}); }} /> )} - content={Play sound when new messages arrive.} + content={{t('Organisms.Settings.notifications_and_sound.desktop.description')}} />
@@ -298,30 +300,32 @@ function EmojiSection() { } function SecuritySection() { + const { t } = useTranslation(); + return (
- Cross signing and backup + {t('Organisms.Settings.security.cross_signing.title')}
- Export/Import encryption keys + {t('Organisms.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('Organisms.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('Organisms.Settings.security.import_encryption_keys.description')} )} @@ -332,10 +336,12 @@ function SecuritySection() { } function AboutSection() { + const { t } = useTranslation(); + return (
- Application + {t('Organisms.Settings.about.application')}
Cinny logo
@@ -346,15 +352,15 @@ function AboutSection() { Yet another matrix client
- - - + + +
- Credits + {t('Organisms.Settings.about.credits')}
  • @@ -438,9 +444,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('Organisms.Settings.logout.dialog.title'), t('Organisms.Settings.logout.dialog.description'), t('Organisms.Settings.logout.dialog.confirm'), 'danger')) { initMatrix.logout(); } }; @@ -453,9 +461,9 @@ function Settings() { contentOptions={( <> - + )} onRequestClose={requestClose} diff --git a/src/app/utils/time.ts b/src/app/utils/time.ts index 3ee6720c0..862d3937c 100644 --- a/src/app/utils/time.ts +++ b/src/app/utils/time.ts @@ -1,16 +1,20 @@ import dayjs from 'dayjs'; +import 'dayjs/locale/de'; import isToday from 'dayjs/plugin/isToday'; import isYesterday from 'dayjs/plugin/isYesterday'; +import i18next from 'i18next'; dayjs.extend(isToday); dayjs.extend(isYesterday); +i18next.on('languageChanged', (lng) => { + dayjs.locale(lng); +}); + export const today = (ts: number): boolean => dayjs(ts).isToday(); export const yesterday = (ts: number): boolean => dayjs(ts).isYesterday(); -export const timeHourMinute = (ts: number): string => dayjs(ts).format('hh:mm A'); - export const timeDayMonYear = (ts: number): string => dayjs(ts).format('D MMM YYYY'); export const timeDayMonthYear = (ts: number): string => dayjs(ts).format('D MMMM YYYY');