diff --git a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts index 01cb16e37d..8af053fd54 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts @@ -7,7 +7,7 @@ import { type Page } from "@playwright/test"; -import { test, expect } from "../../../element-web-test"; +import { expect, test } from "../../../element-web-test"; test.describe("Room list", () => { test.use({ @@ -144,6 +144,32 @@ test.describe("Room list", () => { }); }); + test.describe("Avatar decoration", () => { + test.use({ labsFlags: ["feature_video_rooms", "feature_new_room_list"] }); + + test("should be a public room", { tag: "@screenshot" }, async ({ page, app, user }) => { + // @ts-ignore Visibility enum is not accessible + await app.client.createRoom({ name: "public room", visibility: "public" }); + const roomListView = getRoomList(page); + const publicRoom = roomListView.getByRole("gridcell", { name: "public room" }); + + await expect(publicRoom).toBeVisible(); + await expect(publicRoom).toMatchScreenshot("room-list-item-public.png"); + }); + + test("should be a video room", { tag: "@screenshot" }, async ({ page, app, user }) => { + await page.getByTestId("room-list-panel").getByRole("button", { name: "Add" }).click(); + await page.getByRole("menuitem", { name: "New video room" }).click(); + await page.getByRole("textbox", { name: "Name" }).fill("video room"); + await page.getByRole("button", { name: "Create video room" }).click(); + + const roomListView = getRoomList(page); + const videoRoom = roomListView.getByRole("gridcell", { name: "video room" }); + await expect(videoRoom).toBeVisible(); + await expect(videoRoom).toMatchScreenshot("room-list-item-video.png"); + }); + }); + test.describe("Notification decoration", () => { test("should render the invitation decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => { const roomListView = getRoomList(page); diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png index 43cae04270..6f2daa3017 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png index 7adeb314fd..2a75d0d91c 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png index b0cab67c66..f4aa4d56a9 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png index 05e2172e24..cfa08c2008 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png index 89a975c309..28d5719733 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png index eab0846f76..60f4916270 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png index f9417d52da..6257aa7af8 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png index d5a2d8a4c0..cad0f83b4a 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png new file mode 100644 index 0000000000..e83fee3ed2 Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png index 35bcbea2a1..72f0c42425 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png new file mode 100644 index 0000000000..41881886f6 Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png index 35de485457..1f7a84c9a6 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png differ diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 4691d71e25..b58a53a325 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -116,6 +116,7 @@ @import "./views/auth/_Welcome.pcss"; @import "./views/avatars/_BaseAvatar.pcss"; @import "./views/avatars/_DecoratedRoomAvatar.pcss"; +@import "./views/avatars/_RoomAvatarView.pcss"; @import "./views/avatars/_WidgetAvatar.pcss"; @import "./views/avatars/_WithPresenceIndicator.pcss"; @import "./views/beta/_BetaCard.pcss"; diff --git a/res/css/views/avatars/_RoomAvatarView.pcss b/res/css/views/avatars/_RoomAvatarView.pcss new file mode 100644 index 0000000000..0d5523a9f1 --- /dev/null +++ b/res/css/views/avatars/_RoomAvatarView.pcss @@ -0,0 +1,48 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +.mx_RoomAvatarView { + --room-avatar-size: 32px; + + position: relative; + + /* Keep the container to the same size than the avatar */ + inline-size: var(--room-avatar-size); + block-size: var(--room-avatar-size); + + .mx_RoomAvatarView_RoomAvatar { + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + } + + .mx_RoomAvatarView_RoomAvatar_icon { + mask-image: url("$(res)/img/element-icons/roomlist/room-avatar-view-icon-mask.svg"); + } + + .mx_RoomAvatarView_RoomAvatar_presence { + mask-image: url("$(res)/img/element-icons/roomlist/room-avatar-view-presence-mask.svg"); + } + + .mx_RoomAvatarView_icon { + position: absolute; + + /* Place half the icon inside the avatar */ + /* Avatar size - (icon size (16px) / 2) */ + left: calc((var(--room-avatar-size) - 8px)); + bottom: var(--cpd-space-0-5x); + } + + .mx_RoomAvatarView_PresenceDecoration { + position: absolute; + + /* Place half the icon inside the avatar */ + /* Avatar size - (icon size (8px) / 2) */ + left: calc((var(--room-avatar-size) - 4px)); + bottom: var(--cpd-space-0-5x); + } +} diff --git a/res/img/element-icons/roomlist/room-avatar-view-icon-mask.svg b/res/img/element-icons/roomlist/room-avatar-view-icon-mask.svg new file mode 100644 index 0000000000..8eaf2f1831 --- /dev/null +++ b/res/img/element-icons/roomlist/room-avatar-view-icon-mask.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/roomlist/room-avatar-view-presence-mask.svg b/res/img/element-icons/roomlist/room-avatar-view-presence-mask.svg new file mode 100644 index 0000000000..b40f382d4b --- /dev/null +++ b/res/img/element-icons/roomlist/room-avatar-view-presence-mask.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx b/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx new file mode 100644 index 0000000000..784184e159 --- /dev/null +++ b/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx @@ -0,0 +1,139 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import { + EventType, + JoinRule, + type MatrixEvent, + type Room, + RoomEvent, + type User, + UserEvent, +} from "matrix-js-sdk/src/matrix"; +import { useState } from "react"; + +import { useTypedEventEmitter } from "../../../hooks/useEventEmitter"; +import DMRoomMap from "../../../utils/DMRoomMap"; +import { getJoinedNonFunctionalMembers } from "../../../utils/room/getJoinedNonFunctionalMembers"; +import { BUSY_PRESENCE_NAME } from "../../views/rooms/PresenceLabel"; +import { isPresenceEnabled } from "../../../utils/presence"; + +/** + * The presence of a user in a DM room. + * - "online": The user is online. + * - "offline": The user is offline. + * - "busy": The user is busy. + * - "unavailable": the presence is unavailable. + * - null: the user is not in a DM room or presence is not enabled. + */ +export type Presence = "online" | "offline" | "busy" | "unavailable" | null; + +export interface RoomAvatarViewState { + /** + * Whether the room avatar has a decoration. + * A decoration can be a public or a video call icon or an indicator of presence. + */ + hasDecoration: boolean; + /** + * Whether the room is public. + */ + isPublic: boolean; + /** + * Whether the room is a video room. + */ + isVideoRoom: boolean; + /** + * The presence of the user in the DM room. + * If null, the user is not in a DM room or presence is not enabled. + */ + presence: Presence; +} + +/** + * Hook to get the state of the room avatar. + * @param room + */ +export function useRoomAvatarViewModel(room: Room): RoomAvatarViewState { + const isVideoRoom = room.isElementVideoRoom() || room.isCallRoom(); + const presence = useDMPresence(room); + const isPublic = useIsPublic(room); + + const hasDecoration = isPublic || isVideoRoom || presence !== null; + + return { hasDecoration, isPublic, isVideoRoom, presence }; +} + +/** + * Hook listening to the room join rules. + * Return true if the room is public. + * @param room + */ +function useIsPublic(room: Room): boolean { + const [isPublic, setIsPublic] = useState(isRoomPublic(room)); + // We don't use `useTypedEventEmitterState` because we don't want to update `isPublic` value at every `RoomEvent.Timeline` event. + useTypedEventEmitter(room, RoomEvent.Timeline, (ev: MatrixEvent, _room: Room) => { + if (room.roomId !== _room.roomId) return; + if (ev.getType() !== EventType.RoomJoinRules && ev.getType() !== EventType.RoomMember) return; + + setIsPublic(isRoomPublic(_room)); + }); + + return isPublic; +} + +/** + * Whether the room is public. + * @param room + */ +function isRoomPublic(room: Room): boolean { + return room.getJoinRule() === JoinRule.Public; +} + +/** + * Hook listening to the presence of the DM user. + * @param room + */ +function useDMPresence(room: Room): Presence { + const dmUser = getDMUser(room); + const [presence, setPresence] = useState(getPresence(dmUser)); + useTypedEventEmitter(dmUser, UserEvent.Presence, () => setPresence(getPresence(dmUser))); + useTypedEventEmitter(dmUser, UserEvent.CurrentlyActive, () => setPresence(getPresence(dmUser))); + + return presence; +} + +/** + * Get the DM user of the room. + * Return undefined if the room is not a DM room, if we can't find the user or if the presence is not enabled. + * @param room + * @returns found user + */ +function getDMUser(room: Room): User | undefined { + const otherUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId); + if (!otherUserId) return; + if (getJoinedNonFunctionalMembers(room).length !== 2) return; + if (!isPresenceEnabled(room.client)) return; + + return room.client.getUser(otherUserId) || undefined; +} + +/** + * Get the presence of the DM user. + * @param dmUser + */ +function getPresence(dmUser: User | undefined): Presence { + if (!dmUser) return null; + if (BUSY_PRESENCE_NAME.matches(dmUser.presence)) return "busy"; + + const isOnline = dmUser.currentlyActive || dmUser.presence === "online"; + if (isOnline) return "online"; + + if (dmUser.presence === "offline") return "offline"; + if (dmUser.presence === "unavailable") return "unavailable"; + + return null; +} diff --git a/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx b/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx index 8a1fdb1fe7..66b44e0086 100644 --- a/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx +++ b/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx @@ -128,8 +128,8 @@ export function useRoomListHeaderViewModel(): RoomListHeaderViewState { const isSpaceRoom = Boolean(activeSpace); const canCreateRoom = hasCreateRoomRights(matrixClient, activeSpace); - const canCreateVideoRoom = useFeatureEnabled("feature_video_rooms"); - const displayComposeMenu = canCreateRoom || canCreateVideoRoom; + const canCreateVideoRoom = useFeatureEnabled("feature_video_rooms") && canCreateRoom; + const displayComposeMenu = canCreateRoom; const displaySpaceMenu = isSpaceRoom; const canInviteInSpace = Boolean( activeSpace?.getJoinRule() === JoinRule.Public || activeSpace?.canInvite(matrixClient.getSafeUserId()), diff --git a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx index d15ad0a42b..ad6a271e57 100644 --- a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx +++ b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx @@ -18,6 +18,8 @@ import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNo import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; import { useEventEmitterState } from "../../../hooks/useEventEmitter"; import { DefaultTagID } from "../../../stores/room-list/models"; +import { useCall, useConnectionState, useParticipantCount } from "../../../hooks/useCall"; +import { type ConnectionState } from "../../../models/Call"; export interface RoomListItemViewState { /** @@ -40,6 +42,19 @@ export interface RoomListItemViewState { * Whether the room should be bolded. */ isBold: boolean; + /** + * Whether the room is a video room + */ + isVideoRoom: boolean; + /** + * The connection state of the call. + * `null` if there is no call in the room. + */ + callConnectionState: ConnectionState | null; + /** + * Whether there are participants in the call. + */ + hasParticipantInCall: boolean; } /** @@ -57,6 +72,14 @@ export function useRoomListItemViewModel(room: Room): RoomListItemViewState { const a11yLabel = getA11yLabel(room, notificationState); const isBold = notificationState.hasAnyNotificationOrActivity; + // Video room + const isVideoRoom = room.isElementVideoRoom() || room.isCallRoom(); + // EC video call or video room + const call = useCall(room.roomId); + const connectionState = useConnectionState(call); + const hasParticipantInCall = useParticipantCount(call) > 0; + const callConnectionState = call ? connectionState : null; + // Actions const openRoom = useCallback((): void => { @@ -73,6 +96,9 @@ export function useRoomListItemViewModel(room: Room): RoomListItemViewState { openRoom, a11yLabel, isBold, + isVideoRoom, + callConnectionState, + hasParticipantInCall, }; } diff --git a/src/components/views/avatars/DecoratedRoomAvatar.tsx b/src/components/views/avatars/DecoratedRoomAvatar.tsx index c4b17aea53..321c0501dc 100644 --- a/src/components/views/avatars/DecoratedRoomAvatar.tsx +++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx @@ -79,6 +79,9 @@ function tooltipText(variant: Icon): string | undefined { } } +/** + * @deprecated Use {@link RoomAvatarView} instead. + */ export default class DecoratedRoomAvatar extends React.PureComponent { private _dmUser: User | null = null; private isUnmounted = false; diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx index 9ecef35635..137ac5de7b 100644 --- a/src/components/views/avatars/RoomAvatar.tsx +++ b/src/components/views/avatars/RoomAvatar.tsx @@ -49,7 +49,7 @@ export function idNameForRoom(room: Room): string { return room.roomId; } -const RoomAvatar: React.FC = ({ room, viewAvatarOnClick, onClick, className, oobData, ...otherProps }) => { +const RoomAvatar: React.FC = ({ room, viewAvatarOnClick, onClick, oobData, ...otherProps }) => { const size = otherProps.size ?? "36px"; const roomName = room?.name ?? oobData?.name ?? "?"; diff --git a/src/components/views/avatars/RoomAvatarView.tsx b/src/components/views/avatars/RoomAvatarView.tsx new file mode 100644 index 0000000000..96bfbaf198 --- /dev/null +++ b/src/components/views/avatars/RoomAvatarView.tsx @@ -0,0 +1,127 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import React, { type JSX } from "react"; +import { type Room } from "matrix-js-sdk/src/matrix"; +import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public"; +import VideoIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid"; +import OnlineOrUnavailableIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-solid-8x8"; +import OfflineIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-outline-8x8"; +import BusyIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-strikethrough-8x8"; +import classNames from "classnames"; + +import RoomAvatar from "./RoomAvatar"; +import { useRoomAvatarViewModel, type Presence } from "../../viewmodels/avatars/RoomAvatarViewModel"; +import { _t } from "../../../languageHandler"; + +interface RoomAvatarViewProps { + /** + * The room to display the avatar for. + */ + room: Room; +} + +/** + * Component to display the avatar of a room. + * Currently only 32px size is supported. + */ +export function RoomAvatarView({ room }: RoomAvatarViewProps): JSX.Element { + const vm = useRoomAvatarViewModel(room); + // No decoration, we just show the avatar + if (!vm.hasDecoration) return ; + + return ( +
+ + + {/* If the room is a public video room, we prefer to display only the video icon */} + {vm.isPublic && !vm.isVideoRoom && ( + + )} + {vm.isVideoRoom && ( + + )} + {vm.presence && } +
+ ); +} + +type PresenceDecorationProps = { + /** + * The presence of the user in the DM room. + */ + presence: NonNullable; +}; + +/** + * Component to display the presence of a user in a DM room. + */ +function PresenceDecoration({ presence }: PresenceDecorationProps): JSX.Element { + switch (presence) { + case "online": + return ( + + ); + case "unavailable": + return ( + + ); + case "offline": + return ( + + ); + case "busy": + return ( + + ); + } +} diff --git a/src/components/views/rooms/NotificationDecoration.tsx b/src/components/views/rooms/NotificationDecoration.tsx index 3266445782..cfb82c461c 100644 --- a/src/components/views/rooms/NotificationDecoration.tsx +++ b/src/components/views/rooms/NotificationDecoration.tsx @@ -9,6 +9,7 @@ import React, { type HTMLProps, type JSX } from "react"; import MentionIcon from "@vector-im/compound-design-tokens/assets/web/icons/mention"; import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid"; import NotificationOffIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications-off-solid"; +import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid"; import { UnreadCounter, Unread } from "@vector-im/compound-web"; import { Flex } from "../../utils/Flex"; @@ -19,6 +20,10 @@ interface NotificationDecorationProps extends HTMLProps { * The notification state of the room or thread. */ notificationState: RoomNotificationState; + /** + * Whether the room has a video call. + */ + hasVideoCall: boolean; } /** @@ -26,6 +31,7 @@ interface NotificationDecorationProps extends HTMLProps { */ export function NotificationDecoration({ notificationState, + hasVideoCall, ...props }: NotificationDecorationProps): JSX.Element | null { const { @@ -38,7 +44,7 @@ export function NotificationDecoration({ count, muted, } = notificationState; - if (!hasAnyNotificationOrActivity && !muted) return null; + if (!hasAnyNotificationOrActivity && !muted && !hasVideoCall) return null; return ( {isUnsetMessage && } + {hasVideoCall && } {invited && } {isMention && } {(isMention || isNotification) && } diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx index c0b204fc01..89a353afb1 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx @@ -10,10 +10,10 @@ import { type Room } from "matrix-js-sdk/src/matrix"; import classNames from "classnames"; import { useRoomListItemViewModel } from "../../../viewmodels/roomlist/RoomListItemViewModel"; -import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar"; import { Flex } from "../../../utils/Flex"; import { RoomListItemMenuView } from "./RoomListItemMenuView"; import { NotificationDecoration } from "../NotificationDecoration"; +import { RoomAvatarView } from "../../avatars/RoomAvatarView"; interface RoomListItemViewPropsProps extends React.HTMLAttributes { /** @@ -39,7 +39,8 @@ export function RoomListItemView({ room, isSelected, ...props }: RoomListItemVie const showHoverDecoration = (isMenuOpen || isHover) && vm.showHoverMenu; const isNotificationDecorationVisible = - !showHoverDecoration && (vm.notificationState.hasAnyNotificationOrActivity || vm.notificationState.muted); + !showHoverDecoration && + (vm.notificationState.hasAnyNotificationOrActivity || vm.notificationState.muted || vm.hasParticipantInCall); return ( .", + "enter_phrase_or_key_prompt": "Jätkamiseks sisesta oma turvafraas või .", "key_validation_text": { - "invalid_security_key": "Vigane turvavõti", + "invalid_security_key": "Vigane taastevõti", "recovery_key_is_correct": "Tundub õige!", "wrong_file_type": "Vale failitüüp", - "wrong_security_key": "Vale turvavõti" + "wrong_security_key": "Vale taastevõti" }, "reset_title": "Alusta kõigega algusest", "reset_warning_1": "Toimi nii vaid siis, kui sul pole jäänud ühtegi seadet, millega verifitseerimist lõpuni teha.", "reset_warning_2": "Kui sa kõik krüptoseosed lähtestad, siis sul esimese hooga pole ühtegi usaldusväärseks tunnistatud sessiooni ega kasutajat ning ilmselt ei saa sa lugeda vanu sõnumeid.", "restoring": "Taastan võtmed varundusest", - "security_key_title": "Turvavõti", + "security_key_title": "Taastevõti", "security_phrase_incorrect_error": "Ei õnnestu saada ligipääsu turvahoidlale. Palun kontrolli, et sa oleksid sisestanud õige turvafraasi.", "security_phrase_title": "Turvafraas", "separator": "%(securityKey)s või %(recoveryFile)s", - "use_security_key_prompt": "Jätkamiseks kasuta turvavõtit." + "use_security_key_prompt": "Jätkamiseks kasuta oma taastevõtit." }, "bootstrap_title": "Võtame krüptovõtmed kasutusele", "cancel_entering_passphrase_description": "Kas oled kindel et sa soovid katkestada paroolifraasi sisestamise?", @@ -900,6 +936,7 @@ "cross_signing_user_normal": "Sa ei ole seda kasutajat verifitseerinud.", "cross_signing_user_verified": "Sa oled selle kasutaja verifitseerinud. See kasutaja on verifitseerinud kõik nende sessioonid.", "cross_signing_user_warning": "See kasutaja ei ole verifitseerinud kõiki oma sessioone.", + "enter_recovery_key": "Sisesta taastevõti", "event_shield_reason_authenticity_not_guaranteed": "Selle krüptitud sõnumi autentsus pole selles seadmes tagatud.", "event_shield_reason_mismatched_sender_key": "Krüptitud verifitseerimata sessiooni poolt", "event_shield_reason_unknown_device": "Krüptitud tundmatu või kustutatud seadme poolt.", @@ -925,8 +962,8 @@ "title": "Uus taastamise meetod", "warning": "Kui sa ei ole ise uusi taastamise meetodeid lisanud, siis võib olla tegemist ründega sinu konto vastu. Palun vaheta koheselt oma kasutajakonto salasõna ning määra seadistustes uus taastemeetod." }, - "pinned_identity_changed": "Kasutaja %(displayName)s (%(userId)s) võrguidentiteet tundub olema muutunud. Lisateave", - "pinned_identity_changed_no_displayname": "Kasutaja %(userId)s võrguidentiteet tundub olema muutunud. Lisateave", + "pinned_identity_changed": "Kasutaja %(displayName)s (%(userId)s) võrguidentiteet on lähtestatud. Lisateave", + "pinned_identity_changed_no_displayname": "Kasutaja %(userId)s võrguidentiteet on lähtestatud. Lisateave", "recovery_method_removed": { "description_1": "Oleme tuvastanud, et selles sessioonis ei leidu turvafraasi ega krüptitud sõnumite turvavõtit.", "description_2": "Kui sa tegid seda juhuslikult, siis sa võid selles sessioonis uuesti seadistada sõnumite krüptimise, mille tulemusel krüptime uuesti kõik sõnumid ja loome uue taastamise meetodi.", @@ -977,7 +1014,7 @@ "incoming_sas_dialog_waiting": "Ootan teise osapoole kinnitust…", "incoming_sas_user_dialog_text_1": "Selle kasutaja usaldamiseks peaksid ta verifitseerima. Kui sa pruugid läbivalt krüptitud sõnumeid, siis kasutajate verifitseerimine tagab sulle täiendava meelerahu.", "incoming_sas_user_dialog_text_2": "Selle kasutaja verifitseerimisel märgitakse tema sessioon usaldusväärseks ning samuti märgitakse sinu sessioon tema jaoks usaldusväärseks.", - "no_key_or_device": "Tundub, et sul ei ole ei turvavõtit ega muid seadmeid, mida saaksid verifitseerimiseks kasutada. Siin seadmes ei saa lugeda vanu krüptitud sõnumeid. Enda tuvastamiseks selles seadmed pead oma vanad verifitseerimisvõtmed kustutama.", + "no_key_or_device": "Tundub, et sul ei ole ei taastevõtit ega muid seadmeid, mida saaksid verifitseerimiseks kasutada. Siin seadmes ei saa lugeda vanu krüptitud sõnumeid. Enda tuvastamiseks selles seadmes pead oma vanad verifitseerimisvõtmed kustutama.", "no_support_qr_emoji": "See seade, mida sa tahad verifitseerida ei toeta QR-koodi ega emoji-põhist verifitseerimist, aga just neid %(brand)s oskab kasutada. Proovi mõne muu Matrix'i kliendiga.", "other_party_cancelled": "Teine osapool tühistas verifitseerimise.", "prompt_encrypted": "Tagamaks, et jututuba on turvaline, verifitseeri kõik selle kasutajad.", @@ -1027,18 +1064,21 @@ "verify_emoji_prompt_qr": "Kui sa ei saa skaneerida eespool kuvatud koodi, siis verifitseeri unikaalsete emoji'de võrdlemise teel.", "verify_later": "Ma verifitseerin hiljem", "verify_reset_warning_1": "Verifitseerimisvõtmete kustutamist ei saa hiljem tagasi võtta. Peale seda sul puudub ligipääs vanadele krüptitud sõnumitele ja kõik sinu verifitseeritud sõbrad-tuttavad näevad turvahoiatusi seni kuni sa uuesti nad verifitseerid.", - "verify_reset_warning_2": "Palun jätka ainult siis, kui sa oled kaotanud ligipääsu kõikidele oma seadmetele ning oma turvavõtmele.", + "verify_reset_warning_2": "Palun jätka ainult siis, kui sa oled kaotanud ligipääsu oma kõikidele muudele seadmetele ning oma taastevõtmele.", "verify_using_device": "Verifitseeri teise seadmega", - "verify_using_key": "Verifitseeri turvavõtmega", - "verify_using_key_or_phrase": "Verifitseeri turvavõtme või turvafraasiga", + "verify_using_key": "Verifitseeri taastevõtmega", + "verify_using_key_or_phrase": "Verifitseeri taastevõtme või -fraasiga", "waiting_for_user_accept": "Ootan, et %(displayName)s nõustuks…", "waiting_other_device": "Ootan, et sa verifitseeriksid oma teises seadmes…", "waiting_other_device_details": "Ootan, et sa verifitseerid oma teises seadmes: %(deviceName)s (%(deviceId)s)…", "waiting_other_user": "Ootan kasutaja %(displayName)s verifitseerimist…" }, "verification_requested_toast_title": "Verifitseerimistaotlus on saadetud", + "verified_identity_changed": "Kasutaja %(displayName)s (%(userId)s) verifitseeritud identiteet on muutunud. Lisateave", + "verified_identity_changed_no_displayname": "Kasutaja %(userId)s verifitseeritud identiteet on muutunud. Lisateave", "verify_toast_description": "Teised kasutajad ei pruugi seda usaldada", - "verify_toast_title": "Verifitseeri see sessioon" + "verify_toast_title": "Verifitseeri see sessioon", + "withdraw_verification_action": "Eemalda verifitseerimine" }, "error": { "admin_contact": "Jätkamaks selle teenuse kasutamist palun võta ühendust oma teenuse haldajaga.", @@ -1222,6 +1262,7 @@ "change": "Muuda isikutuvastusserverit", "change_prompt": "Kas katkestame ühenduse isikutuvastusserveriga ning selle asemel loome uue ühenduse serveriga ?", "change_server_prompt": "Kui sa ei soovi kasutada serverit, et olla leitav ja ise leida sinule teadaolevaid inimesi, siis sisesta alljärgnevalt mõni teine isikutuvastusserver.", + "changed": "Sinu kasutatav isikutuvastusserver on muutunud", "checking": "Kontrollin serverit", "description_connected": "Sa hetkel kasutad serverit, et olla leitav ja ise leida sinule teadaolevaid inimesi. Alljärgnevalt saad sa muuta oma isikutuvastusserverit.", "description_disconnected": "Sa hetkel ei kasuta isikutuvastusserverit. Et olla leitav ja ise leida sinule teadaolevaid inimesi seadista ta alljärgnevalt.", @@ -1264,7 +1305,9 @@ "title": "Sellele brauserile puudub tugi", "use_desktop_heading": "Selle asemel kasuta %(brand)s töölauaversiooni", "use_mobile_heading": "Selle asemel kasuta %(brand)s nutiseadmeversiooni", - "use_mobile_heading_after_desktop": "Või kasuta meie rakendust nutiseadmetele" + "use_mobile_heading_after_desktop": "Või kasuta meie rakendust nutiseadmetele", + "windows_64bit": "Windows (64-bitine)", + "windows_arm_64bit": "Windows (64-bitine ARM-platvormil)" }, "info_tooltip_title": "Teave", "integration_manager": { @@ -1273,6 +1316,7 @@ "error_connecting_heading": "Ei saa ühendust lõiminguhalduriga", "explainer": "Lõiminguhalduritel on laiad volitused - nad võivad sinu nimel lugeda seadistusi, kohandada vidinaid, saata jututubade kutseid ning määrata õigusi.", "manage_title": "Halda lõiminguid", + "toggle_label": "Kasuta lõimingute haldurit", "use_im": "Robotite, vidinate ja kleepsupakkide seadistamiseks kasuta lõiminguhaldurit.", "use_im_default": "Robotite, vidinate ja kleepsupakkide jaoks kasuta lõiminguhaldurit (%(serverName)s)." }, @@ -1474,6 +1518,7 @@ "location_share_live_description": "Tegemist on ajutise ja esialgse lahendusega: asukohad on jututoa ajaloos näha.", "mjolnir": "Uued võimalused osalejate eiramiseks", "msc3531_hide_messages_pending_moderation": "Luba modereerimist ootavate sõnumite peitmist.", + "new_room_list": "Võta kasutusele uus jututubade loend", "notification_settings": "Uued teavituste seadistused", "notification_settings_beta_caption": "Võtame kasutusele senisest lihtsama viisi teavituste seadistamiseks. Kohanda rakendust %(brand)s nii nagu soovid.", "notification_settings_beta_title": "Teavituste seadistused", @@ -1627,6 +1672,7 @@ "class_global": "Üldised", "class_other": "Muud", "default": "Tavaline", + "default_settings": "Sobita vaikimisi seadistustega", "email_pusher_app_display_name": "E-posti teel saadetavad teavitused", "enable_prompt_toast_description": "Võta kasutusele töölauakeskkonna teavitused", "enable_prompt_toast_title": "Teavitused", @@ -1645,7 +1691,8 @@ "mentions_and_keywords_description": "Soovin teavitusi sellisena mainimiste ja võtmesõnade puhul, nagu ma neid olen seadistanud", "mentions_keywords": "Mainimised ja märksõnad", "message_didnt_send": "Sõnum jäi saatmata. Lisateabe saamiseks klõpsi.", - "mute_description": "Sa ei saa üldse teavitusi" + "mute_description": "Sa ei saa üldse teavitusi", + "mute_room": "Summuta jututuba" }, "notifier": { "m.key.verification.request": "%(name)s soovib verifitseerimist" @@ -1769,27 +1816,31 @@ "spam_or_propaganda": "Spämm või propaganda", "toxic_behaviour": "Ebasobilik käitumine" }, + "report_room": { + "description": "Teata sellest jututoast oma koduserveri haldajale. Kui sõnumid on krüptitud, ei saa haldaja neid lugeda ega jagatud faile vaadata.", + "reason_label": "Palun kirjelda põhjust" + }, "restore_key_backup_dialog": { "count_of_decryption_failures": "%(failedCount)s sessiooni dekrüptimine ei õnnestunud!", "count_of_successfully_restored_keys": "%(sessionCount)s sessiooni võtme taastamine õnnestus", - "enter_key_description": "Sisestades turvavõtme pääsed ligi oma turvatud sõnumitele ning sätid tööle krüptitud sõnumivahetuse.", - "enter_key_title": "Sisesta turvavõti", + "enter_key_description": "Sisestades taastevõtme pääsed ligi oma turvatud sõnumitele ning sätid tööle krüptitud sõnumivahetuse.", + "enter_key_title": "Sisesta taastevõti", "enter_phrase_description": "Sisestades turvafraasi, saad ligipääsu oma turvatud sõnumitele ning sätid toimima krüptitud sõnumivahetuse.", "enter_phrase_title": "Sisesta turvafraas", "incorrect_security_phrase_dialog": "Selle turvafraasiga ei õnnestunud varundust dekrüptida: palun kontrolli, kas sa kasutad õiget turvafraasi.", "incorrect_security_phrase_title": "Vigane turvafraas", "key_backup_warning": "Hoiatus: sa peaksid võtmete varunduse seadistama vaid usaldusväärsest arvutist.", "key_fetch_in_progress": "Laadin serverist võtmeid…", - "key_forgotten_text": "Kui sa oled unustanud oma turvavõtme, siis sa võid ", - "key_is_invalid": "Vigane turvavõti", - "key_is_valid": "See tundub olema õige turvavõti!", + "key_forgotten_text": "Kui sa oled unustanud oma taastevõtme, siis sa võid ", + "key_is_invalid": "See pole korrektne taastevõti", + "key_is_valid": "See tundub olema õige taastevõti!", "keys_restored_title": "Krüptimise võtmed on taastatud", "load_error_content": "Varunduse oleku laadimine ei õnnestunud", "load_keys_progress": "%(completed)s / %(total)s võtit taastatud", "no_backup_error": "Varukoopiat ei leidunud!", - "phrase_forgotten_text": "Kui sa oled unustanud turvafraasi, siis sa saad kasutada oma turvavõtit või seadistada uued taastamise võimalused", - "recovery_key_mismatch_description": "Selle turvavõtmega ei õnnestunud varundust dekrüptida: palun kontrolli, kas sa kasutad õiget turvavõtit.", - "recovery_key_mismatch_title": "Turvavõtmed ei klapi", + "phrase_forgotten_text": "Kui sa oled unustanud turvafraasi, siis sa saad kasutada oma taastevõtit või seadistada uued taastamise võimalused", + "recovery_key_mismatch_description": "Selle taastevõtmega ei õnnestunud varukoopiat dekrüptida: palun kontrolli, kas sa kasutad õiget taastevõtit.", + "recovery_key_mismatch_title": "Taastevõtmed ei klapi", "restore_failed_error": "Varukoopiast taastamine ei õnnestu" }, "right_panel": { @@ -2055,20 +2106,54 @@ "add_space_label": "Lisa kogukonnakeskus", "breadcrumbs_empty": "Hiljuti külastatud jututubasid ei leidu", "breadcrumbs_label": "Hiljuti külastatud jututoad", + "empty": { + "no_chats": "Vestlusi veel ei leidu", + "no_chats_description": "Alusta sellest, et leia mõni vestluspartner või loo oma jututuba", + "no_chats_description_no_room_rights": "Alusta sellest, et leia mõni vestluspartner", + "no_favourites": "Sa pole veel ühtegi vestlust märkinud lemmikuks", + "no_favourites_description": "Vestluse saad märkida lemmikuks tema seadistustest", + "no_people": "Sul pole veel ühtegi otsevestlust kellegagi", + "no_people_description": "Kõikide muude vestluste nägemiseks eemalda otsingufiltrid", + "no_rooms": "Sa veel ei osale mitte üheski jututoas", + "no_rooms_description": "Kõikide oma muude vestluste nägemiseks eemalda otsingufiltrid", + "no_unread": "Õnnitlused! Sul pole ühtegi lugemata sõnumit", + "show_chats": "Näita kõiki vestlusi" + }, "failed_add_tag": "Sildi %(tagName)s lisamine jututoale ebaõnnestus", "failed_remove_tag": "Sildi %(tagName)s eemaldamine jututoast ebaõnnestus", "failed_set_dm_tag": "Otsevestluse sildi seadmine ei õnnestunud", + "filters": { + "favourite": "Lemmikud", + "people": "Inimesed", + "rooms": "Jututoad", + "unread": "Lugemata" + }, "home_menu_label": "Avalehe valikud", "join_public_room_label": "Liitu avaliku jututoaga", "joining_rooms_status": { "other": "Parasjagu liitun %(count)s jututoaga", "one": "Parasjagu liitun %(count)s jututoaga" }, + "list_title": "Jututubade loend", + "more_options": { + "copy_link": "Kopeeri jututoa link", + "favourited": "Määratud lemmikuks", + "leave_room": "Lahku jututoast", + "low_priority": "Vähetähtis", + "mark_read": "Märgi loetuks", + "mark_unread": "Märgi mitteloetuks" + }, "notification_options": "Teavituste eelistused", + "open_space_menu": "Ava kogukonna menüü", + "primary_filters": "Jututubade loendi filtrid", "redacting_messages_status": { "other": "Kustutame sõnumeid %(count)s jututoas", "one": "Kustutame sõnumeid %(count)s jututoas" }, + "room": { + "more_options": "Täiendavad seadistused", + "open_room": "Ava jututuba: %(roomName)s" + }, "show_less": "Näita vähem", "show_n_more": { "one": "Näita veel %(count)s vestlust", @@ -2079,6 +2164,10 @@ "sort_by_activity": "Aktiivsuse alusel", "sort_by_alphabet": "Tähestiku järjekorras", "sort_unread_first": "Näita lugemata sõnumitega jututubasid esimesena", + "space_menu": { + "home": "Kogukonna avaleht", + "space_settings": "Kogukonna seadistused" + }, "space_menu_label": "%(spaceName)s menüü", "sublist_options": "Loendi valikud", "suggested_rooms_heading": "Soovitatud jututoad" @@ -2307,7 +2396,7 @@ "public_without_alias_warning": "Sellele jututoale viitamiseks palun lisa talle aadress.", "publish_room": "Tee see jututuba nähtavaks avalikus jututubade kataloogis.", "publish_space": "Tee see kogukond nähtavaks avalikus jututubade kataloogis.", - "strict_encryption": "Ära iialgi saada sellest sessioonist krüptitud sõnumeid verifitseerimata sessioonidesse selles jututoas", + "strict_encryption": "Saada sõnumeid vaid verifitseeritud kasutajatele.", "title": "Turvalisus ja privaatsus" }, "title": "Jututoa seadistused - %(roomName)s", @@ -2438,35 +2527,60 @@ "breadcrumb_second_description": "Sa kaotad ligipääsu sõnumite ajalooole, mis on salvestatud vaid serveris", "breadcrumb_third_description": "Sa pead kõik oma olemasolevad seadmed ja kontaktid uuesti verifitseerima", "breadcrumb_title": "Kas sa oled kindel, et soovid oma krüptoidentiteeti lähtestada?", + "breadcrumb_title_forgot": "Kas unustasid oma taastevõtme? Pead oma identiteedi lähtestama.", + "breadcrumb_title_sync_failed": "Võtmehoidla sünkroniseerimine ei õnnestunud. Sa pead võrguidentiteedi lähtestama.", "breadcrumb_warning": "Tee seda ainult siis, kui arvad, et sinu kasutajakonto võib olla ohustatud kolmandate osapoolet poolt.", "details_title": "Krüptimise üksikasjad", + "do_not_close_warning": "Ära sulge seda akent enne, kui lähtestamine on lõppenud", "export_keys": "Ekspordi võtmed", "import_keys": "Impordi võtmed", - "other_people_device_description": "Vaikimisi ei saadeta krüptitud jututubadest sõnumeid verifitseerimata kasutajatele", - "other_people_device_label": "Ära iialgi saada krüptitud sõnumeid verifitseerimata seadmetesse", + "other_people_device_description": "Hoiatus: kui kasutaja pole sinuga verifitseerimist läbi teinud (näiteks emojide võrdlemise abil), siis ta ei saa sinu krüptitud sõnumeid. Lisaks ei saadeta krüptitud sõnumeid verifitseeritud kasutajate verifitseerimata seadmetesse.", + "other_people_device_label": "Krüptitud jututubades saada sõnumeid vaid verifitseeritud kasutajatele", "other_people_device_title": "Teiste kasutajate seadmed", "reset_identity": "Lähtesta krüptoidentiteet", + "reset_in_progress": "Lähtestamine on töös...", "session_id": "Sessiooni tunnus:", "session_key": "Sessioonivõti:", "title": "Täiendav teave" }, + "delete_key_storage": { + "breadcrumb_page": "Kustuta krüptovõtmete hoidla", + "confirm": "Kustuta krüptovõtmete hoidla", + "description": "Krüptovõtmete hoidla hoidla kustutamisega eemaldad serverist oma krüptoidentiteedi ja sõnumite võtmed ning lülitad välja järgnevad turvalisusega seotud funktsionaalsused:", + "list_first": "Sa ei saa uutes seadmetes lugeda varasemaid krüptitud sõnumeid", + "list_second": "Kui logid kõikjal %(brand)s rakendusest välja, siis kaotad ligipääsu kõikidele oma krüptitud sõnumitele", + "title": "Kas sa oled kindel, et soovid krüptovõtmete hoidla välja lülitada ning seejärel kustutada?" + }, "device_not_verified_button": "Verifitseeri see seade", "device_not_verified_description": "Oma krüptoseadistuste nägemiseks palun verifitseeri see seade.", "device_not_verified_title": "Seade on verifitseerimata", "dialog_title": "Seadistused:Krüptimine", + "key_storage": { + "allow_key_storage": "Kasuta krüptovõtmete hoidlat", + "description": "Hoia oma krüptoidentiteeti ja sõnumite krüptovõtmeid turvaliselt serveris. See võimaldab sul lugeda oma varasemaid sõnumeid kõikides uutes seadmetes. Lisateave", + "title": "Krüptovõtmete hoidla" + }, "recovery": { + "change_recovery_confirm_button": "Korda uut taastevõtit", "change_recovery_confirm_description": "Toimingu lõpetamiseks palun sisesta alljärgnevalt oma uus taastevõti. Senine taastevõti enam ei toimi.", + "change_recovery_confirm_title": "Sisesta oma uus taastevõti", + "change_recovery_key": "Muuda taastevõtit", "change_recovery_key_description": "Palun salvesta see taastevõti turvalisel viisil. Muutuse kinnitamiseks klõpsi „Jätka“.", "change_recovery_key_title": "Kas muudame taastevõtit?", "description": "Kui sa oled kaotanud ligipääsu kõikidele oma olemasolevatele seadmetele, siis sa saad taastevõtme abil taastada ligipääsu oma krüptoidentiteedile ja sõnumite ajaloole.", "enter_key_error": "Sinu sisestatud taastevõti pole korrektne.", - "key_storage_warning": "Sinu võtmehoidla pole sünkroonis. Vea parandamiseks palun klõpsi järgnevat nuppu.", + "enter_recovery_key": "Sisesta taastevõti", + "forgot_recovery_key": "Kas unustasid taastevõtme?", + "key_storage_warning": "Sinu võtmehoidla pole sünkroonis. Vea parandamiseks palun klõpsi ühte järgnevatest nuppudest.", "save_key_description": "Ära jaga seda mitte kellegagi!", + "save_key_title": "Taastevõti", "set_up_recovery": "Seadista taastamine", "set_up_recovery_confirm_button": "Lõpeta seadistamine", "set_up_recovery_confirm_description": "Taastamise seadistamise lõpetamiseks palun sisesta eelmises vaates näidatud taastevõti.", + "set_up_recovery_confirm_title": "Kinnitamiseks sisesta oma taastevõti", "set_up_recovery_description": "Sinu krüptovõtmete hoidlat kaitseb taastevõti. Kui peale seadistamist peaksid vajama uut taastevõtit, siis saad ta uuesti luua valikust „%(changeRecoveryKeyButton)s“.", "set_up_recovery_save_key_description": "Palun märgi see taastevõti üles ja hoia teda turvaliselt, näiteks digitaalses salasõnalaekas, krüptitud märkmetes või vana kooli seifis.", + "set_up_recovery_save_key_title": "Salvesta oma taastevõti turvalisel viisil", "set_up_recovery_secondary_description": "Kui klõpsid nuppu „Jätka“, loome me sulle uue taastevõtme.", "title": "Taastamine" }, @@ -2562,6 +2676,7 @@ "inline_url_previews_room": "Luba URL'ide vaikimisi eelvaated selles jututoas osalejate jaoks", "inline_url_previews_room_account": "Luba URL'ide eelvaated selle jututoa jaoks (mõjutab vaid sind)", "insert_trailing_colon_mentions": "Mainimiste järel näita sõnumi alguses koolonit", + "invite_avatars": "Näita nende jututubade tunnuspilte, kuhu oled saanud kutse", "jump_to_bottom_on_send": "Sõnumi saatmiseks hüppa ajajoone lõppu", "key_backup": { "backup_in_progress": "Sinu krüptovõtmeid varundatakse (esimese varukoopia tegemine võib võtta paar minutit).", @@ -2585,15 +2700,15 @@ "pass_phrase_match_success": "Klapib!", "phrase_strong_enough": "Suurepärane! Turvafraas on piisavalt kange.", "secret_storage_query_failure": "Ei õnnestu tuvastada turvahoidla olekut", - "security_key_safety_reminder": "Kuna seda kasutatakse sinu krüptitud andmete kaitsmiseks, siis hoia oma turvavõtit kaitstud ja turvalises kohas, nagu näiteks arvutis salasõnade halduris või vana kooli seifis.", + "security_key_safety_reminder": "Kuna seda kasutatakse sinu krüptitud andmete kaitsmiseks, siis hoia oma taastevõtit kaitstud ja turvalises kohas, nagu näiteks arvutis salasõnade halduris või vana kooli seifis.", "set_phrase_again": "Mine tagasi ja sisesta nad uuesti.", "settings_reminder": "Samuti võid sa seadetes võtta kasutusse turvalise varunduse ning hallata oma krüptovõtmeid.", "title_confirm_phrase": "Kinnita turvafraas", - "title_save_key": "Salvesta turvavõti", + "title_save_key": "Salvesta oma taastevõti", "title_set_phrase": "Määra turvafraas", "unable_to_setup": "Turvahoidla kasutuselevõtmine ei õnnestu", "use_different_passphrase": "Kas kasutame muud paroolifraasi?", - "use_phrase_only_you_know": "Sisesta turvafraas, mida vaid sina tead ning lisaks võid salvestada varunduse turvavõtme." + "use_phrase_only_you_know": "Sisesta turvafraas, mida vaid sina tead ning lisaks võid salvestada varunduse taastevõtme." } }, "key_export_import": { @@ -3000,6 +3115,7 @@ "view": "Vaata sellise aadressiga jututuba", "whois": "Näitab teavet kasutaja kohta" }, + "sliding_sync_legacy_no_longer_supported": "Järkjärgulise sünkroniseerimise (Sliding sync) varasem lahendus pole enam toetatud: uue lahenduse kasutamiseks palun logi rakendusest välja ja uuesti sisse", "space": { "add_existing_room_space": { "create": "Kas sa selle asemel soovid lisada jututuba?", @@ -3213,7 +3329,7 @@ "historical_event_no_key_backup": "Varasemad sõnumid pole selles seadmes loetavad", "historical_event_unverified_device": "Varasemate sõnumite nägemiseks pead selle seadme verifitseerima", "historical_event_user_not_joined": "Sul puudub ligipääs sellele sõnumile", - "sender_identity_previously_verified": "Verifitseeritud võrguidentiteet on muutunud", + "sender_identity_previously_verified": "Saatja verifitseeritud võrguidentiteet on lähtestatud", "sender_unsigned_device": "Krüptitud seadme poolt, mida tema omanik pole verifitseerinud.", "unable_to_decrypt": "Sõnumi dekrüptimine ei õnnestu" }, @@ -3377,6 +3493,7 @@ "left_reason": "%(targetName)s lahkus jututoast: %(reason)s", "no_change": "%(senderName)s ei teinud muutusi", "reject_invite": "%(targetName)s lükkas kutse tagasi", + "reject_invite_reason": "%(targetName)s lükkas kutse tagasi: %(reason)s", "remove_avatar": "%(senderName)s eemaldas oma profiilipildi", "remove_name": "%(senderName)s eemaldas oma kuvatava nime (%(oldDisplayName)s)", "set_avatar": "%(senderName)s määras oma profiilipildi", @@ -3413,11 +3530,13 @@ }, "m.room.tombstone": "%(senderDisplayName)s uuendas seda jututuba.", "m.room.topic": { - "changed": "%(senderDisplayName)s muutis uueks teemaks „%(topic)s“." + "changed": "%(senderDisplayName)s muutis uueks teemaks „%(topic)s“.", + "removed": "%(senderDisplayName)s eemaldas teema." }, "m.sticker": "%(senderDisplayName)s saatis kleepsu.", "m.video": { - "error_decrypting": "Viga videovoo dekrüptimisel" + "error_decrypting": "Viga videovoo dekrüptimisel", + "show_video": "Näita videot" }, "m.widget": { "added": "%(senderName)s lisas vidina %(widgetName)s", @@ -3762,6 +3881,7 @@ "unban_space_specific": "Eemalda kasutajalt suhtluskeeld valitud kohtadest, kust ma saan", "unban_space_warning": "Kasutaja ei saa ligi kohtadele, kus sul pole peakasutaja õigusi.", "unignore_button": "Lõpeta eiramine", + "verification_unavailable": "Kasutaja verifitseerimine pole saadaval", "verify_button": "Verifitseeri kasutaja", "verify_explainer": "Lisaturvalisus mõttes verifitseeri see kasutaja võrreldes selleks üheks korraks loodud koodi mõlemas seadmes." }, @@ -3968,7 +4088,7 @@ "error_need_to_be_logged_in": "Sa peaksid olema sisse loginud.", "error_unable_start_audio_stream_description": "Audiovoo käivitamine ei õnnestu.", "error_unable_start_audio_stream_title": "Videovoo käivitamine ei õnnestu", - "modal_data_warning": "Andmeid selles vaates jagatakse %(widgetDomain)s serveriga", + "modal_data_warning": "Alljärgnevaid andmeid jagatakse %(widgetDomain)s serveriga", "modal_title_default": "Modaalne vidin", "no_name": "Tundmatu rakendus", "open_id_permissions_dialog": { diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index d459fad50d..35c5440714 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -420,12 +420,12 @@ "download_logs": "Naplók letöltése", "downloading_logs": "Naplók letöltése folyamatban", "error_empty": "Mondja el nekünk, hogy mi az, ami nem működött, vagy még jobb, ha egy GitHub-jegyben írja le a problémát.", - "failed_download_logs": "A hibakeresési naplók letöltése sikertelen: ", + "failed_download_logs": "Nem sikerült letölteni a hibakeresési naplókat: ", "failed_send_logs_causes": { "disallowed_app": "A hibajelentését elutasították. A rageshake kiszolgáló nem támogatja ezt az alkalmazást.", - "rejected_generic": "A hibajelentését elutasították. A rageshake kiszolgáló egy irányelv miatt elutasította a jelentés tartalmát.", - "rejected_recovery_key": "A hibajelentését biztonsági okokból elutasították, mivel helyreállítási kulcsot tartalmazott.", - "rejected_version": "A hibajelentését elutasították, mivel a futtatott verziója túl régi.", + "rejected_generic": "A hibajelentését elutasították. A rageshake kiszolgáló egy házirend miatt elutasította a jelentés tartalmát.", + "rejected_recovery_key": "A hibajelentést biztonsági okokból elutasították, mivel helyreállítási kulcsot tartalmazott.", + "rejected_version": "Hibajelentését elutasították, mivel az Ön által használt verzió túl régi.", "server_unknown_error": "A rageshake kiszolgáló ismeretlen hibát észlelt, és nem tudta kezelni a jelentést.", "unknown_error": "A naplók elküldése sikertelen." }, @@ -1663,7 +1663,7 @@ "class_global": "Globális", "class_other": "Egyéb", "default": "Alapértelmezett", - "default_settings": "Egyezzen meg az alapértelmezett beállításokkal", + "default_settings": "Megegyezik az alapértelmezett beállításokkal", "email_pusher_app_display_name": "E-mail értesítések", "enable_prompt_toast_description": "Asztali értesítések engedélyezése", "enable_prompt_toast_title": "Értesítések", @@ -2508,16 +2508,17 @@ "breadcrumb_third_description": "Újra ellenőriznie kell az összes meglévő eszközét és névjegyét", "breadcrumb_title": "Biztos, hogy alaphelyzetbe állítja a személyazonosságát?", "breadcrumb_title_forgot": "Elfelejtette a helyreállítási kulcsot? Újra be kell állítania a személyazonosságát.", + "breadcrumb_title_sync_failed": "A kulcstár szinkronizálása sikertelen. Vissza kell állítania személyazonosságát.", "breadcrumb_warning": "Csak akkor tegye ezt, ha úgy gondolja, hogy fiókját feltörték.", "details_title": "Titkosítás részletei", - "do_not_close_warning": "Ne zárja be ezt az ablakot, amíg az alaphelyzetbe állítás be nem fejeződik", + "do_not_close_warning": "Ne zárja be ezt az ablakot, amíg a visszaállítás be nem fejeződik", "export_keys": "Kulcsok exportálása", "import_keys": "Kulcsok importálása", "other_people_device_description": "Figyelmeztetés: azok a felhasználók, akikkel nem ellenőrizték kölcsönösen egymást (például emodzsik használatával), nem kapják meg a titkosított üzeneteket. Ezenkívül az ellenőrzött felhasználók nem ellenőrzött eszközei sem kapják meg a titkosított üzeneteket.", "other_people_device_label": "Titkosított szobákban csak az ellenőrzött felhasználók kapják meg az üzeneteket", "other_people_device_title": "Mások eszközei", "reset_identity": "Kriptográfiai személyazonosság alaphelyzetbe állítása", - "reset_in_progress": "Alaphelyzetbe állítás folyamatban…", + "reset_in_progress": "Visszaállítás folyamatban…", "session_id": "Munkamenet-azonosító:", "session_key": "Munkamenetkulcs:", "title": "Speciális" @@ -2525,9 +2526,9 @@ "delete_key_storage": { "breadcrumb_page": "Kulcstároló törlése", "confirm": "Kulcstároló törlése", - "description": "A kulcstároló törlése eltávolítja a kriptográfiai személyazonosságát és üzenetkulcsait a kiszolgálóról, és kikapcsolja a következő biztonsági funkciókat:", + "description": "A kulcstároló törlése eltávolítja a kriptográfiai személyazonosságát és üzenetkulcsát a kiszolgálóról, és kikapcsolja a következő biztonsági funkciókat:", "list_first": "Nem lesznek meg a titkosított üzenetek előzményei az új eszközein", - "list_second": "Elveszíti a titkosított üzenetekhez való hozzáférését, ha mindenhol kijelentkezik az %(brand)sből.", + "list_second": "Elveszíti hozzáférését titkosított üzeneteihez, ha mindenhol kijelentkezett az %(brand)sből.", "title": "Biztos, hogy kikapcsolja a kulcstárolót és törli azt?" }, "device_not_verified_button": "Az eszköz ellenőrzése", @@ -2536,7 +2537,7 @@ "dialog_title": "Beállítások: Titkosítás", "key_storage": { "allow_key_storage": "Kulcstárolás engedélyezése", - "description": "Tárolja biztonságosan a kriptográfiai személyazonosságát és az üzenetkulcsokat a kiszolgálón. Ez lehetővé teszi az üzenetek előzményeinek megtekintését bármely új eszközön. További információk", + "description": "Tárolja biztonságosan a kriptográfiai személyazonosságát és az üzenetkulcsait a kiszolgálón. Ez lehetővé teszi az üzenetek előzményeinek megtekintését bármely új eszközön. További információ", "title": "Kulcstároló" }, "recovery": { @@ -4056,7 +4057,7 @@ "error_need_to_be_logged_in": "Be kell jelentkeznie.", "error_unable_start_audio_stream_description": "A hangközvetítés indítása sikertelen.", "error_unable_start_audio_stream_title": "Az élő adás indítása sikertelen", - "modal_data_warning": "A lenti adatok megosztásra kerülnek ezzel: %(widgetDomain)s", + "modal_data_warning": "Az alábbi adatok a következővel vannak megosztva: %(widgetDomain)s", "modal_title_default": "Előugró kisalkalmazás", "no_name": "Ismeretlen alkalmazás", "open_id_permissions_dialog": { diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index c6bdd6623f..4f49f62f9b 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2233,14 +2233,14 @@ "custom_font_description": "Imposta il nome di un font installato nel tuo sistema e %(brand)s proverà ad usarlo.", "custom_font_name": "Nome carattere di sistema", "custom_font_size": "Usa dimensione personalizzata", - "custom_theme_error_downloading": "Errore scaricando informazioni sul tema.", + "custom_theme_error_downloading": "Errore di scaricamento del tema", "custom_theme_invalid": "Schema del tema non valido.", "font_size": "Dimensione carattere", "font_size_default": "%(fontSize)s (predefinito)", "image_size_default": "Predefinito", "image_size_large": "Grande", "layout_bubbles": "Messaggi", - "layout_irc": "IRC (Sperimentale)", + "layout_irc": "IRC (sperimentale)", "match_system_theme": "Usa il tema di sistema", "timeline_image_size": "Dimensione immagine nella linea temporale" }, @@ -2281,9 +2281,9 @@ "deactivate_confirm_erase_label": "Nascondi i miei messaggi ai nuovi membri", "deactivate_section": "Disattiva l'account", "deactivate_warning": "La disattivazione dell'account è permanente - attenzione!", - "discovery_email_empty": "Le opzioni di scoperta appariranno dopo aver aggiunto un'email sopra.", + "discovery_email_empty": "Le opzioni di scoperta appariranno dopo aver aggiunto un'email.", "discovery_email_verification_instructions": "Verifica il link nella tua posta in arrivo", - "discovery_msisdn_empty": "Le opzioni di scoperta appariranno dopo aver aggiunto un numero di telefono sopra.", + "discovery_msisdn_empty": "Le opzioni di scoperta appariranno dopo aver aggiunto un numero di telefono.", "discovery_needs_terms": "Accetta le condizioni di servizio del server di identità (%(serverName)s) per poter essere trovabile tramite indirizzo email o numero di telefono.", "email_address_in_use": "Questo indirizzo e-mail è già in uso", "email_address_label": "Indirizzo email", @@ -2309,7 +2309,7 @@ "error_share_msisdn_discovery": "Impossibile condividere il numero di telefono", "identity_server_no_token": "Nessun token di accesso d'identità trovato", "identity_server_not_set": "Server d'identità non impostato", - "language_section": "Lingua e regione", + "language_section": "Lingua", "msisdn_in_use": "Questo numero di telefono è già in uso", "msisdn_label": "Numero di telefono", "msisdn_verification_field_label": "Codice di verifica", @@ -2559,7 +2559,7 @@ "security_recommendations_description": "Migliora la sicurezza del tuo account seguendo questi consigli.", "session_id": "ID sessione", "show_details": "Mostra dettagli", - "sign_in_with_qr": "Accedi con codice QR", + "sign_in_with_qr": "Collega un nuovo dispositivo", "sign_in_with_qr_button": "Mostra codice QR", "sign_in_with_qr_description": "Puoi usare questo dispositivo per accedere in un altro con un codice QR. Dovrai scansionare il codice QR mostrato in questo dispositivo con l'altro.", "sign_out": "Disconnetti da questa sessione", diff --git a/src/i18n/strings/nb_NO.json b/src/i18n/strings/nb_NO.json index fc183125d2..0d878f6442 100644 --- a/src/i18n/strings/nb_NO.json +++ b/src/i18n/strings/nb_NO.json @@ -55,6 +55,8 @@ "create_a_room": "Opprett rom", "create_account": "Opprett konto", "decline": "Avslå", + "decline_and_block": "Avvise og blokkere", + "decline_invite": "Avvis invitasjon", "delete": "Slett", "deny": "Avvis", "disable": "Slå av", @@ -744,6 +746,13 @@ "twemoji": "Emoji-grafikken Twemoji er © av Twitter, Inc og andre bidragsytere og brukt under vilkårene i CC-BY 4.0.", "twemoji_colr": "Fonten twemoji-colr er © Mozilla Foundation kan brukes under vilkårene i Apache 2.0." }, + "decline_invitation_dialog": { + "confirm": "Er du sikker på at du vil takke nei til invitasjonen om å bli med i \"%(roomName)s\"?", + "ignore_user_help": "Du vil ikke se noen meldinger eller rominvitasjoner fra denne brukeren.", + "reason_description": "Beskriv årsaken for å rapportere rommet.", + "report_room_description": "Rapporter dette rommet til din kontoleverandør.", + "title": "Avvis invitasjon" + }, "desktop_default_device_name": "%(brand)sSkrivebordet: %(platformName)s", "devtools": { "active_widgets": "Aktive moduler", @@ -2518,6 +2527,7 @@ "breadcrumb_third_description": "Du må bekrefte alle eksisterende enheter og kontakter på nytt", "breadcrumb_title": "Er du sikker på at du vil tilbakestille identiteten din?", "breadcrumb_title_forgot": "Har du glemt gjenopprettingsnøkkelen din? Du må tilbakestille identiteten din.", + "breadcrumb_title_sync_failed": "Kunne ikke synkronisere nøkkellageret. Du må tilbakestille identiteten din.", "breadcrumb_warning": "Gjør dette bare hvis du tror at kontoen din har blitt kompromittert.", "details_title": "Krypteringsdetaljer", "do_not_close_warning": "Ikke lukk dette vinduet før tilbakestillingen er fullført", diff --git a/src/i18n/strings/pl.json b/src/i18n/strings/pl.json index 32fd3d1fb4..6c7daa8bf8 100644 --- a/src/i18n/strings/pl.json +++ b/src/i18n/strings/pl.json @@ -295,7 +295,7 @@ "scan_qr_code": "Zaloguj się kodem QR", "security_code": "Kod bezpieczeństwa", "security_code_prompt": "Jeśli zostaniesz poproszony, wprowadź poniższy kod na drugim urządzeniu.", - "select_qr_code": "Wybierz \"%(scanQRCode)s\"", + "select_qr_code": "Wybierz '%(scanQRCode)s'", "unsupported_explainer": "Twój dostawca konta nie obsługuje logowania nowego urządzenia za pomocą kodu QR.", "unsupported_heading": "Kod QR nie jest wspierany", "waiting_for_device": "Oczekiwanie na logowanie urządzenia" @@ -841,9 +841,9 @@ "room_notifications_type": "Typ: ", "room_status": "Status pokoju", "room_unread_status_count": { - "one": "Status nieprzeczytanej w pokoju: %(status)s, liczba: %(count)s", - "few": "Status nieprzeczytanych w pokoju: %(status)s, liczba: %(count)s", - "many": "Status nieprzeczytanych w pokoju: %(status)s, liczba: %(count)s" + "one": "Status nieprzeczytanej pokoju: %(status)s, ilość: %(count)s", + "few": "Status nieprzeczytanych pokoju: %(status)s, ilość: %(count)s", + "many": "Status nieprzeczytanych pokoju: %(status)s, ilość: %(count)s" }, "save_setting_values": "Zapisz ustawione wartości", "see_history": "Pokaż historię", @@ -1399,8 +1399,9 @@ "many": "Zapraszanie %(user)s i %(count)s innych" }, "items_and_n_others": { - "other": " i %(count)s innych", - "one": " i jedna inna osoba" + "one": " i jeszcze jedna osoba", + "few": " i %(count)s inne", + "many": " i %(count)s innych" }, "keyboard": { "activate_button": "Aktywuj wybrany przycisk", @@ -2090,7 +2091,7 @@ "monthly_user_limit_reached": "Wiadomość nie została wysłana, ponieważ serwer domowy przekroczył miesięczny limit aktywnych użytkowników. Skontaktuj się z administratorem serwisu, aby kontynuować.", "requires_consent_agreement": "Nie możesz wysłać żadnej wiadomości, dopóki nie zaakceptujesz naszych warunków i kondycji.", "retry_all": "Spróbuj ponownie wszystkie", - "select_messages_to_retry": "Możesz zaznaczyć wszystkie lub wybrane wiadomości aby spróbować ponownie lub usunąć je", + "select_messages_to_retry": "Możesz zaznaczyć wszystkie lub wybrane wiadomości, aby spróbować ponownie lub je usunąć", "server_connectivity_lost_description": "Wysłane wiadomości będą przechowywane aż do momentu odzyskania połączenia.", "server_connectivity_lost_title": "Połączenie z serwerem zostało utracone.", "some_messages_not_sent": "Niektóre z Twoich wiadomości nie zostały wysłane" @@ -2942,7 +2943,7 @@ "security_recommendations_description": "Zwiększ bezpieczeństwo swojego konta kierując się tymi rekomendacjami.", "session_id": "Identyfikator sesji", "show_details": "Pokaż szczegóły", - "sign_in_with_qr": "Połącz nowe urządzenie", + "sign_in_with_qr": "Powiąż nowe urządzenie", "sign_in_with_qr_button": "Pokaż kod QR", "sign_in_with_qr_description": "Użyj kodu QR, aby zalogować się na innym urządzeniu i skonfigurować bezpieczne przesyłanie wiadomości.", "sign_in_with_qr_unsupported": "Nieobsługiwane przez dostawcę konta", @@ -3676,16 +3677,19 @@ "other": "zostało zaproszonych %(count)s razy" }, "joined": { - "other": "%(oneUser)s dołączył %(count)s razy", - "one": "%(oneUser)s dołączył" + "one": "%(oneUser)s dołączył", + "few": "%(oneUser)s dołączył %(count)s raz", + "many": "%(oneUser)s dołączył %(count)s razy" }, "joined_and_left": { - "other": "%(oneUser)s dołączył i wyszedł %(count)s razy", - "one": "%(oneUser)s dołączył i wyszedł" + "one": "%(oneUser)s dołączył i wyszedł", + "few": "%(oneUser)s dołączył i wyszedł %(count)s razy", + "many": "%(oneUser)s dołączył i wyszedł %(count)s razy" }, "joined_and_left_multiple": { "one": "%(severalUsers)s dołączyło i wyszło", - "other": "%(severalUsers)s dołączyło i wyszło %(count)s razy" + "few": "%(severalUsers)s dołączyło i wyszło %(count)s razy", + "many": "%(severalUsers)s dołączyło i wyszło %(count)s razy" }, "joined_multiple": { "one": "%(severalUsers)s dołączył", @@ -3741,12 +3745,14 @@ "other": "%(severalUsers)sodrzuciło ich zaproszenia %(count)s razy" }, "rejoined": { - "other": "%(oneUser)s wyszedł i dołączył ponownie %(count)s razy", - "one": "%(oneUser)s wyszedł i dołączył ponownie" + "one": "%(oneUser)s wyszedł i dołączył ponownie", + "few": "%(oneUser)s wyszedł i dołączył ponownie %(count)s razy", + "many": "%(oneUser)s wyszedł i dołączył ponownie %(count)s razy" }, "rejoined_multiple": { - "other": "%(severalUsers)swyszło i dołączyło ponownie %(count)s razy", - "one": "%(severalUsers)swyszło i dołączyło ponownie" + "one": "%(severalUsers)s wyszło i dołączyło ponownie", + "few": "%(severalUsers)s wyszło i dołączyło ponownie %(count)s razy", + "many": "%(severalUsers)s wyszło i dołączyło ponownie %(count)s razy" }, "server_acls": { "one": "%(oneUser)szmienił ACL serwera", @@ -3969,7 +3975,8 @@ "msisdn_transfer_failed": "Nie udało się przekazać połączenia", "n_people_joined": { "one": "%(count)s osoba dołączyła", - "other": "%(count)s osób dołączyło" + "few": "%(count)s osoby dołączyły", + "many": "%(count)s osób dołączyło" }, "no_audio_input_description": "Nie udało się znaleźć żadnego mikrofonu w twoim urządzeniu. Sprawdź ustawienia i spróbuj ponownie.", "no_audio_input_title": "Nie znaleziono mikrofonu", diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 081807f6b8..73a54cf37b 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -468,6 +468,7 @@ "beta": "Бета", "camera": "Камера", "cameras": "Камеры", + "cancel": "Отменить", "capabilities": "Возможности", "copied": "Скопировано!", "credits": "Благодарности", @@ -512,7 +513,8 @@ "mute": "Приглушить", "n_members": { "one": "%(count)s участник", - "other": "%(count)s участников" + "few": "%(count)s участника", + "many": "%(count)s участников" }, "n_rooms": { "one": "%(count)s комната", @@ -543,6 +545,7 @@ "qr_code": "QR-код", "random": "Случайный", "reactions": "Реакции", + "recommended": "Рекомендуемое", "report_a_bug": "Сообщить об ошибке", "room": "Комната", "room_name": "Название комнаты", @@ -737,7 +740,27 @@ "client_versions": "Версия клиента", "crypto": { "4s_public_key_in_account_data": "в данных учётной записи", - "4s_public_key_not_in_account_data": "не найдено" + "4s_public_key_not_in_account_data": "не найдено", + "backup_key_cached_status": "Кэшированный резервный ключ:", + "backup_key_stored_status": "Сохраненный резервный ключ:", + "backup_key_well_formed": "корректный", + "cross_signing": "Кросс-подпись", + "cross_signing_cached": "сохранено локально", + "cross_signing_not_ready": "Кросс-подпись не настроена.", + "cross_signing_private_keys_in_storage_status": "Приватные ключи для кросс-подписи:", + "cross_signing_private_keys_not_in_storage": "не найдено в хранилище", + "cross_signing_public_keys_on_device_status": "Публичные ключи для кросс-подписи:", + "cross_signing_ready": "Кросс-подпись готова к использованию.", + "cross_signing_status": "Статус кросс-подписи:", + "cross_signing_untrusted": "У вашей учётной записи есть кросс-подпись в секретное хранилище, но она пока не является доверенной в этом сеансе.", + "crypto_not_available": "Криптографический модуль недоступен", + "key_backup_active_version": "Активная резервная версия:", + "key_backup_active_version_none": "Нет", + "key_backup_inactive_warning": "Резервное копирование ваших ключей из этого сеанса не выполняется.", + "key_storage": "Хранилище ключей", + "master_private_key_cached_status": "Приватный мастер-ключ:", + "secret_storage_ready": "готово", + "title": "Сквозное шифрование" }, "developer_mode": "Режим разработчика", "developer_tools": "Инструменты разработчика", @@ -848,22 +871,22 @@ "empty_room_was_name": "Пустая комната (без %(oldName)s)", "encryption": { "access_secret_storage_dialog": { - "enter_phrase_or_key_prompt": "Введите свою секретную фразу или для продолжения.", + "enter_phrase_or_key_prompt": "Введите секретную фразу или , чтобы продолжить.", "key_validation_text": { - "invalid_security_key": "Неверный ключ безопасности", + "invalid_security_key": "Неверный ключ восстановления", "recovery_key_is_correct": "Выглядит неплохо!", "wrong_file_type": "Неправильный тип файла", - "wrong_security_key": "Неправильный ключ безопасности" + "wrong_security_key": "Неправильный ключ восстановления" }, "reset_title": "Сбросить всё", "reset_warning_1": "Делайте это только в том случае, если у вас нет другого устройства для завершения проверки.", "reset_warning_2": "Если вы сбросите все настройки, вы перезагрузитесь без доверенных сеансов, без доверенных пользователей, и скорее всего не сможете просматривать прошлые сообщения.", "restoring": "Восстановление ключей из резервной копии", - "security_key_title": "Бумажный ключ", + "security_key_title": "Ключ восстановления", "security_phrase_incorrect_error": "Невозможно получить доступ к секретному хранилищу. Убедитесь, что вы ввели правильную секретную фразу.", "security_phrase_title": "Мнемоническая фраза", "separator": "%(securityKey)s или %(recoveryFile)s", - "use_security_key_prompt": "Чтобы продолжить, используйте свой бумажный ключ." + "use_security_key_prompt": "Используйте ключ восстановления, чтобы продолжить." }, "bootstrap_title": "Настройка ключей", "cancel_entering_passphrase_description": "Вы уверены, что хотите отменить ввод кодовой фразы?", @@ -876,14 +899,18 @@ "cross_signing_user_normal": "Вы не подтвердили этого пользователя.", "cross_signing_user_verified": "Вы подтвердили этого пользователя. Пользователь подтвердил все свои сеансы.", "cross_signing_user_warning": "Этот пользователь не подтвердил все свои сеансы.", + "enter_recovery_key": "Введите ключ восстановления", "event_shield_reason_authenticity_not_guaranteed": "Подлинность этого зашифрованного сообщения не может быть гарантирована на этом устройстве.", "event_shield_reason_mismatched_sender_key": "Зашифровано неподтверждённым сеансом", "event_shield_reason_unknown_device": "Зашифровано неизвестным или удаленным устройством.", "event_shield_reason_unsigned_device": "Зашифровано устройством, не проверенным владельцем.", "event_shield_reason_unverified_identity": "Зашифровано неподтвержденным пользователем.", "export_unsupported": "Ваш браузер не поддерживает необходимые криптографические расширения", + "forgot_recovery_key": "Забыли ключ восстановления?", "import_invalid_keyfile": "Недействительный файл ключей %(brand)s", "import_invalid_passphrase": "Ошибка аутентификации: возможно, неправильный пароль?", + "key_storage_out_of_sync": "Хранилище ключей не синхронизировано.", + "key_storage_out_of_sync_description": "Подтвердите ключ восстановления, чтобы сохранить доступ к хранилищу ключей и истории сообщений.", "messages_not_secure": { "cause_1": "Ваш домашний сервер", "cause_2": "Домашний сервер пользователя, которого вы подтверждаете", @@ -905,6 +932,8 @@ "warning": "Если вы не убрали метод восстановления, злоумышленник может получить доступ к вашей учётной записи. Смените пароль учётной записи и сразу задайте новый способ восстановления в настройках." }, "reset_all_button": "Забыли или потеряли все варианты восстановления? Сбросить всё", + "set_up_recovery": "Настроить восстановление", + "set_up_recovery_later": "Не сейчас", "set_up_toast_description": "Защита от потери доступа к зашифрованным сообщениям и данным", "set_up_toast_title": "Настроить безопасное резервное копирование", "setup_secure_backup": { @@ -955,9 +984,10 @@ "qr_or_sas": "%(qrCode)s или %(emojiCompare)s", "qr_or_sas_header": "Заверьте этот сеанс, выполнив одно из следующих действий:", "qr_prompt": "Отсканируйте этот уникальный код", - "qr_reciprocate_same_shield_device": "Почти готово! Ваше другое устройство показывает такой же щит?", + "qr_reciprocate_same_shield_device": "Почти готово! На другом устройстве отображается такой же щит?", "qr_reciprocate_same_shield_user": "Почти готово! Отображает ли %(displayName)s такой же щит?", "request_toast_accept": "Проверка сеанса", + "request_toast_accept_user": "Подтвердить пользователя", "request_toast_decline_counter": "Игнорировать (%(counter)s)", "request_toast_detail": "%(deviceId)s с %(ip)s", "reset_proceed_prompt": "Выполнить сброс", @@ -1084,7 +1114,10 @@ "you": "Вы отреагировали %(reaction)s на %(message)s" }, "m.sticker": "%(senderName)s: %(stickerName)s", - "m.text": "%(senderName)s: %(message)s" + "m.text": "%(senderName)s: %(message)s", + "prefix": { + "poll": "Опрос" + } }, "export_chat": { "cancelled": "Экспорт отменён", @@ -1207,7 +1240,15 @@ "other": "В %(spaceName)s и %(count)s других пространствах." }, "incompatible_browser": { - "title": "Неподдерживаемый браузер" + "detail_no_continue": "Попробуйте обновить этот браузер, если вы используете не последнюю версию, и повторите попытку.", + "learn_more": "Подробнее", + "linux": "Linux", + "macos": "Mac", + "title": "Неподдерживаемый браузер", + "use_desktop_heading": "Вместо этого используйте %(brand)s Desktop", + "use_mobile_heading_after_desktop": "Или воспользуйтесь нашим мобильным приложением", + "windows_64bit": "Windows (64-бит)", + "windows_arm_64bit": "Windows (ARM 64-бит)" }, "info_tooltip_title": "Информация", "integration_manager": { @@ -1216,6 +1257,7 @@ "error_connecting_heading": "Не удалось подключиться к менеджеру интеграций", "explainer": "Менеджеры по интеграции получают данные конфигурации и могут изменять виджеты, отправлять приглашения в комнаты и устанавливать уровни доступа от вашего имени.", "manage_title": "Управление интеграциями", + "toggle_label": "Включить менеджер интеграции", "use_im": "Используйте менеджер интеграций для управления ботами, виджетами и наклейками.", "use_im_default": "Используйте менеджер интеграций %(serverName)s для управления ботами, виджетами и наклейками." }, @@ -1395,6 +1437,7 @@ "group_rooms": "Комнаты", "group_spaces": "Пространства", "group_themes": "Темы", + "group_ui": "Пользовательский интерфейс", "group_voip": "Голос и видео", "group_widgets": "Виджеты", "hidebold": "Скрыть точку уведомления (отображать только значки счетчиков)", @@ -1531,6 +1574,11 @@ "toggle_attribution": "Переключить атрибуцию" }, "member_list": { + "count": { + "one": "%(count)s Участник", + "few": "%(count)s Участника", + "many": "%(count)s Участников" + }, "filter_placeholder": "Поиск по участникам", "invite_button_no_perms_tooltip": "У вас нет разрешения приглашать пользователей", "power_label": "%(userName)s (уровень прав %(powerLevelNumber)s)" @@ -1561,6 +1609,10 @@ "error_change_title": "Изменить настройки уведомлений", "keyword": "Ключевое слово", "keyword_new": "Новое ключевое слово", + "level_activity": "Активность", + "level_none": "Пусто", + "level_notification": "Уведомление", + "level_unsent": "Не отправлено", "mark_all_read": "Отметить всё как прочитанное", "mentions_and_keywords": "@упоминания и ключевые слова", "mentions_and_keywords_description": "Получать уведомления только по упоминаниям и ключевым словам, установленным в ваших настройках", @@ -1693,38 +1745,58 @@ "restore_key_backup_dialog": { "count_of_decryption_failures": "Не удалось расшифровать сеансы (%(failedCount)s)!", "count_of_successfully_restored_keys": "Успешно восстановлены ключи (%(sessionCount)s)", - "enter_key_description": "Получите доступ к своей истории защищенных сообщений и настройте безопасный обмен сообщениями, введя ключ безопасности.", - "enter_key_title": "Введите ключ безопасности", + "enter_key_description": "Получите доступ к защищенной истории сообщений и настройте безопасный обмен сообщениями, введя ключ восстановления.", + "enter_key_title": "Введите ключ восстановления", "enter_phrase_description": "Получите доступ к своей истории защищенных сообщений и настройте безопасный обмен сообщениями, введя секретную фразу.", "enter_phrase_title": "Введите мнемоническую фразу", "incorrect_security_phrase_dialog": "Не удалось расшифровать резервную копию с помощью этой секретной фразы: убедитесь, что вы ввели правильную секретную фразу.", "incorrect_security_phrase_title": "Неверная секретная фраза", "key_backup_warning": "Предупреждение: вам следует настроить резервное копирование ключей только с доверенного компьютера.", - "key_fetch_in_progress": "Получение ключей с сервера...", - "key_forgotten_text": "Если вы забыли свой ключ безопасности, вы можете ", - "key_is_invalid": "Неправильный ключ безопасности", - "key_is_valid": "Похоже, это правильный ключ безопасности!", + "key_fetch_in_progress": "Получение ключей с сервера…", + "key_forgotten_text": "Если вы забыли ваш ключ восстановления, вы можете ", + "key_is_invalid": "Недействительный ключ восстановления", + "key_is_valid": "Похоже, это действительный ключ восстановления!", "keys_restored_title": "Ключи восстановлены", "load_error_content": "Невозможно загрузить статус резервной копии", "load_keys_progress": "%(completed)s из %(total)s ключей восстановлено", "no_backup_error": "Резервных копий не найдено!", - "phrase_forgotten_text": "Если вы забыли секретную фразу, вы можете использовать ключ безопасности или настроить новые параметры восстановления", - "recovery_key_mismatch_description": "Не удалось расшифровать резервную копию с помощью этого ключа безопасности: убедитесь, что вы ввели правильный ключ безопасности.", - "recovery_key_mismatch_title": "Ключ безопасности не подходит", + "phrase_forgotten_text": "Если вы забыли секретную фразу, вы можете использовать ключ восстановления или настроить новые параметры восстановления. ", + "recovery_key_mismatch_description": "Не удалось расшифровать резервную копию с помощью этого ключа восстановления. Убедитесь, что вы ввели правильный ключ восстановления.", + "recovery_key_mismatch_title": "Несоответствие ключа восстановления", "restore_failed_error": "Невозможно восстановить резервную копию" }, "right_panel": { - "add_integrations": "Добавить виджеты, мосты и ботов", + "add_integrations": "Добавить расширения", + "add_topic": "Добавить тему", + "extensions_button": "Расширения", + "extensions_empty_description": "Нажмите “%(addIntegrations)s”, чтобы просмотреть и добавить расширения в эту комнату", + "extensions_empty_title": "Повысьте производительность с помощью дополнительных инструментов, виджетов и ботов", "files_button": "Файлы", "pinned_messages": { + "empty_description": "Выберите сообщение и нажмите «%(pinAction)s», чтобы включить его сюда.", + "empty_title": "Закрепите важные сообщения, чтобы их можно было легко найти", + "header": { + "one": "1 Закреплённое сообщение", + "few": "%(count)s Закреплённых сообщения", + "many": "%(count)s Закреплённых сообщений" + }, "limits": { "other": "Вы можете закрепить не более %(count)s виджетов" }, + "menu": "Открыть меню", "release_announcement": { + "close": "Ок", + "description": "Все прикрепленные сообщения можно найти здесь. Наведите курсор на любое сообщение и нажмите «Закрепить», чтобы добавить его.", "title": "Все новые закрепленные сообщения" - } + }, + "unpin_all": { + "button": "Открепить все сообщения", + "content": "Убедитесь, что вы действительно хотите удалить все прикреплённые сообщения. Это действие нельзя отменить.", + "title": "Открепить все сообщения?" + }, + "view": "Просмотр в хронологии" }, - "pinned_messages_button": "Закреплено", + "pinned_messages_button": "Закрепленные сообщения", "poll": { "active_heading": "Активные опросы", "empty_active": "В этой комнате нет активных опросов", @@ -1780,6 +1852,7 @@ "forget": "Забыть комнату", "low_priority": "Маловажные", "mark_read": "Отметить как прочитанное", + "mark_unread": "Отметить как непрочитанное", "notifications_default": "Соответствует настройке по умолчанию", "notifications_mute": "Заглушить комнату", "title": "Настройки комнаты", @@ -1829,6 +1902,8 @@ }, "room_is_public": "Это публичная комната" }, + "header_avatar_open_settings_label": "Открыть настройки комнаты", + "header_face_pile_tooltip": "Люди", "header_untrusted_label": "Ненадёжный", "inaccessible": "Эта комната или пространство в данный момент недоступны.", "inaccessible_name": "%(roomName)s на данный момент недоступна.", @@ -1897,11 +1972,22 @@ "not_found_title": "Такой комнаты или пространства не существует.", "not_found_title_name": "%(roomName)s не существует.", "peek_join_prompt": "Вы просматриваете %(roomName)s. Хотите присоединиться?", + "pinned_message_badge": "Закреплённое сообщение", + "pinned_message_banner": { + "button_close_list": "Закрыть список", + "button_view_all": "Посмотреть все" + }, "read_topic": "Нажмите, чтобы увидеть тему", "rejecting": "Отклонение приглашения…", "rejoin_button": "Пере-присоединение", "search": { "all_rooms_button": "Поиск по всем комнатам", + "placeholder": "Поиск сообщений...", + "summary": { + "one": "Найден 1 результат по запросу «»", + "few": "%(count)s результата найдено по запросу “”", + "many": "%(count)s результатов найдено по запросу “”" + }, "this_room_button": "Поиск в этой комнате" }, "status_bar": { @@ -1942,20 +2028,48 @@ "add_space_label": "Добавить пространство", "breadcrumbs_empty": "Нет недавно посещенных комнат", "breadcrumbs_label": "Недавно посещённые комнаты", + "empty": { + "no_chats": "Пока нет доступных чатов", + "no_chats_description": "Начните с отправки сообщений или создания комнаты", + "no_chats_description_no_room_rights": "Начните переписку с отправки сообщения", + "no_favourites": "У вас пока нет чатов в Избранное", + "no_favourites_description": "Вы можете добавить в Избранное в настройках чата", + "no_people": "У вас пока нет личных чатов", + "no_rooms": "Вы еще не находитесь ни в одной комнате", + "no_unread": "Поздравляю! У вас нет непрочитанных сообщений", + "show_chats": "Показать все чаты" + }, "failed_add_tag": "Не удалось добавить тег %(tagName)s в комнату", "failed_remove_tag": "Не удалось удалить тег %(tagName)s из комнаты", "failed_set_dm_tag": "Не удалось установить метку личного сообщения", + "filters": { + "favourite": "Избранное", + "people": "Люди", + "rooms": "Комнаты", + "unread": "Непрочитанное" + }, "home_menu_label": "Параметры раздела \"Главная\"", "join_public_room_label": "Присоединиться к публичной комнате", "joining_rooms_status": { "one": "Сейчас вы состоите в %(count)s комнате", "other": "Сейчас вы состоите в %(count)s комнатах" }, + "list_title": "Список комнат", + "more_options": { + "copy_link": "Скопировать ссылку на комнату", + "leave_room": "Покинуть комнату", + "low_priority": "Низкий приоритет", + "mark_read": "Отметить как прочитанное", + "mark_unread": "Отметить как непрочитанное" + }, "notification_options": "Настройки уведомлений", "redacting_messages_status": { "one": "Удаляются сообщения в %(count)s комнате", "other": "Удаляются сообщения в %(count)s комнатах" }, + "room": { + "more_options": "Дополнительные параметры" + }, "show_less": "Показать меньше", "show_n_more": { "other": "Показать ещё %(count)s", @@ -2169,12 +2283,14 @@ "join_rule_restricted_dialog_heading_unknown": "Это, скорее всего, те, в которых участвуют другие администраторы комнат.", "join_rule_restricted_dialog_title": "Выберите места", "join_rule_restricted_n_more": { - "other": "и %(count)s ещё", - "one": "и %(count)s еще" + "one": "и еще %(count)s", + "few": "и еще %(count)s", + "many": "и еще %(count)s" }, "join_rule_restricted_summary": { - "other": "В настоящее время %(count)s пространств имеют доступ", - "one": "В настоящее время пространство имеет доступ" + "one": "В настоящее время пространство имеет доступ", + "few": "В настоящее время %(count)s пространства имеют доступ", + "many": "В настоящее время %(count)s пространств имеют доступ" }, "join_rule_restricted_upgrade_description": "Это обновление позволит участникам выбранных пространств получить доступ в эту комнату без приглашения.", "join_rule_restricted_upgrade_warning": "Эта комната находится в некоторых пространствах, администратором которых вы не являетесь. В этих пространствах старая комната будет по-прежнему отображаться, но людям будет предложено присоединиться к новой.", @@ -2265,14 +2381,18 @@ "brand_version": "Версия %(brand)s:", "clear_cache_reload": "Очистить кэш и перезагрузить", "crypto_version": "Криптоверсия:", + "dialog_title": "Настройки: Помощь и информация", "help_link": "Для получения помощи по использованию %(brand)s, нажмите здесь.", - "homeserver": "Homeserver это %(homeserverUrl)s", - "identity_server": "Сервер идентификации - это %(identityServerUrl)s", + "homeserver": "Домашний сервер: %(homeserverUrl)s", + "identity_server": "Сервер идентификации: %(identityServerUrl)s", "title": "Помощь и о программе", "versions": "Версии" } }, "settings": { + "account": { + "title": "Учетная запись" + }, "all_rooms_home": "Показывать все комнаты на Главной", "all_rooms_home_description": "Все комнаты, в которых вы находитесь, будут отображаться на Главной.", "always_show_message_timestamps": "Всегда показывать время отправки сообщений", @@ -2281,9 +2401,13 @@ "custom_font_description": "Установите имя шрифта, установленного в вашей системе, и %(brand)s попытается его использовать.", "custom_font_name": "Название системного шрифта", "custom_font_size": "Использовать другой размер", + "custom_theme_add": "Добавить пользовательскую тему", + "custom_theme_downloading": "Загрузка пользовательской темы…", "custom_theme_error_downloading": "Ошибка при загрузке информации темы.", "custom_theme_invalid": "Неверная схема темы.", "font_size": "Размер шрифта", + "font_size_default": "%(fontSize)s (по умолчанию)", + "high_contrast": "Высокая контрастность", "image_size_default": "По умолчанию", "image_size_large": "Большой", "layout_bubbles": "Пузыри сообщений", @@ -2302,12 +2426,52 @@ "enable_markdown": "Использовать Markdown", "enable_markdown_description": "Начинайте сообщения с /plain, чтобы отправлять их без markdown.", "encryption": { + "advanced": { + "breadcrumb_first_description": "Данные вашей учетной записи, контакты, настройки и список чатов будут сохранены", + "breadcrumb_page": "Сбросить шифрование", + "breadcrumb_second_description": "Вы потеряете историю сообщений, которая хранится только на сервере", + "breadcrumb_third_description": "Вам нужно будет заново подтвердить все существующие устройства и контакты.", + "breadcrumb_title": "Вы уверены, что хотите сбросить свою идентификацию?", + "breadcrumb_title_forgot": "Забыли ключ восстановления? Вам нужно будет восстановить свою идентификацию.", + "breadcrumb_warning": "Делайте это только в том случае, если вы считаете, что ваша учетная запись взломана.", + "details_title": "Сведения о шифровании", + "do_not_close_warning": "Не закрывайте это окно до тех пор, пока сброс не будет завершен", + "export_keys": "Экспортировать ключи", + "import_keys": "Импортировать ключи", + "other_people_device_description": "Внимание: пользователи, которые явно не подтвердили вашу личность (например, с помощью emoji), не получат ваши зашифрованные сообщения. Кроме того, неверифицированные устройства верифицированных пользователей не будут получать ваши зашифрованные сообщения.", + "other_people_device_label": "В зашифрованных комнатах отправляйте сообщения только проверенным пользователям", + "other_people_device_title": "Устройства других людей", + "reset_identity": "Сбросить криптографическую идентификацию", + "reset_in_progress": "Выполняется сброс...", + "session_id": "ID сеанса:", + "session_key": "Ключ сеанса:", + "title": "Дополнительно" + }, "delete_key_storage": { - "list_first": "Нет зашифрованной истории сообщений на новых устройствах" + "breadcrumb_page": "Удалить хранилище ключей", + "confirm": "Удалить хранилище ключей", + "description": "Удаление хранилища ключей приведёт к удалению вашей идентификации и ключей сообщений с сервера, а также отключению следующих функций безопасности:", + "list_first": "Нет зашифрованной истории сообщений на новых устройствах", + "title": "Вы уверены, что хотите отключить хранение ключей и удалить их?" + }, + "device_not_verified_button": "Проверить это устройство", + "device_not_verified_title": "Устройство не проверено", + "dialog_title": "Настройки: Шифрование", + "key_storage": { + "allow_key_storage": "Разрешить хранение ключей", + "description": "Безопасно храните свою идентификацию и ключи сообщений на сервере. Это позволит вам просматривать историю сообщений на любых новых устройствах. Узнайте больше", + "title": "Хранилище ключей" }, "recovery": { "change_recovery_confirm_button": "Подтвердите новый ключ восстановления", - "change_recovery_confirm_title": "Введите новый ключ восстановления" + "change_recovery_confirm_title": "Введите новый ключ восстановления", + "change_recovery_key": "Изменить ключ восстановления", + "change_recovery_key_title": "Изменить ключ восстановления?", + "enter_key_error": "Ключ восстановления, который вы ввел, неверный.", + "enter_recovery_key": "Введите ключ восстановления", + "forgot_recovery_key": "Забыли ключ восстановления?", + "save_key_description": "Не сообщайте эту информацию никому!", + "save_key_title": "Ключ восстановления" } }, "general": { @@ -2555,19 +2719,23 @@ "browser": "Браузер", "confirm_sign_out": { "one": "Подтвердите выход из этого устройства", - "other": "Подтвердите выход из этих устройств" + "few": "Подтвердите выход из этих устройств", + "many": "Подтвердите выход из этих устройств" }, "confirm_sign_out_body": { "one": "Нажмите кнопку ниже, чтобы подтвердить выход из этого устройства.", - "other": "Нажмите кнопку ниже, чтобы подтвердить выход из этих устройств." + "few": "Нажмите кнопку ниже, чтобы подтвердить выход из этих устройств.", + "many": "Нажмите кнопку ниже, чтобы подтвердить выход из этих устройств." }, "confirm_sign_out_continue": { "one": "Выйти из устройства", - "other": "Выйти из устройств" + "few": "Выйти из устройств", + "many": "Выйти из устройств" }, "confirm_sign_out_sso": { "one": "Подтвердите выход из этого устройства с помощью единого входа, чтобы подтвердить свою личность.", - "other": "Подтвердите выход из этих устройств с помощью единого входа, чтобы подтвердить свою личность." + "few": "Подтвердите выход из этих устройств с помощью единого входа, чтобы подтвердить свою личность.", + "many": "Подтвердите выход из этих устройств с помощью единого входа, чтобы подтвердить свою личность." }, "current_session": "Текущий сеанс", "desktop_session": "Сеанс рабочего стола", @@ -2595,7 +2763,8 @@ "mobile_session": "Сеанс мобильного устройства", "n_sessions_selected": { "one": "%(count)s сеанс выбран", - "other": "Сеансов выбрано: %(count)s" + "few": "%(count)s сеанса выбраны", + "many": "%(count)s сеансов выбрано" }, "no_inactive_sessions": "Неактивных сеансов не обнаружено.", "no_sessions": "Сеансов не найдено.", @@ -2622,11 +2791,13 @@ "sign_out_all_other_sessions": "Выйти из всех остальных сеансов (%(otherSessionsCount)s)", "sign_out_confirm_description": { "one": "Вы уверены, что хотите выйти из %(count)s сеанса?", - "other": "Вы уверены, что хотите выйти из %(count)s сеансов?" + "few": "Вы уверены, что хотите выйти из %(count)s сеансов?", + "many": "Вы уверены, что хотите выйти из %(count)s сеансов?" }, "sign_out_n_sessions": { "one": "Выйти из %(count)s сеанса", - "other": "Выйти из сеансов: %(count)s" + "few": "Выйти из %(count)s сеансов", + "many": "Выйти из %(count)s сеансов" }, "title": "Сеансы", "unknown_session": "Неизвестный тип сеанса", @@ -2943,7 +3114,8 @@ "all_threads_description": "Показывает все обсуждения из текущей комнаты", "count_of_reply": { "one": "%(count)s ответ", - "other": "%(count)s ответов" + "few": "%(count)s ответа", + "many": "%(count)s ответов" }, "error_start_thread_existing_relation": "Невозможно создать обсуждение из события с существующей связью", "my_threads": "Мои обсуждения", @@ -3422,7 +3594,9 @@ } }, "truncated_list_n_more": { - "other": "Еще %(count)s…" + "one": "%(count)s...", + "few": "И еще %(count)s...", + "many": "И еще %(count)s..." }, "unsupported_server_description": "На этом сервере используется старая версия Matrix. Перейдите на Matrix%(version)s, чтобы использовать %(brand)s ее без ошибок.", "unsupported_server_title": "Ваш сервер не поддерживается", diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 432fefcb6e..17cef4f70d 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -12,6 +12,8 @@ "one": "1 oläst omnämnande." }, "recent_rooms": "Nyliga rum", + "room_messsage_not_sent": "Öppna rummet %(roomName)s med ett osänt meddelande.", + "room_n_unread_invite": "Öppna inbjudan till rummet %(roomName)s.", "room_name": "Rum %(name)s", "room_status_bar": "Rumsstatusfält", "seek_bar_label": "Förloppsfält för ljud", @@ -64,6 +66,7 @@ "go": "Gå", "go_back": "Gå tillbaka", "got_it": "Uppfattat", + "hide": "Dölj", "hide_advanced": "Dölj avancerat", "hold": "Parkera", "ignore": "Ignorera", @@ -80,12 +83,14 @@ "maximise": "Maximera", "mention": "Nämn", "minimise": "Minimera", + "new_message": "Nytt meddelande", "new_room": "Nytt rum", "new_video_room": "Nytt videorum", "next": "Nästa", "no": "Nej", "ok": "OK", "open": "Öppna", + "open_menu": "Öppna menyn", "pause": "Pausa", "pin": "Häftstift", "play": "Spela", @@ -100,6 +105,7 @@ "reply": "Svara", "reply_in_thread": "Svara i tråd", "report_content": "Rapportera innehåll", + "report_room": "Anmäl rum", "resend": "Skicka igen", "reset": "Återställ", "resume": "Återuppta", @@ -404,6 +410,15 @@ "download_logs": "Ladda ner loggar", "downloading_logs": "Laddar ner loggar", "error_empty": "Berätta vad som gick fel, eller skapa ännu hellre ett GitHub-ärende som beskriver problemet.", + "failed_download_logs": "Misslyckades att ladda ner felsökningsloggar: ", + "failed_send_logs_causes": { + "disallowed_app": "Din felrapport avvisades. Raseriskak-servern stöder inte den här applikationen.", + "rejected_generic": "Din felrapport avvisades. Raseriskak-servern avvisade innehållet i rapporten på grund av en policy.", + "rejected_recovery_key": "Din felrapport avvisades av säkerhetsskäl, eftersom den innehöll en återställningsnyckel.", + "rejected_version": "Din felrapport avvisades eftersom versionen du kör är för gammal.", + "server_unknown_error": "Raseriskak-servern stötte på ett okänt fel och kunde inte hantera rapporten.", + "unknown_error": "Misslyckades att skicka loggar." + }, "github_issue": "GitHub-ärende", "introduction": "Om du har rapporterat en bugg via GitHub så kan felsökningsloggar hjälpa oss att hitta problemet. ", "log_request": "För att hjälpa oss att förhindra detta i framtiden, vänligen skicka oss loggar.", @@ -443,6 +458,7 @@ "access_token": "Åtkomsttoken", "accessibility": "Tillgänglighet", "advanced": "Avancerat", + "all_chats": "Alla chattar", "analytics": "Statistik", "and_n_others": { "other": "och %(count)s andra…", @@ -534,6 +550,7 @@ "qr_code": "QR-kod", "random": "Slumpmässig", "reactions": "Reaktioner", + "recommended": "Rekommenderad", "report_a_bug": "Rapportera en bugg", "room": "Rum", "room_name": "Rumsnamn", @@ -726,6 +743,44 @@ "category_room": "Rum", "caution_colon": "Varning:", "client_versions": "Klientversioner", + "crypto": { + "4s_public_key_in_account_data": "i kontouppgifter", + "4s_public_key_not_in_account_data": "Hittades inte", + "4s_public_key_status": "Publik nyckel för hemlig lagring:", + "backup_key_cached": "cachad lokalt", + "backup_key_cached_status": "Säkerhetskopierad nyckel cachad:", + "backup_key_not_stored": "inte lagrad", + "backup_key_stored": "i hemlig lagring", + "backup_key_stored_status": "Säkerhetskopieringsnyckel lagrad:", + "backup_key_unexpected_type": "oväntad typ", + "backup_key_well_formed": "välformad", + "cross_signing": "Korssignering", + "cross_signing_cached": "cachad lokalt", + "cross_signing_not_ready": "Korssignering är inte konfigurerad.", + "cross_signing_private_keys_in_storage": "i hemlig lagring", + "cross_signing_private_keys_in_storage_status": "Korssignering av privata nycklar:", + "cross_signing_private_keys_not_in_storage": "hittades inte i lagring", + "cross_signing_public_keys_on_device": "i minnet", + "cross_signing_public_keys_on_device_status": "Korssignerade publika nycklar:", + "cross_signing_ready": "Korssignering är klar för användning.", + "cross_signing_status": "Korssigneringsstatus:", + "cross_signing_untrusted": "Ditt konto har en korssigneringsidentitet i hemlig lagring, men det är ännu inte betrodd av den här sessionen.", + "crypto_not_available": "Kryptografisk modul är inte tillgänglig", + "key_backup_active_version": "Aktiv backupversion:", + "key_backup_active_version_none": "Ingen", + "key_backup_inactive_warning": "Dina nycklar säkerhetskopieras inte från den här sessionen.", + "key_backup_latest_version": "Senaste backupversionen på servern:", + "key_storage": "Nyckellagring", + "master_private_key_cached_status": "Privat huvudnyckel:", + "not_found": "Hittades inte", + "not_found_locally": "hittades inte lokalt", + "secret_storage_not_ready": "Inte redo", + "secret_storage_ready": "Klar", + "secret_storage_status": "Hemlig lagring:", + "self_signing_private_key_cached_status": "Självsignerande privat nyckel:", + "title": "End-to-end-kryptering", + "user_signing_private_key_cached_status": "Privat nyckel för användarsignering:" + }, "developer_mode": "Utvecklarläge", "developer_tools": "Utvecklarverktyg", "edit_setting": "Redigera inställningar", @@ -861,14 +916,18 @@ "cross_signing_user_normal": "Du har inte verifierat den här användaren.", "cross_signing_user_verified": "Du har verifierat den här användaren. Den här användaren har verifierat alla sina sessioner.", "cross_signing_user_warning": "Den här användaren har inte verifierat alla sina sessioner.", + "enter_recovery_key": "Ange återställningsnyckel", "event_shield_reason_authenticity_not_guaranteed": "Det krypterade meddelandets äkthet kan inte garanteras på den här enheten.", "event_shield_reason_mismatched_sender_key": "Krypterat av en overifierad session", "event_shield_reason_unknown_device": "Krypterad av en okänd eller raderad enhet.", "event_shield_reason_unsigned_device": "Krypterad av en enhet som inte har verifierats av dess ägare.", "event_shield_reason_unverified_identity": "Krypterad av en overifierad användare.", "export_unsupported": "Din webbläsare stödjer inte nödvändiga kryptografitillägg", + "forgot_recovery_key": "Har du glömt återställningsnyckeln?", "import_invalid_keyfile": "Inte en giltig %(brand)s-nyckelfil", "import_invalid_passphrase": "Autentiseringskontroll misslyckades: felaktigt lösenord?", + "key_storage_out_of_sync": "Din nyckellagring är inte synkroniserad.", + "key_storage_out_of_sync_description": "Bekräfta din återställningsnyckel för att behålla åtkomsten till din nyckellagring och meddelandehistorik.", "messages_not_secure": { "cause_1": "Din hemserver", "cause_2": "Hemservern användaren du verifierar är ansluten till", @@ -892,6 +951,9 @@ "warning": "Om du inte tog bort återställningsmetoden kan en angripare försöka komma åt ditt konto. Byt ditt kontolösenord och ställ in en ny återställningsmetod omedelbart i inställningarna." }, "reset_all_button": "Glömt eller förlorat alla återställningsalternativ? Återställ allt", + "set_up_recovery": "Ställ in återställning", + "set_up_recovery_later": "Inte nu", + "set_up_recovery_toast_description": "Generera en återställningsnyckel som kan användas för att återställa din krypterade meddelandehistorik om du förlorar åtkomst till dina enheter.", "set_up_toast_description": "Skydda mot att förlora åtkomst till krypterade meddelanden och data", "set_up_toast_title": "Ställ in säker säkerhetskopiering", "setup_secure_backup": { @@ -992,8 +1054,11 @@ "waiting_other_user": "Väntar på att %(displayName)s ska verifiera…" }, "verification_requested_toast_title": "Verifiering begärd", + "verified_identity_changed": "Verifierad identitet för %(displayName)s (%(userId)s) har ändrats. Läs mer ", + "verified_identity_changed_no_displayname": "Verifierad identitet för %(userId)s har ändrats. Läs mer ", "verify_toast_description": "Andra användare kanske inta litar på den", - "verify_toast_title": "Verifiera denna session" + "verify_toast_title": "Verifiera denna session", + "withdraw_verification_action": "Återkalla verifieringen" }, "error": { "admin_contact": "Vänligen kontakta din tjänstadministratör för att fortsätta använda tjänsten.", @@ -1177,6 +1242,7 @@ "change": "Byt identitetsserver", "change_prompt": "Koppla ifrån från identitetsservern och anslut till istället?", "change_server_prompt": "Om du inte vill använda för att upptäcka och upptäckas av befintliga kontakter som du känner, ange en annan identitetsserver nedan.", + "changed": "Din identitetsserver har ändrats", "checking": "Kontrollerar servern", "description_connected": "Du använder för närvarande för att upptäcka och upptäckas av befintliga kontakter som du känner. Du kan byta din identitetsserver nedan.", "description_disconnected": "Du använder för närvarande inte en identitetsserver. Lägg till en nedan om du vill upptäcka och bli upptäckbar av befintliga kontakter som du känner.", @@ -1219,7 +1285,9 @@ "title": "Webbläsaren stöds ej", "use_desktop_heading": "Använd %(brand)s skrivbord istället", "use_mobile_heading": "Använd %(brand)s på mobilen istället", - "use_mobile_heading_after_desktop": "Eller använd vår mobilapp" + "use_mobile_heading_after_desktop": "Eller använd vår mobilapp", + "windows_64bit": "Windows (64-bitars)", + "windows_arm_64bit": "Windows (ARM 64-bitars)" }, "info_tooltip_title": "Information", "integration_manager": { @@ -1228,6 +1296,7 @@ "error_connecting_heading": "Kan inte ansluta till integrationshanteraren", "explainer": "Integrationshanterare får konfigurationsdata och kan ändra widgetar, skicka rumsinbjudningar och ställa in behörighetsnivåer å dina vägnar.", "manage_title": "Hantera integrationer", + "toggle_label": "Aktivera integrationshanteraren", "use_im": "Använd en integrationshanterare för att hantera bottar, widgets och dekalpaket.", "use_im_default": "Använd en integrationshanterare (%(serverName)s) för att hantera bottar, widgets och dekalpaket." }, @@ -1429,6 +1498,7 @@ "location_share_live_description": "Temporär implementation. Platser stannar kvar i rumshistoriken.", "mjolnir": "Nya sätt att ignorera personer", "msc3531_hide_messages_pending_moderation": "Låt moderatorer dölja meddelanden i väntan på moderering.", + "new_room_list": "Aktivera ny rumslista", "notification_settings": "Nya aviseringsinställningar", "notification_settings_beta_caption": "Vi introducerar ett enklare sätt att ändra dina aviseringsinställningar. Anpassa din %(brand)s precis som du vill ha den.", "notification_settings_beta_title": "Aviseringsinställningar", @@ -1552,8 +1622,14 @@ "toggle_attribution": "Växla tillskrivning" }, "member_list": { + "count": { + "one": "%(count)s medlem", + "other": "%(count)s medlemmar" + }, "filter_placeholder": "Filtrera rumsmedlemmar", "invite_button_no_perms_tooltip": "Du är inte behörig att bjuda in användare", + "invited_label": "Inbjuden", + "no_matches": "Inga matchningar", "power_label": "%(userName)s (behörighet %(powerLevelNumber)s)" }, "member_list_back_action_label": "Rumsmedlemmar", @@ -1718,6 +1794,9 @@ "spam_or_propaganda": "Spam eller propaganda", "toxic_behaviour": "Stötande beteende" }, + "report_room": { + "description": "Rapportera det här rummet till din hemserveradministratör. Detta skickar rummets unika ID, men om meddelanden är krypterade kan administratören inte läsa dem eller visa delade filer." + }, "restore_key_backup_dialog": { "count_of_decryption_failures": "Misslyckades att avkryptera %(failedCount)s sessioner!", "count_of_successfully_restored_keys": "Återställde framgångsrikt %(sessionCount)s nycklar", @@ -2003,20 +2082,54 @@ "add_space_label": "Lägg till utrymme", "breadcrumbs_empty": "Inga nyligen besökta rum", "breadcrumbs_label": "Nyligen besökta rum", + "empty": { + "no_chats": "Inga chattar än", + "no_chats_description": "Kom igång genom att skicka meddelanden till någon eller genom att skapa ett rum", + "no_chats_description_no_room_rights": "Kom igång genom att skicka meddelanden till någon", + "no_favourites": "Du har ingen favoritchatt än", + "no_favourites_description": "Du kan lägga till en chatt till dina favoriter i chattinställningarna", + "no_people": "Du har inte direktchattar med någon ännu", + "no_people_description": "Du kan avmarkera filter för att se dina andra chattar", + "no_rooms": "Du är inte i något rum än", + "no_rooms_description": "Du kan avmarkera filter för att se dina andra chattar", + "no_unread": "Grattis! Du har inga olästa meddelanden", + "show_chats": "Visa alla chattar" + }, "failed_add_tag": "Misslyckades att lägga till etiketten %(tagName)s till rummet", "failed_remove_tag": "Misslyckades att radera etiketten %(tagName)s från rummet", "failed_set_dm_tag": "Misslyckades att sätta direktmeddelandetagg", + "filters": { + "favourite": "Favoriter", + "people": "Personer", + "rooms": "Rum", + "unread": "Olästa" + }, "home_menu_label": "Hemalternativ", "join_public_room_label": "Gå med i offentligt rum", "joining_rooms_status": { "one": "Går just nu med i %(count)s rum", "other": "Går just nu med i %(count)s rum" }, + "list_title": "Rumslista", + "more_options": { + "copy_link": "Kopiera rumslänk", + "favourited": "Favoritmarkerad", + "leave_room": "Lämna rum", + "low_priority": "Låg prioritet", + "mark_read": "Markera som läst", + "mark_unread": "Markera som oläst" + }, "notification_options": "Aviseringsinställningar", + "open_space_menu": "Öppna utrymmesmenyn", + "primary_filters": "Filter för rumslista", "redacting_messages_status": { "one": "Tar just nu bort meddelanden i %(count)s rum", "other": "Tar just nu bort meddelanden i %(count)s rum" }, + "room": { + "more_options": "Fler alternativ", + "open_room": "Öppet rummet %(roomName)s" + }, "show_less": "Visa mindre", "show_n_more": { "other": "Visa %(count)s till", @@ -2027,6 +2140,10 @@ "sort_by_activity": "Aktivitet", "sort_by_alphabet": "A-Ö", "sort_unread_first": "Visa rum med olästa meddelanden först", + "space_menu": { + "home": "Utrymmeshem", + "space_settings": "Utrymmesinställningar" + }, "space_menu_label": "%(spaceName)s-alternativ", "sublist_options": "Listalternativ", "suggested_rooms_heading": "Föreslagna rum" @@ -2379,6 +2496,71 @@ "emoji_autocomplete": "Aktivera emojiförslag medan du skriver", "enable_markdown": "Aktivera Markdown", "enable_markdown_description": "Börja meddelanden med /plain för att skicka utan markdown.", + "encryption": { + "advanced": { + "breadcrumb_first_description": "Dina kontouppgifter, kontakter, inställningar och chattlista sparas", + "breadcrumb_page": "Återställ kryptering", + "breadcrumb_second_description": "Du kommer att förlora all meddelandehistorik som bara lagras på servern", + "breadcrumb_third_description": "Du måste verifiera alla dina befintliga enheter och kontakter igen", + "breadcrumb_title": "Är du säker på att du vill återställa din identitet?", + "breadcrumb_title_forgot": "Glömt din återställningsnyckel? Du måste återställa din identitet.", + "breadcrumb_warning": "Gör bara detta om du tror att ditt konto har utsatts för intrång.", + "details_title": "Krypteringsdetaljer", + "do_not_close_warning": "Stäng inte fönstret förrän återställningen är klar", + "export_keys": "Exportera nycklar", + "import_keys": "Importera nycklar", + "other_people_device_description": "Som standard i krypterade rum, skicka inte krypterade meddelanden till någon förrän du har verifierat dem", + "other_people_device_label": "Skicka aldrig krypterade meddelanden till obekräftade enheter", + "other_people_device_title": "Andras enheter", + "reset_identity": "Återställ kryptografisk identitet", + "reset_in_progress": "Återställning pågår …", + "session_id": "Sessions-ID:", + "session_key": "Sessionsnyckel:", + "title": "Avancerad" + }, + "delete_key_storage": { + "breadcrumb_page": "Radera nyckellagring", + "confirm": "Radera nyckellagring", + "description": "Om du raderar nyckelförvaringen tas din kryptografiska identitet och dina meddelandenycklar bort från servern och följande säkerhetsfunktioner stängs av:", + "list_first": "Du kommer inte att ha krypterad meddelandehistorik på nya enheter", + "list_second": "Du kommer att förlora åtkomst till dina krypterade meddelanden om du loggas ut från %(brand)s överallt", + "title": "Är du säker på att du vill stänga av nyckellagring och radera den?" + }, + "device_not_verified_button": "Verifiera den här enheten", + "device_not_verified_description": "Du måste verifiera den här enheten för att se dina krypteringsinställningar.", + "device_not_verified_title": "Enhet inte verifierad", + "dialog_title": "Inställningar: Kryptering", + "key_storage": { + "allow_key_storage": "Tillåt nyckellagring", + "description": "Lagra din kryptografiska identitet och dina meddelandenycklar säkert på servern. Detta gör att du kan se din meddelandehistorik på alla nya enheter. Läs mer", + "title": "Nyckellagring" + }, + "recovery": { + "change_recovery_confirm_button": "Bekräfta ny återställningsnyckel", + "change_recovery_confirm_description": "Ange din nya återställningsnyckel nedan för att slutföra. Din gamla kommer inte att funka längre.", + "change_recovery_confirm_title": "Ange din nya återställningsnyckel", + "change_recovery_key": "Byt återställningsnyckel", + "change_recovery_key_description": "Skriv ner den här nya återställningsnyckeln någonstans säkert. Klicka sedan på Fortsätt för att bekräfta ändringen.", + "change_recovery_key_title": "Byt återställningsnyckel?", + "description": "Återställ din kryptografiska identitet och meddelandehistorik med en återställningsnyckel om du har tappat bort alla dina befintliga enheter.", + "enter_key_error": "Återställningsnyckeln du angav är inte korrekt.", + "enter_recovery_key": "Ange återställningsnyckel", + "forgot_recovery_key": "Glömt återställningsnyckeln?", + "key_storage_warning": "Din nyckellagring är inte synkroniserad. Klicka på knappen nedan för att åtgärda problemet.", + "save_key_description": "Dela inte detta med någon!", + "save_key_title": "Återställningsnyckel", + "set_up_recovery": "Konfigurera återställning", + "set_up_recovery_confirm_button": "Slutför inställningen", + "set_up_recovery_confirm_description": "Ange återställningsnyckeln som visas på föregående skärm för att slutföra inställningen av återställningen.", + "set_up_recovery_confirm_title": "Ange din återställningsnyckel för att bekräfta", + "set_up_recovery_description": "Din nyckellagring skyddas av en återställningsnyckel. Om du behöver en ny återställningsnyckel efter installationen kan du återskapa den genom att välja ”%(changeRecoveryKeyButton)s”.", + "set_up_recovery_save_key_description": "Skriv ner den här återställningsnyckeln någonstans säkert, som en lösenordshanterare, krypterad anteckning eller ett fysiskt kassaskåp.", + "set_up_recovery_save_key_title": "Spara din återställningsnyckel på ett säkert ställe", + "set_up_recovery_secondary_description": "När du har klickat på Fortsätt genererar vi en återställningsnyckel åt dig.", + "title": "Återställa" + }, + "title": "Kryptering" + }, "general": { "account_management_section": "Kontohantering", "account_section": "Konto", @@ -2469,6 +2651,7 @@ "inline_url_previews_room": "Aktivera URL-förhandsgranskning som standard för deltagare i detta rum", "inline_url_previews_room_account": "Aktivera URL-förhandsgranskning för detta rum (påverkar bara dig)", "insert_trailing_colon_mentions": "Infoga kolon efter användaromnämnande på början av ett meddelande", + "invite_avatars": "Visa avatarer av rum du har blivit inbjuden till", "jump_to_bottom_on_send": "Hoppa till botten av tidslinjen när du skickar ett meddelande", "key_backup": { "backup_in_progress": "Dina nycklar säkerhetskopieras (den första säkerhetskopieringen kan ta några minuter).", @@ -2694,6 +2877,7 @@ "inactive_sessions_list_description": "Överväg att logga ut ur gamla sessioner (%(inactiveAgeDays)s dagar eller äldre) du inte använder längre.", "ip": "IP-adress", "last_activity": "Senaste aktiviteten", + "manage": "Hantera denna session", "mobile_session": "Mobil session", "n_sessions_selected": { "one": "%(count)s session vald", @@ -2906,6 +3090,7 @@ "view": "Visar rum med den angivna adressen", "whois": "Visar information om en användare" }, + "sliding_sync_legacy_no_longer_supported": "Äldre sliding sync stöds inte längre: logga ut och in igen för att aktivera den nya sliding sync-flaggan", "space": { "add_existing_room_space": { "create": "Vill du lägga till ett nytt rum istället?", @@ -3283,6 +3468,7 @@ "left_reason": "%(targetName)s lämnade rummet: %(reason)s", "no_change": "%(senderName)s gjorde ingen ändring", "reject_invite": "%(targetName)s avböjde inbjudan", + "reject_invite_reason": "%(targetName)s avvisade inbjudan: %(reason)s", "remove_avatar": "%(senderName)s tog bort sin profilbild", "remove_name": "%(senderName)s tog bort sitt visningsnamn %(oldDisplayName)s", "set_avatar": "%(senderName)s satte en profilbild", @@ -3319,11 +3505,13 @@ }, "m.room.tombstone": "%(senderDisplayName)s uppgraderade det här rummet.", "m.room.topic": { - "changed": "%(senderDisplayName)s bytte rummets ämne till \"%(topic)s\"." + "changed": "%(senderDisplayName)s bytte rummets ämne till \"%(topic)s\".", + "removed": "%(senderDisplayName)s tog bort ämnet." }, "m.sticker": "%(senderDisplayName)s skickade en dekal.", "m.video": { - "error_decrypting": "Fel vid avkryptering av video" + "error_decrypting": "Fel vid avkryptering av video", + "show_video": "Visa video" }, "m.widget": { "added": "%(widgetName)s-widget har lagts till av %(senderName)s", @@ -3668,6 +3856,7 @@ "unban_space_specific": "Avbanna dem från specifika saker jag kan", "unban_space_warning": "Personen kommer inte kunna komma åt saker du inte är admin för.", "unignore_button": "Avignorera", + "verification_unavailable": "Användarverifiering inte tillgänglig", "verify_button": "Verifiera användare", "verify_explainer": "För extra säkerhet, verifiera den här användaren genom att kolla en engångskod på båda era enheter." }, diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index ca301a2d68..ab63d1002b 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -80,12 +80,14 @@ "maximise": "Maksimize", "mention": "Sizden bahsediyor", "minimise": "Minimize", + "new_message": "Yeni mesaj", "new_room": "Yeni oda", "new_video_room": "Yeni video odası", "next": "İleri", "no": "Hayır", "ok": "Tamam", "open": "Aç", + "open_menu": "Menüyü aç", "pause": "Durdur", "pin": "PIN Kodu", "play": "Oynat", @@ -404,6 +406,15 @@ "download_logs": "Günlükleri indir", "downloading_logs": "Günlükler indiriliyor", "error_empty": "Lütfen neyin yanlış gittiğini bize bildirin ya da en güzeli problemi tanımlayan bir GitHub talebi oluşturun.", + "failed_download_logs": "Hata ayıklama günlükleri indirilemedi: ", + "failed_send_logs_causes": { + "disallowed_app": "Hata raporunuz reddedildi. rageshake sunucusu bu uygulamayı desteklemiyor.", + "rejected_generic": "Hata raporunuz reddedildi. Rageshake sunucusu, bir politika nedeniyle raporun içeriğini reddetti.", + "rejected_recovery_key": "Hata raporunuz bir kurtarma anahtarı içerdiği için güvenlik nedeniyle reddedildi.", + "rejected_version": "Çalıştırdığınız sürüm çok eski olduğu için hata raporunuz reddedildi.", + "server_unknown_error": "Rageshake sunucusu bilinmeyen bir hatayla karşılaştı ve raporu işleyemedi.", + "unknown_error": "Günlükler gönderilemedi." + }, "github_issue": "GitHub sorunu", "introduction": "GitHub aracılığıyla bir hata bildirdiyseniz, hata ayıklama günlükleri sorunu bulmamızda yardımcı olabilir. ", "log_request": "Bunun gelecekte de olmasının önüne geçmek için lütfen günceleri bize gönderin.", @@ -1226,6 +1237,7 @@ "change": "Kimlik sunucusunu değiştir", "change_prompt": " kimlik sunucusundan bağlantı kesilip kimlik sunucusuna bağlanılsın mı?", "change_server_prompt": "Eğer kimlik sunucusunu kullanarak başkalarını bulmak ve başkalarını tarafından bulunabilmek istemiyorsanız aşağıya bir başka kimlik sunucusu giriniz.", + "changed": "Kimlik sunucunuz değiştirildi", "checking": "Sunucu kontrol ediliyor", "description_connected": "Şu anda kimlik sunucusunu kullanarak başkalarını buluyorsunuz ve başkalarını tarafından bulunabiliyorsunuz. Aşağıdan kimlik sunucunuzu değiştirebilirsiniz.", "description_disconnected": "Şu anda herhangi bir kimlik sunucusu kullanmıyorsunuz. Başkalarını bulmak ve başkaları tarafından bulunabilmek için aşağıya bir kimlik sunucusu ekleyin.", @@ -1268,7 +1280,9 @@ "title": "%(brand)s bu tarayıcıyı desteklemiyor", "use_desktop_heading": "Bunun yerine %(brand)s Masaüstü kullanın", "use_mobile_heading": "Bunun yerine mobil cihazlarda %(brand)s kullanın", - "use_mobile_heading_after_desktop": "Veya mobil uygulamamızı kullanın" + "use_mobile_heading_after_desktop": "Veya mobil uygulamamızı kullanın", + "windows_64bit": "Windows (64 bit)", + "windows_arm_64bit": "Windows (ARM 64-bit)" }, "info_tooltip_title": "Bilgi", "integration_manager": { @@ -1277,6 +1291,7 @@ "error_connecting_heading": "Entegrasyon yöneticisine bağlanılamadı", "explainer": "Bütünleştirme yöneticileri yapılandırma verilerini alır ve sizin adınıza widget'ları değiştirebilir, oda davetleri gönderebilir ve güç düzeylerini ayarlayabilir.", "manage_title": "Entegrasyonları yönet", + "toggle_label": "Entegrasyon yöneticisini etkinleştirin", "use_im": "Botları, görsel bileşenleri ve çıkartma paketlerini yönetmek için bir entegrasyon yöneticisi kullanın.", "use_im_default": "Botları, widget'ları ve çıkartma paketlerini yönetmek için bir bütünleştirme yöneticisi (%(serverName)s) kullanın." }, @@ -1478,6 +1493,7 @@ "location_share_live_description": "Geçici uygulama. Konumlar oda geçmişinde kalır.", "mjolnir": "İnsanları görmezden gelmenin yeni yolları", "msc3531_hide_messages_pending_moderation": "Moderatörlerin onay bekleyen mesajları gizlemesine izin verin.", + "new_room_list": "Yeni oda listesini etkinleştir", "notification_settings": "Yeni Bildirim Ayarları", "notification_settings_beta_caption": "Bildirim ayarlarınızı değiştirmenin daha basit bir yolunu sunuyoruz. %(brand)s istediğiniz gibi özelleştirin.", "notification_settings_beta_title": "Bildirim Ayarları", @@ -2062,17 +2078,29 @@ "failed_add_tag": "%(tagName)s etiketi odaya eklenemedi", "failed_remove_tag": "Odadan %(tagName)s etiketi kaldırılamadı", "failed_set_dm_tag": "Doğrudan mesaj etiketi ayarlanamadı", + "filters": { + "favourite": "Favoriler", + "people": "Kişiler", + "rooms": "Odalar", + "unread": "Okunmamış" + }, "home_menu_label": "Ana sayfa seçenekleri", "join_public_room_label": "Herkese açık odaya katıl", "joining_rooms_status": { "one": "Şu anda %(count)s odaya katılıyor", "other": "Şu anda %(count)s odaya katılıyor" }, + "list_title": "Oda listesi", "notification_options": "Bildirim ayarları", + "open_space_menu": "Açık alan menüsü", + "primary_filters": "Oda listesi filtreleri", "redacting_messages_status": { "one": "Şu anda %(count)s odadaki mesajlar kaldırılıyor", "other": "Şu anda %(count)s odadaki mesajlar kaldırılıyor" }, + "room": { + "open_room": "Açık oda %(roomName)s" + }, "show_less": "Daha az göster", "show_n_more": { "one": "%(count)s adet daha fazla göster", @@ -2083,6 +2111,10 @@ "sort_by_activity": "Aktivite", "sort_by_alphabet": "A-Z", "sort_unread_first": "Önce okunmamış mesajları olan odaları göster", + "space_menu": { + "home": "Alan ana sayfa", + "space_settings": "Alan Ayarları" + }, "space_menu_label": "%(spaceName)s menü", "sublist_options": "Liste seçenekleri", "suggested_rooms_heading": "Önerilen Odalar" @@ -2445,16 +2477,25 @@ "breadcrumb_title_forgot": "Kurtarma anahtarınızı mı unuttunuz? Kimliğinizi sıfırlamanız gerekecek.", "breadcrumb_warning": "Bunu yalnızca hesabınızın tehlikeye girdiğini düşünüyorsanız yapın.", "details_title": "Şifreleme detayları", + "do_not_close_warning": "Sıfırlama işlemi bitene kadar bu pencereyi kapatmayın", "export_keys": "Anahtarları dışa aktar", "import_keys": "Anahtarları içe aktar", "other_people_device_description": "Şifrelenmiş odalarda varsayılan olarak, doğrulayana kadar kimseye şifrelenmiş mesajlar göndermeyin", "other_people_device_label": "Doğrulanmamış cihazlara asla şifrelenmiş mesajlar göndermeyin", "other_people_device_title": "Diğer kişilerin cihazları", "reset_identity": "Şifreleme kimliğini sıfırla", + "reset_in_progress": "Sıfırlama devam ediyor...", "session_id": "Oturum Kimliği:", "session_key": "Oturum anahtarı:", "title": "Gelişmiş" }, + "delete_key_storage": { + "breadcrumb_page": "Anahtar depolamasını sil", + "confirm": "Anahtar depolamasını sil", + "description": "Anahtar depolamanın silinmesi kriptografik kimliğinizi ve mesaj anahtarlarınızı sunucudan kaldıracak ve aşağıdaki güvenlik özelliklerini kapatacaktır:", + "list_first": "Yeni cihazlarda şifrelenmiş mesaj geçmişine sahip olmayacaksınız", + "list_second": "%(brand)s adresinden her yerde oturumunuzu kapatırsanız şifrelenmiş mesajlarınıza erişiminizi kaybedersiniz" + }, "device_not_verified_button": "Bu cihazı doğrulayın", "device_not_verified_description": "Şifreleme ayarlarınızı görüntülemek için bu cihazı doğrulamanız gerekiyor.", "device_not_verified_title": "Cihaz doğrulanmamış", @@ -3390,6 +3431,7 @@ "left_reason": "%(targetName)s odadan çıktı: %(reason)s", "no_change": "%(senderName)s hiçbir değişiklik yapmadı", "reject_invite": "%(targetName)s daveti geri çevirdi", + "reject_invite_reason": "%(targetName)s daveti reddetti: %(reason)s", "remove_avatar": "%(senderName)s profil resmini kaldırdı", "remove_name": "%(senderName)s, %(oldDisplayName)s görünür adını kaldırdı", "set_avatar": "%(senderName)s profil resmi belirledi", @@ -3772,6 +3814,7 @@ "unban_space_specific": "Yasaklarını kaldırabileceğim belirli yerlerden yasaklarını kaldır", "unban_space_warning": "Yönetici olmadığınız hiçbir şeye erişemezler.", "unignore_button": "Görmezden Gelmeyi Kaldır", + "verification_unavailable": "Kullanıcı doğrulaması kullanılamıyor", "verify_button": "Kullanıcıyı Doğrula", "verify_explainer": "Ekstra güvenlik için, her iki cihazınızda da tek seferlik bir kodu kontrol ederek bu kullanıcıyı doğrulayın." }, @@ -3978,7 +4021,7 @@ "error_need_to_be_logged_in": "Oturum açmanız gerekiyor.", "error_unable_start_audio_stream_description": "Ses akışı başlatılamıyor.", "error_unable_start_audio_stream_title": "Canlı yayın başlatılamadı", - "modal_data_warning": "Bu ekrandaki veriler %(widgetDomain)s ile paylaşılıyor.", + "modal_data_warning": "Aşağıdaki veriler şu kişilerle paylaşılmaktadır:%(widgetDomain)s", "modal_title_default": "Modal Widget", "no_name": "Bilinmeyen uygulama", "open_id_permissions_dialog": { diff --git a/src/i18n/strings/vi.json b/src/i18n/strings/vi.json index 0cc3b8be4d..897f9e4f3b 100644 --- a/src/i18n/strings/vi.json +++ b/src/i18n/strings/vi.json @@ -369,6 +369,7 @@ }, "common": { "access_token": "Token truy cập", + "accessibility": "Trợ năng", "advanced": "Nâng cao", "analytics": "Về dữ liệu phân tích", "and_n_others": { @@ -515,6 +516,8 @@ "edit_composer_label": "Chỉnh sửa tin nhắn", "format_bold": "In đậm", "format_code_block": "Khối mã", + "format_decrease_indent": "Giảm thụt lề", + "format_increase_indent": "Tăng thụt lề", "format_inline_code": "Mã", "format_insert_link": "Chèn liên kết", "format_italic": "Nghiêng", @@ -624,7 +627,9 @@ "subspace_join_rule_restricted_description": "Bất kỳ ai trong đều có thể tìm và tham gia." }, "credits": { - "default_cover_photo": "Các ảnh bìa mặc định Là © Chúa Jesus Roncero Được sử dụng theo các điều khoản của CC-BY-SA 4.0." + "default_cover_photo": "Các ảnh bìa mặc định Là © Chúa Jesus Roncero Được sử dụng theo các điều khoản của CC-BY-SA 4.0.", + "twemoji": "Bộ Twemoji được © Twitter, Inc and other contributors sử dụng dưới các điều khoản trong giấy phép CC-BY 4.0.", + "twemoji_colr": "twemoji-colr được Quỹ ©Mozilla Foundation sử dụng dựa trên các điều khoản trong giấy phép Apache 2.0." }, "desktop_default_device_name": "%(brand)s máy tính: %(platformName)s", "devtools": { @@ -977,6 +982,7 @@ "generating_zip": "Tạo ZIP", "html_title": "Dữ liệu được trích xuất", "include_attachments": "Bao gồm các đính kèm", + "json": "JSON", "media_omitted": "Phương tiện bị bỏ qua", "media_omitted_file_size": "Phương tiện bị bỏ qua - kích thước tệp vượt quá giới hạn", "messages": "Tin nhắn", @@ -1141,6 +1147,7 @@ }, "keyboard": { "activate_button": "Kích hoạt nút đã chọn", + "alt": "Alt", "autocomplete_cancel": "Hủy tự động hoàn thành", "backspace": "Phím lùi", "cancel_reply": "Hủy trả lời tin nhắn", @@ -1161,6 +1168,7 @@ "composer_toggle_link": "Chuyển đổi liên kết", "composer_toggle_quote": "Chuyển sang Trích dẫn", "composer_undo": "Hoàn tác chỉnh sửa", + "control": "Phím Ctrl", "dismiss_read_marker_and_jump_bottom": "Bỏ qua điểm đánh dấu đã đọc và chuyển xuống cuối", "end": "Kết thúc", "enter": "Vào", @@ -1182,6 +1190,7 @@ "scroll_up_timeline": "Cuộn lên trong dòng thời gian", "search": "Tìm kiếm (phải được bật)", "send_sticker": "Gửi nhãn dán", + "shift": "Phím Shift", "space": "space", "toggle_microphone_mute": "Chuyển đổi chế độ tắt tiếng micrô", "toggle_right_panel": "Chuyển đổi bảng điều khiển bên phải", diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index ba2b0393a0..1053148355 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -413,6 +413,7 @@ "attachment": "附件", "authentication": "授權", "avatar": "大頭照", + "beta": "試用版", "camera": "相機", "cameras": "相機", "capabilities": "功能", @@ -1049,8 +1050,10 @@ "format": "格式", "from_the_beginning": "從一開始", "generating_zip": "產生 ZIP", + "html": "HTML", "html_title": "已匯出的資料", "include_attachments": "包含附件", + "json": "JSON", "media_omitted": "媒體省略", "media_omitted_file_size": "媒體省略 - 超過檔案大小限制", "messages": "訊息", @@ -1215,6 +1218,7 @@ }, "keyboard": { "activate_button": "啟動已選取按鈕", + "alt": "Alt", "autocomplete_cancel": "取消自動完成", "autocomplete_force": "強制完成", "autocomplete_navigate_next": "下一個自動完成建議", @@ -1240,6 +1244,8 @@ "composer_undo": "復原編輯", "dismiss_read_marker_and_jump_bottom": "清除讀取標記並跳至底部", "end": "", + "enter": "你好", + "escape": "Esc", "go_home_view": "前往主畫面", "home": "首頁", "jump_first_message": "跳至第一則訊息", @@ -2365,6 +2371,7 @@ "voip": "音訊與視訊通話" }, "preferences": { + "Electron.enableHardwareAcceleration": "啟用硬體加速 (重新啟動%(appName)s以生效)", "always_show_menu_bar": "總是顯示視窗選單列", "autocomplete_delay": "自動完成延遲(毫秒)", "code_blocks_heading": "程式碼區塊", @@ -2388,6 +2395,7 @@ "prompt_invite": "在傳送邀請給潛在的無效 Matrix ID 前提示", "replace_plain_emoji": "自動取代純文字為表情符號", "security": { + "analytics_description": "共享匿名資料以幫助我們識別問題。沒有個人資料。沒有第三方。", "bulk_options_accept_all_invites": "接受所有 %(invitedRooms)s 邀請", "bulk_options_reject_all_invites": "拒絕所有 %(invitedRooms)s 邀請", "bulk_options_section": "大量選項", @@ -2537,6 +2545,7 @@ "metaspaces_orphans_description": "將所有不屬於某個聊天空間的聊天室集中在同一個地方。", "metaspaces_people_description": "將您所有的夥伴集中在同一個地方。", "metaspaces_subsection": "要顯示的聊天空間", + "spaces_explainer": "空間是將房間和人群分組的方式。除了您所在的空間之外,您也可以使用一些預先構建的空間。", "title": "側邊欄" }, "start_automatically": "在系統登入後自動開始", @@ -2835,9 +2844,13 @@ "n_hours_ago": "%(num)s 小時前", "n_minutes_ago": "%(num)s 分鐘前", "seconds_left": "剩 %(seconds)s 秒", + "short_days": "%(value)sd", "short_days_hours_minutes_seconds": "%(days)s 天 %(hours)s 小時 %(minutes)s 分鐘 %(seconds)s 秒", + "short_hours": "%(value)sh", "short_hours_minutes_seconds": "%(hours)s 小時 %(minutes)s 分鐘 %(seconds)s 秒", - "short_minutes_seconds": "%(minutes)s 分鐘 %(seconds)s 秒" + "short_minutes": "%(value)sm", + "short_minutes_seconds": "%(minutes)s 分鐘 %(seconds)s 秒", + "short_seconds": "%(value)sS" }, "timeline": { "context_menu": { diff --git a/src/theme.ts b/src/theme.ts index de384021aa..bfc471544c 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -129,7 +129,7 @@ function clearCustomTheme(): void { // remove all css variables, we assume these are there because of the custom theme const inlineStyleProps = Object.values(document.body.style); for (const prop of inlineStyleProps) { - if (prop.startsWith("--")) { + if (typeof prop === "string" && prop.startsWith("--")) { document.body.style.removeProperty(prop); } } @@ -206,16 +206,49 @@ function generateCustomCompoundCSS(theme: CompoundTheme): string { return `@layer compound.custom { :root, [class*="cpd-theme-"] { ${properties.join(" ")} } }`; } +/** + * Normalizes the hex colour to 8 characters (including alpha) + * @param hexColor the hex colour to normalize + */ +function normalizeHexColour(hexColor: string): string { + switch (hexColor.length) { + case 4: + case 5: + // Short RGB or RGBA hex + return `#${hexColor + .slice(1) + .split("") + .map((c) => c + c) + .join("")}`; + case 7: + // Long RGB hex + return `${hexColor}ff`; + default: + return hexColor; + } +} + +function setHexAlpha(normalizedHexColor: string, alpha: number): string { + return normalizeHexColour(normalizedHexColor).slice(0, 7) + Math.round(alpha).toString(16).padStart(2, "0"); +} + +function parseAlpha(normalizedHexColor: string): number { + return parseInt(normalizedHexColor.slice(7), 16); +} + function setCustomThemeVars(customTheme: CustomTheme): void { const { style } = document.body; function setCSSColorVariable(name: string, hexColor: string, doPct = true): void { style.setProperty(`--${name}`, hexColor); + const normalizedHexColor = normalizeHexColour(hexColor); + const baseAlpha = parseAlpha(normalizedHexColor); + if (doPct) { - // uses #rrggbbaa to define the color with alpha values at 0%, 15% and 50% - style.setProperty(`--${name}-0pct`, hexColor + "00"); - style.setProperty(`--${name}-15pct`, hexColor + "26"); - style.setProperty(`--${name}-50pct`, hexColor + "7F"); + // uses #rrggbbaa to define the color with alpha values at 0%, 15% and 50% (relative to base alpha channel) + style.setProperty(`--${name}-0pct`, setHexAlpha(normalizedHexColor, 0)); + style.setProperty(`--${name}-15pct`, setHexAlpha(normalizedHexColor, baseAlpha * 0.15)); + style.setProperty(`--${name}-50pct`, setHexAlpha(normalizedHexColor, baseAlpha * 0.5)); } } diff --git a/test/unit-tests/components/viewmodels/avatars/RoomAvatarViewModel-test.tsx b/test/unit-tests/components/viewmodels/avatars/RoomAvatarViewModel-test.tsx new file mode 100644 index 0000000000..5a06bb01f5 --- /dev/null +++ b/test/unit-tests/components/viewmodels/avatars/RoomAvatarViewModel-test.tsx @@ -0,0 +1,124 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import { renderHook, waitFor, act } from "jest-matrix-react"; +import { + JoinRule, + type MatrixClient, + MatrixEvent, + type Room, + type RoomMember, + User, + UserEvent, +} from "matrix-js-sdk/src/matrix"; +import { mocked } from "jest-mock"; + +import { useRoomAvatarViewModel } from "../../../../../src/components/viewmodels/avatars/RoomAvatarViewModel"; +import { createTestClient, mkStubRoom } from "../../../../test-utils"; +import DMRoomMap from "../../../../../src/utils/DMRoomMap"; +import { getJoinedNonFunctionalMembers } from "../../../../../src/utils/room/getJoinedNonFunctionalMembers"; +import { isPresenceEnabled } from "../../../../../src/utils/presence"; + +jest.mock("../../../../../src/utils/room/getJoinedNonFunctionalMembers", () => ({ + getJoinedNonFunctionalMembers: jest.fn().mockReturnValue([]), +})); + +jest.mock("../../../../../src/utils/presence", () => ({ + isPresenceEnabled: jest.fn().mockReturnValue(false), +})); + +describe("RoomAvatarViewModel", () => { + let matrixClient: MatrixClient; + let room: Room; + + beforeEach(() => { + matrixClient = createTestClient(); + room = mkStubRoom("roomId", "roomName", matrixClient); + + DMRoomMap.makeShared(matrixClient); + jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(null); + }); + + it("should has hasDecoration to false", async () => { + const { result: vm } = renderHook(() => useRoomAvatarViewModel(room)); + expect(vm.current.hasDecoration).toBe(false); + }); + + it("should has isVideoRoom set to true", () => { + jest.spyOn(room, "isCallRoom").mockReturnValue(true); + const { result: vm } = renderHook(() => useRoomAvatarViewModel(room)); + expect(vm.current.isVideoRoom).toBe(true); + expect(vm.current.hasDecoration).toBe(true); + }); + + it("should has isPublic set to true", () => { + jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public); + + const { result: vm } = renderHook(() => useRoomAvatarViewModel(room)); + expect(vm.current.isPublic).toBe(true); + expect(vm.current.hasDecoration).toBe(true); + }); + + describe("presence", () => { + let user: User; + + beforeEach(() => { + jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue("userId"); + mocked(getJoinedNonFunctionalMembers).mockReturnValue([{}, {}] as RoomMember[]); + mocked(isPresenceEnabled).mockReturnValue(true); + + user = User.createUser("userId", matrixClient); + jest.spyOn(matrixClient, "getUser").mockReturnValue(user); + }); + + it("should has presence set to null", () => { + jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(null); + + const { result: vm } = renderHook(() => useRoomAvatarViewModel(room)); + expect(vm.current.presence).toBe(null); + }); + + it("should has online presence", async () => { + const { result: vm } = renderHook(() => useRoomAvatarViewModel(room)); + expect(vm.current.presence).toBe("offline"); + + user.presence = "online"; + + await act(() => user.emit(UserEvent.Presence, new MatrixEvent(), user)); + await waitFor(() => expect(vm.current.presence).toBe("online")); + + user.currentlyActive = true; + user.presence = "offline"; + + await act(() => user.emit(UserEvent.CurrentlyActive, new MatrixEvent(), user)); + await waitFor(() => expect(vm.current.presence).toBe("online")); + }); + + it("should has busy presence", async () => { + user.presence = "busy"; + const { result: vm } = renderHook(() => useRoomAvatarViewModel(room)); + expect(vm.current.presence).toBe("busy"); + }); + + it("should has offline presence", async () => { + user.presence = "offline"; + const { result: vm } = renderHook(() => useRoomAvatarViewModel(room)); + expect(vm.current.presence).toBe("offline"); + }); + + it("should has unavailable presence", async () => { + user.presence = "unavailable"; + const { result: vm } = renderHook(() => useRoomAvatarViewModel(room)); + expect(vm.current.presence).toBe("unavailable"); + }); + + it("should has hasDecoration to true", async () => { + const { result: vm } = renderHook(() => useRoomAvatarViewModel(room)); + expect(vm.current.hasDecoration).toBe(true); + }); + }); +}); diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx index 399943b6cf..729ab02836 100644 --- a/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx +++ b/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx @@ -80,14 +80,6 @@ describe("useRoomListHeaderViewModel", () => { expect(result.current.canCreateRoom).toBe(true); }); - it("should be displayComposeMenu=true if the user can creates video room", () => { - mocked(hasCreateRoomRights).mockReturnValue(false); - jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); - - const { result } = render(); - expect(result.current.displayComposeMenu).toBe(true); - }); - it("should be displaySpaceMenu=true if the user is in a space", () => { jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space); const { result } = render(); @@ -118,8 +110,10 @@ describe("useRoomListHeaderViewModel", () => { expect(result.current.canAccessSpaceSettings).toBe(true); }); - it("should be canCreateVideoRoom=true if feature_video_rooms is enabled", () => { + it("should be canCreateVideoRoom=true if feature_video_rooms is enabled and can create room", () => { + mocked(hasCreateRoomRights).mockReturnValue(true); jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); + const { result } = render(); expect(result.current.canCreateVideoRoom).toBe(true); }); diff --git a/test/unit-tests/components/views/avatars/RoomAvatarView-test.tsx b/test/unit-tests/components/views/avatars/RoomAvatarView-test.tsx new file mode 100644 index 0000000000..f86c6b949f --- /dev/null +++ b/test/unit-tests/components/views/avatars/RoomAvatarView-test.tsx @@ -0,0 +1,101 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import React from "react"; +import { render, screen } from "jest-matrix-react"; +import { mocked } from "jest-mock"; + +import { RoomAvatarView } from "../../../../../src/components/views/avatars/RoomAvatarView"; +import { mkStubRoom, stubClient } from "../../../../test-utils"; +import { + type Presence, + type RoomAvatarViewState, + useRoomAvatarViewModel, +} from "../../../../../src/components/viewmodels/avatars/RoomAvatarViewModel"; +import DMRoomMap from "../../../../../src/utils/DMRoomMap"; + +jest.mock("../../../../../src/components/viewmodels/avatars/RoomAvatarViewModel", () => ({ + useRoomAvatarViewModel: jest.fn(), +})); + +describe("", () => { + const matrixClient = stubClient(); + const room = mkStubRoom("roomId", "roomName", matrixClient); + + DMRoomMap.makeShared(matrixClient); + jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(null); + + let defaultValue: RoomAvatarViewState; + + beforeEach(() => { + defaultValue = { + hasDecoration: true, + isPublic: true, + isVideoRoom: true, + presence: null, + }; + + mocked(useRoomAvatarViewModel).mockReturnValue(defaultValue); + }); + + it("should not render a decoration", () => { + mocked(useRoomAvatarViewModel).mockReturnValue({ ...defaultValue, hasDecoration: false }); + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should render a video room decoration", () => { + mocked(useRoomAvatarViewModel).mockReturnValue({ ...defaultValue, hasDecoration: true, isVideoRoom: true }); + const { asFragment } = render(); + + expect(screen.getByLabelText("This room is a video room")).toBeInTheDocument(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should render a public room decoration", () => { + mocked(useRoomAvatarViewModel).mockReturnValue({ + ...defaultValue, + hasDecoration: true, + isPublic: true, + isVideoRoom: false, + }); + const { asFragment } = render(); + + expect(screen.getByLabelText("This room is public")).toBeInTheDocument(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should not render a public room decoration if the room is a video room", () => { + mocked(useRoomAvatarViewModel).mockReturnValue({ + ...defaultValue, + hasDecoration: true, + isPublic: true, + isVideoRoom: true, + }); + render(); + + expect(screen.getByLabelText("This room is a video room")).toBeInTheDocument(); + expect(screen.queryByLabelText("This room is public")).toBeNull(); + }); + + it.each([ + { presence: "online" as Presence, label: "Online" }, + { presence: "offline" as Presence, label: "Offline" }, + { presence: "busy" as Presence, label: "Busy" }, + { presence: "unavailable" as Presence, label: "Away" }, + ])("should render the $presence presence", ({ presence, label }) => { + mocked(useRoomAvatarViewModel).mockReturnValue({ + ...defaultValue, + hasDecoration: true, + presence, + }); + const { asFragment } = render(); + + expect(screen.getByLabelText(label)).toBeInTheDocument(); + expect(asFragment()).toMatchSnapshot(); + }); +}); diff --git a/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatarView-test.tsx.snap b/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatarView-test.tsx.snap new file mode 100644 index 0000000000..744d4cf4ad --- /dev/null +++ b/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatarView-test.tsx.snap @@ -0,0 +1,389 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should not render a decoration 1`] = ` + + + + + +`; + +exports[` should render a public room decoration 1`] = ` + +
+ + + + + + +
+
+`; + +exports[` should render a video room decoration 1`] = ` + +
+ + + + + + +
+
+`; + +exports[` should render the busy presence 1`] = ` + +
+ + + + + + + + + + + + + + + + +
+
+`; + +exports[` should render the offline presence 1`] = ` + +
+ + + + + + + + + + + + + + + + +
+
+`; + +exports[` should render the online presence 1`] = ` + +
+ + + + + + + + + + + + + + + + +
+
+`; + +exports[` should render the unavailable presence 1`] = ` + +
+ + + + + + + + + + + + + + + + +
+
+`; diff --git a/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx b/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx index dd3ae7bc31..9687ed86ec 100644 --- a/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx +++ b/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx @@ -14,49 +14,54 @@ import { NotificationDecoration } from "../../../../../src/components/views/room describe("", () => { it("should not render if RoomNotificationState.isSilent=true", () => { const state = { hasAnyNotificationOrActivity: false } as RoomNotificationState; - render(); + render(); expect(screen.queryByTestId("notification-decoration")).toBeNull(); }); it("should render the unset message decoration", () => { const state = { hasAnyNotificationOrActivity: true, isUnsetMessage: true } as RoomNotificationState; - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); it("should render the invitation decoration", () => { const state = { hasAnyNotificationOrActivity: true, invited: true } as RoomNotificationState; - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); it("should render the mention decoration", () => { const state = { hasAnyNotificationOrActivity: true, isMention: true, count: 1 } as RoomNotificationState; - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); it("should render the notification decoration", () => { const state = { hasAnyNotificationOrActivity: true, isNotification: true, count: 1 } as RoomNotificationState; - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); it("should render the notification decoration without count", () => { const state = { hasAnyNotificationOrActivity: true, isNotification: true, count: 0 } as RoomNotificationState; - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); it("should render the activity decoration", () => { const state = { hasAnyNotificationOrActivity: true, isActivityNotification: true } as RoomNotificationState; - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); it("should render the muted decoration", () => { const state = { hasAnyNotificationOrActivity: true, muted: true } as RoomNotificationState; - const { asFragment } = render(); + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); + it("should render the video decoration", () => { + const state = { hasAnyNotificationOrActivity: false } as RoomNotificationState; + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx index 5237afe710..9cf2cefa7b 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx @@ -42,6 +42,9 @@ describe("", () => { notificationState: new RoomNotificationState(room, false), a11yLabel: "Open room room1", isBold: false, + isVideoRoom: false, + callConnectionState: null, + hasParticipantInCall: false, }; mocked(useRoomListItemViewModel).mockReturnValue(defaultValue); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap index b65c6ec70d..e6053ccdbb 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap @@ -34,29 +34,25 @@ exports[` should render a room list 1`] = ` class="mx_Flex mx_RoomListItemView_container" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;" > -
- - - -
+ height="32px" + loading="lazy" + referrerpolicy="no-referrer" + src="http://this.is.a.url/avatar.url/room.png" + width="32px" + /> +
should render a room list 1`] = ` class="mx_Flex mx_RoomListItemView_container" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;" > -
- - - -
+ height="32px" + loading="lazy" + referrerpolicy="no-referrer" + src="http://this.is.a.url/avatar.url/room.png" + width="32px" + /> +
should render a room list 1`] = ` class="mx_Flex mx_RoomListItemView_container" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;" > -
- - - -
+ height="32px" + loading="lazy" + referrerpolicy="no-referrer" + src="http://this.is.a.url/avatar.url/room.png" + width="32px" + /> +
should render a room list 1`] = ` class="mx_Flex mx_RoomListItemView_container" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;" > -
- - - -
+ height="32px" + loading="lazy" + referrerpolicy="no-referrer" + src="http://this.is.a.url/avatar.url/room.png" + width="32px" + /> +
should render a room list 1`] = ` class="mx_Flex mx_RoomListItemView_container" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;" > -
- - - -
+ height="32px" + loading="lazy" + referrerpolicy="no-referrer" + src="http://this.is.a.url/avatar.url/room.png" + width="32px" + /> +
should render a room list 1`] = ` class="mx_Flex mx_RoomListItemView_container" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;" > -
- - - -
+ height="32px" + loading="lazy" + referrerpolicy="no-referrer" + src="http://this.is.a.url/avatar.url/room.png" + width="32px" + /> +
should render a room list 1`] = ` class="mx_Flex mx_RoomListItemView_container" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;" > -
- - - -
+ height="32px" + loading="lazy" + referrerpolicy="no-referrer" + src="http://this.is.a.url/avatar.url/room.png" + width="32px" + /> +
should render a room list 1`] = ` class="mx_Flex mx_RoomListItemView_container" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;" > -
- - - -
+ height="32px" + loading="lazy" + referrerpolicy="no-referrer" + src="http://this.is.a.url/avatar.url/room.png" + width="32px" + /> +
should render a room list 1`] = ` class="mx_Flex mx_RoomListItemView_container" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;" > -
- - - -
+ height="32px" + loading="lazy" + referrerpolicy="no-referrer" + src="http://this.is.a.url/avatar.url/room.png" + width="32px" + /> +
should render a room list 1`] = ` class="mx_Flex mx_RoomListItemView_container" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;" > -
- - - -
+ height="32px" + loading="lazy" + referrerpolicy="no-referrer" + src="http://this.is.a.url/avatar.url/room.png" + width="32px" + /> +
should be selected if isSelected=true 1`] = ` class="mx_Flex mx_RoomListItemView_container" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;" > -
- - - -
+ height="32px" + loading="lazy" + referrerpolicy="no-referrer" + src="http://this.is.a.url/avatar.url/room.png" + width="32px" + /> +
should render a room item 1`] = ` class="mx_Flex mx_RoomListItemView_container" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;" > -
- - - -
+ height="32px" + loading="lazy" + referrerpolicy="no-referrer" + src="http://this.is.a.url/avatar.url/room.png" + width="32px" + /> +
should render the unset message decoration 1
`; + +exports[` should render the video decoration 1`] = ` + +
+ + + +
+
+`; diff --git a/test/unit-tests/theme-test.ts b/test/unit-tests/theme-test.ts index 6c2f247e4e..b9c7e9341c 100644 --- a/test/unit-tests/theme-test.ts +++ b/test/unit-tests/theme-test.ts @@ -135,6 +135,54 @@ describe("theme", () => { expect(spy.mock.calls[0][0].textContent).toMatchSnapshot(); spy.mockRestore(); }); + + it("should handle 4-char rgba hex strings", async () => { + jest.spyOn(SettingsStore, "getValue").mockReturnValue([ + { + name: "blue", + colors: { + "sidebar-color": "#abcd", + }, + }, + ]); + + const spy = jest.fn(); + jest.spyOn(document.body, "style", "get").mockReturnValue({ + setProperty: spy, + } as any); + await new Promise((resolve) => { + setTheme("custom-blue").then(resolve); + lightCustomTheme.onload!({} as Event); + }); + expect(spy).toHaveBeenCalledWith("--sidebar-color", "#abcd"); + expect(spy).toHaveBeenCalledWith("--sidebar-color-0pct", "#aabbcc00"); + expect(spy).toHaveBeenCalledWith("--sidebar-color-15pct", "#aabbcc21"); + expect(spy).toHaveBeenCalledWith("--sidebar-color-50pct", "#aabbcc6f"); + }); + + it("should handle 6-char rgb hex strings", async () => { + jest.spyOn(SettingsStore, "getValue").mockReturnValue([ + { + name: "blue", + colors: { + "sidebar-color": "#abcdef", + }, + }, + ]); + + const spy = jest.fn(); + jest.spyOn(document.body, "style", "get").mockReturnValue({ + setProperty: spy, + } as any); + await new Promise((resolve) => { + setTheme("custom-blue").then(resolve); + lightCustomTheme.onload!({} as Event); + }); + expect(spy).toHaveBeenCalledWith("--sidebar-color", "#abcdef"); + expect(spy).toHaveBeenCalledWith("--sidebar-color-0pct", "#abcdef00"); + expect(spy).toHaveBeenCalledWith("--sidebar-color-15pct", "#abcdef26"); + expect(spy).toHaveBeenCalledWith("--sidebar-color-50pct", "#abcdef80"); + }); }); describe("enumerateThemes", () => {